From arigo at codespeak.net Mon Oct 2 12:52:50 2006 From: arigo at codespeak.net (arigo at codespeak.net) Date: Mon, 2 Oct 2006 12:52:50 +0200 (CEST) Subject: [py-svn] r32813 - py/dist/py/execnet Message-ID: <20061002105250.813F310072@code0.codespeak.net> Author: arigo Date: Mon Oct 2 12:52:49 2006 New Revision: 32813 Modified: py/dist/py/execnet/message.py Log: A simple execnet optimization to avoid using repr/eval in the case of a direct channel.send("string"). This seems to be a common pattern when exchanging large quantities of data, so it's probably a good idea. Modified: py/dist/py/execnet/message.py ============================================================================== --- py/dist/py/execnet/message.py (original) +++ py/dist/py/execnet/message.py Mon Oct 2 12:52:49 2006 @@ -5,8 +5,9 @@ # # Messages # ___________________________________________________________________________ -# the size of a number on the wire -numsize = struct.calcsize("!i") +# the header format +HDR_FORMAT = "!hhii" +HDR_SIZE = struct.calcsize(HDR_FORMAT) class Message: """ encapsulates Messages and their wire protocol. """ @@ -16,26 +17,31 @@ self.data = data def writeto(self, io): - # big XXX and all this - # data = marshal.dumps(self.data) - # doesn't work for exchanging data across Python version :-((( - data = repr(self.data) # argh - - header = struct.pack("!iii", self.msgtype, self.channelid, len(data)) - io.write(header) - io.write(data) + # XXX marshal.dumps doesn't work for exchanging data across Python + # version :-((( There is no sane solution, short of a custom + # pure Python marshaller + data = self.data + if isinstance(data, str): + dataformat = 1 + else: + data = repr(self.data) # argh + dataformat = 2 + header = struct.pack(HDR_FORMAT, self.msgtype, dataformat, + self.channelid, len(data)) + io.write(header + data) def readfrom(cls, io): - header = io.read(numsize*3) - msgtype, senderid, stringlen = struct.unpack("!iii", header) - if stringlen: - string = io.read(stringlen) - # same big XXX as in writeto() - # string = marshal.loads(string) - string = eval(string, {}) + header = io.read(HDR_SIZE) + (msgtype, dataformat, + senderid, stringlen) = struct.unpack(HDR_FORMAT, header) + data = io.read(stringlen) + if dataformat == 1: + pass + elif dataformat == 2: + data = eval(data, {}) # reversed argh else: - string = '' - msg = cls._types[msgtype](senderid, string) + raise ValueError("bad data format") + msg = cls._types[msgtype](senderid, data) return msg readfrom = classmethod(readfrom) From guido at codespeak.net Tue Oct 3 14:03:01 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Tue, 3 Oct 2006 14:03:01 +0200 (CEST) Subject: [py-svn] r32840 - in py/dist/py/xmlobj: . testing Message-ID: <20061003120301.6A70010060@code0.codespeak.net> Author: guido Date: Tue Oct 3 14:03:00 2006 New Revision: 32840 Modified: py/dist/py/xmlobj/testing/test_xml.py py/dist/py/xmlobj/visit.py Log: Fixed entitizing problem in XML code. Modified: py/dist/py/xmlobj/testing/test_xml.py ============================================================================== --- py/dist/py/xmlobj/testing/test_xml.py (original) +++ py/dist/py/xmlobj/testing/test_xml.py Tue Oct 3 14:03:00 2006 @@ -8,7 +8,7 @@ x = ns.hello("world") u = unicode(x) assert u == "world" - + def test_class_identity(): assert ns.hello is ns.hello @@ -39,3 +39,15 @@ xmlname = 'world' u = unicode(my()) assert u == '' + +def test_tag_with_text_entity(): + x = ns.hello('world & rest') + u = unicode(x) + assert u == "world & rest" + +def test_tag_with_text_and_attributes_entity(): + x = ns.some(name="hello & world") + assert x.attr.name == "hello & world" + u = unicode(x) + assert u == '' + Modified: py/dist/py/xmlobj/visit.py ============================================================================== --- py/dist/py/xmlobj/visit.py (original) +++ py/dist/py/xmlobj/visit.py Tue Oct 3 14:03:00 2006 @@ -32,7 +32,7 @@ def object(self, obj): #self.write(obj) - self.write(unicode(obj)) + self.write(escape(unicode(obj))) def list(self, obj): assert id(obj) not in self.visited From fijal at codespeak.net Tue Oct 3 17:01:07 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Tue, 3 Oct 2006 17:01:07 +0200 (CEST) Subject: [py-svn] r32845 - py/dist/py/documentation/apigen Message-ID: <20061003150107.BC8EE10060@code0.codespeak.net> Author: fijal Date: Tue Oct 3 17:01:06 2006 New Revision: 32845 Added: py/dist/py/documentation/apigen/ py/dist/py/documentation/apigen/api-docs.txt (contents, props changed) Log: (briandorsey, fijal) - Added document created with brian at Trillke sprint. I think should land here. Added: py/dist/py/documentation/apigen/api-docs.txt ============================================================================== --- (empty file) +++ py/dist/py/documentation/apigen/api-docs.txt Tue Oct 3 17:01:06 2006 @@ -0,0 +1,116 @@ + +Automatic API documentation generation +====================================== + +Motivation +---------- + +* We want better automatic hyperlinking within the documentation. +* Test driven documentation generation. Get more semantic info from the tests (return values, exceptions raised, etc). Also, expose tests in the documentation itself as usage examples. +* Should work with generated code, even without source files. +* We want to expose some workflow/context information around the API. What are the inputs, and where do they come from. +* Ease of use, setup, perhaps an option to py.test to generate the docs. +* The generator itself should be nicely unit tested small modules. + +Related projects +---------------- + +* pydoc +* epydoc +* eric3-doc/eric3-api +* doxygen +* pydoctor +* PyUMLGraph (source page down? http://www.python.org/pypi/PyUMLGraph/0.1.4) +* ... + +Proposed features +----------------- + +* Minimal dependencies +* Easy to setup, easy to use. +* Frontend independent, with multiple frontends. HTML, HTML + javascript, pygame. +* Whenever possible the documentation is hyperlinked to relevant info. +* The documents contain links to the source code. +* Can look at running code and generate docs from it. +* Can generate some sort of static document output which could be easily distributed in a zip/tarball. +* Works with generated code, even code without source files. +* Collected data should be usable for other purposes, for example websites, IDE code completion, etc. +* By default, only document the exposed API, don't dig too deep inside the objects. +* Try to learn additional semantic information from the tests. What types are input and returned from functions? What order are functions usually called in? Tests can become usage examples in the documentation. There is a nice social aspect to getting data from the tests, because it encourages the developer to create more complete tests. +* Report code coverage in the tests? (does this really belong in py.test?) +* Possibly create some kind of diagrams/graphs. +* Possibly use the same introspection techniques to produce very rich state information after a crash. + + +Which things to implement first +------------------------------- + +* Create simple ReST output, just documenting the API. + +Implementation ideas +-------------------- + +* Somehow stop a running application and introspect it. +* Use a tracehook while running tests to collect information. +* Use pypy to solve all problems. ;) + + +Automatic API documentation generation - architecture overview +============================================================== + +Abstraction layers: +------------------- + +* ``extractor`` - library API extraction +* ``presenter`` - presentation backend (pygame, html, http + ajax) +* ``test runner`` - just `py.test`_ (modified or not) +* ``tracer`` - middleware responsible for generating necessary + annotations +* ``reporter`` - object API for presenter which gets info from tracer and runner + + +Extractor: +---------- + +Right now it's quite easy - we'll just take pylib ``__package__`` semantics, +which allows us not to try to guess what's API and what is internall. (I +believe that having explicit split is better than trying to guess it) + +Presenter: +---------- + +XXX: Whatever fits here (use weird imagination) + +Test runner: +------------ + +For test runner we'll use `py.test`_ with some hooks from ``tracer`` to +perform stuff we wan't him to do. Basically we'll run all of the tests +which covers some amount of API (specified explicitly) and gather as much +information as possible. Probably this will need some support for special +calls like ``py.test.fail`` or ``py.test.raises`` to support specific +situations explicitly. + +Tracer: +------- + +This is the most involved (and probably requiring most work) part of +application it should support as-much-operations-as-possible to make +information meaningfull. Mostly: + +* Tracing the information of argument and result types from things + that are performed by API-defined functions (by debugger hook) +* Tracing variables that comes along to make crosslinking between API + possible. +* Various other (possibly endless) stuff that might be performed by + tracing flow graphs. + +Reporter: +--------- + +Reporter is the crucial glue between components. It gathers all the +information actually coming from ``extractor`` and ``tracer`` about +gathered information and tries to present them in an object-oriented +way for any possible backend which might want to actually read it. + +_`py.test` - http://codespeak.net/py/test/ From fijal at codespeak.net Tue Oct 3 17:10:49 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Tue, 3 Oct 2006 17:10:49 +0200 (CEST) Subject: [py-svn] r32846 - py/dist/py/documentation/apigen Message-ID: <20061003151049.956AC10060@code0.codespeak.net> Author: fijal Date: Tue Oct 3 17:10:43 2006 New Revision: 32846 Added: py/dist/py/documentation/apigen/assumptions.txt (contents, props changed) Log: Added document about assumptions. Added: py/dist/py/documentation/apigen/assumptions.txt ============================================================================== --- (empty file) +++ py/dist/py/documentation/apigen/assumptions.txt Tue Oct 3 17:10:43 2006 @@ -0,0 +1,35 @@ + +Automatic API generation assumptions +==================================== + +This document tries to document what assumptions/needs are essential +for automatical generation of API based on unittests. + +XXX This document overlaps a little bit with api-docs.txt, but still +a purpose is slightly different. + +* We use debugger hook to trace certain function calls/returns. Basically + we install global tracing function at point where we call test and we + catch all the calls to functions which are needed by us. + +* API is explicitely fixed (the formats of input should differ, ie. pylib + uses py.__pkg__, while pypy would have some explicit text files/source files + which define API of certain functionallity) + +* Unittests used for doc generation are fixed per API (for pylib it's very + easy - we just specify directory, or collect all the data which py.test + gathers). + +* We have to agree on some format for __doc__ specification. ReST seems + obvious for ReST backend, while different backends might reinterpret it + or even leave it as is. + +* The events about seen stuff (like FunctionCall(args)) goes to flexible + enough object to support possibly any backend. + +* We can use pypy annotation type system to track most-general type + of call, or better steal it as much as possible and define some + on our own. (Like usually it's better to track interface for some + object which was really used, than least basic superclass of arguments + that went inside). + From fijal at codespeak.net Tue Oct 3 18:00:34 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Tue, 3 Oct 2006 18:00:34 +0200 (CEST) Subject: [py-svn] r32851 - py/branch/apigen Message-ID: <20061003160034.032401006E@code0.codespeak.net> Author: fijal Date: Tue Oct 3 18:00:26 2006 New Revision: 32851 Added: py/branch/apigen/ - copied from r32850, py/dist/ Log: Branched for apigen. From fijal at codespeak.net Wed Oct 4 13:48:46 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Wed, 4 Oct 2006 13:48:46 +0200 (CEST) Subject: [py-svn] r32868 - in py/branch/apigen/py: code test/tracer test/tracer/testing Message-ID: <20061004114846.85C7E1009C@code0.codespeak.net> Author: fijal Date: Wed Oct 4 13:48:43 2006 New Revision: 32868 Added: py/branch/apigen/py/test/tracer/ py/branch/apigen/py/test/tracer/__init__.py (contents, props changed) py/branch/apigen/py/test/tracer/testing/ py/branch/apigen/py/test/tracer/testing/test_docgen.py (contents, props changed) py/branch/apigen/py/test/tracer/tracer.py (contents, props changed) Modified: py/branch/apigen/py/code/code.py py/branch/apigen/py/code/frame.py Log: Added simple wrappers. Added first test for type gathering of functions. Need to steal something from pypy annotation suite. Modified: py/branch/apigen/py/code/code.py ============================================================================== --- py/branch/apigen/py/code/code.py (original) +++ py/branch/apigen/py/code/code.py Wed Oct 4 13:48:43 2006 @@ -68,3 +68,7 @@ fullsource = property(fullsource, None, None, "full source containing this code object") + def getargs(self): + # handfull shortcut for getting args + raw = self.raw + return raw.co_varnames[:raw.co_argcount] Modified: py/branch/apigen/py/code/frame.py ============================================================================== --- py/branch/apigen/py/code/frame.py (original) +++ py/branch/apigen/py/code/frame.py Wed Oct 4 13:48:43 2006 @@ -31,5 +31,3 @@ def is_true(self, object): return object - - Added: py/branch/apigen/py/test/tracer/__init__.py ============================================================================== Added: py/branch/apigen/py/test/tracer/testing/test_docgen.py ============================================================================== --- (empty file) +++ py/branch/apigen/py/test/tracer/testing/test_docgen.py Wed Oct 4 13:48:43 2006 @@ -0,0 +1,29 @@ + +""" test doc generation +""" + +import py +import sys + +from py.__.test.tracer.tracer import DocStorage, Tracer + +#def setup_module(mod): +# data_path = py.path.local(mod.__file__).dirpath().join("data") +# sys.path.insert(0, str(data_path)) + +def fun(a, b, c): + return "d" + +def test_basic(): + descs = {"fun":fun} + ds = DocStorage.from_dict(descs) + t = Tracer(ds) + t.start_tracing() + fun(1, ("g", 3), 8) + t.end_tracing() + funcdesc = ds.descs[0] + assert funcdesc.fullname == 'fun' + assert len(funcdesc.args) == 3 + assert funcdesc.args['a']._type is int + assert funcdesc.args['b']._type is tuple + assert funcdesc.args['c']._type is int Added: py/branch/apigen/py/test/tracer/tracer.py ============================================================================== --- (empty file) +++ py/branch/apigen/py/test/tracer/tracer.py Wed Oct 4 13:48:43 2006 @@ -0,0 +1,126 @@ + +""" simple tracer for API generation +""" + +import py +import sys +import types + +# We have to steal as much as possible from union of types +# from pypy's code. +# BLAH BLAH BLAH + +class UnionError(Exception): + pass + +class TypeDesc(object): + def __init__(self, _type): + self._type = _type + + def generalize(self, _type): + if self._type is _type: + return + raise UnionError("%r and %r incompatible" % (self._type, _type)) + +class FuncDesc(object): + """ XXX This is totally random description used for testing. + We'll need descriptions for all tracked object types + """ + def __init__(self, fullname, obj): + self.fullname = fullname + self.obj = obj + self.args = {} + + def check_is(self, frame): + return self.obj.func_code is frame.code.raw + + def __eq__(self, other): + return self.fullname == other.fullname and self.obj is other.obj + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return hash(self.fullname) + + def consider_call(self, frame): + assert isinstance(frame, py.code.Frame) + for arg in frame.code.getargs(): + self.setarg(arg, frame.f_locals[arg]) + + def setarg(self, arg, value): + # set argument annotation + if arg in self.args: + self.args[arg].generalize(type(value)) + else: + self.args[arg] = TypeDesc(type(value)) + +def make_desc(fullname, obj): + # XXX hash it at some point + if type(obj) is types.FunctionType: + return FuncDesc(fullname, obj) + raise NotImplementedError("%r type has no description" % type(obj)) + +class DocStorage(object): + """ Class storing info about API + """ + def __init__(self, descs): + self.descs = descs + + def consider_call(self, frame): + # check if we have available description for that call + self.frame = frame + desc = self.check_desc(frame) + if not desc: + return + desc.consider_call(frame) + + def consider_return(self, frame, arg): + pass + + def check_desc(self, frame): + assert isinstance(frame, py.code.Frame) + for desc in self.descs: + if desc.check_is(frame): + return desc + return None + + def from_pkg(self, module): + """ Initialize DocStorage from module.__package__ + """ + # all elements should be stored in some kind of dictionary. + # they're usually not hashable, so we are creating some + # descriptions of them + descs = [make_desc(key, val) for key, val in + module.__package__.__dict__.iteritems()] + return DocStorage(descs) + from_pkg = staticmethod(from_pkg) + + def from_dict(_dict): + return DocStorage([make_desc(key, val) for key, val in _dict.iteritems()]) + from_dict = staticmethod(from_dict) + +class Tracer(object): + """ Basic tracer object, used for gathering additional info + about API functions + """ + def __init__(self, docstorage): + self.docstorage = docstorage + + def _tracer(self, frame, event, arg): + + # perform actuall tracing + frame = py.code.Frame(frame) + if event == 'call': + assert arg is None + self.docstorage.consider_call(frame) + elif event == 'return': + self.docstorage.consider_return(frame, arg) + + return self._tracer + + def start_tracing(self): + sys.settrace(self._tracer) + + def end_tracing(self): + sys.settrace(None) From fijal at codespeak.net Thu Oct 5 13:22:30 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Thu, 5 Oct 2006 13:22:30 +0200 (CEST) Subject: [py-svn] r32906 - in py/branch/apigen/py/test/tracer: . testing Message-ID: <20061005112230.A6CC210084@code0.codespeak.net> Author: fijal Date: Thu Oct 5 13:22:09 2006 New Revision: 32906 Added: py/branch/apigen/py/test/tracer/description.py (contents, props changed) Modified: py/branch/apigen/py/test/tracer/ (props changed) py/branch/apigen/py/test/tracer/testing/ (props changed) py/branch/apigen/py/test/tracer/testing/test_docgen.py py/branch/apigen/py/test/tracer/tracer.py Log: Added simple test for using pypy's annotation model. Added: py/branch/apigen/py/test/tracer/description.py ============================================================================== --- (empty file) +++ py/branch/apigen/py/test/tracer/description.py Thu Oct 5 13:22:09 2006 @@ -0,0 +1,13 @@ + +from pypy.annotation.description import FunctionDesc +from pypy.annotation.model import unionof + +#class __extend__(FunctionDesc): +def consider_call(self, inputcells): + if not hasattr(self, 'inputcells'): + self.inputcells = inputcells + return + for cell_num, cell in enumerate(inputcells): + self.inputcells[cell_num] = unionof(cell, self.inputcells[cell_num]) + +FunctionDesc.consider_call = consider_call 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 Thu Oct 5 13:22:09 2006 @@ -6,6 +6,7 @@ import sys from py.__.test.tracer.tracer import DocStorage, Tracer +from pypy.annotation import model #def setup_module(mod): # data_path = py.path.local(mod.__file__).dirpath().join("data") @@ -16,14 +17,14 @@ def test_basic(): descs = {"fun":fun} - ds = DocStorage.from_dict(descs) + ds = DocStorage().from_dict(descs) t = Tracer(ds) t.start_tracing() fun(1, ("g", 3), 8) + fun(2., ("a", 1.), "a") t.end_tracing() - funcdesc = ds.descs[0] - assert funcdesc.fullname == 'fun' - assert len(funcdesc.args) == 3 - assert funcdesc.args['a']._type is int - assert funcdesc.args['b']._type is tuple - assert funcdesc.args['c']._type is int + inputcells = ds.descs['fun'].inputcells + assert len(inputcells) == 3 + assert isinstance(inputcells[0], model.SomeFloat) + assert isinstance(inputcells[1], model.SomeTuple) + assert isinstance(inputcells[2], model.SomeObject) Modified: py/branch/apigen/py/test/tracer/tracer.py ============================================================================== --- py/branch/apigen/py/test/tracer/tracer.py (original) +++ py/branch/apigen/py/test/tracer/tracer.py Thu Oct 5 13:22:09 2006 @@ -10,95 +10,54 @@ # from pypy's code. # BLAH BLAH BLAH +from pypy.annotation.bookkeeper import Bookkeeper +from pypy.annotation.policy import AnnotatorPolicy +from py.__.test.tracer.description import FunctionDesc + +class DummyAnnotator(object): + policy = AnnotatorPolicy() + class UnionError(Exception): pass -class TypeDesc(object): - def __init__(self, _type): - self._type = _type - - def generalize(self, _type): - if self._type is _type: - return - raise UnionError("%r and %r incompatible" % (self._type, _type)) - -class FuncDesc(object): - """ XXX This is totally random description used for testing. - We'll need descriptions for all tracked object types - """ - def __init__(self, fullname, obj): - self.fullname = fullname - self.obj = obj - self.args = {} - - def check_is(self, frame): - return self.obj.func_code is frame.code.raw - - def __eq__(self, other): - return self.fullname == other.fullname and self.obj is other.obj - - def __ne__(self, other): - return not self.__eq__(other) - - def __hash__(self): - return hash(self.fullname) - - def consider_call(self, frame): - assert isinstance(frame, py.code.Frame) - for arg in frame.code.getargs(): - self.setarg(arg, frame.f_locals[arg]) - - def setarg(self, arg, value): - # set argument annotation - if arg in self.args: - self.args[arg].generalize(type(value)) - else: - self.args[arg] = TypeDesc(type(value)) - -def make_desc(fullname, obj): - # XXX hash it at some point - if type(obj) is types.FunctionType: - return FuncDesc(fullname, obj) - raise NotImplementedError("%r type has no description" % type(obj)) - class DocStorage(object): """ Class storing info about API """ - def __init__(self, descs): - self.descs = descs + def __init__(self): + self.bookkeeper = Bookkeeper(DummyAnnotator()) + #self.call_stack = [] def consider_call(self, frame): - # check if we have available description for that call - self.frame = frame - desc = self.check_desc(frame) - if not desc: - return - desc.consider_call(frame) - + assert isinstance(frame, py.code.Frame) + desc = self.find_desc(frame) + if desc: + self.generalize_args(desc, frame) + + def generalize_args(self, desc, frame): + args = [arg for key, arg in frame.getargs()] + #self.call_stack.append((desc, args)) + desc.consider_call([self.bookkeeper.immutablevalue(arg) for arg in args]) + def consider_return(self, frame, arg): pass - - def check_desc(self, frame): - assert isinstance(frame, py.code.Frame) - for desc in self.descs: - if desc.check_is(frame): + + def find_desc(self, frame): + for desc in self.descs.values(): + if desc.pyobj.func_code is frame.code.raw: return desc return None + def from_dict(self, _dict): + self.descs = dict([self.make_desc(key, val) for key, val in _dict.iteritems()]) + return self + + def make_desc(self, key, value): + desc = self.bookkeeper.getdesc(value) + return (key, desc) + def from_pkg(self, module): - """ Initialize DocStorage from module.__package__ - """ - # all elements should be stored in some kind of dictionary. - # they're usually not hashable, so we are creating some - # descriptions of them - descs = [make_desc(key, val) for key, val in - module.__package__.__dict__.iteritems()] - return DocStorage(descs) - from_pkg = staticmethod(from_pkg) - - def from_dict(_dict): - return DocStorage([make_desc(key, val) for key, val in _dict.iteritems()]) - from_dict = staticmethod(from_dict) + self.from_dict(module.__package__.__dict__) + return self class Tracer(object): """ Basic tracer object, used for gathering additional info From fijal at codespeak.net Thu Oct 5 13:57:03 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Thu, 5 Oct 2006 13:57:03 +0200 (CEST) Subject: [py-svn] r32907 - in py/branch/apigen/py/test/tracer: . testing Message-ID: <20061005115703.F12A310088@code0.codespeak.net> Author: fijal Date: Thu Oct 5 13:56:38 2006 New Revision: 32907 Added: py/branch/apigen/py/test/tracer/docstorage.py (contents, props changed) Modified: py/branch/apigen/py/test/tracer/description.py py/branch/apigen/py/test/tracer/testing/test_docgen.py py/branch/apigen/py/test/tracer/tracer.py Log: A little bit refactoring, added (untested) accessor, added call_site 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 Thu Oct 5 13:56:38 2006 @@ -2,6 +2,12 @@ from pypy.annotation.description import FunctionDesc from pypy.annotation.model import unionof +class CallSite(object): + def __init__(self, filename, lineno, source): + self.filename = filename + self.lineno = lineno + self.source = source + #class __extend__(FunctionDesc): def consider_call(self, inputcells): if not hasattr(self, 'inputcells'): @@ -10,4 +16,10 @@ for cell_num, cell in enumerate(inputcells): self.inputcells[cell_num] = unionof(cell, self.inputcells[cell_num]) +def consider_call_site(self, frame): + 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))) + FunctionDesc.consider_call = consider_call +FunctionDesc.consider_call_site = consider_call_site Added: py/branch/apigen/py/test/tracer/docstorage.py ============================================================================== --- (empty file) +++ py/branch/apigen/py/test/tracer/docstorage.py Thu Oct 5 13:56:38 2006 @@ -0,0 +1,77 @@ + +""" This module is keeping track about API informations as well as +providing some interface to easily access stored data +""" + +import py +import sys + +from pypy.annotation.bookkeeper import Bookkeeper +from pypy.annotation.policy import AnnotatorPolicy + +class DummyAnnotator(object): + policy = AnnotatorPolicy() + +class DocStorage(object): + """ Class storing info about API + """ + def __init__(self): + self.bookkeeper = Bookkeeper(DummyAnnotator()) + #self.call_stack = [] + + def consider_call(self, frame, caller_frame): + assert isinstance(frame, py.code.Frame) + desc = self.find_desc(frame) + if desc: + self.generalize_args(desc, frame) + desc.consider_call_site(caller_frame) + + def generalize_args(self, desc, frame): + args = [arg for key, arg in frame.getargs()] + #self.call_stack.append((desc, args)) + desc.consider_call([self.bookkeeper.immutablevalue(arg) for arg in args]) + + def consider_return(self, frame, arg): + 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 + + def from_dict(self, _dict): + self.descs = dict([self.make_desc(key, val) for key, val in _dict.iteritems()]) + return self + + def make_desc(self, key, value): + desc = self.bookkeeper.getdesc(value) + return (key, desc) + + def from_pkg(self, module): + self.from_dict(module.__package__.__dict__) + return self + +class DocStorageAccessor(object): + """ Set of helper functions to access DocStorage, separated in different + class to keep abstraction + """ + def __init__(self, ds): + self.ds = ds + + def get_names(self): + # 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_doc(self, name): + return self.ds.descs[name].pyobj.__doc__ or "*Not documented*" + + def get_function_args(self, name): + return self.ds.descs[name].inputcells + + def get_function_callpoints(self, name): + # return list of tuple (filename, fileline, line) + return self.ds.descs[name].call_sites 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 Thu Oct 5 13:56:38 2006 @@ -13,6 +13,7 @@ # sys.path.insert(0, str(data_path)) def fun(a, b, c): + "Some docstring" return "d" def test_basic(): @@ -23,8 +24,15 @@ fun(1, ("g", 3), 8) fun(2., ("a", 1.), "a") t.end_tracing() - inputcells = ds.descs['fun'].inputcells + desc = ds.descs['fun'] + inputcells = desc.inputcells assert len(inputcells) == 3 assert isinstance(inputcells[0], model.SomeFloat) assert isinstance(inputcells[1], model.SomeTuple) assert isinstance(inputcells[2], model.SomeObject) + cs = desc.call_sites + assert len(cs) == 2 + assert cs[0].filename == __file__ + assert cs[0].lineno == test_basic.func_code.co_firstlineno + 5 + assert cs[1].filename == __file__ + assert cs[1].lineno == test_basic.func_code.co_firstlineno + 6 Modified: py/branch/apigen/py/test/tracer/tracer.py ============================================================================== --- py/branch/apigen/py/test/tracer/tracer.py (original) +++ py/branch/apigen/py/test/tracer/tracer.py Thu Oct 5 13:56:38 2006 @@ -10,55 +10,12 @@ # from pypy's code. # BLAH BLAH BLAH -from pypy.annotation.bookkeeper import Bookkeeper -from pypy.annotation.policy import AnnotatorPolicy from py.__.test.tracer.description import FunctionDesc - -class DummyAnnotator(object): - policy = AnnotatorPolicy() +from py.__.test.tracer.docstorage import DocStorage class UnionError(Exception): pass -class DocStorage(object): - """ Class storing info about API - """ - def __init__(self): - self.bookkeeper = Bookkeeper(DummyAnnotator()) - #self.call_stack = [] - - def consider_call(self, frame): - assert isinstance(frame, py.code.Frame) - desc = self.find_desc(frame) - if desc: - self.generalize_args(desc, frame) - - def generalize_args(self, desc, frame): - args = [arg for key, arg in frame.getargs()] - #self.call_stack.append((desc, args)) - desc.consider_call([self.bookkeeper.immutablevalue(arg) for arg in args]) - - def consider_return(self, frame, arg): - 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 - - def from_dict(self, _dict): - self.descs = dict([self.make_desc(key, val) for key, val in _dict.iteritems()]) - return self - - def make_desc(self, key, value): - desc = self.bookkeeper.getdesc(value) - return (key, desc) - - def from_pkg(self, module): - self.from_dict(module.__package__.__dict__) - return self - class Tracer(object): """ Basic tracer object, used for gathering additional info about API functions @@ -72,7 +29,7 @@ frame = py.code.Frame(frame) if event == 'call': assert arg is None - self.docstorage.consider_call(frame) + self.docstorage.consider_call(frame, py.code.Frame(sys._getframe(2))) elif event == 'return': self.docstorage.consider_return(frame, arg) From fijal at codespeak.net Thu Oct 5 13:58:19 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Thu, 5 Oct 2006 13:58:19 +0200 (CEST) Subject: [py-svn] r32909 - py/branch/apigen/py/code Message-ID: <20061005115819.F24831008A@code0.codespeak.net> Author: fijal Date: Thu Oct 5 13:58:08 2006 New Revision: 32909 Modified: py/branch/apigen/py/code/frame.py Log: Helper. Modified: py/branch/apigen/py/code/frame.py ============================================================================== --- py/branch/apigen/py/code/frame.py (original) +++ py/branch/apigen/py/code/frame.py Thu Oct 5 13:58:08 2006 @@ -31,3 +31,9 @@ def is_true(self, object): return object + + def getargs(self): + retval = [] + for arg in self.code.getargs(): + retval.append((arg, self.f_locals[arg])) + return retval From fijal at codespeak.net Thu Oct 5 14:19:34 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Thu, 5 Oct 2006 14:19:34 +0200 (CEST) Subject: [py-svn] r32910 - in py/branch/apigen/py/test/tracer: . testing Message-ID: <20061005121934.776271008E@code0.codespeak.net> Author: fijal Date: Thu Oct 5 14:19:12 2006 New Revision: 32910 Added: py/branch/apigen/py/test/tracer/genrest.py (contents, props changed) Modified: py/branch/apigen/py/test/tracer/description.py py/branch/apigen/py/test/tracer/docstorage.py py/branch/apigen/py/test/tracer/testing/test_docgen.py Log: First version of rest generator. Intermediate checkin (battery dying), Need rest fixes and test for that. 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 Thu Oct 5 14:19:12 2006 @@ -7,6 +7,9 @@ self.filename = filename self.lineno = lineno self.source = source + + def get_tuple(self): + return self.filename, self.lineno, self.source #class __extend__(FunctionDesc): def consider_call(self, inputcells): 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 Thu Oct 5 14:19:12 2006 @@ -49,6 +49,7 @@ return (key, desc) def from_pkg(self, module): + self.module = module self.from_dict(module.__package__.__dict__) return self @@ -75,3 +76,15 @@ def get_function_callpoints(self, name): # return list of tuple (filename, fileline, line) return self.ds.descs[name].call_sites + + def get_module_info(self): + module = getattr(self.ds, 'module', None) + if module is None: + return "Lack of module info" + try: + retval = module.__doc__ or "*undocumented*" + retval = module.__package__.description + retval = module.__package__.long_description + except AttributeError: + pass + return retval Added: py/branch/apigen/py/test/tracer/genrest.py ============================================================================== --- (empty file) +++ py/branch/apigen/py/test/tracer/genrest.py Thu Oct 5 14:19:12 2006 @@ -0,0 +1,36 @@ + +""" Generating ReST output (raw, not python) +out of data that we know about function calls +""" + +import sys + +from py.__.test.tracer.docstorage import DocStorageAccessor + +class RestGen(object): + def __init__(self, ds, output=sys.stdout): + self.dsa = DocStorageAccessor(ds) + self.output = output + + def writeline(self, data=""): + self.output.write(data + "\n") + + def write(self): + self.writeline("=======") + self.writeline(self.dsa.get_module_info()) + self.writeline("=======") + self.writeline() + self.writeline("Functions exported:") + self.writeline("===================") + for key in self.dsa.get_names(): + self.writeline() + self.writeline("%s description:" % key) + self.writeline("-------") + self.writeline(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:") + for call_site in self.dsa.get_function_callpoints(key): + self.writeline("File %s:%s\n%s" % call_site.get_tuple()) 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 Thu Oct 5 14:19:12 2006 @@ -32,7 +32,10 @@ assert isinstance(inputcells[2], model.SomeObject) cs = desc.call_sites assert len(cs) == 2 - assert cs[0].filename == __file__ + f_name = __file__ + if f_name.endswith('.pyc'): + f_name = f_name[:-1] + assert cs[0].filename == f_name assert cs[0].lineno == test_basic.func_code.co_firstlineno + 5 - assert cs[1].filename == __file__ + assert cs[1].filename == f_name assert cs[1].lineno == test_basic.func_code.co_firstlineno + 6 From fijal at codespeak.net Thu Oct 5 20:16:20 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Thu, 5 Oct 2006 20:16:20 +0200 (CEST) Subject: [py-svn] r32917 - in py/branch/apigen/py/test/tracer: . testing Message-ID: <20061005181620.348DB1008D@code0.codespeak.net> Author: fijal Date: Thu Oct 5 20:16:18 2006 New Revision: 32917 Modified: py/branch/apigen/py/test/tracer/description.py py/branch/apigen/py/test/tracer/genrest.py py/branch/apigen/py/test/tracer/testing/test_docgen.py Log: Make use of __extend__ (looks cleaner than adding new functions) 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 Thu Oct 5 20:16:18 2006 @@ -11,18 +11,15 @@ def get_tuple(self): return self.filename, self.lineno, self.source -#class __extend__(FunctionDesc): -def consider_call(self, inputcells): - if not hasattr(self, 'inputcells'): - self.inputcells = inputcells - return - for cell_num, cell in enumerate(inputcells): - self.inputcells[cell_num] = unionof(cell, self.inputcells[cell_num]) +class __extend__(FunctionDesc): + def consider_call(self, inputcells): + if not hasattr(self, 'inputcells'): + self.inputcells = inputcells + return + for cell_num, cell in enumerate(inputcells): + self.inputcells[cell_num] = unionof(cell, self.inputcells[cell_num]) -def consider_call_site(self, frame): - 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))) - -FunctionDesc.consider_call = consider_call -FunctionDesc.consider_call_site = consider_call_site + def consider_call_site(self, frame): + 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))) 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 Thu Oct 5 20:16:18 2006 @@ -6,6 +6,7 @@ import sys from py.__.test.tracer.docstorage import DocStorageAccessor +from py.__.rest import rst class RestGen(object): def __init__(self, ds, output=sys.stdout): 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 Thu Oct 5 20:16:18 2006 @@ -39,3 +39,4 @@ assert cs[0].lineno == test_basic.func_code.co_firstlineno + 5 assert cs[1].filename == f_name assert cs[1].lineno == test_basic.func_code.co_firstlineno + 6 + From fijal at codespeak.net Thu Oct 5 22:16:07 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Thu, 5 Oct 2006 22:16:07 +0200 (CEST) Subject: [py-svn] r32920 - in py/branch/apigen/py/rst: . testing Message-ID: <20061005201607.9E45710097@code0.codespeak.net> Author: fijal Date: Thu Oct 5 22:16:05 2006 New Revision: 32920 Added: py/branch/apigen/py/rst/ (props changed) py/branch/apigen/py/rst/__init__.py (contents, props changed) py/branch/apigen/py/rst/rst.py (contents, props changed) py/branch/apigen/py/rst/testing/ (props changed) py/branch/apigen/py/rst/testing/__init__.py (contents, props changed) py/branch/apigen/py/rst/testing/test_rst.py (contents, props changed) Log: Added first version of simple ReST producer. I think is way easier to use than py.rest (altough who knows?) Added: py/branch/apigen/py/rst/__init__.py ============================================================================== Added: py/branch/apigen/py/rst/rst.py ============================================================================== --- (empty file) +++ py/branch/apigen/py/rst/rst.py Thu Oct 5 22:16:05 2006 @@ -0,0 +1,113 @@ + +""" reStructuredText manipulation tools +""" + +from __future__ import generators + +import py + +class RestError(Exception): + pass + +class AbstractMetaclass(type): + def __new__(cls, *args): + obj = super(AbstractMetaclass, cls).__new__(cls, *args) + parent_cls = obj.parentclass + if parent_cls is not None: + if parent_cls.allowed_child: + parent_cls.allowed_child = {obj:True} + else: + parent_cls.allowed_child[obj] = True + return obj + +class AbstractNode(object): + """ Basic class implementing writing rest code + """ + sep = '' + __metaclass__ = AbstractMetaclass + parentclass = None # this exists to allow parent to know what + # children can exist + allowed_child = {} + defaults = {} + + def __init__(self, *args, **kwargs): + self.parent = None + self.childs = args + for arg in kwargs: + setattr(self, arg, kwargs[arg]) + + def join(self, child): + self._add(child) + return self + + def add(self, child): + self._add(child) + return child + + def _add(self, child): + if child.__class__ not in self.allowed_child: + raise RestError("%r cannot be child of %r" % \ + (child.__class__, self.__class__)) + self.childs.append(child) + child.parent = self + + def __getitem__(self, item): + return self.childs[item] + + def __setitem__(self, item, value): + self.childs[item] = value + + def text(self): + return self.sep.join([child.text() for child in self.childs]) + + def wordlist(self): + return [self.text()] + +class Rest(AbstractNode): + sep = "\n\n" + +class Paragraph(AbstractNode): + parentclass = Rest + sep = " " + indent = "" + width = 80 + + def text(self): + texts = [] + for child in self.childs: + texts += child.wordlist() + + buf = [] + outcome = [] + lgt = len(self.indent) + + def grab(buf): + outcome.append(self.indent + self.sep.join(buf)) + + while texts: + next = texts[-1] + if lgt + len(self.sep) + len(next) <= self.width or not buf: + buf.append(next) + lgt += len(next) + len(self.sep) + texts.pop() + else: + grab(buf) + lgt = len(self.indent) + buf = [] + grab(buf) + outcome.reverse() + return "\n".join(outcome) +## +class SubParagraph(Paragraph): + indent = " " + +class Text(AbstractNode): + parentclass = Paragraph + def __init__(self, _text): + self._text = _text + + def text(self): + return self._text + + def wordlist(self): + return self._text.split(" ") Added: py/branch/apigen/py/rst/testing/__init__.py ============================================================================== Added: py/branch/apigen/py/rst/testing/test_rst.py ============================================================================== --- (empty file) +++ py/branch/apigen/py/rst/testing/test_rst.py Thu Oct 5 22:16:05 2006 @@ -0,0 +1,15 @@ + +""" rst generation tests +""" + +from py.__.rst.rst import * + +def test_textgen(): + assert Rest(Paragraph(Text("dupa"))).text() == "dupa" + assert Rest(Paragraph(Text("dupa"), Text("dupa"))).text() == "dupa dupa" + assert Rest(Paragraph(Text("a")), Paragraph(Text("b"))).text() == "a\n\nb" + assert Rest(Paragraph(Text("a"), indent=" ")).text() == " a" + +def test_join(): + txt = Rest(Paragraph(Text("a b c d e f"), width=3, indent=" ")).text() + assert txt == ' a\n b\n c\n d\n e\n f' From fijal at codespeak.net Fri Oct 6 11:20:54 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Fri, 6 Oct 2006 11:20:54 +0200 (CEST) Subject: [py-svn] r32943 - in py/branch/apigen/py/rst: . testing Message-ID: <20061006092054.BE8B31009C@code0.codespeak.net> Author: fijal Date: Fri Oct 6 11:20:51 2006 New Revision: 32943 Modified: py/branch/apigen/py/rst/rst.py py/branch/apigen/py/rst/testing/test_rst.py Log: Added blockquote. Modified: py/branch/apigen/py/rst/rst.py ============================================================================== --- py/branch/apigen/py/rst/rst.py (original) +++ py/branch/apigen/py/rst/rst.py Fri Oct 6 11:20:51 2006 @@ -65,12 +65,29 @@ class Rest(AbstractNode): sep = "\n\n" + + def text(self): + outcome = [] + for child in self.childs: + if child.previous_paragraph and len(outcome): + outcome[-1] += child.previous_paragraph + outcome.append(child.text()) + return self.sep.join(outcome) + "\n" # trailing newline class Paragraph(AbstractNode): parentclass = Rest sep = " " indent = "" width = 80 + previous_paragraph = "" + + def __init__(self, *args, **kwargs): + # make shortcut + args = list(args) + for num, arg in enumerate(args): + if isinstance(arg, str): + args[num] = Text(arg) + super(Paragraph, self).__init__(*args, **kwargs) def text(self): texts = [] @@ -97,17 +114,48 @@ grab(buf) outcome.reverse() return "\n".join(outcome) -## + class SubParagraph(Paragraph): indent = " " + +class BlockQuote(Paragraph): + indent = " " + previous_paragraph = "::" + sep = "" + + def text(self): + all_txt = AbstractNode.text(self) + all_txts = all_txt.split("\n") + return "\n".join([self.indent + i for i in all_txts]) -class Text(AbstractNode): +class AbstractText(AbstractNode): parentclass = Paragraph + start = "" + end = "" def __init__(self, _text): self._text = _text def text(self): - return self._text + return self.start + self._text + self.end +class Text(AbstractText): def wordlist(self): return self._text.split(" ") + +class Emph(AbstractText): + start = "*" + end = "*" + +class Title(AbstractNode): + parentclass = Rest + belowchar = "" + abovechar = "" + + def text(self): + txt = AbstractNode.text(self) + lines = [] + if abovechar: + lines.append(abovechar * len(txt)) + lines.append(txt) + if belowchar: + lines.append(belowchar * len(txt)) Modified: py/branch/apigen/py/rst/testing/test_rst.py ============================================================================== --- py/branch/apigen/py/rst/testing/test_rst.py (original) +++ py/branch/apigen/py/rst/testing/test_rst.py Fri Oct 6 11:20:51 2006 @@ -5,11 +5,23 @@ from py.__.rst.rst import * def test_textgen(): - assert Rest(Paragraph(Text("dupa"))).text() == "dupa" - assert Rest(Paragraph(Text("dupa"), Text("dupa"))).text() == "dupa dupa" - assert Rest(Paragraph(Text("a")), Paragraph(Text("b"))).text() == "a\n\nb" - assert Rest(Paragraph(Text("a"), indent=" ")).text() == " a" + assert Rest(Paragraph(Text("dupa"))).text() == "dupa\n" + assert Rest(Paragraph(Text("dupa"), Text("dupa"))).text() == "dupa dupa\n" + assert Rest(Paragraph(Text("a")), Paragraph(Text("b"))).text() == "a\n\nb\n" + assert Rest(Paragraph(Text("a"), indent=" ")).text() == " a\n" def test_join(): txt = Rest(Paragraph(Text("a b c d e f"), width=3, indent=" ")).text() - assert txt == ' a\n b\n c\n d\n e\n f' + assert txt == ' a\n b\n c\n d\n e\n f\n' + +def test_blockquote(): + expected = """Text:: + + def fun(): + some + +Paragraph +""" + txt = Rest(Paragraph("Text"), BlockQuote("def fun():\n some"), \ + Paragraph("Paragraph")).text() + assert txt == expected From guido at codespeak.net Fri Oct 6 11:43:56 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Fri, 6 Oct 2006 11:43:56 +0200 (CEST) Subject: [py-svn] r32944 - py/dist/py/path/svn/testing Message-ID: <20061006094356.11E7910095@code0.codespeak.net> Author: guido Date: Fri Oct 6 11:43:54 2006 New Revision: 32944 Modified: py/dist/py/path/svn/testing/test_urlcommand.py py/dist/py/path/svn/testing/test_wccommand.py Log: Added some broken tests (currently disabled) to mark problems with character handling in both svn urls and working copies. Modified: py/dist/py/path/svn/testing/test_urlcommand.py ============================================================================== --- py/dist/py/path/svn/testing/test_urlcommand.py (original) +++ py/dist/py/path/svn/testing/test_urlcommand.py Fri Oct 6 11:43:54 2006 @@ -12,9 +12,28 @@ repo, wc = getrepowc() cls.root = py.path.svnurl(repo) - def test_svnurl_characters(self): - test1 = py.path.svnurl("svn+ssh://hello/world") + def test_svnurl_characters_simple(self): + py.path.svnurl("svn+ssh://hello/world") + + def test_svnurl_characters_at_user(self): + py.test.skip('XXX fix me') + py.path.svnurl("http://user at host.com/some/dir") + + def test_svnurl_characters_at_path(self): + py.test.raises(ValueError, 'py.path.svnurl("http://host.com/foo at bar")') + + def test_svnurl_characters_colon_port(self): + py.test.skip('XXX fix me') + py.path.svnurl("http://host.com:8080/some/dir") + + def test_svnurl_characters_colon_path(self): + py.test.raises(ValueError, 'py.path.svnurl("http://host.com/foo:bar")') + def test_svnurl_characters_tilde_end(self): + py.test.skip('XXX fix me') + py.path.svnurl("http://host.com/some/file~") + + # XXX def xtest_copy_file(self): raise py.test.Skipped(msg="XXX fix svnurl first") @@ -29,7 +48,6 @@ t = gmtime(res[0].date) assert t.tm_year == 2003 and t.tm_mon == 7 and t.tm_mday == 17 - class TestSvnInfoCommand: def test_svn_1_2(self): Modified: py/dist/py/path/svn/testing/test_wccommand.py ============================================================================== --- py/dist/py/path/svn/testing/test_wccommand.py (original) +++ py/dist/py/path/svn/testing/test_wccommand.py Fri Oct 6 11:43:54 2006 @@ -299,5 +299,9 @@ def test_info(self): self.wc.info().rev = 0 -def test_badchars(): +def test_characters_at(): py.test.raises(ValueError, "py.path.svnwc('/tmp/@@@:')") + +def test_characters_tilde(): + py.test.skip('XXX fix me') + py.path.svnwc('/tmp/test~') From cfbolz at codespeak.net Fri Oct 6 11:51:11 2006 From: cfbolz at codespeak.net (cfbolz at codespeak.net) Date: Fri, 6 Oct 2006 11:51:11 +0200 (CEST) Subject: [py-svn] r32946 - in py/dist/py/path/svn: . testing Message-ID: <20061006095111.75ABE10098@code0.codespeak.net> Author: cfbolz Date: Fri Oct 6 11:51:09 2006 New Revision: 32946 Modified: py/dist/py/path/svn/svncommon.py py/dist/py/path/svn/testing/svntestbase.py Log: add ~ as an allowed character Modified: py/dist/py/path/svn/svncommon.py ============================================================================== --- py/dist/py/path/svn/svncommon.py (original) +++ py/dist/py/path/svn/svncommon.py Fri Oct 6 11:51:09 2006 @@ -5,7 +5,7 @@ import py from py.__.path import common -ALLOWED_CHARS = "_ -/\\=$." #add characters as necessary when tested +ALLOWED_CHARS = "_ -/\\=$.~" #add characters as necessary when tested if sys.platform == "win32": ALLOWED_CHARS += ":" Modified: py/dist/py/path/svn/testing/svntestbase.py ============================================================================== --- py/dist/py/path/svn/testing/svntestbase.py (original) +++ py/dist/py/path/svn/testing/svntestbase.py Fri Oct 6 11:51:09 2006 @@ -10,7 +10,7 @@ # make a wc directory out of a given root url # cache previously obtained wcs! # -def getrepowc(reponame='abc$aaa', wcname='wc'): +def getrepowc(reponame='a~bc$aaa~', wcname='wc'): repo = py.test.ensuretemp(reponame) wcdir = py.test.ensuretemp(wcname) if not repo.listdir(): From guido at codespeak.net Fri Oct 6 11:55:28 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Fri, 6 Oct 2006 11:55:28 +0200 (CEST) Subject: [py-svn] r32947 - py/dist/py/path/svn/testing Message-ID: <20061006095528.8E68310098@code0.codespeak.net> Author: guido Date: Fri Oct 6 11:55:27 2006 New Revision: 32947 Modified: py/dist/py/path/svn/testing/test_urlcommand.py py/dist/py/path/svn/testing/test_wccommand.py Log: 'Unskipping' tilde character in file name tests, as cfbolz just fixed the problem. Modified: py/dist/py/path/svn/testing/test_urlcommand.py ============================================================================== --- py/dist/py/path/svn/testing/test_urlcommand.py (original) +++ py/dist/py/path/svn/testing/test_urlcommand.py Fri Oct 6 11:55:27 2006 @@ -30,7 +30,6 @@ py.test.raises(ValueError, 'py.path.svnurl("http://host.com/foo:bar")') def test_svnurl_characters_tilde_end(self): - py.test.skip('XXX fix me') py.path.svnurl("http://host.com/some/file~") # XXX Modified: py/dist/py/path/svn/testing/test_wccommand.py ============================================================================== --- py/dist/py/path/svn/testing/test_wccommand.py (original) +++ py/dist/py/path/svn/testing/test_wccommand.py Fri Oct 6 11:55:27 2006 @@ -303,5 +303,4 @@ py.test.raises(ValueError, "py.path.svnwc('/tmp/@@@:')") def test_characters_tilde(): - py.test.skip('XXX fix me') py.path.svnwc('/tmp/test~') From fijal at codespeak.net Fri Oct 6 12:00:17 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Fri, 6 Oct 2006 12:00:17 +0200 (CEST) Subject: [py-svn] r32948 - in py/branch/apigen/py/rst: . testing Message-ID: <20061006100017.C912F10098@code0.codespeak.net> Author: fijal Date: Fri Oct 6 12:00:15 2006 New Revision: 32948 Modified: py/branch/apigen/py/rst/rst.py py/branch/apigen/py/rst/testing/test_rst.py Log: Added Link, fixed some issues with parentclass Modified: py/branch/apigen/py/rst/rst.py ============================================================================== --- py/branch/apigen/py/rst/rst.py (original) +++ py/branch/apigen/py/rst/rst.py Fri Oct 6 12:00:15 2006 @@ -13,11 +13,18 @@ def __new__(cls, *args): obj = super(AbstractMetaclass, cls).__new__(cls, *args) parent_cls = obj.parentclass - if parent_cls is not None: - if parent_cls.allowed_child: - parent_cls.allowed_child = {obj:True} + if parent_cls is None: + return obj + if not isinstance(parent_cls, list): + class_list = [parent_cls] + else: + class_list = parent_cls + + for _class in class_list: + if not _class.allowed_child: + _class.allowed_child = {obj:True} else: - parent_cls.allowed_child[obj] = True + _class.allowed_child[obj] = True return obj class AbstractNode(object): @@ -32,7 +39,9 @@ def __init__(self, *args, **kwargs): self.parent = None - self.childs = args + self.childs = [] + for child in args: + self._add(child) for arg in kwargs: setattr(self, arg, kwargs[arg]) @@ -65,14 +74,28 @@ class Rest(AbstractNode): sep = "\n\n" + def __init__(self, *args, **kwargs): + AbstractNode.__init__(self, *args, **kwargs) + self.links = {} + def render_links(self, check=False): + assert not check, "Link checking not implemented" + if not self.links: + return "" + link_texts = [] + for link, target in self.links.iteritems(): + link_texts.append(".. _`%s`: %s" % (link, target)) + return "\n".join(link_texts) + "\n" + def text(self): outcome = [] for child in self.childs: if child.previous_paragraph and len(outcome): outcome[-1] += child.previous_paragraph outcome.append(child.text()) - return self.sep.join(outcome) + "\n" # trailing newline + + text = self.sep.join(outcome) + "\n" # trailing newline + return text + self.render_links() class Paragraph(AbstractNode): parentclass = Rest @@ -128,8 +151,24 @@ all_txts = all_txt.split("\n") return "\n".join([self.indent + i for i in all_txts]) +class Title(AbstractNode): + parentclass = Rest + belowchar = "" + abovechar = "" + previous_paragraph = None + + def text(self): + txt = AbstractNode.text(self) + lines = [] + if self.abovechar: + lines.append(self.abovechar * len(txt)) + lines.append(txt) + if self.belowchar: + lines.append(self.belowchar * len(txt)) + return "\n".join(lines) + class AbstractText(AbstractNode): - parentclass = Paragraph + parentclass = [Paragraph, Title] start = "" end = "" def __init__(self, _text): @@ -146,16 +185,24 @@ start = "*" end = "*" -class Title(AbstractNode): - parentclass = Rest - belowchar = "" - abovechar = "" +class Link(AbstractText): + start = '`' + end = '`_' + + def __init__(self, _text, target): + self._text = _text + self.target = target + self.rest = None def text(self): - txt = AbstractNode.text(self) - lines = [] - if abovechar: - lines.append(abovechar * len(txt)) - lines.append(txt) - if belowchar: - lines.append(belowchar * len(txt)) + if self.rest is None: + self.rest = self.find_rest() + self.rest.links[self._text] = self.target + return AbstractText.text(self) + + def find_rest(self): + # XXX little overkill, but who cares... + next = self + while next.parent is not None: + next = next.parent + return next Modified: py/branch/apigen/py/rst/testing/test_rst.py ============================================================================== --- py/branch/apigen/py/rst/testing/test_rst.py (original) +++ py/branch/apigen/py/rst/testing/test_rst.py Fri Oct 6 12:00:15 2006 @@ -25,3 +25,13 @@ txt = Rest(Paragraph("Text"), BlockQuote("def fun():\n some"), \ Paragraph("Paragraph")).text() assert txt == expected + +def test_title(): + assert Rest(Title(Text("Some title"), belowchar="=")).text() == \ + "Some title\n==========\n" + assert Rest(Title(Text("Some title"), belowchar="#", abovechar="#")).text() \ + == "##########\nSome title\n##########\n" + +def test_link(): + expected = "`some link`_\n.. _`some link`: http://codespeak.net\n" + txt = Rest(Paragraph(Link("some link", "http://codespeak.net"))).text() From fijal at codespeak.net Fri Oct 6 12:01:37 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Fri, 6 Oct 2006 12:01:37 +0200 (CEST) Subject: [py-svn] r32949 - in py/dist/py/rst: . testing Message-ID: <20061006100137.75E2710098@code0.codespeak.net> Author: fijal Date: Fri Oct 6 12:01:35 2006 New Revision: 32949 Added: py/dist/py/rst/ - copied from r32920, py/branch/apigen/py/rst/ py/dist/py/rst/rst.py - copied unchanged from r32948, py/branch/apigen/py/rst/rst.py py/dist/py/rst/testing/test_rst.py - copied unchanged from r32948, py/branch/apigen/py/rst/testing/test_rst.py Log: Copied from a branch... From guido at codespeak.net Fri Oct 6 14:35:55 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Fri, 6 Oct 2006 14:35:55 +0200 (CEST) Subject: [py-svn] r32954 - in py/dist/py/path/svn: . testing Message-ID: <20061006123555.223AD1006F@code0.codespeak.net> Author: guido Date: Fri Oct 6 14:35:53 2006 New Revision: 32954 Modified: py/dist/py/path/svn/svncommon.py py/dist/py/path/svn/testing/test_urlcommand.py py/dist/py/path/svn/urlcommand.py py/dist/py/path/svn/wccommand.py Log: Fixed support for @ and : in the host part of the URL, while disallowing it in the path part. Modified: py/dist/py/path/svn/svncommon.py ============================================================================== --- py/dist/py/path/svn/svncommon.py (original) +++ py/dist/py/path/svn/svncommon.py Fri Oct 6 14:35:53 2006 @@ -8,6 +8,7 @@ ALLOWED_CHARS = "_ -/\\=$.~" #add characters as necessary when tested if sys.platform == "win32": ALLOWED_CHARS += ":" +ALLOWED_CHARS_HOST = ALLOWED_CHARS + '@:' def _getsvnversion(ver=[]): try: @@ -25,12 +26,11 @@ text = str(text).replace('$', '\\$') return text - -def _check_for_bad_chars(text): +def _check_for_bad_chars(text, allowed_chars=ALLOWED_CHARS): for c in str(text): if c.isalnum(): continue - if c in ALLOWED_CHARS: + if c in allowed_chars: continue return True return False Modified: py/dist/py/path/svn/testing/test_urlcommand.py ============================================================================== --- py/dist/py/path/svn/testing/test_urlcommand.py (original) +++ py/dist/py/path/svn/testing/test_urlcommand.py Fri Oct 6 14:35:53 2006 @@ -16,14 +16,12 @@ py.path.svnurl("svn+ssh://hello/world") def test_svnurl_characters_at_user(self): - py.test.skip('XXX fix me') py.path.svnurl("http://user at host.com/some/dir") def test_svnurl_characters_at_path(self): py.test.raises(ValueError, 'py.path.svnurl("http://host.com/foo at bar")') def test_svnurl_characters_colon_port(self): - py.test.skip('XXX fix me') py.path.svnurl("http://host.com:8080/some/dir") def test_svnurl_characters_colon_path(self): Modified: py/dist/py/path/svn/urlcommand.py ============================================================================== --- py/dist/py/path/svn/urlcommand.py (original) +++ py/dist/py/path/svn/urlcommand.py Fri Oct 6 14:35:53 2006 @@ -22,10 +22,12 @@ self = object.__new__(cls) if not isinstance(path, str): path = str(path) - parts = path.split(":") + proto, uri = path.split("://", 1) + host, uripath = uri.split('/', 1) # only check for bad chars in the non-protocol parts - # XXX don't check svn+ssh host sections either - if len(parts) > 2 or svncommon._check_for_bad_chars(''.join(parts[1:])): + if (svncommon._check_for_bad_chars(host, svncommon.ALLOWED_CHARS_HOST) + or svncommon._check_for_bad_chars(uripath, + svncommon.ALLOWED_CHARS)): raise ValueError("bad char in path %s" % (path, )) path = path.rstrip('/') self.strpath = path Modified: py/dist/py/path/svn/wccommand.py ============================================================================== --- py/dist/py/path/svn/wccommand.py (original) +++ py/dist/py/path/svn/wccommand.py Fri Oct 6 14:35:53 2006 @@ -23,11 +23,12 @@ def __new__(cls, wcpath=None): self = object.__new__(cls) - if isinstance(wcpath, cls): - if wcpath.__class__ == cls: - return wcpath - wcpath = wcpath.localpath - if svncommon._check_for_bad_chars(str(wcpath)): + if isinstance(wcpath, cls): + if wcpath.__class__ == cls: + return wcpath + wcpath = wcpath.localpath + if svncommon._check_for_bad_chars(str(wcpath), + svncommon.ALLOWED_CHARS): raise ValueError("bad char in wcpath %s" % (wcpath, )) self.localpath = py.path.local(wcpath) return self From fijal at codespeak.net Fri Oct 6 14:38:28 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Fri, 6 Oct 2006 14:38:28 +0200 (CEST) Subject: [py-svn] r32955 - in py/branch/apigen/py/rst: . testing Message-ID: <20061006123828.198541006F@code0.codespeak.net> Author: fijal Date: Fri Oct 6 14:38:26 2006 New Revision: 32955 Modified: py/branch/apigen/py/rst/rst.py py/branch/apigen/py/rst/testing/test_rst.py Log: Stupid word-reverse stuff and a test for it. Modified: py/branch/apigen/py/rst/rst.py ============================================================================== --- py/branch/apigen/py/rst/rst.py (original) +++ py/branch/apigen/py/rst/rst.py Fri Oct 6 14:38:26 2006 @@ -124,6 +124,7 @@ def grab(buf): outcome.append(self.indent + self.sep.join(buf)) + texts.reverse() while texts: next = texts[-1] if lgt + len(self.sep) + len(next) <= self.width or not buf: @@ -135,7 +136,6 @@ lgt = len(self.indent) buf = [] grab(buf) - outcome.reverse() return "\n".join(outcome) class SubParagraph(Paragraph): Modified: py/branch/apigen/py/rst/testing/test_rst.py ============================================================================== --- py/branch/apigen/py/rst/testing/test_rst.py (original) +++ py/branch/apigen/py/rst/testing/test_rst.py Fri Oct 6 14:38:26 2006 @@ -35,3 +35,8 @@ def test_link(): expected = "`some link`_\n.. _`some link`: http://codespeak.net\n" txt = Rest(Paragraph(Link("some link", "http://codespeak.net"))).text() + +def test_text(): + expected = "This is a test!\n" + txt = Rest(Paragraph("This is a test!")).text() + assert txt == expected From fijal at codespeak.net Sat Oct 7 07:54:29 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Sat, 7 Oct 2006 07:54:29 +0200 (CEST) Subject: [py-svn] r32976 - py/dist/py/documentation/apigen Message-ID: <20061007055429.E50BB10068@code0.codespeak.net> Author: fijal Date: Sat Oct 7 07:53:35 2006 New Revision: 32976 Added: py/dist/py/documentation/apigen/roadmap.txt (contents, props changed) Log: Added draft of roadmap. Added: py/dist/py/documentation/apigen/roadmap.txt ============================================================================== --- (empty file) +++ py/dist/py/documentation/apigen/roadmap.txt Sat Oct 7 07:53:35 2006 @@ -0,0 +1,102 @@ + +Pylib roadmap, from my point of view +==================================== + +Author: Maciek Fijalkowski +-------------------------- + +Date: 7.X.2006 + +Rough specifications of what needs to be done regarding py.test +(especially distributed version), py.api and others. + +So main targets (for near future) of pylib developement would be: + +* further py.test distributed developement, especially incorporating + some command line options (I think -x and -k are done). This might be: + - options transfer to clients (maybe whole conftest) to allow them + to perform special things. + - -s (how???) --view for pypy, etc. + - screen/--pdb (there is some work done in this area) + - If someone (merlinux?) set up test farm (few machines connected + together for running tests), provide convenient interface for + running at least nightly tests (web one? offline html?) or even + just running tests for branch, trunk, custom svn without need to + upload local changes every time (RSync daemon?). I'm not sure if + it does make any sense, but makes convinient if we provide + front machine with access, not all the nodes (XXX and what with + screen?) + - Make sure web frontend works when more than one client (webbrowser) + connects to it (actually it does not). + - XXX: anything else? + + +* integration from py.test distributed into normal py.test, especially + cool reporting features as well as web frontend (and extend it a bit) + +* benchmark integration into py.test. This issue haven't been discussed + in details. From my POV there should be done like running test in a + loop till having at least few seconds of run (some pypy tests takes + minutes to run, so this will not re-run the tests, but just take one), + than gather information and report it somehow. Should work as well + in distributed version (due to cool reporting, this might be the first + place to integrate). Various issues: + - Measure in MegaPystones or some other not-CPU-dependant units, so + we can compare results from different machines. + +PY.API: +------- + +(actually lying down in py.test.tracer in branch, maybe not +too nice name for that). + +Actual working version can provide simple ReST out of running very +simple tests (functions only). + +So, todo list is (in random order): + +* Make interface for every possible single item on the end (function, + method, generator) to provide better coverage. + +* Make more... (frontend for API extraction, backend for output + generation, different source link generators, etc. etc.) + +* Integrate it into py.test, as well as make possibility of gathering + this data from running process (this might allow to get it remotely + as well) with quite different tool (console/web, with possibility + to snapshot to static outcome). + +* Provide some model of determining which methods are exposed as API + and which are internal (just called directly in unittest looks like + enough for now, altough we do not have method of determining that yet) + +* Make clean vision of pypy annotation typesystem, probably even + extend it a bit (it's still easier to use than providing all + the interface on our own). Possible extensions: + - Provide SomeUnion instead of SomeObject which will behave like + gathering all possibilities together (this might be beneficient to + pypy as well). + - Provide some convinient way of getting common interface out of + objects that are contained in SomeObject. Like "what are the + common methods of both, even if they do not share a baseclass". + In this place, it should be quite easy to add different type + joining functions by user, because different projects might have + different needs at this point. + +* Test it on generating pylib documentation. + +* Make some API definition tool (object format) for defining API in + pypy. This will make a lot easier annotating the pypy source with + info (like "what the heck type comes here as an argument"....) + +Timeline: +--------- + +This is quite huge wishlist.... I don't know exactly what are priorities +on all the stuff, what I would go first is to make API tool usable and +easy to use as soon as possible, to show it at HHU on sprint in some +running fashion. It should be well tested and maybe not containing +all-the-possible features, but runnable. + +Next thing to do is to extend py.test distributed, especially with +screen functionality. From arigo at codespeak.net Sun Oct 8 15:42:12 2006 From: arigo at codespeak.net (arigo at codespeak.net) Date: Sun, 8 Oct 2006 15:42:12 +0200 (CEST) Subject: [py-svn] r33000 - in py/dist/py/test: terminal testing Message-ID: <20061008134212.65F251006F@code0.codespeak.net> Author: arigo Date: Sun Oct 8 15:42:09 2006 New Revision: 33000 Modified: py/dist/py/test/terminal/terminal.py py/dist/py/test/testing/test_session.py Log: (pedronis, arigo) Change triggered by us getting very confused when the 'E' was not located on the expected line. Usually it doesn't matter much, because the 'E' is on the last line and the last line is part of the statement that failed. Sometimes, though, the guessing-of-statement-range gets confused, and more source is printed. For this case let's at least put the 'E' on the correct line... Modified: py/dist/py/test/terminal/terminal.py ============================================================================== --- py/dist/py/test/terminal/terminal.py (original) +++ py/dist/py/test/terminal/terminal.py Sun Oct 8 15:42:09 2006 @@ -294,11 +294,13 @@ else: self.out.line("") source = self.getentrysource(entry) + firstsourceline = entry.getfirstlinesource() + marker_location = entry.lineno - firstsourceline if entry == last: - self.repr_source(source, 'E') + self.repr_source(source, 'E', marker_location) self.repr_failure_explanation(excinfo, source) else: - self.repr_source(source, '>') + self.repr_source(source, '>', marker_location) self.out.line("") self.out.line("[%s:%d]" %(entry.frame.code.path, entry.lineno+1)) self.repr_locals(entry) @@ -387,11 +389,19 @@ source = py.code.Source("[failure to get at sourcelines from %r]\n" % entry) return source.deindent() - def repr_source(self, source, marker=">"): - for line in source[:-1]: - self.out.line(" " + line) - lastline = source[-1] - self.out.line(marker + " " + lastline) + def repr_source(self, source, marker=">", marker_location=-1): + if marker_location < 0: + marker_location += len(source) + if marker_location < 0: + marker_location = 0 + if marker_location >= len(source): + marker_location = len(source) - 1 + for i in range(len(source)): + if i == marker_location: + prefix = marker + " " + else: + prefix = " " + self.out.line(prefix + source[i]) def repr_failure_explanation(self, excinfo, source): try: Modified: py/dist/py/test/testing/test_session.py ============================================================================== --- py/dist/py/test/testing/test_session.py (original) +++ py/dist/py/test/testing/test_session.py Sun Oct 8 15:42:09 2006 @@ -306,6 +306,31 @@ assert out.find("""[Exception("Ha Ha fooled you, I'm a broken repr().") raised in repr()]""") != -1 #' assert out.find("[unknown exception raised in repr()]") != -1 + def test_E_on_correct_line(self): + o = tmpdir.ensure('E_on_correct_line', dir=1) + tfile = o.join('test_correct_line.py') + source = py.code.Source(""" + import py + def test_hello(): + assert (None == + ['a', + 'b', + 'c']) + """) + tfile.write(source) + session = self.session + session.main([o]) + out = self.file.getvalue() + print 'Output of simulated "py.test test_correct_line.py":' + print out + i = out.find('test_correct_line.py:') + assert i >= 0 + linenum = int(out[i+len('test_correct_line.py:')]) # a single char + line_to_report = source[linenum-1] + expected_output = '\nE ' + line_to_report + '\n' + print 'Looking for:', expected_output + assert expected_output in out + from py.__.test.terminal.remote import getrootdir class TestRemote: From fijal at codespeak.net Sun Oct 8 21:04:24 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Sun, 8 Oct 2006 21:04:24 +0200 (CEST) Subject: [py-svn] r33012 - in py/branch/apigen/py/test/tracer: . testing Message-ID: <20061008190424.EB59910068@code0.codespeak.net> 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) From simonb at codespeak.net Sun Oct 8 22:00:25 2006 From: simonb at codespeak.net (simonb at codespeak.net) Date: Sun, 8 Oct 2006 22:00:25 +0200 (CEST) Subject: [py-svn] r33016 - py/dist/py/code Message-ID: <20061008200025.B7A0310068@code0.codespeak.net> Author: simonb Date: Sun Oct 8 22:00:12 2006 New Revision: 33016 Modified: py/dist/py/code/traceback2.py Log: added getfirstlinesource method to TracebackEntry class Modified: py/dist/py/code/traceback2.py ============================================================================== --- py/dist/py/code/traceback2.py (original) +++ py/dist/py/code/traceback2.py Sun Oct 8 22:00:12 2006 @@ -52,6 +52,10 @@ break return source[start:end] + def getfirstlinesource(self): + start = self.frame.code.firstlineno + return start + def ishidden(self): try: return self.frame.eval("__tracebackhide__") From fijal at codespeak.net Sun Oct 8 23:03:05 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Sun, 8 Oct 2006 23:03:05 +0200 (CEST) Subject: [py-svn] r33019 - py/dist/py/documentation/apigen Message-ID: <20061008210305.A99601006F@code0.codespeak.net> Author: fijal Date: Sun Oct 8 23:03:03 2006 New Revision: 33019 Modified: py/dist/py/documentation/apigen/assumptions.txt py/dist/py/documentation/apigen/roadmap.txt Log: Fixed rest. Modified: py/dist/py/documentation/apigen/assumptions.txt ============================================================================== --- py/dist/py/documentation/apigen/assumptions.txt (original) +++ py/dist/py/documentation/apigen/assumptions.txt Sun Oct 8 23:03:03 2006 @@ -33,3 +33,8 @@ object which was really used, than least basic superclass of arguments that went inside). +Milestone 1: +------------ + +Date is set till sprint in Duesseldorf. + Modified: py/dist/py/documentation/apigen/roadmap.txt ============================================================================== --- py/dist/py/documentation/apigen/roadmap.txt (original) +++ py/dist/py/documentation/apigen/roadmap.txt Sun Oct 8 23:03:03 2006 @@ -14,6 +14,7 @@ * further py.test distributed developement, especially incorporating some command line options (I think -x and -k are done). This might be: + - options transfer to clients (maybe whole conftest) to allow them to perform special things. - -s (how???) --view for pypy, etc. @@ -41,6 +42,7 @@ than gather information and report it somehow. Should work as well in distributed version (due to cool reporting, this might be the first place to integrate). Various issues: + - Measure in MegaPystones or some other not-CPU-dependant units, so we can compare results from different machines. @@ -73,6 +75,7 @@ * Make clean vision of pypy annotation typesystem, probably even extend it a bit (it's still easier to use than providing all the interface on our own). Possible extensions: + - Provide SomeUnion instead of SomeObject which will behave like gathering all possibilities together (this might be beneficient to pypy as well). From arigo at codespeak.net Sun Oct 8 23:08:32 2006 From: arigo at codespeak.net (arigo at codespeak.net) Date: Sun, 8 Oct 2006 23:08:32 +0200 (CEST) Subject: [py-svn] r33020 - py/dist/py/code Message-ID: <20061008210832.D28C91006F@code0.codespeak.net> Author: arigo Date: Sun Oct 8 23:08:31 2006 New Revision: 33020 Modified: py/dist/py/code/traceback2.py Log: Oups oups oups! Forgot to check this file in. Modified: py/dist/py/code/traceback2.py ============================================================================== --- py/dist/py/code/traceback2.py (original) +++ py/dist/py/code/traceback2.py Sun Oct 8 23:08:31 2006 @@ -34,10 +34,14 @@ self.exprinfo = x return self.exprinfo + def getfirstlinesource(self): + return self.frame.code.firstlineno + def getsource(self): """ return failing source code. """ source = self.frame.code.fullsource - start, end = self.frame.code.firstlineno, self.lineno + start = self.getfirstlinesource() + end = self.lineno try: _, end = source.getstatementrange(end) except IndexError: From arigo at codespeak.net Sun Oct 8 23:12:12 2006 From: arigo at codespeak.net (arigo at codespeak.net) Date: Sun, 8 Oct 2006 23:12:12 +0200 (CEST) Subject: [py-svn] r33021 - py/dist/py/code Message-ID: <20061008211212.492881006F@code0.codespeak.net> Author: arigo Date: Sun Oct 8 23:12:09 2006 New Revision: 33021 Modified: py/dist/py/code/traceback2.py Log: Did not realize that this method was also added in the meantime (at a different location, so no conflict). I'm keeping my commit because it also adds a delegation from getsource() to getfirstlinesource(). Modified: py/dist/py/code/traceback2.py ============================================================================== --- py/dist/py/code/traceback2.py (original) +++ py/dist/py/code/traceback2.py Sun Oct 8 23:12:09 2006 @@ -56,10 +56,6 @@ break return source[start:end] - def getfirstlinesource(self): - start = self.frame.code.firstlineno - return start - def ishidden(self): try: return self.frame.eval("__tracebackhide__") From fijal at codespeak.net Sun Oct 8 23:19:00 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Sun, 8 Oct 2006 23:19:00 +0200 (CEST) Subject: [py-svn] r33022 - py/dist/py/documentation/apigen Message-ID: <20061008211900.87B6C1006F@code0.codespeak.net> Author: fijal Date: Sun Oct 8 23:18:59 2006 New Revision: 33022 Modified: py/dist/py/documentation/apigen/roadmap.txt Log: Added more ideas. Modified: py/dist/py/documentation/apigen/roadmap.txt ============================================================================== --- py/dist/py/documentation/apigen/roadmap.txt (original) +++ py/dist/py/documentation/apigen/roadmap.txt Sun Oct 8 23:18:59 2006 @@ -92,6 +92,10 @@ pypy. This will make a lot easier annotating the pypy source with info (like "what the heck type comes here as an argument"....) +* Add several different things to trace, mostly tracing of side effects + (track what method can change what attributes, as well as in child nodes. + Maybe global variables as well). + Timeline: --------- From fijal at codespeak.net Sun Oct 8 23:19:26 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Sun, 8 Oct 2006 23:19:26 +0200 (CEST) Subject: [py-svn] r33023 - in py/branch/apigen/py/test/tracer: . testing Message-ID: <20061008211926.B13431006F@code0.codespeak.net> Author: fijal Date: Sun Oct 8 23:19:24 2006 New Revision: 33023 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 some return value tests as well as fixed it. 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 23:19:24 2006 @@ -1,9 +1,5 @@ -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 +from pypy.annotation import model import types @@ -49,25 +45,27 @@ # define code somehow class FunctionDesc(Desc): + def __init__(self, *args, **kwargs): + super(FunctionDesc, self).__init__(*args, **kwargs) + self.inputcells = [model.s_ImpossibleValue for i in xrange(self.\ + code.co_argcount)] + self.call_sites = [] + self.retval = model.s_ImpossibleValue + def consider_call(self, inputcells): - if not hasattr(self, 'inputcells'): - self.inputcells = inputcells - return for cell_num, cell in enumerate(inputcells): - self.inputcells[cell_num] = unionof(cell, self.inputcells[cell_num]) + self.inputcells[cell_num] = model.unionof(cell, self.inputcells[cell_num]) def consider_call_site(self, frame): - 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 consider_return(self, arg): + self.retval = model.unionof(arg, self.retval) 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) @@ -84,6 +82,9 @@ self.fields['__init__'] = md md.consider_call(inputcells) + def consider_return(self, arg): + pass # we *know* what return value we do have + def consider_call_site(self, frame): self.fields['__init__'].consider_call_site(frame) 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 23:19:24 2006 @@ -15,16 +15,13 @@ 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 = [] + #self.frames = [] def consider_call(self, frame, caller_frame): assert isinstance(frame, py.code.Frame) @@ -32,25 +29,20 @@ 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()] #self.call_stack.append((desc, args)) desc.consider_call([self.bookkeeper.immutablevalue(arg) for arg in args]) + + def generalize_retval(self, desc, arg): + desc.consider_return(self.bookkeeper.immutablevalue(arg)) def consider_return(self, frame, arg): - pass + assert isinstance(frame, py.code.Frame) + desc = self.find_desc(frame) + if desc: + self.generalize_retval(desc, arg) def find_desc(self, frame): return self.desc_cache.get(frame.code.raw, None) @@ -104,9 +96,10 @@ def __init__(self, ds): self.ds = ds - def get_names(self): + def get_function_names(self): # get iterator over all API exposed names - return self.ds.descs.iterkeys() + return [i for i, desc in self.ds.descs.iteritems() if isinstance(desc, + FunctionDesc)] #def get_function(self, name): # return self.ds.descs[name].pyobj 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 23:19:24 2006 @@ -56,11 +56,12 @@ 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() + self.write_classes() def write_functions(self): - for key in self.dsa.get_names(): + self.add(Title("Exported functions:", belowchar="-")) + for key in self.dsa.get_function_names(): title = Title("%s description:" % key, belowchar="^") docstr = Paragraph(self.dsa.get_function_doc(key)) args = self.dsa.get_function_args(key) @@ -74,7 +75,10 @@ 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))) + if link_target: # otherwise it's just inline text + call_sites.append(Paragraph(Link(link_str, link_target))) + else: + call_sites.append(Paragraph(link_str)) call_sites.append(BlockQuote(call_site.source)) for item in [title, docstr, arg_quote, call_site_title] + call_sites: @@ -83,3 +87,7 @@ #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()) + + def write_classes(self): + self.add(Title("Exported classes:", belowchar="-")) + 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 23:19:24 2006 @@ -30,6 +30,7 @@ assert isinstance(inputcells[0], model.SomeFloat) assert isinstance(inputcells[1], model.SomeTuple) assert isinstance(inputcells[2], model.SomeObject) + assert isinstance(desc.retval, model.SomeChar) cs = desc.call_sites assert len(cs) == 2 f_name = __file__ @@ -86,3 +87,4 @@ assert isinstance(inputcells[1], model.SomeInteger) assert isinstance(inputcells[2], model.SomeFloat) assert isinstance(inputcells[3], model.SomeList) + assert isinstance(desc.fields['exposed_method'].retval, model.SomeChar) From fijal at codespeak.net Mon Oct 9 15:31:27 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Mon, 9 Oct 2006 15:31:27 +0200 (CEST) Subject: [py-svn] r33053 - py/branch/apigen/py/rst Message-ID: <20061009133127.8E514100A7@code0.codespeak.net> Author: fijal Date: Mon Oct 9 15:31:26 2006 New Revision: 33053 Modified: py/branch/apigen/py/rst/rst.py Log: Forgotten commit. Modified: py/branch/apigen/py/rst/rst.py ============================================================================== --- py/branch/apigen/py/rst/rst.py (original) +++ py/branch/apigen/py/rst/rst.py Mon Oct 9 15:31:26 2006 @@ -85,7 +85,7 @@ link_texts = [] for link, target in self.links.iteritems(): link_texts.append(".. _`%s`: %s" % (link, target)) - return "\n".join(link_texts) + "\n" + return "\n" + "\n".join(link_texts) + "\n" def text(self): outcome = [] @@ -151,14 +151,14 @@ all_txts = all_txt.split("\n") return "\n".join([self.indent + i for i in all_txts]) -class Title(AbstractNode): +class Title(Paragraph): parentclass = Rest belowchar = "" abovechar = "" previous_paragraph = None def text(self): - txt = AbstractNode.text(self) + txt = Paragraph.text(self) lines = [] if self.abovechar: lines.append(self.abovechar * len(txt)) From fijal at codespeak.net Mon Oct 9 15:41:31 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Mon, 9 Oct 2006 15:41:31 +0200 (CEST) Subject: [py-svn] r33055 - py/branch/apigen/py/test/tracer/testing Message-ID: <20061009134131.0035B100A9@code0.codespeak.net> Author: fijal Date: Mon Oct 9 15:41:30 2006 New Revision: 33055 Added: py/branch/apigen/py/test/tracer/testing/__init__.py (contents, props changed) py/branch/apigen/py/test/tracer/testing/test_rest.py (contents, props changed) Log: Added two missing files. Added: py/branch/apigen/py/test/tracer/testing/__init__.py ============================================================================== Added: py/branch/apigen/py/test/tracer/testing/test_rest.py ============================================================================== --- (empty file) +++ py/branch/apigen/py/test/tracer/testing/test_rest.py Mon Oct 9 15:41:30 2006 @@ -0,0 +1,35 @@ + +""" tests document generation +""" + +from py.__.test.tracer.genrest import ViewVC, RestGen +from py.__.test.tracer.tracer import DocStorage, Tracer + +from StringIO import StringIO + +def test_links(): + vcview = ViewVC("http://codespeak.net/viewvc/") + linkname = vcview.getlink(__file__, 0) + assert linkname == 'http://codespeak.net/viewvc/py/test/'\ + 'tracer/testing/test_rest.py?view=markup' + +def fun(a, b, c): + "Some docstring" + return "d" + +def test_generation(): + descs = {"fun":fun} + ds = DocStorage().from_dict(descs) + lg = ViewVC("http://codespeak.net/viewvc/") + t = Tracer(ds) + t.start_tracing() + fun(1, ("g", 3), 8) + fun(2., ("a", 1.), "a") + t.end_tracing() + s = StringIO() + r = RestGen(ds, lg, output=s) + r.write() + # we cannot check the source, cause the rapidly changing + # visual effect will break it, so we'll make assertion later + # XXX: do that + assert s.getvalue() From fijal at codespeak.net Mon Oct 9 16:20:14 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Mon, 9 Oct 2006 16:20:14 +0200 (CEST) Subject: [py-svn] r33057 - in py/branch/apigen/py/rst: . testing Message-ID: <20061009142014.192A8100A9@code0.codespeak.net> Author: fijal Date: Mon Oct 9 16:20:12 2006 New Revision: 33057 Modified: py/branch/apigen/py/rst/rst.py py/branch/apigen/py/rst/testing/test_rst.py Log: Added list item Modified: py/branch/apigen/py/rst/rst.py ============================================================================== --- py/branch/apigen/py/rst/rst.py (original) +++ py/branch/apigen/py/rst/rst.py Mon Oct 9 16:20:12 2006 @@ -185,6 +185,16 @@ start = "*" end = "*" +class ListItem(Paragraph): + item_char = "*" + + def text(self): + self.indent = self.indent + " " + txt = Paragraph.text(self) + txt = self.item_char + txt[1:] + del self.indent + return txt + class Link(AbstractText): start = '`' end = '`_' Modified: py/branch/apigen/py/rst/testing/test_rst.py ============================================================================== --- py/branch/apigen/py/rst/testing/test_rst.py (original) +++ py/branch/apigen/py/rst/testing/test_rst.py Mon Oct 9 16:20:12 2006 @@ -40,3 +40,8 @@ expected = "This is a test!\n" txt = Rest(Paragraph("This is a test!")).text() assert txt == expected + +def test_list(): + expected = "* a\n\n* b\n" + txt = Rest(ListItem("a"), ListItem("b")).text() + assert txt == expected From fijal at codespeak.net Mon Oct 9 17:03:36 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Mon, 9 Oct 2006 17:03:36 +0200 (CEST) Subject: [py-svn] r33065 - py/dist/py/path/local Message-ID: <20061009150336.C9042100BC@code0.codespeak.net> Author: fijal Date: Mon Oct 9 17:03:35 2006 New Revision: 33065 Modified: py/dist/py/path/local/local.py Log: Added - linking (stolen from pypy) Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Mon Oct 9 17:03:35 2006 @@ -618,6 +618,27 @@ path.remove(rec=1) except py.error.Error: pass + + # make link... + try: + username = os.environ['USER'] #linux, et al + except: + try: + username = os.environ['USERNAME'] #windows + except: + username = 'current' + + src = str(udir) + dest = src[:src.rfind('-')] + '-' + username + try: + os.unlink(dest) + except: + pass + try: + os.symlink(src, dest) + except: + pass + return udir make_numbered_dir = classmethod(make_numbered_dir) From fijal at codespeak.net Mon Oct 9 17:12:15 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Mon, 9 Oct 2006 17:12:15 +0200 (CEST) Subject: [py-svn] r33068 - in py/branch/apigen/py: path/local rst test/tracer test/tracer/testing Message-ID: <20061009151215.1E368100BE@code0.codespeak.net> Author: fijal Date: Mon Oct 9 17:12:12 2006 New Revision: 33068 Added: py/branch/apigen/py/test/tracer/testing/runtest.py (contents, props changed) Modified: py/branch/apigen/py/path/local/local.py py/branch/apigen/py/rst/rst.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 py/branch/apigen/py/test/tracer/testing/test_rest.py Log: Intermediate checkin... Modified: py/branch/apigen/py/path/local/local.py ============================================================================== --- py/branch/apigen/py/path/local/local.py (original) +++ py/branch/apigen/py/path/local/local.py Mon Oct 9 17:12:12 2006 @@ -618,6 +618,27 @@ path.remove(rec=1) except py.error.Error: pass + + # make link... + try: + username = os.environ['USER'] #linux, et al + except: + try: + username = os.environ['USERNAME'] #windows + except: + username = 'current' + + src = str(udir) + dest = src[:src.rfind('-')] + '-' + username + try: + os.unlink(dest) + except: + pass + try: + os.symlink(src, dest) + except: + pass + return udir make_numbered_dir = classmethod(make_numbered_dir) Modified: py/branch/apigen/py/rst/rst.py ============================================================================== --- py/branch/apigen/py/rst/rst.py (original) +++ py/branch/apigen/py/rst/rst.py Mon Oct 9 17:12:12 2006 @@ -216,3 +216,7 @@ while next.parent is not None: next = next.parent return next + +class Quote(AbstractText): + start = '``' + end = '``' 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 Mon Oct 9 17:12:12 2006 @@ -96,10 +96,14 @@ def __init__(self, ds): self.ds = ds + def _get_names(self, filter): + return [i for i, desc in self.ds.descs.iteritems() if filter(i, desc)] + def get_function_names(self): - # get iterator over all API exposed names - return [i for i, desc in self.ds.descs.iteritems() if isinstance(desc, - FunctionDesc)] + return self._get_names(lambda i, desc: type(desc) is FunctionDesc) + + def get_class_names(self): + return self._get_names(lambda i, desc: isinstance(desc, ClassDesc)) #def get_function(self, name): # return self.ds.descs[name].pyobj 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 Mon Oct 9 17:12:12 2006 @@ -41,53 +41,107 @@ relname = filename[len(path):] if relname.endswith('.pyc'): relname = relname[:-1] - return self.basepath + relname[1:] + '?view=markup' + return ('%s:%s' % (filename, lineno), self.basepath + relname[1:] + '?view=markup') + +class DirectPaste(AbstractLinkWriter): + """ No-link writer (inliner) + """ + def getlink(self, filename, lineno): + return (py.path.local(filename).open().readlines()[lineno-1], "") + +class PipeWriter(object): + def __init__(self, output=sys.stdout): + self.output = output + + def write_file(self, filename, data): + text = "Written file: %s" % filename + self.output.write(text + "\n") + self.output.write("=" * len(text) + "\n") + self.output.write("\n") + self.output.write(data + "\n") + +class DirWriter(object): + def __init__(self, directory=None): + if directory is None: + self.directory = py.test.ensuretemp("rstoutput") + else: + self.directory = py.path.local(directory) + + def write_file(self, filename, data): + self.directory.ensure(filename).write(data) class RestGen(object): - def __init__(self, ds, linkgen, output=sys.stdout): + def __init__(self, ds, linkgen, writer=PipeWriter()): self.dsa = DocStorageAccessor(ds) self.linkgen = linkgen - self.output = output - - def add(self, item): - self.list.append(item) + self.writer = writer def write(self): - self.list = [] - self.add(Title("Module: %s" % self.dsa.get_module_name(), belowchar="=")) - self.add(Paragraph(Text(self.dsa.get_module_info()))) - self.write_functions() - self.write_classes() - - def write_functions(self): - self.add(Title("Exported functions:", belowchar="-")) - for key in self.dsa.get_function_names(): - 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])) - 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): - link_str = "File %s:%s" % (call_site.filename, - call_site.lineno) - link_target = self.linkgen.getlink(call_site.filename, call_site.lineno) - if link_target: # otherwise it's just inline text - call_sites.append(Paragraph(Link(link_str, link_target))) - else: - call_sites.append(Paragraph(link_str)) - 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()) - - def write_classes(self): - self.add(Title("Exported classes:", belowchar="-")) + # first write down a file with all the exported interfaces, + # sorted by type + self.write_interface("index.txt") + + def write_interface(self, filename): + lst = [Title("Module: %s" % self.dsa.get_module_name(), belowchar="="), + Paragraph(self.dsa.get_module_info()), + Title("Exported functions:", belowchar="-")] + self.write_function_list(lst) + self.write_class_list(lst) + self.writer.write_file(filename, Rest(*lst).text()) + + def write_function_list(self, lst): + for name in self.dsa.get_function_names(): + # XXX: should be .html here? + lst.append(ListItem(Text("Function: "), Link(name, "function_%s" % name))) + + def write_class_list(self, lst): + for name in self.dsa.get_class_names(): + lst.append(ListItem(Text("Class: "), Link(name, "class_%s" % name))) +##class RestGen(object): +## def __init__(self, ds, linkgen, output=sys.stdout): +## self.dsa = DocStorageAccessor(ds) +## self.linkgen = linkgen +## self.output = output +## +## def add(self, item): +## self.list.append(item) +## +## def write(self): +## self.list = [] +## self.add(Title("Module: %s" % self.dsa.get_module_name(), belowchar="=")) +## self.add(Paragraph(Text(self.dsa.get_module_info()))) +## self.write_functions() +## self.write_classes() +## +## def write_functions(self): +## self.add(Title("Exported functions:", belowchar="-")) +## for key in self.dsa.get_function_names(): +## 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])) +## 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): +## link_str = "File %s:%s" % (call_site.filename, +## call_site.lineno) +## link_target = self.linkgen.getlink(call_site.filename, call_site.lineno) +## if link_target: # otherwise it's just inline text +## call_sites.append(Paragraph(Link(link_str, link_target))) +## else: +## call_sites.append(Paragraph(link_str)) +## 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()) +## +## def write_classes(self): +## self.add(Title("Exported classes:", belowchar="-")) Added: py/branch/apigen/py/test/tracer/testing/runtest.py ============================================================================== --- (empty file) +++ py/branch/apigen/py/test/tracer/testing/runtest.py Mon Oct 9 17:12:12 2006 @@ -0,0 +1,5 @@ + +def cut_pyc(f_name): + if f_name.endswith('.pyc'): + return f_name[:-1] + return f_name 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 Mon Oct 9 17:12:12 2006 @@ -6,6 +6,7 @@ import sys from py.__.test.tracer.tracer import DocStorage, Tracer +from py.__.test.tracer.testing.runtest import cut_pyc from pypy.annotation import model #def setup_module(mod): @@ -33,9 +34,7 @@ assert isinstance(desc.retval, model.SomeChar) cs = desc.call_sites assert len(cs) == 2 - f_name = __file__ - if f_name.endswith('.pyc'): - f_name = f_name[:-1] + f_name = cut_pyc(__file__) assert cs[0].filename == f_name assert cs[0].lineno == test_basic.func_code.co_firstlineno + 5 assert cs[1].filename == f_name Modified: py/branch/apigen/py/test/tracer/testing/test_rest.py ============================================================================== --- py/branch/apigen/py/test/tracer/testing/test_rest.py (original) +++ py/branch/apigen/py/test/tracer/testing/test_rest.py Mon Oct 9 17:12:12 2006 @@ -2,34 +2,80 @@ """ tests document generation """ -from py.__.test.tracer.genrest import ViewVC, RestGen +import py + +from py.__.test.tracer.genrest import ViewVC, RestGen, PipeWriter, DirWriter,\ + DirectPaste from py.__.test.tracer.tracer import DocStorage, Tracer from StringIO import StringIO +from py.__.test.tracer.testing.runtest import cut_pyc def test_links(): vcview = ViewVC("http://codespeak.net/viewvc/") - linkname = vcview.getlink(__file__, 0) - assert linkname == 'http://codespeak.net/viewvc/py/test/'\ - 'tracer/testing/test_rest.py?view=markup' + _, linkname = vcview.getlink(cut_pyc(__file__), 0) + assert linkname == 'http://codespeak.net/viewvc/py/test/tracer/testing/test_rest.py?view=markup' + +class SomeClass(object): + def __init__(self, a): + self.a = a + + def method(self, a, b, c): + return a + b + c def fun(a, b, c): "Some docstring" return "d" -def test_generation(): - descs = {"fun":fun} - ds = DocStorage().from_dict(descs) - lg = ViewVC("http://codespeak.net/viewvc/") - t = Tracer(ds) - t.start_tracing() - fun(1, ("g", 3), 8) - fun(2., ("a", 1.), "a") - t.end_tracing() - s = StringIO() - r = RestGen(ds, lg, output=s) - r.write() - # we cannot check the source, cause the rapidly changing - # visual effect will break it, so we'll make assertion later - # XXX: do that - assert s.getvalue() +def test_dir_writer(): + p = StringIO() + dir = py.test.ensuretemp("dirwriter") + w = DirWriter(dir) + w.write_file("one", "one data") + w.write_file("two", "two data") + assert dir.join("one").read() == "one data" + assert dir.join("two").read() == "two data" + +def test_direct_link(): + assert DirectPaste().getlink(cut_pyc(__file__), 2)[0] == '""" tests document generation\n' + # C-c C-v .... + +class TestRest(object): + def check_rest(self, tmpdir): + pass + + def test_generation_simple_api(self): + descs = {'SomeClass':SomeClass, 'fun':fun} + ds = DocStorage().from_dict(descs) + lg = DirectPaste() + t = Tracer(ds) + t.start_tracing() + s1 = SomeClass("a") + fun(1, 2, s1) + s2 = SomeClass("b") + s2.method(1,2,3) + fun(1, 3, s2) + t.end_tracing() + tmpdir = py.test.ensuretemp("test_generation") + r = RestGen(ds, lg, DirWriter(tmpdir)) + r.write() + # now we check out... + self.check_rest(tmpdir) + +##def test_generation(): +## py.test.skip("WIP") +## descs = {"fun":fun} +## ds = DocStorage().from_dict(descs) +## lg = ViewVC("http://codespeak.net/viewvc/") +## t = Tracer(ds) +## t.start_tracing() +## fun(1, ("g", 3), 8) +## fun(2., ("a", 1.), "a") +## t.end_tracing() +## s = StringIO() +## r = RestGen(ds, lg, output=s) +## r.write() +## # we cannot check the source, cause the rapidly changing +## # visual effect will break it, so we'll make assertion later +## # XXX: do that +## assert s.getvalue() From fijal at codespeak.net Mon Oct 9 21:56:08 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Mon, 9 Oct 2006 21:56:08 +0200 (CEST) Subject: [py-svn] r33074 - in py/branch/apigen/py: code test/tracer test/tracer/testing Message-ID: <20061009195608.BF5C7100CD@code0.codespeak.net> Author: fijal Date: Mon Oct 9 21:56:06 2006 New Revision: 33074 Modified: py/branch/apigen/py/code/code.py 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_rest.py Log: Added some sorting-out-of-outcome. Modified: py/branch/apigen/py/code/code.py ============================================================================== --- py/branch/apigen/py/code/code.py (original) +++ py/branch/apigen/py/code/code.py Mon Oct 9 21:56:06 2006 @@ -67,6 +67,11 @@ return py.code.Source(self.path.read(mode="rU")) fullsource = property(fullsource, None, None, "full source containing this code object") + + def source(self): + # return source only for that part of code + import inspect + return py.code.Source(inspect.getsource(self.raw)) def getargs(self): # handfull shortcut for getting args 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 Mon Oct 9 21:56:06 2006 @@ -4,13 +4,13 @@ import types class CallSite(object): - def __init__(self, filename, lineno, source): + def __init__(self, filename, lineno, frame): self.filename = filename self.lineno = lineno - self.source = source + self.frame = frame def get_tuple(self): - return self.filename, self.lineno, self.source + return self.filename, self.lineno, self.frame class NonHashableObject(object): def __init__(self, cls): @@ -57,7 +57,7 @@ self.inputcells[cell_num] = model.unionof(cell, self.inputcells[cell_num]) def consider_call_site(self, frame): - self.call_sites.append(CallSite(frame.code.raw.co_filename, frame.lineno+1, str(frame.statement))) + self.call_sites.append(CallSite(frame.code.raw.co_filename, frame.lineno+1, frame)) def consider_return(self, arg): self.retval = model.unionof(arg, self.retval) 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 Mon Oct 9 21:56:06 2006 @@ -71,7 +71,7 @@ desc = ClassDesc(key, value) for name in dir(value): field = getattr(value, name) - if name != '__init__' and isinstance(field, types.MethodType): + if isinstance(field, types.MethodType): real_name = key + '.' + name md = MethodDesc(real_name, field) self.descs[real_name] = md @@ -111,6 +111,12 @@ def get_function_doc(self, name): return self.ds.descs[name].pyobj.__doc__ or "*Not documented*" + def get_function_definition(self, name): + desc = self.ds.descs[name] + assert isinstance(desc, FunctionDesc) + code = py.code.Code(desc.code) + return code.fullsource[code.firstlineno] + def get_function_args(self, name): return self.ds.descs[name].inputcells @@ -123,6 +129,11 @@ return self.ds.module.__name__ return "Unknown module" + def get_class_methods(self, name): + desc = self.ds.descs[name] + assert isinstance(desc, ClassDesc) + return desc.getfields() + #def get_object_info(self, key): # 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 Mon Oct 9 21:56:06 2006 @@ -5,6 +5,7 @@ import py import sys +import re from py.__.test.tracer.docstorage import DocStorageAccessor from py.__.rst.rst import * # XXX Maybe we should list it here @@ -86,17 +87,70 @@ Paragraph(self.dsa.get_module_info()), Title("Exported functions:", belowchar="-")] self.write_function_list(lst) + lst.append(Title("Exported classes:", belowchar="-")) self.write_class_list(lst) self.writer.write_file(filename, Rest(*lst).text()) def write_function_list(self, lst): for name in self.dsa.get_function_names(): # XXX: should be .html here? - lst.append(ListItem(Text("Function: "), Link(name, "function_%s" % name))) + lst.append(ListItem(Text("Function: "), Link(name, "function_%s.html" % name))) + self.write_function('function_' + name + '.txt', name) def write_class_list(self, lst): for name in self.dsa.get_class_names(): - lst.append(ListItem(Text("Class: "), Link(name, "class_%s" % name))) + lst.append(ListItem(Text("Class: "), Link(name, "class_%s.html" % name))) + self.write_class('class_' + name + '.txt', name) + + def write_class(self, filename, class_name): + lst = [Title("Class: %s" % class_name, belowchar='-')] + + # write down exported methods + lst.append(Title("Exported methods:", belowchar="^")) + for method in self.dsa.get_class_methods(class_name): + fname = "method_%s" % (class_name + \ + "_" + method) + lst.append(ListItem(Link(method, fname+'.html'))) + self.write_function(fname+'.txt', class_name + "." + method) + + self.writer.write_file(filename, Rest(*lst).text()) + + def write_function(self, filename, fun_name): + lst = [Title("Function: %s" % fun_name, belowchar="-"), + Paragraph(self.dsa.get_function_doc(fun_name)), + BlockQuote(self.dsa.get_function_definition(fun_name))] + args = self.dsa.get_function_args(fun_name) + arg_str = "(%s)" % (",".join([str(i) for i in args])) + arg_quote = Paragraph("Function type:", Quote(arg_str)) + lst.append(arg_quote) + + # call sites.. + call_site_title = Title("Call sites:", belowchar="-") + lst.append(call_site_title) + call_sites = lst + + for call_site in self.dsa.get_function_callpoints(fun_name): + link_str = "File %s:%s" % (call_site.filename, + call_site.lineno) + #_, link_target = self.linkgen.getlink(call_site.filename, call_site.lineno) + #if link_target: # otherwise it's just inline text + # call_sites.append(Paragraph(Link(link_str, link_target))) + #else: + # call_sites.append(Paragraph(link_str)) + #call_sites.append(BlockQuote(call_site.source)) + # XXX: For now, we just paste here the filename of that + call_sites.append(Paragraph(link_str)) + source = call_site.frame.code.source() + lines = [] + for num, line in enumerate(source): + if num == call_site.lineno - call_site.frame.code.firstlineno - 1: + m = re.match("^( *)(.*)", line) + lines.append(">" + "-"*len(m.group(1)) + m.group(2)) + else: + lines.append(" " + line) + call_sites.append(BlockQuote("\n".join(lines))) + + self.writer.write_file(filename, Rest(*lst).text()) ##class RestGen(object): ## def __init__(self, ds, linkgen, output=sys.stdout): ## self.dsa = DocStorageAccessor(ds) Modified: py/branch/apigen/py/test/tracer/testing/test_rest.py ============================================================================== --- py/branch/apigen/py/test/tracer/testing/test_rest.py (original) +++ py/branch/apigen/py/test/tracer/testing/test_rest.py Mon Oct 9 21:56:06 2006 @@ -42,7 +42,9 @@ class TestRest(object): def check_rest(self, tmpdir): - pass + from py.__.misc import rest + for path in tmpdir.listdir('*.txt'): + rest.process(path) def test_generation_simple_api(self): descs = {'SomeClass':SomeClass, 'fun':fun} From cfbolz at codespeak.net Tue Oct 10 18:37:19 2006 From: cfbolz at codespeak.net (cfbolz at codespeak.net) Date: Tue, 10 Oct 2006 18:37:19 +0200 (CEST) Subject: [py-svn] r33125 - py/dist/py/bin Message-ID: <20061010163719.EEC6810125@code0.codespeak.net> Author: cfbolz Date: Tue Oct 10 18:37:19 2006 New Revision: 33125 Modified: py/dist/py/bin/py.rest Log: use the rst2pdfconfig file if one of the same name exists. Modified: py/dist/py/bin/py.rest ============================================================================== --- py/dist/py/bin/py.rest (original) +++ py/dist/py/bin/py.rest Tue Oct 10 18:37:19 2006 @@ -66,8 +66,13 @@ process_configfile(p, options.debug) else: if options.topdf: - process_rest_file(p.localpath, - options.stylesheet, + cfg = p.new(ext=".rst2pdfconfig") + if cfg.check(): + print "using config file %s" % (cfg, ) + process_configfile(cfg, options.debug) + else: + process_rest_file(p.localpath, + options.stylesheet, options.debug) else: rest.process(p) From fijal at codespeak.net Tue Oct 10 19:47:29 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Tue, 10 Oct 2006 19:47:29 +0200 (CEST) Subject: [py-svn] r33132 - in py/dist/py/test/rsession: . testing webdata Message-ID: <20061010174729.34DFD10134@code0.codespeak.net> Author: fijal Date: Tue Oct 10 19:47:02 2006 New Revision: 33132 Modified: py/dist/py/test/rsession/box.py py/dist/py/test/rsession/rsession.py py/dist/py/test/rsession/slave.py py/dist/py/test/rsession/testing/test_boxing.py py/dist/py/test/rsession/testing/test_web.py py/dist/py/test/rsession/webdata/ (props changed) Log: (arigo, fijal) - Fixed corner case bug using generative test. - Added special flag py.test.we_are_remote for some tests - Sorry no tests for this, will write down later :-( Modified: py/dist/py/test/rsession/box.py ============================================================================== --- py/dist/py/test/rsession/box.py (original) +++ py/dist/py/test/rsession/box.py Tue Oct 10 19:47:02 2006 @@ -208,7 +208,60 @@ self.stdoutrepr = self.PYTESTSTDOUT.read() self.stderrrepr = self.PYTESTSTDERR.read() return self.stdoutrepr, self.stderrrepr + +def screen_run(): + # XXX: we *do* bootstrapping once again here + # which sucks a lot + # XXX: We need to make some kind of *blocking* fifo, cause + # we have really no idea when we'll arrive + import os + import py + + try: + pass + except: + pass + import pdb;pdb.set_trace() + +class ScreenBox(FileBox): + def run(self): + tempdir = py.test.ensuretemp("box") + self.tempdir = tempdir + self.PYTESTRETVAL = tempdir.join('retval') + self.PYTESTINPUT = tempdir.join('input') + #self.PYTESTINPUT.write(marshal.dumps({'args':self.args, \ + # 'kwargs':self.kwargs, 'fun':self.fun})) + + pid = os.fork() + if pid: + self.parent() + else: + os.chdir(str(py.path.local(py.__file__).dirpath().dirpath())) + screen = str(py.path.local.sysfind("screen")) + #os.execv(screen, ["python"]) + os.execv(screen, ["screen", "-d", "-m", "-L", "--", "python", \ + "-c", "from py.__.test.rsession import box\nbox.screen_run()"]) + return pid + + def parent(self): + pid, exitstat = os.wait() + self.signal = exitstat & 0x7f + self.exitstat = exitstat & 0xff00 + import pdb;pdb.set_trace() + #if not exitstat: + # retval = self.PYTESTRETVAL.open() + # try: + # retval_data = retval.read() + # finally: + # retval.close() + # self.retval = marshal.loads(retval_data) + #else: + self.retval = None + + self.stdoutrepr = py.path.local(__file__).dirpath().join("screenlog.0").read() + self.stderrrepr = "" + return self.stdoutrepr, self.stderrrepr RealBox = FileBox Box = FileBox Modified: py/dist/py/test/rsession/rsession.py ============================================================================== --- py/dist/py/test/rsession/rsession.py (original) +++ py/dist/py/test/rsession/rsession.py Tue Oct 10 19:47:02 2006 @@ -17,6 +17,8 @@ from py.__.test.terminal.out import getout +we_are_remote = False + class Reporter(object): def __init__(self, config, hosts): self.config = config Modified: py/dist/py/test/rsession/slave.py ============================================================================== --- py/dist/py/test/rsession/slave.py (original) +++ py/dist/py/test/rsession/slave.py Tue Oct 10 19:47:02 2006 @@ -47,12 +47,19 @@ try: node = getnode(nextitem) res = node.run(nextitem[1:]) + except py.test.Item.Skipped, s: + send(Outcome(skipped=str(s)).make_repr()) except: excinfo = py.code.ExceptionInfo() send(Outcome(excinfo=excinfo, is_critical=True).make_repr()) else: send(res) +def setup_screen(): + # We cannot easily just assume that we do have full communication + # channels, so we have to provide a new ones. + pass + def setup(): import os, sys pkgdir = channel.receive() # path is ready @@ -60,6 +67,7 @@ pkgname = os.path.basename(pkgdir) sys.path.insert(0, basedir) import py + py.test.we_are_remote = True # XXX the following assumes that py lib is there, a bit # much of an assumtion mod = __import__(pkgname) Modified: py/dist/py/test/rsession/testing/test_boxing.py ============================================================================== --- py/dist/py/test/rsession/testing/test_boxing.py (original) +++ py/dist/py/test/rsession/testing/test_boxing.py Tue Oct 10 19:47:02 2006 @@ -7,7 +7,7 @@ if sys.platform == 'win32': py.test.skip("rsession is unsupported on Windows.") -from py.__.test.rsession.box import Box, RealBox +from py.__.test.rsession.box import Box, RealBox, ScreenBox from py.__.test.rsession.testing import example2 from py.__.test.rsession.conftest import option @@ -71,4 +71,3 @@ assert b.exitstat == 0 assert b.signal == 0 assert b.retval == 2 - Modified: py/dist/py/test/rsession/testing/test_web.py ============================================================================== --- py/dist/py/test/rsession/testing/test_web.py (original) +++ py/dist/py/test/rsession/testing/test_web.py Tue Oct 10 19:47:02 2006 @@ -10,6 +10,7 @@ commproxy.USE_MOCHIKIT = False Options.debug_transform = True + Options.use_pdb = False except ImportError: py.test.skip("No PyPy detected") From fijal at codespeak.net Wed Oct 11 14:07:59 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Wed, 11 Oct 2006 14:07:59 +0200 (CEST) Subject: [py-svn] r33162 - in py/dist/py: . test/rsession test/rsession/testing Message-ID: <20061011120759.AB8ED1018A@code0.codespeak.net> Author: fijal Date: Wed Oct 11 14:07:54 2006 New Revision: 33162 Modified: py/dist/py/__init__.py py/dist/py/test/rsession/hostmanage.py py/dist/py/test/rsession/master.py py/dist/py/test/rsession/rsession.py py/dist/py/test/rsession/slave.py py/dist/py/test/rsession/testing/test_rsession.py py/dist/py/test/rsession/testing/test_slave.py Log: Added some shortucts, added option traversal (altough only explicit ones by now), fixed some tests, added new tests for we_are_remote. Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Wed Oct 11 14:07:54 2006 @@ -28,6 +28,7 @@ 'test.fail' : ('./test/item.py', 'fail'), 'test.exit' : ('./test/session.py', 'exit'), 'test.compat.TestCase' : ('./test/compat.py', 'TestCase'), + 'test.remote' : ('./test/rsession/rsession.py', 'remote_options'), # configuration/initialization related test api 'test.Config' : ('./test/config.py', 'Config'), Modified: py/dist/py/test/rsession/hostmanage.py ============================================================================== --- py/dist/py/test/rsession/hostmanage.py (original) +++ py/dist/py/test/rsession/hostmanage.py Wed Oct 11 14:07:54 2006 @@ -29,7 +29,7 @@ def init_hosts(reporter, sshhosts, relpath, pkgdir, rsync_roots=None, \ - remote_python=None): + remote_python=None, remote_options={}): assert pkgdir.join("__init__.py").check(), ( "%s probably wrong" %(pkgdir,)) assert relpath, relpath @@ -70,7 +70,8 @@ # hosts ready for host, gw, remoterootpath in hosts: - ch = setup_slave(gw, os.path.join(remoterootpath, pkgdir.basename)) + ch = setup_slave(gw, os.path.join(remoterootpath, pkgdir.basename), + remote_options) nodes.append(MasterNode(ch, reporter)) return nodes Modified: py/dist/py/test/rsession/master.py ============================================================================== --- py/dist/py/test/rsession/master.py (original) +++ py/dist/py/test/rsession/master.py Wed Oct 11 14:07:54 2006 @@ -43,11 +43,12 @@ break waiter() -def setup_slave(gateway, pkgpath): +def setup_slave(gateway, pkgpath, options={}): from py.__.test.rsession import slave import os ch = gateway.remote_exec(str(py.code.Source(slave.setup, "setup()"))) #if hasattr(gateway, 'sshaddress'): # assert not os.path.isabs(pkgpath) ch.send(str(pkgpath)) + ch.send(options) return ch Modified: py/dist/py/test/rsession/rsession.py ============================================================================== --- py/dist/py/test/rsession/rsession.py (original) +++ py/dist/py/test/rsession/rsession.py Wed Oct 11 14:07:54 2006 @@ -17,7 +17,14 @@ from py.__.test.terminal.out import getout -we_are_remote = False +class RemoteOptions(object): + def __init__(self, d): + self.d = d + + def __getattr__(self, attr): + return self.d[attr] + +remote_options = RemoteOptions({'we_are_remote':False}) class Reporter(object): def __init__(self, config, hosts): Modified: py/dist/py/test/rsession/slave.py ============================================================================== --- py/dist/py/test/rsession/slave.py (original) +++ py/dist/py/test/rsession/slave.py Wed Oct 11 14:07:54 2006 @@ -58,16 +58,23 @@ def setup_screen(): # We cannot easily just assume that we do have full communication # channels, so we have to provide a new ones. - pass + import thread + import os, sys + # the idea is simple: we create another process in which we perform + # read/write operations on both channels + def setup(): import os, sys pkgdir = channel.receive() # path is ready + options = channel.receive() # options stuff, should be dictionary basedir = os.path.dirname(pkgdir) pkgname = os.path.basename(pkgdir) sys.path.insert(0, basedir) import py - py.test.we_are_remote = True + from py.__.test.rsession.rsession import RemoteOptions + options['we_are_remote'] = True + py.test.remote = RemoteOptions(options) # XXX the following assumes that py lib is there, a bit # much of an assumtion mod = __import__(pkgname) Modified: py/dist/py/test/rsession/testing/test_rsession.py ============================================================================== --- py/dist/py/test/rsession/testing/test_rsession.py (original) +++ py/dist/py/test/rsession/testing/test_rsession.py Wed Oct 11 14:07:54 2006 @@ -7,7 +7,8 @@ from py.__.test.rsession.rsession import RSession from py.__.test.rsession.hostmanage import init_hosts, teardown_hosts from py.__.test.rsession.testing.test_slave import funcfail_spec,\ - funcpass_spec, funcskip_spec, funcprint_spec, funcprintfail_spec + funcpass_spec, funcskip_spec, funcprint_spec, funcprintfail_spec, \ + funcoptioncustom_spec, funcoption_spec def setup_module(mod): mod.pkgdir = py.path.local(py.__file__).dirpath() @@ -30,7 +31,7 @@ def test_getpkdir_no_inits(): tmp = py.test.ensuretemp("getpkdir1") fn = tmp.ensure("hello.py") - py.test.raises(AssertionError, "RSession.getpkgdir(fn)") + assert RSession.getpkgdir(fn) == fn def test_make_colitems(): one = pkgdir.join("initpkg.py") @@ -112,6 +113,7 @@ tmpdir = py.path.local(py.__file__).dirpath().dirpath() tmpdir.ensure("sub", "conftest.py").write(py.code.Source(""" disthosts = %r + distrsync_roots = ["sub", "py"] """ % self.hosts[:1])) tmpdir.ensure("sub", "__init__.py") tmpdir.ensure("sub", "test_one.py").write(py.code.Source(""" @@ -126,7 +128,7 @@ """)) args = [str(tmpdir.join("sub"))] config, args = py.test.Config.parse(args) - rsession = RSession(config) + rsession = RSession(config) allevents = [] rsession.main(args, reporter=allevents.append) testevents = [x for x in allevents @@ -156,7 +158,8 @@ setup_events = [] teardown_events = [] - nodes = init_hosts(setup_events.append, hosts, 'pytesttest', pkgdir) + nodes = init_hosts(setup_events.append, hosts, 'pytesttest', pkgdir, + rsync_roots=["py"]) teardown_hosts(teardown_events.append, [node.channel for node in nodes], nodes) @@ -179,7 +182,8 @@ hosts = self.hosts allevents = [] - nodes = init_hosts(allevents.append, hosts, 'pytesttest', pkgdir) + nodes = init_hosts(allevents.append, hosts, 'pytesttest', pkgdir, + rsync_roots=["py"]) from py.__.test.rsession.testing.test_executor \ import ItemTestPassing, ItemTestFailing, ItemTestSkipping @@ -211,3 +215,30 @@ # one of passed for each node has non-empty stdout passed_stdout = [i for i in passed if i.outcome.stdout == 'samfing\n'] assert len(passed_stdout) == len(nodes), passed + + def test_config_pass(self): + """ Tests options object passing master -> server + """ + allevents = [] + hosts = self.hosts + nodes = init_hosts(allevents.append, hosts, 'pytesttest', pkgdir, + rsync_roots=["py"], remote_options={'custom':'custom'}) + + rootcol = py.test.collect.Directory(pkgdir.dirpath()) + itempass = rootcol.getitembynames(funcoption_spec) + itempassaswell = rootcol.getitembynames(funcoptioncustom_spec) + + for node in nodes: + node.send(itempass) + node.send(itempassaswell) + + teardown_hosts(allevents.append, [node.channel for node in nodes], nodes) + events = [i for i in allevents + if isinstance(i, report.ReceivedItemOutcome)] + passed = [i for i in events + if i.outcome.passed] + skipped = [i for i in events + if i.outcome.skipped] + assert len(passed) == 2 * len(nodes) + assert len(skipped) == 0 + assert len(events) == len(passed) Modified: py/dist/py/test/rsession/testing/test_slave.py ============================================================================== --- py/dist/py/test/rsession/testing/test_slave.py (original) +++ py/dist/py/test/rsession/testing/test_slave.py Wed Oct 11 14:07:54 2006 @@ -29,11 +29,20 @@ print "samfing elz" asddsa -funcpass_spec = "py/test/rsession/testing/test_slave.py/funcpass".split("/") -funcfail_spec = "py/test/rsession/testing/test_slave.py/funcfail".split("/") -funcskip_spec = "py/test/rsession/testing/test_slave.py/funcskip".split("/") -funcprint_spec = "py/test/rsession/testing/test_slave.py/funcprint".split("/") -funcprintfail_spec = "py/test/rsession/testing/test_slave.py/funcprintfail".split("/") +def funcoption(): + assert py.test.remote.we_are_remote + +def funcoptioncustom(): + assert py.test.remote.custom == "custom" + +BASE = "py/test/rsession/testing/test_slave.py/" +funcpass_spec = (BASE + "funcpass").split("/") +funcfail_spec = (BASE + "funcfail").split("/") +funcskip_spec = (BASE + "funcskip").split("/") +funcprint_spec = (BASE + "funcprint").split("/") +funcprintfail_spec = (BASE + "funcprintfail").split("/") +funcoption_spec = (BASE + "funcoption").split("/") +funcoptioncustom_spec = (BASE + "funcoptioncustom").split("/") # ---------------------------------------------------------------------- From fijal at codespeak.net Wed Oct 11 16:50:12 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Wed, 11 Oct 2006 16:50:12 +0200 (CEST) Subject: [py-svn] r33170 - py/dist/py/test/rsession/testing Message-ID: <20061011145012.1F80810173@code0.codespeak.net> Author: fijal Date: Wed Oct 11 16:50:10 2006 New Revision: 33170 Modified: py/dist/py/test/rsession/testing/test_slave.py Log: Fixed tests. Modified: py/dist/py/test/rsession/testing/test_slave.py ============================================================================== --- py/dist/py/test/rsession/testing/test_slave.py (original) +++ py/dist/py/test/rsession/testing/test_slave.py Wed Oct 11 16:50:10 2006 @@ -116,9 +116,19 @@ def test_slave_setup_fails_on_import_error(): from py.__.test.rsession.slave import setup tmp = py.test.ensuretemp("slavesetup") - class C: + class C: + def __init__(self): + self.count = 0 + def receive(self): - return str(tmp) + if self.count == 0: + retval = str(tmp) + elif self.count == 1: + retval = {} + else: + raise NotImplementedError("mora data") + self.count += 1 + return retval try: exec py.code.Source(setup, "setup()").compile() in { 'channel': C()} @@ -132,8 +142,18 @@ tmp = py.test.ensuretemp("slavesetup2") x = tmp.ensure("sometestpackage", "__init__.py") class C: + def __init__(self): + self.count = 0 + def receive(self): - return str(x.dirpath()) + if self.count == 0: + retval = str(x.dirpath()) + elif self.count == 1: + retval = {} + else: + raise NotImplementedError("mora data") + self.count += 1 + return retval try: exec py.code.Source(setup, "setup()").compile() in {'channel': C()} except AttributeError: # channel.send From arigo at codespeak.net Wed Oct 11 18:00:50 2006 From: arigo at codespeak.net (arigo at codespeak.net) Date: Wed, 11 Oct 2006 18:00:50 +0200 (CEST) Subject: [py-svn] r33171 - in py/dist/py/code: . testing Message-ID: <20061011160050.5BFB410181@code0.codespeak.net> Author: arigo Date: Wed Oct 11 18:00:46 2006 New Revision: 33171 Modified: py/dist/py/code/testing/test_excinfo.py py/dist/py/code/traceback2.py Log: Test and fix for a case where recursionindex() was confused. Modified: py/dist/py/code/testing/test_excinfo.py ============================================================================== --- py/dist/py/code/testing/test_excinfo.py (original) +++ py/dist/py/code/testing/test_excinfo.py Wed Oct 11 18:00:46 2006 @@ -111,6 +111,23 @@ recindex = traceback.recursionindex() assert recindex == 3 + def test_traceback_no_recursion_index(self): + def do_stuff(): + raise RuntimeError + def reraise_me(): + import sys + exc, val, tb = sys.exc_info() + raise exc, val, tb + def f(n): + try: + do_stuff() + except: + reraise_me() + excinfo = py.test.raises(RuntimeError, f, 8) + traceback = excinfo.traceback + recindex = traceback.recursionindex() + assert recindex is None + def test_traceback_getcrashentry(self): def i(): __tracebackhide__ = True Modified: py/dist/py/code/traceback2.py ============================================================================== --- py/dist/py/code/traceback2.py (original) +++ py/dist/py/code/traceback2.py Wed Oct 11 18:00:46 2006 @@ -118,7 +118,7 @@ def recursionindex(self): cache = {} for i, entry in py.builtin.enumerate(self): - key = entry.frame.code.path, entry.frame.lineno + key = entry.frame.code.path, entry.lineno #print "checking for recursion at", key l = cache.setdefault(key, []) if l: From fijal at codespeak.net Wed Oct 11 18:25:25 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Wed, 11 Oct 2006 18:25:25 +0200 (CEST) Subject: [py-svn] r33173 - py/branch/apigen/py/test/tracer Message-ID: <20061011162525.89B2310194@code0.codespeak.net> Author: fijal Date: Wed Oct 11 18:25:23 2006 New Revision: 33173 Modified: py/branch/apigen/py/test/tracer/docstorage.py py/branch/apigen/py/test/tracer/genrest.py Log: Added return type. 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 Wed Oct 11 18:25:23 2006 @@ -88,6 +88,9 @@ self.module = module self.from_dict(module.__package__.__dict__) return self + + def from_module(self, func): + raise NotImplementedError("From module") class DocStorageAccessor(object): """ Set of helper functions to access DocStorage, separated in different @@ -117,8 +120,9 @@ code = py.code.Code(desc.code) return code.fullsource[code.firstlineno] - def get_function_args(self, name): - return self.ds.descs[name].inputcells + def get_function_signature(self, name): + desc = self.ds.descs[name] + return desc.inputcells, desc.retval def get_function_callpoints(self, name): # return list of tuple (filename, fileline, line) 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 Wed Oct 11 18:25:23 2006 @@ -119,9 +119,16 @@ lst = [Title("Function: %s" % fun_name, belowchar="-"), Paragraph(self.dsa.get_function_doc(fun_name)), BlockQuote(self.dsa.get_function_definition(fun_name))] - args = self.dsa.get_function_args(fun_name) - arg_str = "(%s)" % (",".join([str(i) for i in args])) - arg_quote = Paragraph("Function type:", Quote(arg_str)) + + lst.append(Paragraph("Function source: XXX (to write a link here)")) + + args, retval = self.dsa.get_function_signature(fun_name) + # XXX: we just do "knowntype" here, but we should + # present some more informative way, maybe even provide a link + # for the type declaration (if this is known) + arg_str = "(%s)" % (",".join([str(i.knowntype) for i in args])) + ret_str = str(retval.knowntype) + arg_quote = Paragraph("Function type:", Quote(arg_str), '->', Quote(ret_str)) lst.append(arg_quote) # call sites.. From fijal at codespeak.net Wed Oct 11 18:43:26 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Wed, 11 Oct 2006 18:43:26 +0200 (CEST) Subject: [py-svn] r33174 - in py/branch/apigen/py: . bin code code/testing documentation/apigen path/svn path/svn/testing rest/testing test/rsession test/rsession/testing test/rsession/webdata test/terminal test/testing test/tracer test/tracer/testing Message-ID: <20061011164326.2CD0F10190@code0.codespeak.net> Author: fijal Date: Wed Oct 11 18:43:18 2006 New Revision: 33174 Added: py/branch/apigen/py/documentation/apigen/roadmap.txt - copied unchanged from r33173, py/dist/py/documentation/apigen/roadmap.txt Modified: py/branch/apigen/py/__init__.py py/branch/apigen/py/bin/py.rest py/branch/apigen/py/code/testing/test_excinfo.py py/branch/apigen/py/code/traceback2.py py/branch/apigen/py/documentation/apigen/assumptions.txt py/branch/apigen/py/path/svn/svncommon.py py/branch/apigen/py/path/svn/testing/svntestbase.py py/branch/apigen/py/path/svn/testing/test_urlcommand.py py/branch/apigen/py/path/svn/testing/test_wccommand.py py/branch/apigen/py/path/svn/urlcommand.py py/branch/apigen/py/path/svn/wccommand.py py/branch/apigen/py/rest/testing/test_convert.py py/branch/apigen/py/test/rsession/box.py py/branch/apigen/py/test/rsession/hostmanage.py py/branch/apigen/py/test/rsession/master.py py/branch/apigen/py/test/rsession/rsession.py py/branch/apigen/py/test/rsession/slave.py py/branch/apigen/py/test/rsession/testing/test_boxing.py py/branch/apigen/py/test/rsession/testing/test_rsession.py py/branch/apigen/py/test/rsession/testing/test_slave.py py/branch/apigen/py/test/rsession/testing/test_web.py py/branch/apigen/py/test/rsession/webdata/ (props changed) py/branch/apigen/py/test/rsession/webjs.py py/branch/apigen/py/test/terminal/terminal.py py/branch/apigen/py/test/testing/test_session.py py/branch/apigen/py/test/tracer/description.py py/branch/apigen/py/test/tracer/docstorage.py py/branch/apigen/py/test/tracer/testing/test_docgen.py py/branch/apigen/py/test/tracer/testing/test_rest.py Log: Merged branches. One conflict only! but a lot of failing tests for various reasons (They've been failing before merge in some particular cases). Modified: py/branch/apigen/py/__init__.py ============================================================================== --- py/branch/apigen/py/__init__.py (original) +++ py/branch/apigen/py/__init__.py Wed Oct 11 18:43:18 2006 @@ -28,6 +28,7 @@ 'test.fail' : ('./test/item.py', 'fail'), 'test.exit' : ('./test/session.py', 'exit'), 'test.compat.TestCase' : ('./test/compat.py', 'TestCase'), + 'test.remote' : ('./test/rsession/rsession.py', 'remote_options'), # configuration/initialization related test api 'test.Config' : ('./test/config.py', 'Config'), Modified: py/branch/apigen/py/bin/py.rest ============================================================================== --- py/branch/apigen/py/bin/py.rest (original) +++ py/branch/apigen/py/bin/py.rest Wed Oct 11 18:43:18 2006 @@ -66,8 +66,13 @@ process_configfile(p, options.debug) else: if options.topdf: - process_rest_file(p.localpath, - options.stylesheet, + cfg = p.new(ext=".rst2pdfconfig") + if cfg.check(): + print "using config file %s" % (cfg, ) + process_configfile(cfg, options.debug) + else: + process_rest_file(p.localpath, + options.stylesheet, options.debug) else: rest.process(p) Modified: py/branch/apigen/py/code/testing/test_excinfo.py ============================================================================== --- py/branch/apigen/py/code/testing/test_excinfo.py (original) +++ py/branch/apigen/py/code/testing/test_excinfo.py Wed Oct 11 18:43:18 2006 @@ -111,6 +111,23 @@ recindex = traceback.recursionindex() assert recindex == 3 + def test_traceback_no_recursion_index(self): + def do_stuff(): + raise RuntimeError + def reraise_me(): + import sys + exc, val, tb = sys.exc_info() + raise exc, val, tb + def f(n): + try: + do_stuff() + except: + reraise_me() + excinfo = py.test.raises(RuntimeError, f, 8) + traceback = excinfo.traceback + recindex = traceback.recursionindex() + assert recindex is None + def test_traceback_getcrashentry(self): def i(): __tracebackhide__ = True Modified: py/branch/apigen/py/code/traceback2.py ============================================================================== --- py/branch/apigen/py/code/traceback2.py (original) +++ py/branch/apigen/py/code/traceback2.py Wed Oct 11 18:43:18 2006 @@ -34,10 +34,14 @@ self.exprinfo = x return self.exprinfo + def getfirstlinesource(self): + return self.frame.code.firstlineno + def getsource(self): """ return failing source code. """ source = self.frame.code.fullsource - start, end = self.frame.code.firstlineno, self.lineno + start = self.getfirstlinesource() + end = self.lineno try: _, end = source.getstatementrange(end) except IndexError: @@ -114,7 +118,7 @@ def recursionindex(self): cache = {} for i, entry in py.builtin.enumerate(self): - key = entry.frame.code.path, entry.frame.lineno + key = entry.frame.code.path, entry.lineno #print "checking for recursion at", key l = cache.setdefault(key, []) if l: Modified: py/branch/apigen/py/documentation/apigen/assumptions.txt ============================================================================== --- py/branch/apigen/py/documentation/apigen/assumptions.txt (original) +++ py/branch/apigen/py/documentation/apigen/assumptions.txt Wed Oct 11 18:43:18 2006 @@ -33,3 +33,8 @@ object which was really used, than least basic superclass of arguments that went inside). +Milestone 1: +------------ + +Date is set till sprint in Duesseldorf. + Modified: py/branch/apigen/py/path/svn/svncommon.py ============================================================================== --- py/branch/apigen/py/path/svn/svncommon.py (original) +++ py/branch/apigen/py/path/svn/svncommon.py Wed Oct 11 18:43:18 2006 @@ -5,9 +5,10 @@ import py from py.__.path import common -ALLOWED_CHARS = "_ -/\\=$." #add characters as necessary when tested +ALLOWED_CHARS = "_ -/\\=$.~" #add characters as necessary when tested if sys.platform == "win32": ALLOWED_CHARS += ":" +ALLOWED_CHARS_HOST = ALLOWED_CHARS + '@:' def _getsvnversion(ver=[]): try: @@ -25,12 +26,11 @@ text = str(text).replace('$', '\\$') return text - -def _check_for_bad_chars(text): +def _check_for_bad_chars(text, allowed_chars=ALLOWED_CHARS): for c in str(text): if c.isalnum(): continue - if c in ALLOWED_CHARS: + if c in allowed_chars: continue return True return False Modified: py/branch/apigen/py/path/svn/testing/svntestbase.py ============================================================================== --- py/branch/apigen/py/path/svn/testing/svntestbase.py (original) +++ py/branch/apigen/py/path/svn/testing/svntestbase.py Wed Oct 11 18:43:18 2006 @@ -10,7 +10,7 @@ # make a wc directory out of a given root url # cache previously obtained wcs! # -def getrepowc(reponame='abc$aaa', wcname='wc'): +def getrepowc(reponame='a~bc$aaa~', wcname='wc'): repo = py.test.ensuretemp(reponame) wcdir = py.test.ensuretemp(wcname) if not repo.listdir(): Modified: py/branch/apigen/py/path/svn/testing/test_urlcommand.py ============================================================================== --- py/branch/apigen/py/path/svn/testing/test_urlcommand.py (original) +++ py/branch/apigen/py/path/svn/testing/test_urlcommand.py Wed Oct 11 18:43:18 2006 @@ -12,9 +12,25 @@ repo, wc = getrepowc() cls.root = py.path.svnurl(repo) - def test_svnurl_characters(self): - test1 = py.path.svnurl("svn+ssh://hello/world") + def test_svnurl_characters_simple(self): + py.path.svnurl("svn+ssh://hello/world") + + def test_svnurl_characters_at_user(self): + py.path.svnurl("http://user at host.com/some/dir") + + def test_svnurl_characters_at_path(self): + py.test.raises(ValueError, 'py.path.svnurl("http://host.com/foo at bar")') + + def test_svnurl_characters_colon_port(self): + py.path.svnurl("http://host.com:8080/some/dir") + + def test_svnurl_characters_colon_path(self): + py.test.raises(ValueError, 'py.path.svnurl("http://host.com/foo:bar")') + + def test_svnurl_characters_tilde_end(self): + py.path.svnurl("http://host.com/some/file~") + # XXX def xtest_copy_file(self): raise py.test.Skipped(msg="XXX fix svnurl first") @@ -29,7 +45,6 @@ t = gmtime(res[0].date) assert t.tm_year == 2003 and t.tm_mon == 7 and t.tm_mday == 17 - class TestSvnInfoCommand: def test_svn_1_2(self): Modified: py/branch/apigen/py/path/svn/testing/test_wccommand.py ============================================================================== --- py/branch/apigen/py/path/svn/testing/test_wccommand.py (original) +++ py/branch/apigen/py/path/svn/testing/test_wccommand.py Wed Oct 11 18:43:18 2006 @@ -299,5 +299,8 @@ def test_info(self): self.wc.info().rev = 0 -def test_badchars(): +def test_characters_at(): py.test.raises(ValueError, "py.path.svnwc('/tmp/@@@:')") + +def test_characters_tilde(): + py.path.svnwc('/tmp/test~') Modified: py/branch/apigen/py/path/svn/urlcommand.py ============================================================================== --- py/branch/apigen/py/path/svn/urlcommand.py (original) +++ py/branch/apigen/py/path/svn/urlcommand.py Wed Oct 11 18:43:18 2006 @@ -22,10 +22,12 @@ self = object.__new__(cls) if not isinstance(path, str): path = str(path) - parts = path.split(":") + proto, uri = path.split("://", 1) + host, uripath = uri.split('/', 1) # only check for bad chars in the non-protocol parts - # XXX don't check svn+ssh host sections either - if len(parts) > 2 or svncommon._check_for_bad_chars(''.join(parts[1:])): + if (svncommon._check_for_bad_chars(host, svncommon.ALLOWED_CHARS_HOST) + or svncommon._check_for_bad_chars(uripath, + svncommon.ALLOWED_CHARS)): raise ValueError("bad char in path %s" % (path, )) path = path.rstrip('/') self.strpath = path Modified: py/branch/apigen/py/path/svn/wccommand.py ============================================================================== --- py/branch/apigen/py/path/svn/wccommand.py (original) +++ py/branch/apigen/py/path/svn/wccommand.py Wed Oct 11 18:43:18 2006 @@ -23,11 +23,12 @@ def __new__(cls, wcpath=None): self = object.__new__(cls) - if isinstance(wcpath, cls): - if wcpath.__class__ == cls: - return wcpath - wcpath = wcpath.localpath - if svncommon._check_for_bad_chars(str(wcpath)): + if isinstance(wcpath, cls): + if wcpath.__class__ == cls: + return wcpath + wcpath = wcpath.localpath + if svncommon._check_for_bad_chars(str(wcpath), + svncommon.ALLOWED_CHARS): raise ValueError("bad char in wcpath %s" % (wcpath, )) self.localpath = py.path.local(wcpath) return self Modified: py/branch/apigen/py/rest/testing/test_convert.py ============================================================================== --- py/branch/apigen/py/rest/testing/test_convert.py (original) +++ py/branch/apigen/py/rest/testing/test_convert.py Wed Oct 11 18:43:18 2006 @@ -9,6 +9,7 @@ py.test.skip("ghostscript, graphviz and latex needed") def test_convert_dot(): + py.test.skip("Check nothing right now") # XXX not really clear that the result is valid pdf/eps dot = datadir.join("example1.dot") convert_dot(dot, "pdf") @@ -21,6 +22,7 @@ eps.remove() def test_latexformula(): + py.test.skip("Check nothing right now") png = datadir.join("test.png") formula = r'$$Entropy(T) = - \sum^{m}_{j=1} \frac{|T_j|}{|T|} \log \frac{|T_j|}{|T|}$$' #does not crash Modified: py/branch/apigen/py/test/rsession/box.py ============================================================================== --- py/branch/apigen/py/test/rsession/box.py (original) +++ py/branch/apigen/py/test/rsession/box.py Wed Oct 11 18:43:18 2006 @@ -208,7 +208,60 @@ self.stdoutrepr = self.PYTESTSTDOUT.read() self.stderrrepr = self.PYTESTSTDERR.read() return self.stdoutrepr, self.stderrrepr + +def screen_run(): + # XXX: we *do* bootstrapping once again here + # which sucks a lot + # XXX: We need to make some kind of *blocking* fifo, cause + # we have really no idea when we'll arrive + import os + import py + + try: + pass + except: + pass + import pdb;pdb.set_trace() + +class ScreenBox(FileBox): + def run(self): + tempdir = py.test.ensuretemp("box") + self.tempdir = tempdir + self.PYTESTRETVAL = tempdir.join('retval') + self.PYTESTINPUT = tempdir.join('input') + #self.PYTESTINPUT.write(marshal.dumps({'args':self.args, \ + # 'kwargs':self.kwargs, 'fun':self.fun})) + + pid = os.fork() + if pid: + self.parent() + else: + os.chdir(str(py.path.local(py.__file__).dirpath().dirpath())) + screen = str(py.path.local.sysfind("screen")) + #os.execv(screen, ["python"]) + os.execv(screen, ["screen", "-d", "-m", "-L", "--", "python", \ + "-c", "from py.__.test.rsession import box\nbox.screen_run()"]) + return pid + + def parent(self): + pid, exitstat = os.wait() + self.signal = exitstat & 0x7f + self.exitstat = exitstat & 0xff00 + import pdb;pdb.set_trace() + #if not exitstat: + # retval = self.PYTESTRETVAL.open() + # try: + # retval_data = retval.read() + # finally: + # retval.close() + # self.retval = marshal.loads(retval_data) + #else: + self.retval = None + + self.stdoutrepr = py.path.local(__file__).dirpath().join("screenlog.0").read() + self.stderrrepr = "" + return self.stdoutrepr, self.stderrrepr RealBox = FileBox Box = FileBox Modified: py/branch/apigen/py/test/rsession/hostmanage.py ============================================================================== --- py/branch/apigen/py/test/rsession/hostmanage.py (original) +++ py/branch/apigen/py/test/rsession/hostmanage.py Wed Oct 11 18:43:18 2006 @@ -29,7 +29,7 @@ def init_hosts(reporter, sshhosts, relpath, pkgdir, rsync_roots=None, \ - remote_python=None): + remote_python=None, remote_options={}): assert pkgdir.join("__init__.py").check(), ( "%s probably wrong" %(pkgdir,)) assert relpath, relpath @@ -70,7 +70,8 @@ # hosts ready for host, gw, remoterootpath in hosts: - ch = setup_slave(gw, os.path.join(remoterootpath, pkgdir.basename)) + ch = setup_slave(gw, os.path.join(remoterootpath, pkgdir.basename), + remote_options) nodes.append(MasterNode(ch, reporter)) return nodes Modified: py/branch/apigen/py/test/rsession/master.py ============================================================================== --- py/branch/apigen/py/test/rsession/master.py (original) +++ py/branch/apigen/py/test/rsession/master.py Wed Oct 11 18:43:18 2006 @@ -43,11 +43,12 @@ break waiter() -def setup_slave(gateway, pkgpath): +def setup_slave(gateway, pkgpath, options={}): from py.__.test.rsession import slave import os ch = gateway.remote_exec(str(py.code.Source(slave.setup, "setup()"))) #if hasattr(gateway, 'sshaddress'): # assert not os.path.isabs(pkgpath) ch.send(str(pkgpath)) + ch.send(options) return ch Modified: py/branch/apigen/py/test/rsession/rsession.py ============================================================================== --- py/branch/apigen/py/test/rsession/rsession.py (original) +++ py/branch/apigen/py/test/rsession/rsession.py Wed Oct 11 18:43:18 2006 @@ -17,6 +17,15 @@ from py.__.test.terminal.out import getout +class RemoteOptions(object): + def __init__(self, d): + self.d = d + + def __getattr__(self, attr): + return self.d[attr] + +remote_options = RemoteOptions({'we_are_remote':False}) + class Reporter(object): def __init__(self, config, hosts): self.config = config Modified: py/branch/apigen/py/test/rsession/slave.py ============================================================================== --- py/branch/apigen/py/test/rsession/slave.py (original) +++ py/branch/apigen/py/test/rsession/slave.py Wed Oct 11 18:43:18 2006 @@ -47,19 +47,34 @@ try: node = getnode(nextitem) res = node.run(nextitem[1:]) + except py.test.Item.Skipped, s: + send(Outcome(skipped=str(s)).make_repr()) except: excinfo = py.code.ExceptionInfo() send(Outcome(excinfo=excinfo, is_critical=True).make_repr()) else: send(res) +def setup_screen(): + # We cannot easily just assume that we do have full communication + # channels, so we have to provide a new ones. + import thread + import os, sys + # the idea is simple: we create another process in which we perform + # read/write operations on both channels + + def setup(): import os, sys pkgdir = channel.receive() # path is ready + options = channel.receive() # options stuff, should be dictionary basedir = os.path.dirname(pkgdir) pkgname = os.path.basename(pkgdir) sys.path.insert(0, basedir) import py + from py.__.test.rsession.rsession import RemoteOptions + options['we_are_remote'] = True + py.test.remote = RemoteOptions(options) # XXX the following assumes that py lib is there, a bit # much of an assumtion mod = __import__(pkgname) Modified: py/branch/apigen/py/test/rsession/testing/test_boxing.py ============================================================================== --- py/branch/apigen/py/test/rsession/testing/test_boxing.py (original) +++ py/branch/apigen/py/test/rsession/testing/test_boxing.py Wed Oct 11 18:43:18 2006 @@ -7,7 +7,7 @@ if sys.platform == 'win32': py.test.skip("rsession is unsupported on Windows.") -from py.__.test.rsession.box import Box, RealBox +from py.__.test.rsession.box import Box, RealBox, ScreenBox from py.__.test.rsession.testing import example2 from py.__.test.rsession.conftest import option @@ -71,4 +71,3 @@ assert b.exitstat == 0 assert b.signal == 0 assert b.retval == 2 - Modified: py/branch/apigen/py/test/rsession/testing/test_rsession.py ============================================================================== --- py/branch/apigen/py/test/rsession/testing/test_rsession.py (original) +++ py/branch/apigen/py/test/rsession/testing/test_rsession.py Wed Oct 11 18:43:18 2006 @@ -7,7 +7,8 @@ from py.__.test.rsession.rsession import RSession from py.__.test.rsession.hostmanage import init_hosts, teardown_hosts from py.__.test.rsession.testing.test_slave import funcfail_spec,\ - funcpass_spec, funcskip_spec, funcprint_spec, funcprintfail_spec + funcpass_spec, funcskip_spec, funcprint_spec, funcprintfail_spec, \ + funcoptioncustom_spec, funcoption_spec def setup_module(mod): mod.pkgdir = py.path.local(py.__file__).dirpath() @@ -30,7 +31,7 @@ def test_getpkdir_no_inits(): tmp = py.test.ensuretemp("getpkdir1") fn = tmp.ensure("hello.py") - py.test.raises(AssertionError, "RSession.getpkgdir(fn)") + assert RSession.getpkgdir(fn) == fn def test_make_colitems(): one = pkgdir.join("initpkg.py") @@ -112,6 +113,7 @@ tmpdir = py.path.local(py.__file__).dirpath().dirpath() tmpdir.ensure("sub", "conftest.py").write(py.code.Source(""" disthosts = %r + distrsync_roots = ["sub", "py"] """ % self.hosts[:1])) tmpdir.ensure("sub", "__init__.py") tmpdir.ensure("sub", "test_one.py").write(py.code.Source(""" @@ -126,7 +128,7 @@ """)) args = [str(tmpdir.join("sub"))] config, args = py.test.Config.parse(args) - rsession = RSession(config) + rsession = RSession(config) allevents = [] rsession.main(args, reporter=allevents.append) testevents = [x for x in allevents @@ -156,7 +158,8 @@ setup_events = [] teardown_events = [] - nodes = init_hosts(setup_events.append, hosts, 'pytesttest', pkgdir) + nodes = init_hosts(setup_events.append, hosts, 'pytesttest', pkgdir, + rsync_roots=["py"]) teardown_hosts(teardown_events.append, [node.channel for node in nodes], nodes) @@ -179,7 +182,8 @@ hosts = self.hosts allevents = [] - nodes = init_hosts(allevents.append, hosts, 'pytesttest', pkgdir) + nodes = init_hosts(allevents.append, hosts, 'pytesttest', pkgdir, + rsync_roots=["py"]) from py.__.test.rsession.testing.test_executor \ import ItemTestPassing, ItemTestFailing, ItemTestSkipping @@ -211,3 +215,30 @@ # one of passed for each node has non-empty stdout passed_stdout = [i for i in passed if i.outcome.stdout == 'samfing\n'] assert len(passed_stdout) == len(nodes), passed + + def test_config_pass(self): + """ Tests options object passing master -> server + """ + allevents = [] + hosts = self.hosts + nodes = init_hosts(allevents.append, hosts, 'pytesttest', pkgdir, + rsync_roots=["py"], remote_options={'custom':'custom'}) + + rootcol = py.test.collect.Directory(pkgdir.dirpath()) + itempass = rootcol.getitembynames(funcoption_spec) + itempassaswell = rootcol.getitembynames(funcoptioncustom_spec) + + for node in nodes: + node.send(itempass) + node.send(itempassaswell) + + teardown_hosts(allevents.append, [node.channel for node in nodes], nodes) + events = [i for i in allevents + if isinstance(i, report.ReceivedItemOutcome)] + passed = [i for i in events + if i.outcome.passed] + skipped = [i for i in events + if i.outcome.skipped] + assert len(passed) == 2 * len(nodes) + assert len(skipped) == 0 + assert len(events) == len(passed) Modified: py/branch/apigen/py/test/rsession/testing/test_slave.py ============================================================================== --- py/branch/apigen/py/test/rsession/testing/test_slave.py (original) +++ py/branch/apigen/py/test/rsession/testing/test_slave.py Wed Oct 11 18:43:18 2006 @@ -29,11 +29,20 @@ print "samfing elz" asddsa -funcpass_spec = "py/test/rsession/testing/test_slave.py/funcpass".split("/") -funcfail_spec = "py/test/rsession/testing/test_slave.py/funcfail".split("/") -funcskip_spec = "py/test/rsession/testing/test_slave.py/funcskip".split("/") -funcprint_spec = "py/test/rsession/testing/test_slave.py/funcprint".split("/") -funcprintfail_spec = "py/test/rsession/testing/test_slave.py/funcprintfail".split("/") +def funcoption(): + assert py.test.remote.we_are_remote + +def funcoptioncustom(): + assert py.test.remote.custom == "custom" + +BASE = "py/test/rsession/testing/test_slave.py/" +funcpass_spec = (BASE + "funcpass").split("/") +funcfail_spec = (BASE + "funcfail").split("/") +funcskip_spec = (BASE + "funcskip").split("/") +funcprint_spec = (BASE + "funcprint").split("/") +funcprintfail_spec = (BASE + "funcprintfail").split("/") +funcoption_spec = (BASE + "funcoption").split("/") +funcoptioncustom_spec = (BASE + "funcoptioncustom").split("/") # ---------------------------------------------------------------------- @@ -107,9 +116,19 @@ def test_slave_setup_fails_on_import_error(): from py.__.test.rsession.slave import setup tmp = py.test.ensuretemp("slavesetup") - class C: + class C: + def __init__(self): + self.count = 0 + def receive(self): - return str(tmp) + if self.count == 0: + retval = str(tmp) + elif self.count == 1: + retval = {} + else: + raise NotImplementedError("mora data") + self.count += 1 + return retval try: exec py.code.Source(setup, "setup()").compile() in { 'channel': C()} @@ -123,8 +142,18 @@ tmp = py.test.ensuretemp("slavesetup2") x = tmp.ensure("sometestpackage", "__init__.py") class C: + def __init__(self): + self.count = 0 + def receive(self): - return str(x.dirpath()) + if self.count == 0: + retval = str(x.dirpath()) + elif self.count == 1: + retval = {} + else: + raise NotImplementedError("mora data") + self.count += 1 + return retval try: exec py.code.Source(setup, "setup()").compile() in {'channel': C()} except AttributeError: # channel.send Modified: py/branch/apigen/py/test/rsession/testing/test_web.py ============================================================================== --- py/branch/apigen/py/test/rsession/testing/test_web.py (original) +++ py/branch/apigen/py/test/rsession/testing/test_web.py Wed Oct 11 18:43:18 2006 @@ -10,6 +10,7 @@ commproxy.USE_MOCHIKIT = False Options.debug_transform = True + Options.use_pdb = False except ImportError: py.test.skip("No PyPy detected") Modified: py/branch/apigen/py/test/rsession/webjs.py ============================================================================== --- py/branch/apigen/py/test/rsession/webjs.py (original) +++ py/branch/apigen/py/test/rsession/webjs.py Wed Oct 11 18:43:18 2006 @@ -2,10 +2,14 @@ """ javascript source for py.test distributed """ +import py from py.__.test.rsession.web import exported_methods -from pypy.translator.js.modules import _dom as dom -from pypy.translator.js.helper import __show_traceback -from pypy.translator.transformer.debug import traceback_handler +try: + from pypy.translator.js.modules import _dom as dom + from pypy.translator.js.helper import __show_traceback + from pypy.translator.transformer.debug import traceback_handler +except ImportError, s: + py.test.skip("Cannot import: %s" % str(s)) def create_elem(s): return dom.get_document().createElement(s) Modified: py/branch/apigen/py/test/terminal/terminal.py ============================================================================== --- py/branch/apigen/py/test/terminal/terminal.py (original) +++ py/branch/apigen/py/test/terminal/terminal.py Wed Oct 11 18:43:18 2006 @@ -294,11 +294,13 @@ else: self.out.line("") source = self.getentrysource(entry) + firstsourceline = entry.getfirstlinesource() + marker_location = entry.lineno - firstsourceline if entry == last: - self.repr_source(source, 'E') + self.repr_source(source, 'E', marker_location) self.repr_failure_explanation(excinfo, source) else: - self.repr_source(source, '>') + self.repr_source(source, '>', marker_location) self.out.line("") self.out.line("[%s:%d]" %(entry.frame.code.path, entry.lineno+1)) self.repr_locals(entry) @@ -387,11 +389,19 @@ source = py.code.Source("[failure to get at sourcelines from %r]\n" % entry) return source.deindent() - def repr_source(self, source, marker=">"): - for line in source[:-1]: - self.out.line(" " + line) - lastline = source[-1] - self.out.line(marker + " " + lastline) + def repr_source(self, source, marker=">", marker_location=-1): + if marker_location < 0: + marker_location += len(source) + if marker_location < 0: + marker_location = 0 + if marker_location >= len(source): + marker_location = len(source) - 1 + for i in range(len(source)): + if i == marker_location: + prefix = marker + " " + else: + prefix = " " + self.out.line(prefix + source[i]) def repr_failure_explanation(self, excinfo, source): try: Modified: py/branch/apigen/py/test/testing/test_session.py ============================================================================== --- py/branch/apigen/py/test/testing/test_session.py (original) +++ py/branch/apigen/py/test/testing/test_session.py Wed Oct 11 18:43:18 2006 @@ -306,6 +306,31 @@ assert out.find("""[Exception("Ha Ha fooled you, I'm a broken repr().") raised in repr()]""") != -1 #' assert out.find("[unknown exception raised in repr()]") != -1 + def test_E_on_correct_line(self): + o = tmpdir.ensure('E_on_correct_line', dir=1) + tfile = o.join('test_correct_line.py') + source = py.code.Source(""" + import py + def test_hello(): + assert (None == + ['a', + 'b', + 'c']) + """) + tfile.write(source) + session = self.session + session.main([o]) + out = self.file.getvalue() + print 'Output of simulated "py.test test_correct_line.py":' + print out + i = out.find('test_correct_line.py:') + assert i >= 0 + linenum = int(out[i+len('test_correct_line.py:')]) # a single char + line_to_report = source[linenum-1] + expected_output = '\nE ' + line_to_report + '\n' + print 'Looking for:', expected_output + assert expected_output in out + from py.__.test.terminal.remote import getrootdir class TestRemote: 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 Wed Oct 11 18:43:18 2006 @@ -1,5 +1,8 @@ -from pypy.annotation import model +try: + from pypy.annotation import model +except ImportError: + pass import types 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 Wed Oct 11 18:43:18 2006 @@ -9,8 +9,12 @@ from py.__.test.tracer.description import FunctionDesc, ClassDesc, MethodDesc, \ Desc -from pypy.annotation.policy import AnnotatorPolicy -from pypy.annotation.bookkeeper import Bookkeeper + +try: + from pypy.annotation.policy import AnnotatorPolicy + from pypy.annotation.bookkeeper import Bookkeeper +except ImportError: + py.test.skip("No PyPy") class DummyAnnotator(object): policy = AnnotatorPolicy() 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 Wed Oct 11 18:43:18 2006 @@ -5,14 +5,18 @@ import py import sys -from py.__.test.tracer.tracer import DocStorage, Tracer -from py.__.test.tracer.testing.runtest import cut_pyc -from pypy.annotation import model +try: + from py.__.test.tracer.tracer import DocStorage, Tracer + from py.__.test.tracer.testing.runtest import cut_py + from pypy.annotation import model +except ImportError, s: + py.test.skip("Cannot import: %s" % str(s)) #def setup_module(mod): # data_path = py.path.local(mod.__file__).dirpath().join("data") # sys.path.insert(0, str(data_path)) + def fun(a, b, c): "Some docstring" return "d" Modified: py/branch/apigen/py/test/tracer/testing/test_rest.py ============================================================================== --- py/branch/apigen/py/test/tracer/testing/test_rest.py (original) +++ py/branch/apigen/py/test/tracer/testing/test_rest.py Wed Oct 11 18:43:18 2006 @@ -4,12 +4,15 @@ import py -from py.__.test.tracer.genrest import ViewVC, RestGen, PipeWriter, DirWriter,\ - DirectPaste -from py.__.test.tracer.tracer import DocStorage, Tracer - -from StringIO import StringIO -from py.__.test.tracer.testing.runtest import cut_pyc +try: + from py.__.test.tracer.genrest import ViewVC, RestGen, PipeWriter, DirWriter,\ + DirectPaste + from py.__.test.tracer.tracer import DocStorage, Tracer + + from StringIO import StringIO + from py.__.test.tracer.testing.runtest import cut_pyc +except ImportError, s: + py.test.skip("Cannot import: %s" % str(s)) def test_links(): vcview = ViewVC("http://codespeak.net/viewvc/") From fijal at codespeak.net Wed Oct 11 19:09:36 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Wed, 11 Oct 2006 19:09:36 +0200 (CEST) Subject: [py-svn] r33177 - py/dist/py/documentation/apigen Message-ID: <20061011170936.44D011019E@code0.codespeak.net> Author: fijal Date: Wed Oct 11 19:09:34 2006 New Revision: 33177 Modified: py/dist/py/documentation/apigen/roadmap.txt Log: Updated. Modified: py/dist/py/documentation/apigen/roadmap.txt ============================================================================== --- py/dist/py/documentation/apigen/roadmap.txt (original) +++ py/dist/py/documentation/apigen/roadmap.txt Wed Oct 11 19:09:34 2006 @@ -15,7 +15,7 @@ * further py.test distributed developement, especially incorporating some command line options (I think -x and -k are done). This might be: - - options transfer to clients (maybe whole conftest) to allow them + - DONE options transfer to clients (maybe whole conftest) to allow them to perform special things. - -s (how???) --view for pypy, etc. - screen/--pdb (there is some work done in this area) @@ -31,47 +31,37 @@ connects to it (actually it does not). - XXX: anything else? - * integration from py.test distributed into normal py.test, especially cool reporting features as well as web frontend (and extend it a bit) -* benchmark integration into py.test. This issue haven't been discussed - in details. From my POV there should be done like running test in a - loop till having at least few seconds of run (some pypy tests takes - minutes to run, so this will not re-run the tests, but just take one), - than gather information and report it somehow. Should work as well - in distributed version (due to cool reporting, this might be the first - place to integrate). Various issues: +* integrate some kind of TestRunner, which will run the tests and perform + some additional stuff. This is related to (but not limited): + + - benchmarks - - Measure in MegaPystones or some other not-CPU-dependant units, so - we can compare results from different machines. + - py.api integration PY.API: ------- (actually lying down in py.test.tracer in branch, maybe not -too nice name for that). - -Actual working version can provide simple ReST out of running very -simple tests (functions only). +too nice name for that, will go to py.api). So, todo list is (in random order): * Make interface for every possible single item on the end (function, - method, generator) to provide better coverage. + method, generator) to provide better coverage. Function/method DONE. * Make more... (frontend for API extraction, backend for output generation, different source link generators, etc. etc.) -* Integrate it into py.test, as well as make possibility of gathering - this data from running process (this might allow to get it remotely - as well) with quite different tool (console/web, with possibility - to snapshot to static outcome). - * Provide some model of determining which methods are exposed as API and which are internal (just called directly in unittest looks like enough for now, altough we do not have method of determining that yet) +* Provide some command line debugger-like tool (PDB hook?) which will expose + API information additionally to debugging info. + * Make clean vision of pypy annotation typesystem, probably even extend it a bit (it's still easier to use than providing all the interface on our own). Possible extensions: From fijal at codespeak.net Wed Oct 11 19:10:24 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Wed, 11 Oct 2006 19:10:24 +0200 (CEST) Subject: [py-svn] r33178 - in py/dist/py/documentation: . apigen Message-ID: <20061011171024.C161D1019E@code0.codespeak.net> Author: fijal Date: Wed Oct 11 19:10:22 2006 New Revision: 33178 Added: py/dist/py/documentation/roadmap.txt - copied unchanged from r33177, py/dist/py/documentation/apigen/roadmap.txt Removed: py/dist/py/documentation/apigen/roadmap.txt Log: Moved to more apropriate place. Deleted: /py/dist/py/documentation/apigen/roadmap.txt ============================================================================== --- /py/dist/py/documentation/apigen/roadmap.txt Wed Oct 11 19:10:22 2006 +++ (empty file) @@ -1,99 +0,0 @@ - -Pylib roadmap, from my point of view -==================================== - -Author: Maciek Fijalkowski --------------------------- - -Date: 7.X.2006 - -Rough specifications of what needs to be done regarding py.test -(especially distributed version), py.api and others. - -So main targets (for near future) of pylib developement would be: - -* further py.test distributed developement, especially incorporating - some command line options (I think -x and -k are done). This might be: - - - DONE options transfer to clients (maybe whole conftest) to allow them - to perform special things. - - -s (how???) --view for pypy, etc. - - screen/--pdb (there is some work done in this area) - - If someone (merlinux?) set up test farm (few machines connected - together for running tests), provide convenient interface for - running at least nightly tests (web one? offline html?) or even - just running tests for branch, trunk, custom svn without need to - upload local changes every time (RSync daemon?). I'm not sure if - it does make any sense, but makes convinient if we provide - front machine with access, not all the nodes (XXX and what with - screen?) - - Make sure web frontend works when more than one client (webbrowser) - connects to it (actually it does not). - - XXX: anything else? - -* integration from py.test distributed into normal py.test, especially - cool reporting features as well as web frontend (and extend it a bit) - -* integrate some kind of TestRunner, which will run the tests and perform - some additional stuff. This is related to (but not limited): - - - benchmarks - - - py.api integration - -PY.API: -------- - -(actually lying down in py.test.tracer in branch, maybe not -too nice name for that, will go to py.api). - -So, todo list is (in random order): - -* Make interface for every possible single item on the end (function, - method, generator) to provide better coverage. Function/method DONE. - -* Make more... (frontend for API extraction, backend for output - generation, different source link generators, etc. etc.) - -* Provide some model of determining which methods are exposed as API - and which are internal (just called directly in unittest looks like - enough for now, altough we do not have method of determining that yet) - -* Provide some command line debugger-like tool (PDB hook?) which will expose - API information additionally to debugging info. - -* Make clean vision of pypy annotation typesystem, probably even - extend it a bit (it's still easier to use than providing all - the interface on our own). Possible extensions: - - - Provide SomeUnion instead of SomeObject which will behave like - gathering all possibilities together (this might be beneficient to - pypy as well). - - Provide some convinient way of getting common interface out of - objects that are contained in SomeObject. Like "what are the - common methods of both, even if they do not share a baseclass". - In this place, it should be quite easy to add different type - joining functions by user, because different projects might have - different needs at this point. - -* Test it on generating pylib documentation. - -* Make some API definition tool (object format) for defining API in - pypy. This will make a lot easier annotating the pypy source with - info (like "what the heck type comes here as an argument"....) - -* Add several different things to trace, mostly tracing of side effects - (track what method can change what attributes, as well as in child nodes. - Maybe global variables as well). - -Timeline: ---------- - -This is quite huge wishlist.... I don't know exactly what are priorities -on all the stuff, what I would go first is to make API tool usable and -easy to use as soon as possible, to show it at HHU on sprint in some -running fashion. It should be well tested and maybe not containing -all-the-possible features, but runnable. - -Next thing to do is to extend py.test distributed, especially with -screen functionality. From hpk at codespeak.net Wed Oct 11 19:10:57 2006 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 11 Oct 2006 19:10:57 +0200 (CEST) Subject: [py-svn] r33179 - in py/dist/py/misc: . testing Message-ID: <20061011171057.316B61019F@code0.codespeak.net> Author: hpk Date: Wed Oct 11 19:10:56 2006 New Revision: 33179 Modified: py/dist/py/misc/svnlook.py py/dist/py/misc/testing/test_svnlook.py Log: added a method to get at the author of a commit given local access to a repo Modified: py/dist/py/misc/svnlook.py ============================================================================== --- py/dist/py/misc/svnlook.py (original) +++ py/dist/py/misc/svnlook.py Wed Oct 11 19:10:56 2006 @@ -25,6 +25,10 @@ l.append(ChangeItem(repo, revision, line)) return l +def author(repo, revision): + out = py.process.cmdexec("svnlook author -r %s %s" %(revision, repo)) + return out.strip() + def youngest(repo): out = py.process.cmdexec("svnlook youngest %s" %(repo,)) return int(out) Modified: py/dist/py/misc/testing/test_svnlook.py ============================================================================== --- py/dist/py/misc/testing/test_svnlook.py (original) +++ py/dist/py/misc/testing/test_svnlook.py Wed Oct 11 19:10:56 2006 @@ -15,6 +15,9 @@ py.process.cmdexec("svnadmin create --fs-type fsfs %s" % repo) py.process.cmdexec("svnadmin load %s <%s" %(repo, data.join("svnlookrepo.dump"))) + + author = svnlook.author(repo, 1) + assert author == "hpk" for item in svnlook.changed(repo, 1): svnurl = item.svnurl() From fijal at codespeak.net Thu Oct 12 14:12:39 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Thu, 12 Oct 2006 14:12:39 +0200 (CEST) Subject: [py-svn] r33226 - py/dist/py/test/rsession Message-ID: <20061012121239.19D65101EC@code0.codespeak.net> Author: fijal Date: Thu Oct 12 14:12:37 2006 New Revision: 33226 Modified: py/dist/py/test/rsession/rsession.py Log: Better signal reporting. Modified: py/dist/py/test/rsession/rsession.py ============================================================================== --- py/dist/py/test/rsession/rsession.py (original) +++ py/dist/py/test/rsession/rsession.py Thu Oct 12 14:12:37 2006 @@ -98,7 +98,10 @@ host = event.channel.gateway.sshaddress self.out.sep('_', "%s on %s" % (" ".join(event.item.listnames()), host)) - self.repr_failure(event.item, event.outcome) + if event.outcome.signal: + self.repr_signal(event.item, event.outcome) + else: + self.repr_failure(event.item, event.outcome) def repr_failure(self, item, outcome): excinfo = outcome.excinfo @@ -117,7 +120,16 @@ if outcome.stderr: self.out.sep('-', " Captured process stderr: ") self.out.write(outcome.stderr) - + + def repr_signal(self, item, outcome): + signal = outcome.signal + self.out.line("Received signal: %d" % outcome.signal) + if outcome.stdout: + self.out.sep('-', " Captured process stdout: ") + self.out.write(outcome.stdout) + if outcome.stderr: + self.out.sep('-', " Captured process stderr: ") + self.out.write(outcome.stderr) def repr_failure_tblong(self, item, excinfo, traceback): for index, entry in py.builtin.enumerate(traceback): From fijal at codespeak.net Fri Oct 13 01:25:31 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Fri, 13 Oct 2006 01:25:31 +0200 (CEST) Subject: [py-svn] r33242 - py/dist/py/test/rsession Message-ID: <20061012232531.E423B1009C@code0.codespeak.net> Author: fijal Date: Fri Oct 13 01:25:28 2006 New Revision: 33242 Modified: py/dist/py/test/rsession/webjs.py Log: Fixed test. Modified: py/dist/py/test/rsession/webjs.py ============================================================================== --- py/dist/py/test/rsession/webjs.py (original) +++ py/dist/py/test/rsession/webjs.py Fri Oct 13 01:25:28 2006 @@ -2,10 +2,14 @@ """ javascript source for py.test distributed """ +import py from py.__.test.rsession.web import exported_methods -from pypy.translator.js.modules import _dom as dom -from pypy.translator.js.helper import __show_traceback -from pypy.translator.transformer.debug import traceback_handler +try: + from pypy.translator.js.modules import _dom as dom + from pypy.translator.js.helper import __show_traceback + from pypy.translator.transformer.debug import traceback_handler +except ImportError: + py.test.skip("PyPy not found") def create_elem(s): return dom.get_document().createElement(s) From fijal at codespeak.net Sat Oct 14 14:00:51 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Sat, 14 Oct 2006 14:00:51 +0200 (CEST) Subject: [py-svn] r33283 - in py/dist/py: code rst rst/testing test/rsession test/rsession/testing test/tracer test/tracer/testing Message-ID: <20061014120051.42AB3100F6@code0.codespeak.net> Author: fijal Date: Sat Oct 14 13:58:29 2006 New Revision: 33283 Added: py/dist/py/test/rsession/testing/test_reporter.py (contents, props changed) py/dist/py/test/tracer/ (props changed) py/dist/py/test/tracer/__init__.py (contents, props changed) py/dist/py/test/tracer/description.py (contents, props changed) py/dist/py/test/tracer/docstorage.py (contents, props changed) py/dist/py/test/tracer/genrest.py (contents, props changed) py/dist/py/test/tracer/magic.py (contents, props changed) py/dist/py/test/tracer/testing/ (props changed) py/dist/py/test/tracer/testing/__init__.py (contents, props changed) py/dist/py/test/tracer/testing/runtest.py (contents, props changed) py/dist/py/test/tracer/testing/test_docgen.py (contents, props changed) py/dist/py/test/tracer/testing/test_magic.py (contents, props changed) py/dist/py/test/tracer/testing/test_rest.py (contents, props changed) py/dist/py/test/tracer/tracer.py (contents, props changed) Modified: py/dist/py/code/code.py py/dist/py/code/frame.py py/dist/py/rst/rst.py py/dist/py/rst/testing/test_rst.py py/dist/py/test/rsession/rsession.py Log: Missing branch merge checkin. Modified: py/dist/py/code/code.py ============================================================================== --- py/dist/py/code/code.py (original) +++ py/dist/py/code/code.py Sat Oct 14 13:58:29 2006 @@ -67,4 +67,13 @@ return py.code.Source(self.path.read(mode="rU")) fullsource = property(fullsource, None, None, "full source containing this code object") + + def source(self): + # return source only for that part of code + import inspect + return py.code.Source(inspect.getsource(self.raw)) + def getargs(self): + # handfull shortcut for getting args + raw = self.raw + return raw.co_varnames[:raw.co_argcount] Modified: py/dist/py/code/frame.py ============================================================================== --- py/dist/py/code/frame.py (original) +++ py/dist/py/code/frame.py Sat Oct 14 13:58:29 2006 @@ -10,6 +10,7 @@ self.lineno = frame.f_lineno - 1 self.f_globals = frame.f_globals self.f_locals = frame.f_locals + self.raw = frame def statement(self): return self.code.fullsource.getstatement(self.lineno) @@ -32,4 +33,8 @@ def is_true(self, object): return object - + def getargs(self): + retval = [] + for arg in self.code.getargs(): + retval.append((arg, self.f_locals[arg])) + return retval Modified: py/dist/py/rst/rst.py ============================================================================== --- py/dist/py/rst/rst.py (original) +++ py/dist/py/rst/rst.py Sat Oct 14 13:58:29 2006 @@ -85,7 +85,7 @@ link_texts = [] for link, target in self.links.iteritems(): link_texts.append(".. _`%s`: %s" % (link, target)) - return "\n".join(link_texts) + "\n" + return "\n" + "\n".join(link_texts) + "\n" def text(self): outcome = [] @@ -124,6 +124,7 @@ def grab(buf): outcome.append(self.indent + self.sep.join(buf)) + texts.reverse() while texts: next = texts[-1] if lgt + len(self.sep) + len(next) <= self.width or not buf: @@ -135,7 +136,6 @@ lgt = len(self.indent) buf = [] grab(buf) - outcome.reverse() return "\n".join(outcome) class SubParagraph(Paragraph): @@ -151,14 +151,14 @@ all_txts = all_txt.split("\n") return "\n".join([self.indent + i for i in all_txts]) -class Title(AbstractNode): +class Title(Paragraph): parentclass = Rest belowchar = "" abovechar = "" previous_paragraph = None def text(self): - txt = AbstractNode.text(self) + txt = Paragraph.text(self) lines = [] if self.abovechar: lines.append(self.abovechar * len(txt)) @@ -185,6 +185,16 @@ start = "*" end = "*" +class ListItem(Paragraph): + item_char = "*" + + def text(self): + self.indent = self.indent + " " + txt = Paragraph.text(self) + txt = self.item_char + txt[1:] + del self.indent + return txt + class Link(AbstractText): start = '`' end = '`_' @@ -206,3 +216,7 @@ while next.parent is not None: next = next.parent return next + +class Quote(AbstractText): + start = '``' + end = '``' Modified: py/dist/py/rst/testing/test_rst.py ============================================================================== --- py/dist/py/rst/testing/test_rst.py (original) +++ py/dist/py/rst/testing/test_rst.py Sat Oct 14 13:58:29 2006 @@ -35,3 +35,13 @@ def test_link(): expected = "`some link`_\n.. _`some link`: http://codespeak.net\n" txt = Rest(Paragraph(Link("some link", "http://codespeak.net"))).text() + +def test_text(): + expected = "This is a test!\n" + txt = Rest(Paragraph("This is a test!")).text() + assert txt == expected + +def test_list(): + expected = "* a\n\n* b\n" + txt = Rest(ListItem("a"), ListItem("b")).text() + assert txt == expected Modified: py/dist/py/test/rsession/rsession.py ============================================================================== --- py/dist/py/test/rsession/rsession.py (original) +++ py/dist/py/test/rsession/rsession.py Sat Oct 14 13:58:29 2006 @@ -15,7 +15,7 @@ setup_slave, MasterNode, dispatch_loop from py.__.test.rsession.hostmanage import init_hosts, teardown_hosts -from py.__.test.terminal.out import getout +from py.__.test.terminal.out import getout class RemoteOptions(object): def __init__(self, d): @@ -27,8 +27,8 @@ remote_options = RemoteOptions({'we_are_remote':False}) class Reporter(object): - def __init__(self, config, hosts): - self.config = config + def __init__(self, config, hosts): + self.config = config self.failed_tests_outcome = [] self.skipped_tests_outcome = [] self.out = getout(py.std.sys.stdout) Added: py/dist/py/test/rsession/testing/test_reporter.py ============================================================================== --- (empty file) +++ py/dist/py/test/rsession/testing/test_reporter.py Sat Oct 14 13:58:29 2006 @@ -0,0 +1,38 @@ + +""" reporter tests. This is crippled test, because we just +test if it *works*, not if the output produced is what we really like +""" + +import py +from py.__.test.rsession.rsession import Reporter +from py.__.test.rsession.report import ReceivedItemOutcome +from py.__.test.rsession.outcome import ReprOutcome, Outcome +from py.__.test.rsession.testing.test_slave import funcpass_spec + +def setup_module(mod): + mod.pkgdir = py.path.local(py.__file__).dirpath() + +class TestReporter(object): + def test_report_received_item_outcome(self): + config, args = py.test.Config.parse(["some_sub"]) + r = Reporter(config, ["host"]) + # possible outcomes + try: + 1/0 + except: + exc = py.code.ExceptionInfo() + + outcomes = [Outcome(()), + Outcome(skipped=True), + Outcome(excinfo=exc), + Outcome()] + + outcomes = [ReprOutcome(outcome.make_repr()) for outcome in outcomes] + outcomes[3].signal = 11 + outcomes[0].passed = False + + # we just go... + rootcol = py.test.collect.Directory(pkgdir.dirpath()) + item = rootcol.getitembynames(funcpass_spec) + for outcome in outcomes: + r.report(ReceivedItemOutcome(None, item, outcome)) Added: py/dist/py/test/tracer/__init__.py ============================================================================== Added: py/dist/py/test/tracer/description.py ============================================================================== --- (empty file) +++ py/dist/py/test/tracer/description.py Sat Oct 14 13:58:29 2006 @@ -0,0 +1,156 @@ + +try: + from pypy.annotation import model +except ImportError: + pass + +import types + +class CallSite(object): + def __init__(self, filename, lineno): + self.filename = filename + self.lineno = lineno + + def get_tuple(self): + return self.filename, self.lineno + + def __hash__(self): + return hash((self.filename, self.lineno)) + + def __eq__(self, other): + return (self.filename, self.lineno) == (other.filename, other.lineno) + + def __ne__(self, other): + return not self == other + + def __cmp__(self, other): + if self.filename < other.filename: + return -1 + if self.filename > other.filename: + return 1 + if self.lineno < other.lineno: + return -1 + if self.lineno > other.lineno: + return 1 + return 0 + +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, **kwargs): + 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 __init__(self, *args, **kwargs): + super(FunctionDesc, self).__init__(*args, **kwargs) + self.inputcells = [model.s_ImpossibleValue for i in xrange(self.\ + code.co_argcount)] + self.call_sites = {} + self.keep_frames = kwargs.get('keep_frames', False) + self.frame_copier = kwargs.get('frame_copier', lambda x:x) + self.retval = model.s_ImpossibleValue + + def consider_call(self, inputcells): + for cell_num, cell in enumerate(inputcells): + self.inputcells[cell_num] = model.unionof(cell, self.inputcells[cell_num]) + + def consider_call_site(self, frame): + cs = CallSite(frame.code.raw.co_filename, frame.lineno+1) + if self.keep_frames: + if cs in self.call_sites: + self.call_sites[cs].append(self.frame_copier(frame)) + else: + self.call_sites[cs] = [self.frame_copier(frame)] + else: + # frame copier makes no sense if we want to keep only one + self.call_sites[cs] = frame + + def get_call_sites(self): + # convinient accessor for various data which we keep there + if not self.keep_frames: + return [(key, val) for key, val in self.call_sites.iteritems()] + else: + lst = [] + for key, val in self.call_sites.iteritems(): + for frame in val: + lst.append((key, frame)) + return lst + + def consider_return(self, arg): + self.retval = model.unionof(arg, self.retval) + + def getcode(self): + return self.pyobj.func_code + code = property(getcode) + +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_return(self, arg): + pass # we *know* what return value we do have + + 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 Added: py/dist/py/test/tracer/docstorage.py ============================================================================== --- (empty file) +++ py/dist/py/test/tracer/docstorage.py Sat Oct 14 13:58:29 2006 @@ -0,0 +1,169 @@ + +""" This module is keeping track about API informations as well as +providing some interface to easily access stored data +""" + +import py +import sys +import types + +from py.__.test.tracer.description import FunctionDesc, ClassDesc, MethodDesc, \ + Desc + +try: + from pypy.annotation.policy import AnnotatorPolicy + from pypy.annotation.bookkeeper import Bookkeeper +except ImportError: + py.test.skip("No PyPy") + +class DummyAnnotator(object): + policy = AnnotatorPolicy() + +class DocStorage(object): + """ Class storing info about API + """ + def __init__(self): + self.bookkeeper = Bookkeeper(DummyAnnotator()) + #self.call_stack = [] + #self.frames = [] + + def consider_call(self, frame, caller_frame): + assert isinstance(frame, py.code.Frame) + desc = self.find_desc(frame.code) + if desc: + self.generalize_args(desc, frame) + desc.consider_call_site(caller_frame) + + def generalize_args(self, desc, frame): + args = [arg for key, arg in frame.getargs()] + #self.call_stack.append((desc, args)) + desc.consider_call([self.bookkeeper.immutablevalue(arg) for arg in args]) + + def generalize_retval(self, desc, arg): + desc.consider_return(self.bookkeeper.immutablevalue(arg)) + + def consider_return(self, frame, arg): + assert isinstance(frame, py.code.Frame) + desc = self.find_desc(frame.code) + if desc: + self.generalize_retval(desc, arg) + + def find_desc(self, code): + return self.desc_cache.get(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, keep_frames = False): + 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 add_desc(self, name, value, **kwargs): + key = name + count = 1 + while key in self.descs: + key = "%s_%d" % (name, count) + count += 1 + key, desc = self.make_desc(key, value, **kwargs) + self.descs[key] = desc + self.desc_cache[desc] = desc + return desc + + def make_desc(self, key, value, **kwargs): + if isinstance(value, types.FunctionType): + desc = FunctionDesc(key, value, **kwargs) + elif isinstance(value, (types.ObjectType, types.ClassType)): + desc = ClassDesc(key, value, **kwargs) + for name in dir(value): + field = getattr(value, name) + if 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, **kwargs) + 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__) + return self + + def from_module(self, func): + raise NotImplementedError("From module") + +class DocStorageAccessor(object): + """ Set of helper functions to access DocStorage, separated in different + class to keep abstraction + """ + def __init__(self, ds): + self.ds = ds + + def _get_names(self, filter): + return [i for i, desc in self.ds.descs.iteritems() if filter(i, desc)] + + def get_function_names(self): + return self._get_names(lambda i, desc: type(desc) is FunctionDesc) + + def get_class_names(self): + return self._get_names(lambda i, desc: isinstance(desc, ClassDesc)) + + #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*" + + def get_function_definition(self, name): + desc = self.ds.descs[name] + assert isinstance(desc, FunctionDesc) + code = py.code.Code(desc.code) + return code.fullsource[code.firstlineno] + + def get_function_signature(self, name): + desc = self.ds.descs[name] + return desc.inputcells, desc.retval + + def get_function_callpoints(self, name): + # return list of tuple (filename, fileline, frame) + return self.ds.descs[name].get_call_sites() + + def get_module_name(self): + if hasattr(self.ds, 'module'): + return self.ds.module.__name__ + return "Unknown module" + + def get_class_methods(self, name): + desc = self.ds.descs[name] + assert isinstance(desc, ClassDesc) + return desc.getfields() + + #def get_object_info(self, key): + # + + def get_module_info(self): + module = getattr(self.ds, 'module', None) + if module is None: + return "Lack of module info" + try: + retval = module.__doc__ or "*undocumented*" + retval = module.__package__.description + retval = module.__package__.long_description + except AttributeError: + pass + return retval Added: py/dist/py/test/tracer/genrest.py ============================================================================== --- (empty file) +++ py/dist/py/test/tracer/genrest.py Sat Oct 14 13:58:29 2006 @@ -0,0 +1,208 @@ + +""" Generating ReST output (raw, not python) +out of data that we know about function calls +""" + +import py +import sys +import re + +from py.__.test.tracer.docstorage import DocStorageAccessor +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 ('%s:%s' % (filename, lineno), self.basepath + relname[1:] + '?view=markup') + +class DirectPaste(AbstractLinkWriter): + """ No-link writer (inliner) + """ + def getlink(self, filename, lineno): + return (py.path.local(filename).open().readlines()[lineno-1], "") + +class PipeWriter(object): + def __init__(self, output=sys.stdout): + self.output = output + + def write_file(self, filename, data): + text = "Written file: %s" % filename + self.output.write(text + "\n") + self.output.write("=" * len(text) + "\n") + self.output.write("\n") + self.output.write(data + "\n") + +class DirWriter(object): + def __init__(self, directory=None): + if directory is None: + self.directory = py.test.ensuretemp("rstoutput") + else: + self.directory = py.path.local(directory) + + def write_file(self, filename, data): + self.directory.ensure(filename).write(data) + +class RestGen(object): + def __init__(self, ds, linkgen, writer=PipeWriter()): + self.dsa = DocStorageAccessor(ds) + self.linkgen = linkgen + self.writer = writer + + def write(self): + # first write down a file with all the exported interfaces, + # sorted by type + self.write_interface("index.txt") + + def write_interface(self, filename): + lst = [Title("Module: %s" % self.dsa.get_module_name(), belowchar="="), + Paragraph(self.dsa.get_module_info()), + Title("Exported functions:", belowchar="-")] + self.write_function_list(lst) + lst.append(Title("Exported classes:", belowchar="-")) + self.write_class_list(lst) + self.writer.write_file(filename, Rest(*lst).text()) + + def write_function_list(self, lst): + for name in self.dsa.get_function_names(): + # XXX: should be .html here? + lst.append(ListItem(Text("Function: "), Link(name, "function_%s.html" % name))) + self.write_function('function_' + name + '.txt', name) + + def write_class_list(self, lst): + for name in self.dsa.get_class_names(): + lst.append(ListItem(Text("Class: "), Link(name, "class_%s.html" % name))) + self.write_class('class_' + name + '.txt', name) + + def write_class(self, filename, class_name): + lst = [Title("Class: %s" % class_name, belowchar='-')] + + # write down exported methods + lst.append(Title("Exported methods:", belowchar="^")) + for method in self.dsa.get_class_methods(class_name): + fname = "method_%s" % (class_name + \ + "_" + method) + lst.append(ListItem(Link(method, fname+'.html'))) + self.write_function(fname+'.txt', class_name + "." + method) + + self.writer.write_file(filename, Rest(*lst).text()) + + def write_function(self, filename, fun_name): + lst = [Title("Function: %s" % fun_name, belowchar="-"), + Paragraph(self.dsa.get_function_doc(fun_name)), + BlockQuote(self.dsa.get_function_definition(fun_name))] + + lst.append(Paragraph("Function source: XXX (to write a link here)")) + + args, retval = self.dsa.get_function_signature(fun_name) + # XXX: we just do "knowntype" here, but we should + # present some more informative way, maybe even provide a link + # for the type declaration (if this is known) + arg_str = "(%s)" % (",".join([str(i.knowntype) for i in args])) + ret_str = str(retval.knowntype) + arg_quote = Paragraph("Function type:", Quote(arg_str), '->', Quote(ret_str)) + lst.append(arg_quote) + + # call sites.. + call_site_title = Title("Call sites:", belowchar="-") + lst.append(call_site_title) + call_sites = lst + + for call_site, frame in self.dsa.get_function_callpoints(fun_name): + link_str = "File %s:%s" % (call_site.filename, + call_site.lineno) + #_, link_target = self.linkgen.getlink(call_site.filename, call_site.lineno) + #if link_target: # otherwise it's just inline text + # call_sites.append(Paragraph(Link(link_str, link_target))) + #else: + # call_sites.append(Paragraph(link_str)) + #call_sites.append(BlockQuote(call_site.source)) + # XXX: For now, we just paste here the filename of that + call_sites.append(Paragraph(link_str)) + source = frame.code.source() + lines = [] + for num, line in enumerate(source): + if num == call_site.lineno - frame.code.firstlineno - 1: + m = re.match("^( *)(.*)", line) + lines.append(">" + "-"*len(m.group(1)) + m.group(2)) + else: + lines.append(" " + line) + call_sites.append(BlockQuote("\n".join(lines))) + + self.writer.write_file(filename, Rest(*lst).text()) +##class RestGen(object): +## def __init__(self, ds, linkgen, output=sys.stdout): +## self.dsa = DocStorageAccessor(ds) +## self.linkgen = linkgen +## self.output = output +## +## def add(self, item): +## self.list.append(item) +## +## def write(self): +## self.list = [] +## self.add(Title("Module: %s" % self.dsa.get_module_name(), belowchar="=")) +## self.add(Paragraph(Text(self.dsa.get_module_info()))) +## self.write_functions() +## self.write_classes() +## +## def write_functions(self): +## self.add(Title("Exported functions:", belowchar="-")) +## for key in self.dsa.get_function_names(): +## 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])) +## 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): +## link_str = "File %s:%s" % (call_site.filename, +## call_site.lineno) +## link_target = self.linkgen.getlink(call_site.filename, call_site.lineno) +## if link_target: # otherwise it's just inline text +## call_sites.append(Paragraph(Link(link_str, link_target))) +## else: +## call_sites.append(Paragraph(link_str)) +## 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()) +## +## def write_classes(self): +## self.add(Title("Exported classes:", belowchar="-")) + Added: py/dist/py/test/tracer/magic.py ============================================================================== --- (empty file) +++ py/dist/py/test/tracer/magic.py Sat Oct 14 13:58:29 2006 @@ -0,0 +1,63 @@ + +""" magic - some operations which helps to extend PDB with some magic data. +Actually there is only explicit tracking of data, might be extended to +automatic at some point. +""" + +# some magic stuff to have singleton of DocStorage, but initialised explicitely + +import weakref + +import py +from py.__.test.tracer.docstorage import DocStorage +from py.__.test.tracer.tracer import Tracer +import sys + +class DocStorageKeeper(object): + doc_storage = DocStorage() + doc_storage.tracer = Tracer(doc_storage) + doc_storage.from_dict({}) + + def set_storage(cl, ds): + cl.doc_storage = ds + cl.doc_storage.tracer = Tracer(ds) + set_storage = classmethod(set_storage) + +def get_storage(): + return DocStorageKeeper.doc_storage + +def stack_copier(frame): + # copy all stack, not only frame + num = 0 + gather = False + stack = [] + try: + while 1: + if gather: + stack.append(py.code.Frame(sys._getframe(num))) + else: + if sys._getframe(num) is frame.raw: + gather = True + num += 1 + except ValueError: + pass + return stack + +def trace(keep_frames=False, frame_copier=lambda x:x): + def decorator(fun): + ds = get_storage() + # in case we do not have this function inside doc storage, we + # want to have it + desc = ds.find_desc(py.code.Code(fun.func_code)) + if desc is None: + desc = ds.add_desc(fun.func_name, fun, keep_frames=keep_frames, + frame_copier=frame_copier) + + def wrapper(*args, **kwargs): + ds.tracer.start_tracing() + retval = fun(*args, **kwargs) + ds.tracer.end_tracing() + return retval + + return wrapper + return decorator Added: py/dist/py/test/tracer/testing/__init__.py ============================================================================== Added: py/dist/py/test/tracer/testing/runtest.py ============================================================================== --- (empty file) +++ py/dist/py/test/tracer/testing/runtest.py Sat Oct 14 13:58:29 2006 @@ -0,0 +1,5 @@ + +def cut_pyc(f_name): + if f_name.endswith('.pyc'): + return f_name[:-1] + return f_name Added: py/dist/py/test/tracer/testing/test_docgen.py ============================================================================== --- (empty file) +++ py/dist/py/test/tracer/testing/test_docgen.py Sat Oct 14 13:58:29 2006 @@ -0,0 +1,120 @@ + +""" test doc generation +""" + +import py +import sys + +try: + from py.__.test.tracer.tracer import DocStorage, Tracer + from py.__.test.tracer.testing.runtest import cut_pyc + from py.__.test.tracer.description import FunctionDesc + from pypy.annotation import model +except ImportError, s: + py.test.skip("Cannot import: %s" % str(s)) + +#def setup_module(mod): +# data_path = py.path.local(mod.__file__).dirpath().join("data") +# sys.path.insert(0, str(data_path)) + + +def fun(a, b, c): + "Some docstring" + return "d" + +def test_basic(): + descs = {"fun":fun} + ds = DocStorage().from_dict(descs) + t = Tracer(ds) + t.start_tracing() + fun(1, ("g", 3), 8) + fun(2., ("a", 1.), "a") + t.end_tracing() + desc = ds.descs['fun'] + inputcells = desc.inputcells + assert len(inputcells) == 3 + assert isinstance(inputcells[0], model.SomeFloat) + assert isinstance(inputcells[1], model.SomeTuple) + assert isinstance(inputcells[2], model.SomeObject) + assert isinstance(desc.retval, model.SomeChar) + cs = sorted(desc.call_sites.keys()) + assert len(cs) == 2 + f_name = cut_pyc(__file__) + assert cs[0].filename == f_name + assert cs[0].lineno == test_basic.func_code.co_firstlineno + 5 + 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 = sorted(desc.fields['__init__'].call_sites.keys()) + 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) + assert isinstance(desc.fields['exposed_method'].retval, model.SomeChar) + +def other_fun(): + pass + +def test_add_desc(): + ds = DocStorage().from_dict({}) + ds.add_desc("one", fun) + ds.add_desc("one", other_fun) + assert sorted(ds.descs.keys()) == ["one", "one_1"] + assert isinstance(ds.descs["one"], FunctionDesc) + assert isinstance(ds.descs["one_1"], FunctionDesc) + assert ds.descs["one"].pyobj is fun + assert ds.descs["one_1"].pyobj is other_fun + assert ds.desc_cache[ds.descs["one"]] is ds.descs["one"] + assert ds.desc_cache[ds.descs["one_1"]] is ds.descs["one_1"] + +def test_while_call(): + ds = DocStorage().from_dict({"other_fun":other_fun}) + t = Tracer(ds) + t.start_tracing() + for x in xrange(8): + other_fun() + t.end_tracing() + desc = ds.descs["other_fun"] + assert len(desc.call_sites.keys()) == 1 + assert isinstance(desc.call_sites.values()[0], py.code.Frame) Added: py/dist/py/test/tracer/testing/test_magic.py ============================================================================== --- (empty file) +++ py/dist/py/test/tracer/testing/test_magic.py Sat Oct 14 13:58:29 2006 @@ -0,0 +1,45 @@ + +""" test magic abilities of tracer +""" + +from py.__.test.tracer.magic import trace, get_storage, stack_copier, \ + DocStorageKeeper +from py.__.test.tracer.docstorage import DocStorage +from pypy.annotation import model + +#def setup_function(f): +# DocStorageKeeper.set_storage(DocStorage().from_dict({})) + +def fun(a, b, c): + return "a" +fun = trace()(fun) + +def test_magic(): + fun(1, 2, 3) + + ds = get_storage() + assert 'fun' in ds.descs + assert len(ds.descs.keys()) == 2 + desc = ds.descs['fun'] + inputcells = desc.inputcells + assert isinstance(inputcells[0], model.SomeInteger) + assert isinstance(inputcells[1], model.SomeInteger) + assert isinstance(inputcells[2], model.SomeInteger) + assert isinstance(desc.retval, model.SomeChar) + +def g(x): + return f(x) + +def f(x): + return x + 3 +f = trace(keep_frames=True, frame_copier=stack_copier)(f) + +def test_fancy_copier(): + g(1) + + ds = get_storage() + assert 'f' in ds.descs + desc = ds.descs['f'] + stack = desc.call_sites.values()[0][0] + assert str(stack[0].statement) == ' return f(x)' + assert str(stack[1].statement) == ' g(1)' Added: py/dist/py/test/tracer/testing/test_rest.py ============================================================================== --- (empty file) +++ py/dist/py/test/tracer/testing/test_rest.py Sat Oct 14 13:58:29 2006 @@ -0,0 +1,86 @@ + +""" tests document generation +""" + +import py + +try: + from py.__.test.tracer.genrest import ViewVC, RestGen, PipeWriter, DirWriter,\ + DirectPaste + from py.__.test.tracer.tracer import DocStorage, Tracer + + from StringIO import StringIO + from py.__.test.tracer.testing.runtest import cut_pyc +except ImportError, s: + py.test.skip("Cannot import: %s" % str(s)) + +def test_links(): + vcview = ViewVC("http://codespeak.net/viewvc/") + _, linkname = vcview.getlink(cut_pyc(__file__), 0) + assert linkname == 'http://codespeak.net/viewvc/py/test/tracer/testing/test_rest.py?view=markup' + +class SomeClass(object): + def __init__(self, a): + self.a = a + + def method(self, a, b, c): + return a + b + c + +def fun(a, b, c): + "Some docstring" + return "d" + +def test_dir_writer(): + p = StringIO() + dir = py.test.ensuretemp("dirwriter") + w = DirWriter(dir) + w.write_file("one", "one data") + w.write_file("two", "two data") + assert dir.join("one").read() == "one data" + assert dir.join("two").read() == "two data" + +def test_direct_link(): + assert DirectPaste().getlink(cut_pyc(__file__), 2)[0] == '""" tests document generation\n' + # C-c C-v .... + +class TestRest(object): + def check_rest(self, tmpdir): + from py.__.misc import rest + for path in tmpdir.listdir('*.txt'): + rest.process(path) + + def test_generation_simple_api(self): + descs = {'SomeClass':SomeClass, 'fun':fun} + ds = DocStorage().from_dict(descs) + lg = DirectPaste() + t = Tracer(ds) + t.start_tracing() + s1 = SomeClass("a") + fun(1, 2, s1) + s2 = SomeClass("b") + s2.method(1,2,3) + fun(1, 3, s2) + t.end_tracing() + tmpdir = py.test.ensuretemp("test_generation") + r = RestGen(ds, lg, DirWriter(tmpdir)) + r.write() + # now we check out... + self.check_rest(tmpdir) + +##def test_generation(): +## py.test.skip("WIP") +## descs = {"fun":fun} +## ds = DocStorage().from_dict(descs) +## lg = ViewVC("http://codespeak.net/viewvc/") +## t = Tracer(ds) +## t.start_tracing() +## fun(1, ("g", 3), 8) +## fun(2., ("a", 1.), "a") +## t.end_tracing() +## s = StringIO() +## r = RestGen(ds, lg, output=s) +## r.write() +## # we cannot check the source, cause the rapidly changing +## # visual effect will break it, so we'll make assertion later +## # XXX: do that +## assert s.getvalue() Added: py/dist/py/test/tracer/tracer.py ============================================================================== --- (empty file) +++ py/dist/py/test/tracer/tracer.py Sat Oct 14 13:58:29 2006 @@ -0,0 +1,47 @@ + +""" simple tracer for API generation +""" + +import py +import sys +import types + +# We have to steal as much as possible from union of types +# from pypy's code. +# BLAH BLAH BLAH + +from py.__.test.tracer.description import FunctionDesc +from py.__.test.tracer.docstorage import DocStorage + +class UnionError(Exception): + pass + +class Tracer(object): + """ Basic tracer object, used for gathering additional info + about API functions + """ + def __init__(self, docstorage): + self.docstorage = docstorage + self.tracing = False + + def _tracer(self, frame, event, arg): + + # perform actuall tracing + frame = py.code.Frame(frame) + if event == 'call': + assert arg is None + self.docstorage.consider_call(frame, py.code.Frame(sys._getframe(2))) + elif event == 'return': + self.docstorage.consider_return(frame, arg) + + return self._tracer + + def start_tracing(self): + if self.tracing: + return + self.tracing = True + sys.settrace(self._tracer) + + def end_tracing(self): + self.tracing = False + sys.settrace(None) From hpk at codespeak.net Sun Oct 15 16:34:45 2006 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 15 Oct 2006 16:34:45 +0200 (CEST) Subject: [py-svn] r33314 - py/dist/py/documentation/talk Message-ID: <20061015143445.B45BF10102@code0.codespeak.net> Author: hpk Date: Sun Oct 15 16:34:45 2006 New Revision: 33314 Removed: py/dist/py/documentation/talk/_ref.txt Modified: py/dist/py/documentation/talk/pytest-overview.txt Log: fix referencing Deleted: /py/dist/py/documentation/talk/_ref.txt ============================================================================== --- /py/dist/py/documentation/talk/_ref.txt Sun Oct 15 16:34:45 2006 +++ (empty file) @@ -1 +0,0 @@ -.. _`py/documentation/conftest.py`: ../../../py/documentation/conftest.py \ No newline at end of file Modified: py/dist/py/documentation/talk/pytest-overview.txt ============================================================================== --- py/dist/py/documentation/talk/pytest-overview.txt (original) +++ py/dist/py/documentation/talk/pytest-overview.txt Sun Oct 15 16:34:45 2006 @@ -1,4 +1,3 @@ -.. include:: _ref.txt .. include:: ================================================= @@ -150,6 +149,8 @@ - invokes ``docutils`` processing, reports errors +.. _`py/documentation/conftest.py`: ../conftest.py + Extensions: Distributed Testing ============================================== From fijal at codespeak.net Mon Oct 16 12:29:13 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Mon, 16 Oct 2006 12:29:13 +0200 (CEST) Subject: [py-svn] r33320 - in py/dist/py/test: rsession rsession/testing tracer Message-ID: <20061016102913.2861B10109@code0.codespeak.net> Author: fijal Date: Mon Oct 16 12:28:23 2006 New Revision: 33320 Added: py/dist/py/test/rsession/local.py (contents, props changed) py/dist/py/test/rsession/testing/test_lsession.py (contents, props changed) Modified: py/dist/py/test/rsession/rsession.py py/dist/py/test/tracer/docstorage.py Log: Added first quasi-working version of local-version of rsession. (reporter needs refactoring for sure to work with different outcomes). Added: py/dist/py/test/rsession/local.py ============================================================================== --- (empty file) +++ py/dist/py/test/rsession/local.py Mon Oct 16 12:28:23 2006 @@ -0,0 +1,22 @@ + +""" local-only operations +""" + +from py.__.test.rsession.executor import RunExecutor +from py.__.test.rsession import report + +def run(item): + r = RunExecutor(item) + return r.execute() + +def local_loop(reporter, itemgenerator, shouldstop): + + while 1: + try: + item = itemgenerator.next() + if shouldstop(): + return + outcome = run(item) + reporter(report.ReceivedItemOutcome(None, item, outcome)) + except StopIteration: + break Modified: py/dist/py/test/rsession/rsession.py ============================================================================== --- py/dist/py/test/rsession/rsession.py (original) +++ py/dist/py/test/rsession/rsession.py Mon Oct 16 12:28:23 2006 @@ -16,6 +16,7 @@ from py.__.test.rsession.hostmanage import init_hosts, teardown_hosts from py.__.test.terminal.out import getout +from py.__.test.rsession.local import local_loop class RemoteOptions(object): def __init__(self, d): @@ -224,14 +225,79 @@ def report_Nodes(self, event): self.nodes = event.nodes -class RSession(object): +class AbstractSession(object): """ - An RSession executes collectors/items through a runner. + An abstract session executes collectors/items through a runner. """ def __init__(self, config): self.config = config + + def make_colitems(paths, baseon): + # we presume that from the base we can simply get to + # the target paths by joining the basenames + res = [] + for x in paths: + x = py.path.local(x) + current = py.test.collect.Directory(baseon) + relparts = x.relto(baseon).split(x.sep) + assert relparts + for part in relparts: + next = current.join(part) + assert next is not None, (current, part) + current = next + res.append(current) + return res + make_colitems = staticmethod(make_colitems) + + def getpkgdir(path): + path = py.path.local(path) + pkgpath = path.pypkgpath() + if pkgpath is None: + pkgpath = path + return pkgpath + getpkgdir = staticmethod(getpkgdir) + + def init_reporter(self, reporter, sshhosts): + try: + # XXX: use it like a command line option, but how? + startserverflag = self.config.getinitialvalue("startserver") + except: + startserverflag = False + + checkfun = lambda: None + if startserverflag and reporter is None: + from py.__.test.rsession.web import start_server, exported_methods + + reporter = exported_methods.report + start_server() + elif reporter is None: + reporter_instance = Reporter(self.config, sshhosts) + reporter = reporter_instance.report + checkfun = lambda : self.config.option.exitfirst and \ + reporter_instance.is_failing() + else: + startserverflag = False + if reporter is None: + reporter_instance = Reporter(self.config, sshhosts) + reporter = reporter_instance.report + checkfun = lambda : self.config.option.exitfirst and \ + reporter_instance.is_failing() + return reporter, checkfun, startserverflag + + def reporterror(reporter, data): + excinfo, item = data + if excinfo is None: + reporter(report.ItemStart(item)) + elif excinfo.type is py.test.Item.Skipped: + reporter(report.SkippedTryiter(excinfo, item)) + else: + reporter(report.FailedTryiter(excinfo, item)) + reporterror = staticmethod(reporterror) +class RSession(AbstractSession): + """ Remote version of session + """ def main(self, args, reporter=None): """ main loop for running tests. """ if not args: @@ -251,25 +317,9 @@ rsync_roots = self.config.getinitialvalue("distrsync_roots") except: rsync_roots = None # all files and directories in the pkgdir - try: - # XXX: use it like a command line option, but how? - startserverflag = self.config.getinitialvalue("startserver") - except: - startserverflag = False - checkfun = lambda: None - if startserverflag and reporter is None: - from py.__.test.rsession.web import start_server, exported_methods - - reporter = exported_methods.report - start_server() - elif reporter is None: - reporter_instance = Reporter(self.config, sshhosts) - reporter = reporter_instance.report - checkfun = lambda : self.config.option.exitfirst and \ - reporter_instance.is_failing() - else: - startserverflag = False + reporter, checkfun, startserverflag = self.init_reporter(reporter, + sshhosts) reporter(report.TestStarted(sshhosts)) pkgdir = self.getpkgdir(args[0]) colitems = self.make_colitems(args, baseon=pkgdir.dirpath()) @@ -282,20 +332,11 @@ rsync_roots, remotepython) reporter(report.RsyncFinished()) - def reporterror(data): - excinfo, item = data - if excinfo is None: - reporter(report.ItemStart(item)) - elif excinfo.type is py.test.Item.Skipped: - reporter(report.SkippedTryiter(excinfo, item)) - else: - reporter(report.FailedTryiter(excinfo, item)) - keyword = self.config.option.keyword def itemgen(): for x in colitems: - for y in x.tryiter(reporterror = reporterror, keyword = keyword): + for y in x.tryiter(reporterror = lambda x: self.reporterror(reporter, x), keyword = keyword): yield y itemgenerator = itemgen() @@ -308,28 +349,37 @@ if startserverflag: from py.__.test.rsession.web import kill_server kill_server() + +class LSession(AbstractSession): + """ Local version of session + """ + def main(self, args, reporter=None): + if not args: + args = [py.path.local()] + + sshhosts = ['localhost'] # this is just an info to reporter + + reporter, checkfun, startserverflag = self.init_reporter(reporter, + sshhosts) - def make_colitems(paths, baseon): - # we presume that from the base we can simply get to - # the target paths by joining the basenames - res = [] - for x in paths: - x = py.path.local(x) - current = py.test.collect.Directory(baseon) - relparts = x.relto(baseon).split(x.sep) - assert relparts - for part in relparts: - next = current.join(part) - assert next is not None, (current, part) - current = next - res.append(current) - return res - make_colitems = staticmethod(make_colitems) + reporter(report.TestStarted(sshhosts)) + pkgdir = self.getpkgdir(args[0]) + colitems = self.make_colitems(args, baseon=pkgdir.dirpath()) + reporter(report.RsyncFinished()) - def getpkgdir(path): - path = py.path.local(path) - pkgpath = path.pypkgpath() - if pkgpath is None: - pkgpath = path - return pkgpath - getpkgdir = staticmethod(getpkgdir) + keyword = self.config.option.keyword + + def itemgen(): + for x in colitems: + for y in x.tryiter(reporterror = lambda x: self.reporterror(reporter, x), keyword = keyword): + yield y + + itemgenerator = itemgen() + #assert 0, "\n".join([",".join(x.listnames()) for x in + # list(itemgenerator)]) + local_loop(reporter, itemgenerator, checkfun) + + reporter(report.TestFinished()) + if startserverflag: + from py.__.test.rsession.web import kill_server + kill_server() Added: py/dist/py/test/rsession/testing/test_lsession.py ============================================================================== --- (empty file) +++ py/dist/py/test/rsession/testing/test_lsession.py Mon Oct 16 12:28:23 2006 @@ -0,0 +1,49 @@ + +""" test of local version of py.test distributed +""" + +import py +from py.__.test.rsession.rsession import LSession +from py.__.test.rsession import report + +class TestLSession(object): + def test_example_distribution(self): + # XXX find a better way for the below + tmpdir = py.path.local(py.__file__).dirpath().dirpath() + tmpdir.ensure("sub", "__init__.py") + tmpdir.ensure("sub", "test_one.py").write(py.code.Source(""" + def test_1(): + pass + def test_2(): + assert 0 + def test_3(): + raise ValueError(23) + def test_4(someargs): + pass + """)) + args = [str(tmpdir.join("sub"))] + config, args = py.test.Config.parse(args) + lsession = LSession(config) + allevents = [] + lsession.main(args, reporter=allevents.append) + testevents = [x for x in allevents + if isinstance(x, report.ReceivedItemOutcome)] + assert len(testevents) + passevents = [i for i in testevents if i.outcome.passed] + failevents = [i for i in testevents if i.outcome.excinfo] + skippedevents = [i for i in testevents if i.outcome.skipped] + assert len(passevents) == 1 + assert len(failevents) == 3 + tb = failevents[0].outcome.excinfo.traceback + assert str(tb[0].path).find("test_one") != -1 + assert str(tb[0].getsource()).find("test_2") != -1 + assert failevents[0].outcome.excinfo.type is AssertionError + tb = failevents[1].outcome.excinfo.traceback + assert str(tb[0].path).find("test_one") != -1 + assert str(tb[0].getsource()).find("test_3") != -1 + assert failevents[1].outcome.excinfo.type is ValueError + assert str(failevents[1].outcome.excinfo.value) == '23' + tb = failevents[2].outcome.excinfo.traceback + assert failevents[2].outcome.excinfo.type is TypeError + assert str(tb[0].path).find("executor") != -1 + assert str(tb[0].getsource()).find("execute") != -1 Modified: py/dist/py/test/tracer/docstorage.py ============================================================================== --- py/dist/py/test/tracer/docstorage.py (original) +++ py/dist/py/test/tracer/docstorage.py Mon Oct 16 12:28:23 2006 @@ -13,8 +13,8 @@ try: from pypy.annotation.policy import AnnotatorPolicy from pypy.annotation.bookkeeper import Bookkeeper -except ImportError: - py.test.skip("No PyPy") +except ImportError, i: + py.test.skip("No PyPy %s" % i) class DummyAnnotator(object): policy = AnnotatorPolicy() From fijal at codespeak.net Mon Oct 16 18:24:20 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Mon, 16 Oct 2006 18:24:20 +0200 (CEST) Subject: [py-svn] r33344 - in py/dist/py/test/rsession: . testing Message-ID: <20061016162420.0A89D1010B@code0.codespeak.net> Author: fijal Date: Mon Oct 16 18:23:48 2006 New Revision: 33344 Modified: py/dist/py/test/rsession/executor.py py/dist/py/test/rsession/local.py py/dist/py/test/rsession/outcome.py py/dist/py/test/rsession/rsession.py py/dist/py/test/rsession/testing/test_lsession.py Log: Fixed several local version issues of distributed tests. Right now it has the same funcionallity as remote version, altough it might be extended. Probably we want to split reporters as well. Modified: py/dist/py/test/rsession/executor.py ============================================================================== --- py/dist/py/test/rsession/executor.py (original) +++ py/dist/py/test/rsession/executor.py Mon Oct 16 18:23:48 2006 @@ -17,9 +17,9 @@ def execute(self): try: self.item.run() - return Outcome() + outcome = Outcome() except py.test.Item.Skipped, e: - return Outcome(skipped=str(e)) + outcome = Outcome(skipped=str(e)) except: excinfo = py.code.ExceptionInfo() if isinstance(self.item, py.test.Function): @@ -27,7 +27,10 @@ code = py.code.Code(fun) excinfo.traceback = excinfo.traceback.cut( path=code.path, firstlineno=code.firstlineno) - return Outcome(excinfo=excinfo, setupfailure=False) + outcome = Outcome(excinfo=excinfo, setupfailure=False) + outcome.stdout = "" + outcome.stderr = "" + return outcome class BoxExecutor(RunExecutor): """ Same as run executor, but boxes test instead Modified: py/dist/py/test/rsession/local.py ============================================================================== --- py/dist/py/test/rsession/local.py (original) +++ py/dist/py/test/rsession/local.py Mon Oct 16 18:23:48 2006 @@ -2,12 +2,13 @@ """ local-only operations """ -from py.__.test.rsession.executor import RunExecutor +from py.__.test.rsession.executor import BoxExecutor from py.__.test.rsession import report +from py.__.test.rsession.outcome import ReprOutcome def run(item): - r = RunExecutor(item) - return r.execute() + r = BoxExecutor(item) + return ReprOutcome(r.execute()) def local_loop(reporter, itemgenerator, shouldstop): Modified: py/dist/py/test/rsession/outcome.py ============================================================================== --- py/dist/py/test/rsession/outcome.py (original) +++ py/dist/py/test/rsession/outcome.py Mon Oct 16 18:23:48 2006 @@ -16,6 +16,7 @@ self.setupfailure = setupfailure self.excinfo = excinfo self.is_critical = is_critical + self.signal = 0 assert bool(self.passed) + bool(excinfo) + bool(skipped) == 1 def make_excinfo_repr(self): Modified: py/dist/py/test/rsession/rsession.py ============================================================================== --- py/dist/py/test/rsession/rsession.py (original) +++ py/dist/py/test/rsession/rsession.py Mon Oct 16 18:23:48 2006 @@ -33,6 +33,7 @@ self.failed_tests_outcome = [] self.skipped_tests_outcome = [] self.out = getout(py.std.sys.stdout) + #assert hosts == ['localhost'] self.failed = dict([(host, 0) for host in hosts]) self.skipped = dict([(host, 0) for host in hosts]) self.passed = dict([(host, 0) for host in hosts]) @@ -56,9 +57,13 @@ print "Unknown report: %s" % what def report_SendItem(self, item): + if item.channel: + address = item.channel.gateway.sshaddress + else: + address = 'localhost' if self.config.option.verbose: - print "Sending %s to %s" % (item.item, - item.channel.gateway.sshaddress) + print "Sending %s to %s" % (item.item, + address) def report_HostRSyncing(self, item): print "%10s: RSYNC ==> %s" % (item.hostname[:10], @@ -85,6 +90,8 @@ def hangs(self): h = [] + if not hasattr(self, 'nodes'): + return for node in self.nodes: h += [(i, node.channel.gateway.sshaddress) for i in node.pending] if h: @@ -96,7 +103,10 @@ if self.failed_tests_outcome: self.out.sep("=", " FAILURES ") for event in self.failed_tests_outcome: - host = event.channel.gateway.sshaddress + if event.channel: + host = event.channel.gateway.sshaddress + else: + host = 'localhost' self.out.sep('_', "%s on %s" % (" ".join(event.item.listnames()), host)) if event.outcome.signal: @@ -136,7 +146,7 @@ for index, entry in py.builtin.enumerate(traceback): self.out.sep('-') self.out.line("%s: %s" % (entry.path, entry.lineno)) - self.repr_source(entry.relline, entry.source) + self.repr_source(entry.relline, str(entry.source)) self.out.line("%s: %s" % (excinfo.typename, excinfo.value)) def repr_source(self, relline, source): @@ -153,8 +163,11 @@ if isinstance(event, report.ReceivedItemOutcome): outcome = event.outcome text = outcome.skipped - itemname = event.channel.gateway.sshaddress + ":" + \ - "/".join(colitem.listnames()) + if event.channel: + itemname = event.channel.gateway.sshaddress + ":" + \ + "/".join(colitem.listnames()) + else: + itemname = "/".join(colitem.listnames()) elif isinstance(event, report.SkippedTryiter): text = str(event.excinfo.value) itemname = "/".join(colitem.listnames()) @@ -202,7 +215,10 @@ # XXX: right now we do not do anything with it def report_ReceivedItemOutcome(self, event): - host = event.channel.gateway.sshaddress + if event.channel: + host = event.channel.gateway.sshaddress + else: + host = 'localhost' if event.outcome.passed: status = "PASSED " self.passed[host] += 1 @@ -215,7 +231,10 @@ self.failed[host] += 1 self.failed_tests_outcome.append(event) # we'll take care of them later - sshhost = event.channel.gateway.sshaddress + if event.channel: + sshhost = event.channel.gateway.sshaddress + else: + sshhost = 'localhost' itempath = " ".join(event.item.listnames()[1:]) print "%10s: %s %s" %(sshhost[:10], status, itempath) Modified: py/dist/py/test/rsession/testing/test_lsession.py ============================================================================== --- py/dist/py/test/rsession/testing/test_lsession.py (original) +++ py/dist/py/test/rsession/testing/test_lsession.py Mon Oct 16 18:23:48 2006 @@ -36,14 +36,14 @@ assert len(failevents) == 3 tb = failevents[0].outcome.excinfo.traceback assert str(tb[0].path).find("test_one") != -1 - assert str(tb[0].getsource()).find("test_2") != -1 - assert failevents[0].outcome.excinfo.type is AssertionError + assert str(tb[0].source).find("test_2") != -1 + assert failevents[0].outcome.excinfo.typename == 'AssertionError' tb = failevents[1].outcome.excinfo.traceback assert str(tb[0].path).find("test_one") != -1 - assert str(tb[0].getsource()).find("test_3") != -1 - assert failevents[1].outcome.excinfo.type is ValueError + assert str(tb[0].source).find("test_3") != -1 + assert failevents[1].outcome.excinfo.typename == 'ValueError' assert str(failevents[1].outcome.excinfo.value) == '23' tb = failevents[2].outcome.excinfo.traceback - assert failevents[2].outcome.excinfo.type is TypeError + assert failevents[2].outcome.excinfo.typename == 'TypeError' assert str(tb[0].path).find("executor") != -1 - assert str(tb[0].getsource()).find("execute") != -1 + assert str(tb[0].source).find("execute") != -1 From fijal at codespeak.net Mon Oct 16 22:36:21 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Mon, 16 Oct 2006 22:36:21 +0200 (CEST) Subject: [py-svn] r33349 - in py/dist/py/test/rsession: . testing Message-ID: <20061016203621.73AE910068@code0.codespeak.net> Author: fijal Date: Mon Oct 16 22:35:56 2006 New Revision: 33349 Modified: py/dist/py/test/rsession/rsession.py py/dist/py/test/rsession/testing/test_report.py py/dist/py/test/rsession/testing/test_reporter.py Log: Refactored reporter a bit (not refactored web server - beware) Modified: py/dist/py/test/rsession/rsession.py ============================================================================== --- py/dist/py/test/rsession/rsession.py (original) +++ py/dist/py/test/rsession/rsession.py Mon Oct 16 22:35:56 2006 @@ -27,7 +27,7 @@ remote_options = RemoteOptions({'we_are_remote':False}) -class Reporter(object): +class AbstractReporter(object): def __init__(self, config, hosts): self.config = config self.failed_tests_outcome = [] @@ -57,10 +57,7 @@ print "Unknown report: %s" % what def report_SendItem(self, item): - if item.channel: - address = item.channel.gateway.sshaddress - else: - address = 'localhost' + address = self.get_host(item) if self.config.option.verbose: print "Sending %s to %s" % (item.item, address) @@ -90,8 +87,6 @@ def hangs(self): h = [] - if not hasattr(self, 'nodes'): - return for node in self.nodes: h += [(i, node.channel.gateway.sshaddress) for i in node.pending] if h: @@ -103,10 +98,7 @@ if self.failed_tests_outcome: self.out.sep("=", " FAILURES ") for event in self.failed_tests_outcome: - if event.channel: - host = event.channel.gateway.sshaddress - else: - host = 'localhost' + host = self.get_host(event) self.out.sep('_', "%s on %s" % (" ".join(event.item.listnames()), host)) if event.outcome.signal: @@ -163,11 +155,7 @@ if isinstance(event, report.ReceivedItemOutcome): outcome = event.outcome text = outcome.skipped - if event.channel: - itemname = event.channel.gateway.sshaddress + ":" + \ - "/".join(colitem.listnames()) - else: - itemname = "/".join(colitem.listnames()) + itemname = self.get_item_name(event, colitem) elif isinstance(event, report.SkippedTryiter): text = str(event.excinfo.value) itemname = "/".join(colitem.listnames()) @@ -215,10 +203,7 @@ # XXX: right now we do not do anything with it def report_ReceivedItemOutcome(self, event): - if event.channel: - host = event.channel.gateway.sshaddress - else: - host = 'localhost' + host = self.get_host(event) if event.outcome.passed: status = "PASSED " self.passed[host] += 1 @@ -231,10 +216,7 @@ self.failed[host] += 1 self.failed_tests_outcome.append(event) # we'll take care of them later - if event.channel: - sshhost = event.channel.gateway.sshaddress - else: - sshhost = 'localhost' + sshhost = self.get_host(event) itempath = " ".join(event.item.listnames()[1:]) print "%10s: %s %s" %(sshhost[:10], status, itempath) @@ -244,6 +226,25 @@ def report_Nodes(self, event): self.nodes = event.nodes +class RemoteReporter(AbstractReporter): + def get_host(self, item): + return item.channel.gateway.sshaddress + + def get_item_name(self, event, colitem): + return event.channel.gateway.sshaddress + ":" + \ + "/".join(colitem.listnames()) + + +class LocalReporter(AbstractReporter): + def get_host(self, item): + return 'localhost' + + def get_item_name(self, event, colitem): + return "/".join(colitem.listnames()) + + def hangs(self): + pass + class AbstractSession(object): """ An abstract session executes collectors/items through a runner. @@ -277,7 +278,7 @@ return pkgpath getpkgdir = staticmethod(getpkgdir) - def init_reporter(self, reporter, sshhosts): + def init_reporter(self, reporter, sshhosts, reporter_class): try: # XXX: use it like a command line option, but how? startserverflag = self.config.getinitialvalue("startserver") @@ -291,17 +292,12 @@ reporter = exported_methods.report start_server() elif reporter is None: - reporter_instance = Reporter(self.config, sshhosts) + reporter_instance = reporter_class(self.config, sshhosts) reporter = reporter_instance.report checkfun = lambda : self.config.option.exitfirst and \ reporter_instance.is_failing() else: startserverflag = False - if reporter is None: - reporter_instance = Reporter(self.config, sshhosts) - reporter = reporter_instance.report - checkfun = lambda : self.config.option.exitfirst and \ - reporter_instance.is_failing() return reporter, checkfun, startserverflag def reporterror(reporter, data): @@ -338,7 +334,7 @@ rsync_roots = None # all files and directories in the pkgdir reporter, checkfun, startserverflag = self.init_reporter(reporter, - sshhosts) + sshhosts, RemoteReporter) reporter(report.TestStarted(sshhosts)) pkgdir = self.getpkgdir(args[0]) colitems = self.make_colitems(args, baseon=pkgdir.dirpath()) @@ -379,7 +375,7 @@ sshhosts = ['localhost'] # this is just an info to reporter reporter, checkfun, startserverflag = self.init_reporter(reporter, - sshhosts) + sshhosts, LocalReporter) reporter(report.TestStarted(sshhosts)) pkgdir = self.getpkgdir(args[0]) Modified: py/dist/py/test/rsession/testing/test_report.py ============================================================================== --- py/dist/py/test/rsession/testing/test_report.py (original) +++ py/dist/py/test/rsession/testing/test_report.py Mon Oct 16 22:35:56 2006 @@ -27,10 +27,10 @@ def test_reporter_methods_sanity(): """ Checks if all the methods of reporter are sane """ - from py.__.test.rsession.rsession import Reporter + from py.__.test.rsession.rsession import RemoteReporter from py.__.test.rsession import report - for method in dir(Reporter): + for method in dir(RemoteReporter): if method.startswith("report_") and method != "report_unknown": assert method[len('report_'):] in report.__dict__ Modified: py/dist/py/test/rsession/testing/test_reporter.py ============================================================================== --- py/dist/py/test/rsession/testing/test_reporter.py (original) +++ py/dist/py/test/rsession/testing/test_reporter.py Mon Oct 16 22:35:56 2006 @@ -4,7 +4,7 @@ """ import py -from py.__.test.rsession.rsession import Reporter +from py.__.test.rsession.rsession import RemoteReporter from py.__.test.rsession.report import ReceivedItemOutcome from py.__.test.rsession.outcome import ReprOutcome, Outcome from py.__.test.rsession.testing.test_slave import funcpass_spec @@ -15,7 +15,7 @@ class TestReporter(object): def test_report_received_item_outcome(self): config, args = py.test.Config.parse(["some_sub"]) - r = Reporter(config, ["host"]) + r = RemoteReporter(config, ["host"]) # possible outcomes try: 1/0 From fijal at codespeak.net Mon Oct 16 22:49:08 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Mon, 16 Oct 2006 22:49:08 +0200 (CEST) Subject: [py-svn] r33350 - in py/dist/py: . code test/rsession Message-ID: <20061016204908.64B5A10068@code0.codespeak.net> Author: fijal Date: Mon Oct 16 22:48:34 2006 New Revision: 33350 Modified: py/dist/py/__init__.py py/dist/py/code/excinfo.py py/dist/py/code/traceback2.py py/dist/py/test/rsession/rsession.py Log: Missing commit. Some minor changes to API of code objects (helpers). Also refactored LocalReporter to look like py.test local. Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Mon Oct 16 22:48:34 2006 @@ -39,6 +39,7 @@ 'test.Session' : ('./test/session.py', 'Session'), 'test.TerminalSession' : ('./test/terminal/terminal.py', 'TerminalSession'), 'test.RSession' : ('./test/rsession/rsession.py', 'RSession'), + 'test.LSession' : ('./test/rsession/rsession.py', 'LSession'), 'test.TkinterSession' : ('./test/tkinter/tksession.py', 'TkSession'), 'test.collect.Collector' : ('./test/collect.py', 'Collector'), 'test.collect.Directory' : ('./test/collect.py', 'Directory'), Modified: py/dist/py/code/excinfo.py ============================================================================== --- py/dist/py/code/excinfo.py (original) +++ py/dist/py/code/excinfo.py Mon Oct 16 22:48:34 2006 @@ -18,6 +18,7 @@ self._striptext = 'AssertionError: ' self._excinfo = tup self.type, self.value, tb = self._excinfo + self.typename = str(self.type) self.traceback = py.code.Traceback(tb) def exconly(self, tryshort=False): Modified: py/dist/py/code/traceback2.py ============================================================================== --- py/dist/py/code/traceback2.py (original) +++ py/dist/py/code/traceback2.py Mon Oct 16 22:48:34 2006 @@ -8,6 +8,7 @@ self._rawentry = rawentry self.frame = py.code.Frame(rawentry.tb_frame) self.lineno = rawentry.tb_lineno - 1 + self.relline = self.lineno - self.frame.code.firstlineno def __repr__(self): return "" %(self.frame.code.path, self.lineno+1) @@ -55,6 +56,7 @@ end = i + 1 break return source[start:end] + source = property(getsource) def ishidden(self): try: Modified: py/dist/py/test/rsession/rsession.py ============================================================================== --- py/dist/py/test/rsession/rsession.py (original) +++ py/dist/py/test/rsession/rsession.py Mon Oct 16 22:48:34 2006 @@ -242,6 +242,33 @@ def get_item_name(self, event, colitem): return "/".join(colitem.listnames()) + def report_ReceivedItemOutcome(self, event): + if event.outcome.passed: + self.passed['localhost'] += 1 + self.out.write(".") + elif event.outcome.skipped: + self.skipped_tests_outcome.append(event) + self.skipped['localhost'] += 1 + self.out.write("s") + else: + self.failed['localhost'] += 1 + self.failed_tests_outcome.append(event) + self.out.write("F") + # we'll take care of them later + self.count += 1 + if self.count >= self.lgt: + self.out.write("\n") + #itempath = " ".join(event.item.listnames()[1:]) + #print "%10s: %s %s" %(sshhost[:10], status, itempath) + + def report_ItemStart(self, event): + item = event.item + if isinstance(item, py.test.collect.Module): + self.count = 0 + lgt = len(list(event.item.tryiter())) + self.lgt = lgt + self.out.write("%s[%d] " % (item.name, lgt)) + def hangs(self): pass From fijal at codespeak.net Mon Oct 16 23:25:59 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Mon, 16 Oct 2006 23:25:59 +0200 (CEST) Subject: [py-svn] r33352 - py/dist/py/test/rsession Message-ID: <20061016212559.E741C1006E@code0.codespeak.net> Author: fijal Date: Mon Oct 16 23:25:28 2006 New Revision: 33352 Modified: py/dist/py/test/rsession/hostmanage.py Log: Added localhost optimisation. Modified: py/dist/py/test/rsession/hostmanage.py ============================================================================== --- py/dist/py/test/rsession/hostmanage.py (original) +++ py/dist/py/test/rsession/hostmanage.py Mon Oct 16 23:25:28 2006 @@ -39,23 +39,32 @@ hosts = [] for host in sshhosts: - if isinstance(relpath, str): - assert not os.path.isabs(relpath), relpath - remoterootpath = relpath - else: - remoterootpath = relpath[host] - # XXX: because of NFS we do create different directories - # otherwise, .pyc files overlap - remoterootpath += "-" + host - if remote_python is None: - gw = py.execnet.SshGateway(host) + + if host != 'localhost': + if remote_python is None: + gw = py.execnet.SshGateway(host) + else: + gw = py.execnet.SshGateway(host, remotepython=remote_python) + if isinstance(relpath, str): + assert not os.path.isabs(relpath), relpath + remoterootpath = relpath + else: + remoterootpath = relpath[host] + # XXX: because of NFS we do create different directories + # otherwise, .pyc files overlap + remoterootpath += "-" + host + + hosts.append((host, gw, remoterootpath)) else: - gw = py.execnet.SshGateway(host, remotepython=remote_python) - - hosts.append((host, gw, remoterootpath)) + if remote_python is None: + gw = py.execnet.PopenGateway() + else: + gw = py.execnet.PopenGateway(remotepython=remote_python) + gw.sshaddress = 'localhost' + hosts.append((host, gw, str(pkgdir.dirpath()))) # rsyncing - rsynced = {} + rsynced = {'localhost':True} rsync = HostRSync(rsync_roots) for host, gw, remoterootpath in hosts: From arigo at codespeak.net Tue Oct 17 08:20:30 2006 From: arigo at codespeak.net (arigo at codespeak.net) Date: Tue, 17 Oct 2006 08:20:30 +0200 (CEST) Subject: [py-svn] r33353 - py/dist/py/builtin Message-ID: <20061017062030.393E11006C@code0.codespeak.net> Author: arigo Date: Tue Oct 17 08:20:25 2006 New Revision: 33353 Modified: py/dist/py/builtin/sorted.py Log: A debugging print left behind. Modified: py/dist/py/builtin/sorted.py ============================================================================== --- py/dist/py/builtin/sorted.py (original) +++ py/dist/py/builtin/sorted.py Tue Oct 17 08:20:25 2006 @@ -14,7 +14,7 @@ if cmp is not None: use_cmp = cmp l = list(iterable) - print l + #print l if use_cmp is not None: l.sort(use_cmp) else: From fijal at codespeak.net Tue Oct 17 20:19:16 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Tue, 17 Oct 2006 20:19:16 +0200 (CEST) Subject: [py-svn] r33387 - in py/dist/py/test/rsession: . testing Message-ID: <20061017181916.D280910071@code0.codespeak.net> Author: fijal Date: Tue Oct 17 20:18:52 2006 New Revision: 33387 Modified: py/dist/py/test/rsession/executor.py py/dist/py/test/rsession/local.py py/dist/py/test/rsession/report.py py/dist/py/test/rsession/rsession.py py/dist/py/test/rsession/testing/test_lsession.py Log: Several fixes, extending Lsession. Modified: py/dist/py/test/rsession/executor.py ============================================================================== --- py/dist/py/test/rsession/executor.py (original) +++ py/dist/py/test/rsession/executor.py Tue Oct 17 20:18:52 2006 @@ -3,16 +3,19 @@ import py, os -from py.__.test.rsession.outcome import Outcome +from py.__.test.rsession.outcome import Outcome, ReprOutcome from py.__.test.rsession.box import Box +from py.__.test.rsession import report class RunExecutor(object): """ Same as in executor, but just running run """ wraps = False - def __init__(self, item): + def __init__(self, item, usepdb=False, reporter=None): self.item = item + self.usepdb = usepdb + self.reporter = reporter def execute(self): try: @@ -20,6 +23,8 @@ outcome = Outcome() except py.test.Item.Skipped, e: outcome = Outcome(skipped=str(e)) + except (KeyboardInterrupt, SystemExit): + raise except: excinfo = py.code.ExceptionInfo() if isinstance(self.item, py.test.Function): @@ -28,6 +33,15 @@ excinfo.traceback = excinfo.traceback.cut( path=code.path, firstlineno=code.firstlineno) outcome = Outcome(excinfo=excinfo, setupfailure=False) + if self.usepdb: + if self.reporter is not None: + self.reporter(report.ImmediateFailure(self.item, + ReprOutcome(outcome.make_repr()))) + import pdb + pdb.post_mortem(excinfo._excinfo[2]) + # XXX hmm, we probably will not like to continue from that point + # or we do? + raise SystemExit() outcome.stdout = "" outcome.stderr = "" return outcome @@ -75,4 +89,3 @@ if excinfo is not None: return Outcome(excinfo=excinfo, setupfailure=False) return Outcome() - Modified: py/dist/py/test/rsession/local.py ============================================================================== --- py/dist/py/test/rsession/local.py (original) +++ py/dist/py/test/rsession/local.py Tue Oct 17 20:18:52 2006 @@ -2,22 +2,43 @@ """ local-only operations """ -from py.__.test.rsession.executor import BoxExecutor +from py.__.test.rsession.executor import BoxExecutor, RunExecutor from py.__.test.rsession import report from py.__.test.rsession.outcome import ReprOutcome -def run(item): +def box_runner(item, config, reporter): r = BoxExecutor(item) return ReprOutcome(r.execute()) -def local_loop(reporter, itemgenerator, shouldstop): - +def plain_runner(item, config, reporter): + r = RunExecutor(item, usepdb=config.option.usepdb, reporter=reporter) + return ReprOutcome(r.execute().make_repr()) + +def benchmark_runner(item, config, reporter): + raise NotImplementedError() + +def apigen_runner(item, config, reporter): + pass + +def exec_runner(item, config, reporter): + raise NotImplementedError() + +# runner interface is here to perform several different types of run +#+1. box_runner - for running normal boxed tests +#+2. plain_runner - for performing running without boxing (necessary for pdb) +# XXX: really? +#-3. exec_runner - for running under different interpreter +#-4. benchmark_runner - for running with benchmarking +#-5. apigen_runner - for running under apigen to generate api out of it. +def local_loop(reporter, itemgenerator, shouldstop, config, runner=None): + if runner is None: + runner = box_runner while 1: try: item = itemgenerator.next() if shouldstop(): return - outcome = run(item) + outcome = runner(item, config, reporter) reporter(report.ReceivedItemOutcome(None, item, outcome)) except StopIteration: break Modified: py/dist/py/test/rsession/report.py ============================================================================== --- py/dist/py/test/rsession/report.py (original) +++ py/dist/py/test/rsession/report.py Tue Oct 17 20:18:52 2006 @@ -111,3 +111,8 @@ class RsyncFinished(ReportEvent): def __init__(self): self.time = time.time() + +class ImmediateFailure(ReportEvent): + def __init__(self, item, outcome): + self.item = item + self.outcome = outcome Modified: py/dist/py/test/rsession/rsession.py ============================================================================== --- py/dist/py/test/rsession/rsession.py (original) +++ py/dist/py/test/rsession/rsession.py Tue Oct 17 20:18:52 2006 @@ -16,7 +16,7 @@ from py.__.test.rsession.hostmanage import init_hosts, teardown_hosts from py.__.test.terminal.out import getout -from py.__.test.rsession.local import local_loop +from py.__.test.rsession.local import local_loop, plain_runner class RemoteOptions(object): def __init__(self, d): @@ -77,7 +77,11 @@ def report_RsyncFinished(self, item): self.timersync = item.time + def report_ImmediateFailure(self, event): + self.repr_failure(event.item, event.outcome) + def report_TestFinished(self, item): + self.out.line() assert hasattr(self, 'timestart') self.timeend = item.timeend self.skips() @@ -109,8 +113,8 @@ def repr_failure(self, item, outcome): excinfo = outcome.excinfo traceback = excinfo.traceback - if item and not self.config.option.fulltrace: - path, firstlineno = item.getpathlineno() + #if item and not self.config.option.fulltrace: + # path, firstlineno = item.getpathlineno() if not traceback: self.out.line("empty traceback from item %r" % (item,)) return @@ -395,7 +399,7 @@ class LSession(AbstractSession): """ Local version of session """ - def main(self, args, reporter=None): + def main(self, args, reporter=None, runner=None): if not args: args = [py.path.local()] @@ -419,7 +423,10 @@ itemgenerator = itemgen() #assert 0, "\n".join([",".join(x.listnames()) for x in # list(itemgenerator)]) - local_loop(reporter, itemgenerator, checkfun) + # XXX: We have to decide which runner to use at this point + if runner is None and self.config.option.usepdb: + runner = plain_runner + local_loop(reporter, itemgenerator, checkfun, self.config, runner=runner) reporter(report.TestFinished()) if startserverflag: Modified: py/dist/py/test/rsession/testing/test_lsession.py ============================================================================== --- py/dist/py/test/rsession/testing/test_lsession.py (original) +++ py/dist/py/test/rsession/testing/test_lsession.py Tue Oct 17 20:18:52 2006 @@ -5,9 +5,10 @@ import py from py.__.test.rsession.rsession import LSession from py.__.test.rsession import report +from py.__.test.rsession.local import box_runner, plain_runner class TestLSession(object): - def test_example_distribution(self): + def example_distribution(self, runner): # XXX find a better way for the below tmpdir = py.path.local(py.__file__).dirpath().dirpath() tmpdir.ensure("sub", "__init__.py") @@ -20,20 +21,26 @@ raise ValueError(23) def test_4(someargs): pass + #def test_5(): + # import os + # os.kill(os.getpid(), 11) """)) args = [str(tmpdir.join("sub"))] config, args = py.test.Config.parse(args) lsession = LSession(config) allevents = [] - lsession.main(args, reporter=allevents.append) + lsession.main(args, reporter=allevents.append, runner=runner) testevents = [x for x in allevents if isinstance(x, report.ReceivedItemOutcome)] assert len(testevents) passevents = [i for i in testevents if i.outcome.passed] failevents = [i for i in testevents if i.outcome.excinfo] skippedevents = [i for i in testevents if i.outcome.skipped] + signalevents = [i for i in testevents if i.outcome.signal] assert len(passevents) == 1 assert len(failevents) == 3 + assert len(skippedevents) == 0 + #assert len(signalevents) == 1 tb = failevents[0].outcome.excinfo.traceback assert str(tb[0].path).find("test_one") != -1 assert str(tb[0].source).find("test_2") != -1 @@ -47,3 +54,9 @@ assert failevents[2].outcome.excinfo.typename == 'TypeError' assert str(tb[0].path).find("executor") != -1 assert str(tb[0].source).find("execute") != -1 + + def test_normal(self): + self.example_distribution(box_runner) + + def test_plain(self): + self.example_distribution(plain_runner) From hpk at codespeak.net Tue Oct 17 21:54:33 2006 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 17 Oct 2006 21:54:33 +0200 (CEST) Subject: [py-svn] r33391 - py/dist/py/path/svn/testing Message-ID: <20061017195433.3FFFA10071@code0.codespeak.net> Author: hpk Date: Tue Oct 17 21:54:32 2006 New Revision: 33391 Modified: py/dist/py/path/svn/testing/test_urlcommand.py Log: added a test that actually passes (did it to test the plonesolutions.com "vadm init" problem - but that turned out to be a not enough updated py lib there) Modified: py/dist/py/path/svn/testing/test_urlcommand.py ============================================================================== --- py/dist/py/path/svn/testing/test_urlcommand.py (original) +++ py/dist/py/path/svn/testing/test_urlcommand.py Tue Oct 17 21:54:32 2006 @@ -63,5 +63,11 @@ assert info.last_author == 'hpk' assert info.kind == 'file' + def test_svn_1_3_b(self): + line =" 74 autoadmi Oct 06 23:59 plonesolutions.com/" + info = InfoSvnCommand(line) + assert info.last_author == 'autoadmi' + assert info.kind == 'dir' + def test_badchars(): py.test.raises(ValueError, "py.path.svnurl('file:///tmp/@@@:')") From hpk at codespeak.net Tue Oct 17 22:04:00 2006 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 17 Oct 2006 22:04:00 +0200 (CEST) Subject: [py-svn] r33392 - in py/dist/py/path/svn: . testing Message-ID: <20061017200400.76C501007C@code0.codespeak.net> Author: hpk Date: Tue Oct 17 22:03:59 2006 New Revision: 33392 Modified: py/dist/py/path/svn/testing/test_urlcommand.py py/dist/py/path/svn/urlcommand.py Log: make svnurl command initialization more specific, it should not accept None silently, there is no sensible default. Also we should just care to have py.path.svnurl(py.path.svnurl(...)) work, it's basically a copying operation now (although there is only an indirect test for that, constructor_equality) Tests pass with this, although something might break somewhere :) Modified: py/dist/py/path/svn/testing/test_urlcommand.py ============================================================================== --- py/dist/py/path/svn/testing/test_urlcommand.py (original) +++ py/dist/py/path/svn/testing/test_urlcommand.py Tue Oct 17 22:03:59 2006 @@ -12,6 +12,12 @@ repo, wc = getrepowc() cls.root = py.path.svnurl(repo) + def test_svnurl_needs_arg(self): + py.test.raises(TypeError, "py.path.svnurl()") + + def test_svnurl_does_not_accept_None_either(self): + py.test.raises(Exception, "py.path.svnurl(None)") + def test_svnurl_characters_simple(self): py.path.svnurl("svn+ssh://hello/world") Modified: py/dist/py/path/svn/urlcommand.py ============================================================================== --- py/dist/py/path/svn/urlcommand.py (original) +++ py/dist/py/path/svn/urlcommand.py Tue Oct 17 22:03:59 2006 @@ -20,8 +20,9 @@ def __new__(cls, path, rev=None): self = object.__new__(cls) - if not isinstance(path, str): - path = str(path) + if isinstance(path, cls): + rev = path.rev + path = path.strpath proto, uri = path.split("://", 1) host, uripath = uri.split('/', 1) # only check for bad chars in the non-protocol parts From fijal at codespeak.net Wed Oct 18 14:16:20 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Wed, 18 Oct 2006 14:16:20 +0200 (CEST) Subject: [py-svn] r33400 - in py/dist/py/test/rsession: . testing Message-ID: <20061018121620.1BA6C1005A@code0.codespeak.net> Author: fijal Date: Wed Oct 18 14:16:02 2006 New Revision: 33400 Modified: py/dist/py/test/rsession/local.py py/dist/py/test/rsession/rsession.py py/dist/py/test/rsession/testing/test_lsession.py Log: Added some tests, fixed some bugs. Modified: py/dist/py/test/rsession/local.py ============================================================================== --- py/dist/py/test/rsession/local.py (original) +++ py/dist/py/test/rsession/local.py Wed Oct 18 14:16:02 2006 @@ -18,7 +18,7 @@ raise NotImplementedError() def apigen_runner(item, config, reporter): - pass + raise NotImplementedError() def exec_runner(item, config, reporter): raise NotImplementedError() Modified: py/dist/py/test/rsession/rsession.py ============================================================================== --- py/dist/py/test/rsession/rsession.py (original) +++ py/dist/py/test/rsession/rsession.py Wed Oct 18 14:16:02 2006 @@ -329,6 +329,7 @@ reporter_instance.is_failing() else: startserverflag = False + return reporter, checkfun, startserverflag def reporterror(reporter, data): @@ -399,7 +400,7 @@ class LSession(AbstractSession): """ Local version of session """ - def main(self, args, reporter=None, runner=None): + def main(self, args, reporter=None, runner=None, shouldstop=None): if not args: args = [py.path.local()] @@ -407,6 +408,8 @@ reporter, checkfun, startserverflag = self.init_reporter(reporter, sshhosts, LocalReporter) + if shouldstop: + checkfun = shouldstop reporter(report.TestStarted(sshhosts)) pkgdir = self.getpkgdir(args[0]) Modified: py/dist/py/test/rsession/testing/test_lsession.py ============================================================================== --- py/dist/py/test/rsession/testing/test_lsession.py (original) +++ py/dist/py/test/rsession/testing/test_lsession.py Wed Oct 18 14:16:02 2006 @@ -8,11 +8,14 @@ from py.__.test.rsession.local import box_runner, plain_runner class TestLSession(object): + # XXX: Some tests of that should be run as well on RSession, while + # some not at all def example_distribution(self, runner): # XXX find a better way for the below tmpdir = py.path.local(py.__file__).dirpath().dirpath() - tmpdir.ensure("sub", "__init__.py") - tmpdir.ensure("sub", "test_one.py").write(py.code.Source(""" + dirname = "sub"+runner.func_name + tmpdir.ensure(dirname, "__init__.py") + tmpdir.ensure(dirname, "test_one.py").write(py.code.Source(""" def test_1(): pass def test_2(): @@ -25,7 +28,7 @@ # import os # os.kill(os.getpid(), 11) """)) - args = [str(tmpdir.join("sub"))] + args = [str(tmpdir.join(dirname))] config, args = py.test.Config.parse(args) lsession = LSession(config) allevents = [] @@ -60,3 +63,94 @@ def test_plain(self): self.example_distribution(plain_runner) + + def test_pdb_run(self): + # we make sure that pdb is engaged + tmpdir = py.path.local(py.__file__).dirpath().dirpath() + tmpdir.ensure("sub", "__init__.py") + tmpdir.ensure("sub", "test_one.py").write(py.code.Source(""" + def test_1(): + assert 0 + """)) + import pdb + l = [] + def some_fun(*args): + l.append(args) + + pdb.post_mortem = some_fun + args = [str(tmpdir.join("sub")), '--pdb'] + config, args = py.test.Config.parse(args) + lsession = LSession(config) + allevents = [] + try: + lsession.main(args, reporter=allevents.append, runner=plain_runner) + except SystemExit: + pass + else: + py.test.fail("Didn't raise system exit") + failure_events = [event for event in allevents if isinstance(event, + report.ImmediateFailure)] + assert len(failure_events) == 1 + assert len(l) == 1 + + def test_minus_x(self): + tmpdir = py.path.local(py.__file__).dirpath().dirpath() + tmpdir.ensure("sub2", "__init__.py") + tmpdir.ensure("sub2", "test_one.py").write(py.code.Source(""" + def test_1(): + pass + def test_2(): + assert 0 + def test_3(): + raise ValueError(23) + def test_4(someargs): + pass + """)) + args = [str(tmpdir.join("sub2")), '-x'] + config, args = py.test.Config.parse(args) + assert config.option.exitfirst + lsession = LSession(config) + allevents = [] + + def check_stop(): + testevents = [x for x in allevents + if isinstance(x, report.ReceivedItemOutcome)] + failevents = [i for i in testevents if i.outcome.excinfo] + return len(failevents) > 0 + + lsession.main(args, reporter=allevents.append, runner=box_runner, + shouldstop=check_stop) + testevents = [x for x in allevents + if isinstance(x, report.ReceivedItemOutcome)] + assert len(testevents) + passevents = [i for i in testevents if i.outcome.passed] + failevents = [i for i in testevents if i.outcome.excinfo] + assert len(passevents) == 1 + assert len(failevents) == 1 + + def test_minus_k(self): + tmpdir = py.path.local(py.__file__).dirpath().dirpath() + tmpdir.ensure("sub3", "__init__.py") + tmpdir.ensure("sub3", "test_some.py").write(py.code.Source(""" + def test_one(): + pass + def test_one_one(): + assert 0 + def test_other(): + raise ValueError(23) + def test_two(someargs): + pass + """)) + args = [str(tmpdir.join("sub3")), '-k', 'test_one'] + config, args = py.test.Config.parse(args) + lsession = LSession(config) + allevents = [] + + lsession.main(args, reporter=allevents.append, runner=box_runner) + testevents = [x for x in allevents + if isinstance(x, report.ReceivedItemOutcome)] + assert len(testevents) + passevents = [i for i in testevents if i.outcome.passed] + failevents = [i for i in testevents if i.outcome.excinfo] + assert len(passevents) == 1 + assert len(failevents) == 1 From guido at codespeak.net Wed Oct 18 15:34:25 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Wed, 18 Oct 2006 15:34:25 +0200 (CEST) Subject: [py-svn] r33403 - in py/dist/py/rst: . testing Message-ID: <20061018133425.4DF931005A@code0.codespeak.net> Author: guido Date: Wed Oct 18 15:34:21 2006 New Revision: 33403 Modified: py/dist/py/rst/rst.py py/dist/py/rst/testing/test_rst.py Log: Some whitespace and indentation fixes, splitting on all whitespace instead of just single space for the wordlist now (fixes formatting problems in e.g. Paragraph code), making BlockQuote put its :: on a seperate line instead of the previous (fixes problems with e.g. a BlockQuote following a Title node). Modified: py/dist/py/rst/rst.py ============================================================================== --- py/dist/py/rst/rst.py (original) +++ py/dist/py/rst/rst.py Wed Oct 18 15:34:21 2006 @@ -137,19 +137,19 @@ buf = [] grab(buf) return "\n".join(outcome) - + class SubParagraph(Paragraph): indent = " " class BlockQuote(Paragraph): indent = " " - previous_paragraph = "::" + previous_paragraph = "\n::" sep = "" def text(self): all_txt = AbstractNode.text(self) all_txts = all_txt.split("\n") - return "\n".join([self.indent + i for i in all_txts]) + return '%s\n' % ("\n".join([self.indent + i for i in all_txts]),) class Title(Paragraph): parentclass = Rest @@ -178,8 +178,9 @@ return self.start + self._text + self.end class Text(AbstractText): + _reg_whitespace = py.std.re.compile('\s+') def wordlist(self): - return self._text.split(" ") + return self._reg_whitespace.split(self._text) class Emph(AbstractText): start = "*" @@ -192,7 +193,7 @@ self.indent = self.indent + " " txt = Paragraph.text(self) txt = self.item_char + txt[1:] - del self.indent + del self.indent # XXX ? return txt class Link(AbstractText): Modified: py/dist/py/rst/testing/test_rst.py ============================================================================== --- py/dist/py/rst/testing/test_rst.py (original) +++ py/dist/py/rst/testing/test_rst.py Wed Oct 18 15:34:21 2006 @@ -15,15 +15,19 @@ assert txt == ' a\n b\n c\n d\n e\n f\n' def test_blockquote(): - expected = """Text:: + expected = """\ +Text +:: def fun(): some + Paragraph """ txt = Rest(Paragraph("Text"), BlockQuote("def fun():\n some"), \ Paragraph("Paragraph")).text() + print repr(txt) assert txt == expected def test_title(): @@ -41,7 +45,66 @@ txt = Rest(Paragraph("This is a test!")).text() assert txt == expected +def test_text_multiline(): + expected = """\ +Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Vestibulum malesuada +eleifend leo. Sed faucibus commodo libero. Mauris elementum fringilla velit. Ut +sem urna, aliquet sed, molestie at, viverra id, justo. In ornare lacinia +turpis. Etiam et ipsum. Quisque at lacus. Etiam pellentesque, enim porta +pulvinar viverra, libero elit iaculis justo, vitae convallis pede purus vel +arcu. Morbi aliquam lacus et urna. Donec commodo pellentesque mi. +""" + txt = Rest(Paragraph('Lorem ipsum dolor sit amet, consectetuer adipiscing ' + 'elit. Vestibulum malesuada eleifend leo. Sed ' + 'faucibus commodo libero. Mauris elementum fringilla ' + 'velit. Ut sem urna, aliquet sed, molestie at, ' + 'viverra id, justo. In ornare lacinia turpis. Etiam ' + 'et ipsum. Quisque at lacus. Etiam pellentesque, ' + 'enim porta pulvinar viverra, libero elit iaculis ' + 'justo, vitae convallis pede purus vel arcu. Morbi ' + 'aliquam lacus et urna. Donec commodo pellentesque ' + 'mi.')).text() + assert txt == expected + +def test_text_indented(): + expected = """\ +This is a paragraph with some indentation. The indentation should be removed +and the lines split up nicely. This is still part of the first paragraph. +""" + txt = Rest(Paragraph('This is a paragraph with some\n' + ' indentation. The indentation\n' + ' should be removed and the lines split up nicely.\n' + '\nThis is still part of the first paragraph.') + ).text() + assert txt == expected + def test_list(): expected = "* a\n\n* b\n" txt = Rest(ListItem("a"), ListItem("b")).text() assert txt == expected + +def test_list_multiline(): + expected = """\ +* Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Vestibulum + malesuada eleifend leo. Sed faucibus commodo libero. + +* Mauris elementum fringilla velit. Ut sem urna, aliquet sed, molestie at, + viverra id, justo. In ornare lacinia turpis. Etiam et ipsum. Quisque at + lacus. + +* Etiam pellentesque, enim porta pulvinar viverra, libero elit iaculis justo, + vitae convallis pede purus vel arcu. Morbi aliquam lacus et urna. Donec + commodo pellentesque mi. +""" + txt = Rest(ListItem('Lorem ipsum dolor sit amet, consectetuer adipiscing ' + 'elit. Vestibulum malesuada eleifend leo. Sed ' + 'faucibus commodo libero.'), + ListItem('Mauris elementum fringilla velit. Ut sem urna, ' + 'aliquet sed, molestie at, viverra id, justo. In ' + 'ornare lacinia turpis. Etiam et ipsum. Quisque at ' + 'lacus.'), + ListItem('Etiam pellentesque, enim porta pulvinar viverra, ' + 'libero elit iaculis justo, vitae convallis pede ' + 'purus vel arcu. Morbi aliquam lacus et urna. Donec ' + 'commodo pellentesque mi.')).text() + assert txt == expected From fijal at codespeak.net Wed Oct 18 19:56:53 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Wed, 18 Oct 2006 19:56:53 +0200 (CEST) Subject: [py-svn] r33416 - in py/dist/py: bin magic/testing test/rsession test/rsession/testing Message-ID: <20061018175653.2832110060@code0.codespeak.net> Author: fijal Date: Wed Oct 18 19:55:40 2006 New Revision: 33416 Modified: py/dist/py/bin/py.lookup py/dist/py/magic/testing/test_assertion.py py/dist/py/test/rsession/box.py py/dist/py/test/rsession/outcome.py py/dist/py/test/rsession/rsession.py py/dist/py/test/rsession/slave.py py/dist/py/test/rsession/testing/test_lsession.py py/dist/py/test/rsession/testing/test_reporter.py py/dist/py/test/rsession/testing/test_rsession.py py/dist/py/test/rsession/testing/test_slave.py Log: Maintenance. Tests, fixes. Modified: py/dist/py/bin/py.lookup ============================================================================== --- py/dist/py/bin/py.lookup (original) +++ py/dist/py/bin/py.lookup Wed Oct 18 19:55:40 2006 @@ -1,7 +1,9 @@ #!/usr/bin/env python -from _findpy import py -import re +import sys, os +sys.path.insert(0, os.path.dirname(__file__)) +from _findpy import py +import re curdir = py.path.local() def rec(p): @@ -12,6 +14,8 @@ parser = optparse.OptionParser() parser.add_option("-i", "--ignore-case", action="store_true", dest="ignorecase", help="ignore case distinctions") +parser.add_option("-C", "--context", action="store", type="int", dest="context", + default=0, help="How many lines of output to show") if __name__ == '__main__': (options, args) = parser.parse_args() @@ -31,4 +35,10 @@ searchlines = lines for i, (line, searchline) in enumerate(zip(lines, searchlines)): if searchline.find(string) != -1: - print "%s:%d: %s" %(x.relto(curdir), i+1, line.rstrip()) + if not options.context: + print "%s:%d: %s" %(x.relto(curdir), i+1, line.rstrip()) + else: + context = (options.context)/2 + for count in range(i-context, i+context+1): + print "%s:%d: %s" %(x.relto(curdir), count+1, lines[count].rstrip()) + print "-"*50 Modified: py/dist/py/magic/testing/test_assertion.py ============================================================================== --- py/dist/py/magic/testing/test_assertion.py (original) +++ py/dist/py/magic/testing/test_assertion.py Wed Oct 18 19:55:40 2006 @@ -87,3 +87,12 @@ assert 0, ['list'] except AssertionError, e: assert e.msg.find("list") != -1 + +def test_assert_implicit_multiline(): + try: + x = [1,2,3] + assert x != [1, + 2, 3] + except AssertionError, e: + assert e.msg.find('assert [1, 2, 3] !=') != -1 + Modified: py/dist/py/test/rsession/box.py ============================================================================== --- py/dist/py/test/rsession/box.py (original) +++ py/dist/py/test/rsession/box.py Wed Oct 18 19:55:40 2006 @@ -189,6 +189,7 @@ stdout.close() stderr.close() retvalf.close() + os._exit(0) def parent(self): pid, exitstat = os.wait() Modified: py/dist/py/test/rsession/outcome.py ============================================================================== --- py/dist/py/test/rsession/outcome.py (original) +++ py/dist/py/test/rsession/outcome.py Wed Oct 18 19:55:40 2006 @@ -48,7 +48,7 @@ self.lineno = int(lineno) def __repr__(self): - return "line %s in %s\n %s" %(self.lineno, self.path, self.source) + return "line %s in %s\n %s" %(self.lineno, self.path, self.source[100:]) class ExcInfoRepr(object): def __init__(self, excinfo): Modified: py/dist/py/test/rsession/rsession.py ============================================================================== --- py/dist/py/test/rsession/rsession.py (original) +++ py/dist/py/test/rsession/rsession.py Wed Oct 18 19:55:40 2006 @@ -23,7 +23,12 @@ self.d = d def __getattr__(self, attr): + if attr == 'd': + return self.__dict__['d'] return self.d[attr] + + def __setitem__(self, item, val): + self.d[item] = val remote_options = RemoteOptions({'we_are_remote':False}) @@ -37,6 +42,8 @@ self.failed = dict([(host, 0) for host in hosts]) self.skipped = dict([(host, 0) for host in hosts]) self.passed = dict([(host, 0) for host in hosts]) + self.count = 0 + self.lgt = 1000 def report(self, what): repfun = getattr(self, "report_" + what.__class__.__name__, @@ -61,7 +68,7 @@ if self.config.option.verbose: print "Sending %s to %s" % (item.item, address) - + def report_HostRSyncing(self, item): print "%10s: RSYNC ==> %s" % (item.hostname[:10], item.remoterootpath) @@ -238,6 +245,12 @@ return event.channel.gateway.sshaddress + ":" + \ "/".join(colitem.listnames()) + def report_FailedTryiter(self, event): + self.out.line("FAILED TO LOAD MODULE: %s" % "/".join(event.item.listnames())) + + def report_SkippedTryiter(self, event): + self.out.line("Skipped (%s) %s" % (str(event.excinfo.value), "/". + join(event.item.listnames()))) class LocalReporter(AbstractReporter): def get_host(self, item): @@ -246,6 +259,12 @@ def get_item_name(self, event, colitem): return "/".join(colitem.listnames()) + def report_SkippedTryiter(self, event): + self.out.write("- skipped (%s)\n" % event.excinfo.value) + + def report_FailedTryiter(self, event): + self.out.write("- FAILED TO LOAD MODULE\n") + def report_ReceivedItemOutcome(self, event): if event.outcome.passed: self.passed['localhost'] += 1 @@ -375,8 +394,9 @@ except: remotepython = None + remote_options['nomagic'] = self.config.option.nomagic nodes = init_hosts(reporter, sshhosts, directories, pkgdir, - rsync_roots, remotepython) + rsync_roots, remotepython, remote_options=remote_options.d) reporter(report.RsyncFinished()) keyword = self.config.option.keyword @@ -405,6 +425,9 @@ args = [py.path.local()] sshhosts = ['localhost'] # this is just an info to reporter + + if not self.config.option.nomagic: + py.magic.invoke(assertion=1) reporter, checkfun, startserverflag = self.init_reporter(reporter, sshhosts, LocalReporter) @@ -435,3 +458,7 @@ if startserverflag: from py.__.test.rsession.web import kill_server kill_server() + + #py.test.Function.state.teardown_all() + if not self.config.option.nomagic: + py.magic.revoke(assertion=1) Modified: py/dist/py/test/rsession/slave.py ============================================================================== --- py/dist/py/test/rsession/slave.py (original) +++ py/dist/py/test/rsession/slave.py Wed Oct 18 19:55:40 2006 @@ -43,6 +43,7 @@ while 1: nextitem = receive() if nextitem is None: + #py.test.Function.state.teardown_all() break try: node = getnode(nextitem) @@ -65,19 +66,30 @@ def setup(): + default_options = {'nomagic':False} # XXX should come from somewhere else + # but I don't want to mess with conftest at this point + import os, sys pkgdir = channel.receive() # path is ready options = channel.receive() # options stuff, should be dictionary - basedir = os.path.dirname(pkgdir) - pkgname = os.path.basename(pkgdir) + basedir = os.path.dirname(pkgdir) + pkgname = os.path.basename(pkgdir) + # setup defaults... sys.path.insert(0, basedir) import py - from py.__.test.rsession.rsession import RemoteOptions options['we_are_remote'] = True + for opt, val in default_options.items(): + if opt not in options: + options[opt] = val + from py.__.test.rsession.rsession import RemoteOptions py.test.remote = RemoteOptions(options) # XXX the following assumes that py lib is there, a bit # much of an assumtion + if not py.test.remote.nomagic: + py.magic.invoke(assertion=1) mod = __import__(pkgname) assert py.path.local(mod.__file__).dirpath() == py.path.local(pkgdir) from py.__.test.rsession.slave import slave_main slave_main(channel.receive, channel.send, basedir) + if not py.test.remote.nomagic: + py.magic.revoke(assertion=1) Modified: py/dist/py/test/rsession/testing/test_lsession.py ============================================================================== --- py/dist/py/test/rsession/testing/test_lsession.py (original) +++ py/dist/py/test/rsession/testing/test_lsession.py Wed Oct 18 19:55:40 2006 @@ -7,12 +7,21 @@ from py.__.test.rsession import report from py.__.test.rsession.local import box_runner, plain_runner +basepath = py.path.local(py.__file__).dirpath().dirpath() + +try: + tmp = basepath.mkdir("pytemp") +except py.error.EEXIST: + tmp = basepath.join("pytemp") + tmp.remove(rec=1) + tmp = basepath.mkdir("pytemp") + class TestLSession(object): # XXX: Some tests of that should be run as well on RSession, while # some not at all def example_distribution(self, runner): # XXX find a better way for the below - tmpdir = py.path.local(py.__file__).dirpath().dirpath() + tmpdir = tmp dirname = "sub"+runner.func_name tmpdir.ensure(dirname, "__init__.py") tmpdir.ensure(dirname, "test_one.py").write(py.code.Source(""" @@ -66,7 +75,7 @@ def test_pdb_run(self): # we make sure that pdb is engaged - tmpdir = py.path.local(py.__file__).dirpath().dirpath() + tmpdir = tmp tmpdir.ensure("sub", "__init__.py") tmpdir.ensure("sub", "test_one.py").write(py.code.Source(""" def test_1(): @@ -94,7 +103,7 @@ assert len(l) == 1 def test_minus_x(self): - tmpdir = py.path.local(py.__file__).dirpath().dirpath() + tmpdir = tmp tmpdir.ensure("sub2", "__init__.py") tmpdir.ensure("sub2", "test_one.py").write(py.code.Source(""" def test_1(): @@ -129,7 +138,7 @@ assert len(failevents) == 1 def test_minus_k(self): - tmpdir = py.path.local(py.__file__).dirpath().dirpath() + tmpdir = tmp tmpdir.ensure("sub3", "__init__.py") tmpdir.ensure("sub3", "test_some.py").write(py.code.Source(""" def test_one(): @@ -154,3 +163,83 @@ failevents = [i for i in testevents if i.outcome.excinfo] assert len(passevents) == 1 assert len(failevents) == 1 + + def test_lsession(self): + tmpdir = tmp + tmpdir.ensure("sub4", "__init__.py") + tmpdir.ensure("sub4", "test_some.py").write(py.code.Source(""" + def test_one(): + pass + def test_one_one(): + assert 0 + def test_other(): + raise ValueError(23) + def test_two(someargs): + pass + """)) + + args = [str(tmpdir.join("sub4"))] + config, args = py.test.Config.parse(args) + lsession = LSession(config) + allevents = [] + allruns = [] + def dummy_runner(item, config, reporter): + allruns.append(item) + return item + lsession.main(args, reporter=allevents.append, runner=dummy_runner) + + assert len(allruns) == 4 + testevents = [x for x in allevents + if isinstance(x, report.ReceivedItemOutcome)] + assert len(testevents) == 4 + lst = ['test_one', 'test_one_one', 'test_other', 'test_two'] + for num, i in enumerate(testevents): + assert i.item == i.outcome + assert i.item.name == lst[num] + + def test_module_raising(self): + tmpdir = tmp + tmpdir.ensure("sub5", "__init__.py") + tmpdir.ensure("sub5", "test_some.py").write(py.code.Source(""" + 1/0 + """)) + tmpdir.ensure("sub5", "test_other.py").write(py.code.Source(""" + import py + py.test.skip("reason") + """)) + + args = [str(tmpdir.join("sub5"))] + config, args = py.test.Config.parse(args) + lsession = LSession(config) + allevents = [] + lsession.main(args, reporter=allevents.append, runner=box_runner) + testevents = [x for x in allevents + if isinstance(x, report.ReceivedItemOutcome)] + assert len(testevents) == 0 + failedtryiter = [x for x in allevents + if isinstance(x, report.FailedTryiter)] + assert len(failedtryiter) == 1 + skippedtryiter = [x for x in allevents + if isinstance(x, report.SkippedTryiter)] + assert len(skippedtryiter) == 1 + + + def test_assert_reinterpret(self): + tmpdir = tmp + tmpdir.ensure("sub6", "__init__.py") + tmpdir.ensure("sub6", "test_some.py").write(py.code.Source(""" + def test_one(): + x = [1, 2] + assert [0] == x + """)) + args = [str(tmpdir.join("sub6"))] + config, args = py.test.Config.parse(args) + lsession = LSession(config) + allevents = [] + lsession.main(args, reporter=allevents.append, runner=box_runner) + testevents = [x for x in allevents + if isinstance(x, report.ReceivedItemOutcome)] + failevents = [i for i in testevents if i.outcome.excinfo] + assert len(failevents) == 1 + assert len(testevents) == 1 + assert failevents[0].outcome.excinfo.value == 'assert [0] == [1, 2]' Modified: py/dist/py/test/rsession/testing/test_reporter.py ============================================================================== --- py/dist/py/test/rsession/testing/test_reporter.py (original) +++ py/dist/py/test/rsession/testing/test_reporter.py Wed Oct 18 19:55:40 2006 @@ -4,7 +4,7 @@ """ import py -from py.__.test.rsession.rsession import RemoteReporter +from py.__.test.rsession.rsession import LocalReporter from py.__.test.rsession.report import ReceivedItemOutcome from py.__.test.rsession.outcome import ReprOutcome, Outcome from py.__.test.rsession.testing.test_slave import funcpass_spec @@ -15,7 +15,7 @@ class TestReporter(object): def test_report_received_item_outcome(self): config, args = py.test.Config.parse(["some_sub"]) - r = RemoteReporter(config, ["host"]) + r = LocalReporter(config, ["localhost"]) # possible outcomes try: 1/0 @@ -30,7 +30,7 @@ outcomes = [ReprOutcome(outcome.make_repr()) for outcome in outcomes] outcomes[3].signal = 11 outcomes[0].passed = False - + # we just go... rootcol = py.test.collect.Directory(pkgdir.dirpath()) item = rootcol.getitembynames(funcpass_spec) Modified: py/dist/py/test/rsession/testing/test_rsession.py ============================================================================== --- py/dist/py/test/rsession/testing/test_rsession.py (original) +++ py/dist/py/test/rsession/testing/test_rsession.py Wed Oct 18 19:55:40 2006 @@ -165,10 +165,10 @@ count_rsyn_calls = [i for i in setup_events if isinstance(i, report.HostRSyncing)] - assert len(count_rsyn_calls) == len(hosts) + assert len(count_rsyn_calls) == len([i for i in hosts if i != 'localhost']) count_ready_calls = [i for i in setup_events if isinstance(i, report.HostReady)] - assert len(count_ready_calls) == len(hosts) + assert len(count_ready_calls) == len([i for i in hosts if i != 'localhost']) # same for teardown events teardown_wait_starts = [i for i in teardown_events Modified: py/dist/py/test/rsession/testing/test_slave.py ============================================================================== --- py/dist/py/test/rsession/testing/test_slave.py (original) +++ py/dist/py/test/rsession/testing/test_slave.py Wed Oct 18 19:55:40 2006 @@ -4,12 +4,14 @@ from py.__.test.rsession.outcome import ReprOutcome import py, sys +modlevel = [] +import os + if sys.platform == 'win32': py.test.skip("rsession is unsupported on Windows.") - -def setup_module(mod): - mod.rootdir = py.path.local(py.__file__).dirpath().dirpath() +def setup_module(module): + module.rootdir = py.path.local(py.__file__).dirpath().dirpath() # ---------------------------------------------------------------------- # inlined testing functions used below @@ -106,8 +108,8 @@ slave_main(q.pop, res.append, str(rootdir)) assert len(res) == 2 res_repr = [ReprOutcome(r) for r in res] - assert not res_repr[0].passed - assert res_repr[1].passed + assert (not res_repr[0].passed and res_repr[1].passed) or \ + (not res_repr[1].passed and res_repr[0].passed) def test_slave_run_different_stuff(): node = gettestnode() From guido at codespeak.net Wed Oct 18 22:12:37 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Wed, 18 Oct 2006 22:12:37 +0200 (CEST) Subject: [py-svn] r33422 - in py/dist/py/test/tracer: . testing Message-ID: <20061018201237.C3EDA10078@code0.codespeak.net> Author: guido Date: Wed Oct 18 22:12:36 2006 New Revision: 33422 Modified: py/dist/py/test/tracer/genrest.py py/dist/py/test/tracer/testing/test_rest.py Log: Changed 'write_file' into 'write_section' and made removed hard-coded file and link names, now it's possible to change the directory structure, or even write to a single file. Split up rendering of the index (a list of links to the doc sections) and documentation sections to allow treating them differently (mostly to allow locating the index above the document on single document rendering, not too fond of this solution still, though). Added functionality on the writers to re-write internal links, so the writers can decide whether to link to 'fun_foo.txt', 'fun_foo.html' or '#fun_foo', depending on their output. Some whitespace and indentation cleanups. Modified: py/dist/py/test/tracer/genrest.py ============================================================================== --- py/dist/py/test/tracer/genrest.py (original) +++ py/dist/py/test/tracer/genrest.py Wed Oct 18 22:12:36 2006 @@ -22,8 +22,8 @@ """ Link writer for ViewVC version control viewer """ def __init__(self, basepath): - self.basepath = basepath # XXX: should try to guess from working - # copy of svn + # XXX: should try to guess from a working copy of svn + self.basepath = basepath def getpkgpath(self, filename): # XXX: very simple thing @@ -37,12 +37,13 @@ def getlink(self, filename, lineno): path = str(self.getpkgpath(filename)) - assert filename.startswith(path), "%s does not belong to package %s" %\ - (filename, path) + 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 ('%s:%s' % (filename, lineno), self.basepath + relname[1:] + '?view=markup') + return ('%s:%s' % (filename, lineno), + self.basepath + relname[1:] + '?view=markup') class DirectPaste(AbstractLinkWriter): """ No-link writer (inliner) @@ -54,13 +55,19 @@ def __init__(self, output=sys.stdout): self.output = output - def write_file(self, filename, data): - text = "Written file: %s" % filename + def write_index(self, data): + self.write_section('index', data) + + def write_section(self, name, data): + text = "Written file: %s.txt" % (name,) self.output.write(text + "\n") self.output.write("=" * len(text) + "\n") self.output.write("\n") self.output.write(data + "\n") - + + def rewrite_link(self, target): + return '%s.txt' % (target,) + class DirWriter(object): def __init__(self, directory=None): if directory is None: @@ -68,9 +75,38 @@ else: self.directory = py.path.local(directory) - def write_file(self, filename, data): + def write_section(self, name, data): + filename = '%s.txt' % (name,) self.directory.ensure(filename).write(data) + def write_index(self, data): + self.write_section('index', data) + + def rewrite_link(self, target): + # we assume the result will get converted to HTML... + return '%s.html' % (target,) + +class FileWriter(object): + def __init__(self, fpath): + self.fpath = fpath + self.fp = fpath.open('w+') + + def write_section(self, name, data): + self.fp.write(data) + self.fp.flush() + + def write_index(self, data): + self.fp.seek(0) + curdata = self.fp.read() + data += curdata + self.fp.seek(0) + self.fp.truncate(0) + self.fp.write(data) + self.fp.flush() + + def rewrite_link(self, target): + return '#%s' % (target,) + class RestGen(object): def __init__(self, ds, linkgen, writer=PipeWriter()): self.dsa = DocStorageAccessor(ds) @@ -80,44 +116,54 @@ def write(self): # first write down a file with all the exported interfaces, # sorted by type - self.write_interface("index.txt") + self.write_interface() - def write_interface(self, filename): + def write_interface(self): lst = [Title("Module: %s" % self.dsa.get_module_name(), belowchar="="), - Paragraph(self.dsa.get_module_info()), - Title("Exported functions:", belowchar="-")] + Paragraph(self.dsa.get_module_info()), + Title("Exported functions:", belowchar="-")] self.write_function_list(lst) lst.append(Title("Exported classes:", belowchar="-")) self.write_class_list(lst) - self.writer.write_file(filename, Rest(*lst).text()) + self.writer.write_index(Rest(*lst).text()) def write_function_list(self, lst): for name in self.dsa.get_function_names(): # XXX: should be .html here? - lst.append(ListItem(Text("Function: "), Link(name, "function_%s.html" % name))) - self.write_function('function_' + name + '.txt', name) + sectionname = 'function_%s' % (name,) + linktarget = self.writer.rewrite_link(sectionname) + lst.append(ListItem(Text("Function: "), + Link(name, linktarget))) + self.write_function(sectionname, name) def write_class_list(self, lst): for name in self.dsa.get_class_names(): - lst.append(ListItem(Text("Class: "), Link(name, "class_%s.html" % name))) - self.write_class('class_' + name + '.txt', name) + sectionname = 'class_%s' % (name,) + linktarget = self.writer.rewrite_link(sectionname) + lst.append(ListItem(Text("Class: "), + Link(name, linktarget))) + self.write_class(sectionname, name) - def write_class(self, filename, class_name): + def write_class(self, section_name, class_name): + # XXX no docstring? lst = [Title("Class: %s" % class_name, belowchar='-')] # write down exported methods lst.append(Title("Exported methods:", belowchar="^")) for method in self.dsa.get_class_methods(class_name): - fname = "method_%s" % (class_name + \ - "_" + method) - lst.append(ListItem(Link(method, fname+'.html'))) - self.write_function(fname+'.txt', class_name + "." + method) - - self.writer.write_file(filename, Rest(*lst).text()) - - def write_function(self, filename, fun_name): + sectionname = 'method_%s_%s' % (class_name, method) + linktarget = self.writer.rewrite_link(sectionname) + lst.append(ListItem(Link(method, linktarget))) + self.write_function(sectionname, class_name + "." + method) + + self.writer.write_section(section_name, Rest(*lst).text()) + + def write_function(self, section_name, fun_name): + # XXX I think the docstring should either be split on \n\n and cleaned + # from indentation, or treated as ReST too (although this is obviously + # dangerous for non-ReST docstrings)... lst = [Title("Function: %s" % fun_name, belowchar="-"), - Paragraph(self.dsa.get_function_doc(fun_name)), + BlockQuote(self.dsa.get_function_doc(fun_name)), BlockQuote(self.dsa.get_function_definition(fun_name))] lst.append(Paragraph("Function source: XXX (to write a link here)")) @@ -128,7 +174,8 @@ # for the type declaration (if this is known) arg_str = "(%s)" % (",".join([str(i.knowntype) for i in args])) ret_str = str(retval.knowntype) - arg_quote = Paragraph("Function type:", Quote(arg_str), '->', Quote(ret_str)) + arg_quote = Paragraph("Function type:", Quote(arg_str), '->', + Quote(ret_str)) lst.append(arg_quote) # call sites.. @@ -139,7 +186,8 @@ for call_site, frame in self.dsa.get_function_callpoints(fun_name): link_str = "File %s:%s" % (call_site.filename, call_site.lineno) - #_, link_target = self.linkgen.getlink(call_site.filename, call_site.lineno) + #_, link_target = self.linkgen.getlink(call_site.filename, + # call_site.lineno) #if link_target: # otherwise it's just inline text # call_sites.append(Paragraph(Link(link_str, link_target))) #else: @@ -152,12 +200,12 @@ for num, line in enumerate(source): if num == call_site.lineno - frame.code.firstlineno - 1: m = re.match("^( *)(.*)", line) - lines.append(">" + "-"*len(m.group(1)) + m.group(2)) + lines.append("%s >" + "-"*len(m.group(1)) + m.group(2)) else: lines.append(" " + line) call_sites.append(BlockQuote("\n".join(lines))) - self.writer.write_file(filename, Rest(*lst).text()) + self.writer.write_section(section_name, Rest(*lst).text()) ##class RestGen(object): ## def __init__(self, ds, linkgen, output=sys.stdout): ## self.dsa = DocStorageAccessor(ds) @@ -189,14 +237,16 @@ ## for call_site in self.dsa.get_function_callpoints(key): ## link_str = "File %s:%s" % (call_site.filename, ## call_site.lineno) -## link_target = self.linkgen.getlink(call_site.filename, call_site.lineno) +## link_target = self.linkgen.getlink(call_site.filename, +## call_site.lineno) ## if link_target: # otherwise it's just inline text ## call_sites.append(Paragraph(Link(link_str, link_target))) ## else: ## call_sites.append(Paragraph(link_str)) ## call_sites.append(BlockQuote(call_site.source)) ## -## for item in [title, docstr, arg_quote, call_site_title] + call_sites: +## 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): Modified: py/dist/py/test/tracer/testing/test_rest.py ============================================================================== --- py/dist/py/test/tracer/testing/test_rest.py (original) +++ py/dist/py/test/tracer/testing/test_rest.py Wed Oct 18 22:12:36 2006 @@ -5,8 +5,8 @@ import py try: - from py.__.test.tracer.genrest import ViewVC, RestGen, PipeWriter, DirWriter,\ - DirectPaste + from py.__.test.tracer.genrest import ViewVC, RestGen, PipeWriter, \ + DirWriter, FileWriter, DirectPaste from py.__.test.tracer.tracer import DocStorage, Tracer from StringIO import StringIO @@ -14,10 +14,14 @@ except ImportError, s: py.test.skip("Cannot import: %s" % str(s)) +def setup_module(mod): + mod.temppath = py.test.ensuretemp('restgen') + def test_links(): vcview = ViewVC("http://codespeak.net/viewvc/") _, linkname = vcview.getlink(cut_pyc(__file__), 0) - assert linkname == 'http://codespeak.net/viewvc/py/test/tracer/testing/test_rest.py?view=markup' + assert linkname == ('http://codespeak.net/viewvc/py/test/tracer/' + 'testing/test_rest.py?view=markup') class SomeClass(object): def __init__(self, a): @@ -27,32 +31,37 @@ return a + b + c def fun(a, b, c): - "Some docstring" + """Some docstring + + Let's make it span a couple of lines to be interesting... + + Note: + + * rest + * should + * be + * supported + * or + * ignored... + """ return "d" def test_dir_writer(): p = StringIO() - dir = py.test.ensuretemp("dirwriter") + dir = temppath.ensure("dirwriter", dir=True) w = DirWriter(dir) - w.write_file("one", "one data") - w.write_file("two", "two data") - assert dir.join("one").read() == "one data" - assert dir.join("two").read() == "two data" + w.write_section("one", "one data") + w.write_section("two", "two data") + assert dir.join("one.txt").read() == "one data" + assert dir.join("two.txt").read() == "two data" def test_direct_link(): - assert DirectPaste().getlink(cut_pyc(__file__), 2)[0] == '""" tests document generation\n' + assert DirectPaste().getlink(cut_pyc(__file__), 2)[0] == ( + '""" tests document generation\n') # C-c C-v .... class TestRest(object): - def check_rest(self, tmpdir): - from py.__.misc import rest - for path in tmpdir.listdir('*.txt'): - rest.process(path) - - def test_generation_simple_api(self): - descs = {'SomeClass':SomeClass, 'fun':fun} - ds = DocStorage().from_dict(descs) - lg = DirectPaste() + def get_some_trace(self, ds): t = Tracer(ds) t.start_tracing() s1 = SomeClass("a") @@ -61,11 +70,56 @@ s2.method(1,2,3) fun(1, 3, s2) t.end_tracing() - tmpdir = py.test.ensuretemp("test_generation") - r = RestGen(ds, lg, DirWriter(tmpdir)) + + def check_rest(self, tempdir): + from py.__.misc import rest + for path in tempdir.listdir('*.txt'): + rest.process(path) + + def test_generation_simple_api(self): + descs = {'SomeClass':SomeClass, 'fun':fun} + ds = DocStorage().from_dict(descs) + t = self.get_some_trace(ds) + lg = DirectPaste() + tempdir = temppath.ensure("test_generation_simple_api", dir=True) + r = RestGen(ds, lg, DirWriter(tempdir)) r.write() + basenames = [p.basename for p in tempdir.listdir('*.txt')] + assert sorted(basenames) == [ + 'class_SomeClass.txt', + 'function_fun.txt', + 'index.txt', + 'method_SomeClass___init__.txt', + 'method_SomeClass_method.txt', + ] # now we check out... - self.check_rest(tmpdir) + self.check_rest(tempdir) + + def test_check_internal_links(self): + descs = {'SomeClass': SomeClass, + 'fun': fun} + ds = DocStorage().from_dict(descs) + t = self.get_some_trace(ds) + + lg = DirectPaste() + tempdir = temppath.ensure('test_generation_internal_links', dir=True) + r = RestGen(ds, lg, DirWriter(tempdir)) + r.write() + index = tempdir.join('index.txt') + assert index.check(file=True) + data = index.read() + assert data.find('.. _`fun`: function_fun.html\n') > -1 + assert data.find('.. _`fun`: #fun\n') == -1 + + tempfile = temppath.ensure('test_generation_internal_links.txt', + file=True) + r = RestGen(ds, lg, FileWriter(tempfile)) + r.write() + data = tempfile.read() + print 'data:', data + assert data.find('.. _`fun`: #function_fun\n') > -1 + assert data.find('.. _`fun`: function_fun.html') == -1 + ##def test_generation(): ## py.test.skip("WIP") From arigo at codespeak.net Wed Oct 18 22:13:19 2006 From: arigo at codespeak.net (arigo at codespeak.net) Date: Wed, 18 Oct 2006 22:13:19 +0200 (CEST) Subject: [py-svn] r33423 - py/dist/py/misc Message-ID: <20061018201319.5D52110078@code0.codespeak.net> Author: arigo Date: Wed Oct 18 22:13:18 2006 New Revision: 33423 Modified: py/dist/py/misc/error.py Log: Add a mapping for error 2 (ERROR_FILE_NOT_FOUND). For reference, 3 in ERROR_PATH_NOT_FOUND. Followed #pylib discussion with jpe. Modified: py/dist/py/misc/error.py ============================================================================== --- py/dist/py/misc/error.py (original) +++ py/dist/py/misc/error.py Wed Oct 18 22:13:18 2006 @@ -19,6 +19,7 @@ ) _winerrnomap = { + 2: errno.ENOENT, 3: errno.ENOENT, 267: errno.ENOTDIR, 5: errno.EACCES, # anything better? From guido at codespeak.net Wed Oct 18 23:11:39 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Wed, 18 Oct 2006 23:11:39 +0200 (CEST) Subject: [py-svn] r33431 - in py/dist/py/rst: . testing Message-ID: <20061018211139.7353F1007E@code0.codespeak.net> Author: guido Date: Wed Oct 18 23:11:38 2006 New Revision: 33431 Modified: py/dist/py/rst/rst.py py/dist/py/rst/testing/test_rst.py Log: Fixed whitespace stripping for paragraphs on non-string content. Modified: py/dist/py/rst/rst.py ============================================================================== --- py/dist/py/rst/rst.py (original) +++ py/dist/py/rst/rst.py Wed Oct 18 23:11:38 2006 @@ -127,6 +127,9 @@ texts.reverse() while texts: next = texts[-1] + if not next: + texts.pop() + continue if lgt + len(self.sep) + len(next) <= self.width or not buf: buf.append(next) lgt += len(next) + len(self.sep) Modified: py/dist/py/rst/testing/test_rst.py ============================================================================== --- py/dist/py/rst/testing/test_rst.py (original) +++ py/dist/py/rst/testing/test_rst.py Wed Oct 18 23:11:38 2006 @@ -78,6 +78,11 @@ ).text() assert txt == expected +def test_text_strip(): + expected = "foo\n" + txt = Rest(Paragraph(Text(' foo '))).text() + assert txt == expected + def test_list(): expected = "* a\n\n* b\n" txt = Rest(ListItem("a"), ListItem("b")).text() From guido at codespeak.net Thu Oct 19 12:05:59 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Thu, 19 Oct 2006 12:05:59 +0200 (CEST) Subject: [py-svn] r33446 - in py/dist/py/rst: . testing Message-ID: <20061019100559.769C31005A@code0.codespeak.net> Author: guido Date: Thu Oct 19 12:05:57 2006 New Revision: 33446 Modified: py/dist/py/rst/rst.py py/dist/py/rst/testing/test_rst.py Log: Newline behind links block. Modified: py/dist/py/rst/rst.py ============================================================================== --- py/dist/py/rst/rst.py (original) +++ py/dist/py/rst/rst.py Thu Oct 19 12:05:57 2006 @@ -85,7 +85,7 @@ link_texts = [] for link, target in self.links.iteritems(): link_texts.append(".. _`%s`: %s" % (link, target)) - return "\n" + "\n".join(link_texts) + "\n" + return "\n" + "\n".join(link_texts) + "\n\n" def text(self): outcome = [] Modified: py/dist/py/rst/testing/test_rst.py ============================================================================== --- py/dist/py/rst/testing/test_rst.py (original) +++ py/dist/py/rst/testing/test_rst.py Thu Oct 19 12:05:57 2006 @@ -113,3 +113,17 @@ 'purus vel arcu. Morbi aliquam lacus et urna. Donec ' 'commodo pellentesque mi.')).text() assert txt == expected + +def test_title_following_links_empty_line(): + expected = """\ +Foo, bar and `baz`_. + +.. _`baz`: http://www.baz.com + +Spam +---- + +Spam, eggs and spam. +""" + txt = Rest(Paragraph("Foo, bar and ", Link("baz", "http://www.baz.com")), + Title('Spam'), Paragraph('Spam, eggs and spam.')) From guido at codespeak.net Thu Oct 19 12:41:11 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Thu, 19 Oct 2006 12:41:11 +0200 (CEST) Subject: [py-svn] r33448 - in py/dist/py/test/tracer: . testing Message-ID: <20061019104111.7D1A210060@code0.codespeak.net> Author: guido Date: Thu Oct 19 12:41:09 2006 New Revision: 33448 Modified: py/dist/py/test/tracer/genrest.py py/dist/py/test/tracer/testing/test_rest.py Log: Made that the sections now are produced in a sensible order, fixed internal link rewriting (FileWriter), had to change rewrite_link so it gets more arguments (for building internal vs. external links, docutils builds links using the text of the titles, so that has to be known to the function to be able to generate internal links). Both changes are to allow producing a full single ReST document rather than a dir structure. Also removed commented chunks of code. Modified: py/dist/py/test/tracer/genrest.py ============================================================================== --- py/dist/py/test/tracer/genrest.py (original) +++ py/dist/py/test/tracer/genrest.py Thu Oct 19 12:41:09 2006 @@ -65,8 +65,8 @@ self.output.write("\n") self.output.write(data + "\n") - def rewrite_link(self, target): - return '%s.txt' % (target,) + def rewrite_link(self, type, targetname, targetfilename): + return '%s.txt' % (targetfilename,) class DirWriter(object): def __init__(self, directory=None): @@ -82,9 +82,9 @@ def write_index(self, data): self.write_section('index', data) - def rewrite_link(self, target): + def rewrite_link(self, type, targetname, targetfilename): # we assume the result will get converted to HTML... - return '%s.html' % (target,) + return '%s.html' % (targetfilename,) class FileWriter(object): def __init__(self, fpath): @@ -104,8 +104,13 @@ self.fp.write(data) self.fp.flush() - def rewrite_link(self, target): - return '#%s' % (target,) + _defined_targets = [] + def rewrite_link(self, type, targetname, targetbasename): + if targetname in self._defined_targets: + return None + self._defined_targets.append(targetname) + targetname = targetname.lower().replace('.', '-').replace('_', '') + return '#%s-%s' % (type, targetname) class RestGen(object): def __init__(self, ds, linkgen, writer=PipeWriter()): @@ -119,52 +124,71 @@ self.write_interface() def write_interface(self): - lst = [Title("Module: %s" % self.dsa.get_module_name(), belowchar="="), - Paragraph(self.dsa.get_module_info()), - Title("Exported functions:", belowchar="-")] - self.write_function_list(lst) - lst.append(Title("Exported classes:", belowchar="-")) - self.write_class_list(lst) - self.writer.write_index(Rest(*lst).text()) + indexlst = [Title("Module: %s" % self.dsa.get_module_name(), + belowchar="="), + Paragraph(self.dsa.get_module_info()), + Title("Exported functions:", belowchar="-")] + funclst = self.write_function_list(indexlst) + indexlst.append(Title("Exported classes:", belowchar="-")) + classlst = self.write_class_list(indexlst) + self.writer.write_index(Rest(*indexlst).text()) + for sectionname, restitems in funclst: + self.writer.write_section(sectionname, Rest(*restitems).text()) + for sectionname, classdata in classlst: + restitems, classfunclst = classdata + self.writer.write_section(sectionname, Rest(*restitems).text()) + for fsectionname, frestitems in classfunclst: + self.writer.write_section(fsectionname, + Rest(*frestitems).text()) - def write_function_list(self, lst): + def write_function_list(self, indexlst): + retlst = [] for name in self.dsa.get_function_names(): - # XXX: should be .html here? sectionname = 'function_%s' % (name,) - linktarget = self.writer.rewrite_link(sectionname) - lst.append(ListItem(Text("Function: "), - Link(name, linktarget))) - self.write_function(sectionname, name) + linktarget = self.writer.rewrite_link('function', name, + sectionname) + indexlst.append(ListItem(Text("Function: "), + Link(name, linktarget))) + retlst.append((sectionname, + self.write_function(sectionname, name))) + return retlst - def write_class_list(self, lst): + def write_class_list(self, indexlst): + retlst = [] for name in self.dsa.get_class_names(): sectionname = 'class_%s' % (name,) - linktarget = self.writer.rewrite_link(sectionname) - lst.append(ListItem(Text("Class: "), - Link(name, linktarget))) - self.write_class(sectionname, name) + linktarget = self.writer.rewrite_link('class', name, sectionname) + indexlst.append(ListItem(Text("Class: "), Link(name, linktarget))) + retlst.append((sectionname, self.write_class(sectionname, name))) + return retlst def write_class(self, section_name, class_name): - # XXX no docstring? - lst = [Title("Class: %s" % class_name, belowchar='-')] + classlst = [Title("Class: %s" % class_name, belowchar='-'), + BlockQuote(self.dsa.get_function_doc(class_name))] # write down exported methods - lst.append(Title("Exported methods:", belowchar="^")) + classlst.append(Title("Exported methods:", belowchar="^")) + funclist = [] for method in self.dsa.get_class_methods(class_name): sectionname = 'method_%s_%s' % (class_name, method) - linktarget = self.writer.rewrite_link(sectionname) - lst.append(ListItem(Link(method, linktarget))) - self.write_function(sectionname, class_name + "." + method) - - self.writer.write_section(section_name, Rest(*lst).text()) + linktext = '%s.%s' % (class_name, method) + linktarget = self.writer.rewrite_link('function', linktext, + sectionname) + classlst.append(ListItem(Link(linktext, linktarget))) + # XXX assuming a function is always part of a class section + funclist.append((sectionname, + self.write_function(sectionname, + class_name + "." + method, + '+'))) + return classlst, funclist - def write_function(self, section_name, fun_name): + def write_function(self, section_name, fun_name, belowchar='-'): # XXX I think the docstring should either be split on \n\n and cleaned # from indentation, or treated as ReST too (although this is obviously # dangerous for non-ReST docstrings)... - lst = [Title("Function: %s" % fun_name, belowchar="-"), - BlockQuote(self.dsa.get_function_doc(fun_name)), - BlockQuote(self.dsa.get_function_definition(fun_name))] + lst = [Title("Function: %s" % fun_name, belowchar=belowchar), + BlockQuote(self.dsa.get_function_doc(fun_name)), + BlockQuote(self.dsa.get_function_definition(fun_name))] lst.append(Paragraph("Function source: XXX (to write a link here)")) @@ -179,15 +203,15 @@ lst.append(arg_quote) # call sites.. - call_site_title = Title("Call sites:", belowchar="-") + call_site_title = Title("Call sites:", belowchar=belowchar) lst.append(call_site_title) call_sites = lst for call_site, frame in self.dsa.get_function_callpoints(fun_name): link_str = "File %s:%s" % (call_site.filename, - call_site.lineno) - #_, link_target = self.linkgen.getlink(call_site.filename, - # call_site.lineno) + call_site.lineno) + #link_str, link_target = self.linkgen.getlink(call_site.filename, + # call_site.lineno) #if link_target: # otherwise it's just inline text # call_sites.append(Paragraph(Link(link_str, link_target))) #else: @@ -200,59 +224,11 @@ for num, line in enumerate(source): if num == call_site.lineno - frame.code.firstlineno - 1: m = re.match("^( *)(.*)", line) - lines.append("%s >" + "-"*len(m.group(1)) + m.group(2)) + lines.append("%s >%s%s" % (num, "-" * len(m.group(1)), + m.group(2))) else: lines.append(" " + line) call_sites.append(BlockQuote("\n".join(lines))) - self.writer.write_section(section_name, Rest(*lst).text()) -##class RestGen(object): -## def __init__(self, ds, linkgen, output=sys.stdout): -## self.dsa = DocStorageAccessor(ds) -## self.linkgen = linkgen -## self.output = output -## -## def add(self, item): -## self.list.append(item) -## -## def write(self): -## self.list = [] -## self.add(Title("Module: %s" % self.dsa.get_module_name(), belowchar="=")) -## self.add(Paragraph(Text(self.dsa.get_module_info()))) -## self.write_functions() -## self.write_classes() -## -## def write_functions(self): -## self.add(Title("Exported functions:", belowchar="-")) -## for key in self.dsa.get_function_names(): -## 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])) -## 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): -## link_str = "File %s:%s" % (call_site.filename, -## call_site.lineno) -## link_target = self.linkgen.getlink(call_site.filename, -## call_site.lineno) -## if link_target: # otherwise it's just inline text -## call_sites.append(Paragraph(Link(link_str, link_target))) -## else: -## call_sites.append(Paragraph(link_str)) -## 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()) -## -## def write_classes(self): -## self.add(Title("Exported classes:", belowchar="-")) - + return lst + Modified: py/dist/py/test/tracer/testing/test_rest.py ============================================================================== --- py/dist/py/test/tracer/testing/test_rest.py (original) +++ py/dist/py/test/tracer/testing/test_rest.py Thu Oct 19 12:41:09 2006 @@ -24,12 +24,18 @@ 'testing/test_rest.py?view=markup') class SomeClass(object): + """Some class definition""" + def __init__(self, a): self.a = a def method(self, a, b, c): + """method docstring""" return a + b + c +class SomeSubClass(SomeClass): + """Some subclass definition""" + def fun(a, b, c): """Some docstring @@ -61,7 +67,11 @@ # C-c C-v .... class TestRest(object): - def get_some_trace(self, ds): + def get_filled_docstorage(self): + descs = {'SomeClass': SomeClass, + 'SomeSubClass': SomeSubClass, + 'fun':fun} + ds = DocStorage().from_dict(descs) t = Tracer(ds) t.start_tracing() s1 = SomeClass("a") @@ -70,6 +80,7 @@ s2.method(1,2,3) fun(1, 3, s2) t.end_tracing() + return ds def check_rest(self, tempdir): from py.__.misc import rest @@ -77,49 +88,63 @@ rest.process(path) def test_generation_simple_api(self): - descs = {'SomeClass':SomeClass, 'fun':fun} - ds = DocStorage().from_dict(descs) - t = self.get_some_trace(ds) + ds = self.get_filled_docstorage() lg = DirectPaste() - tempdir = temppath.ensure("test_generation_simple_api", dir=True) + tempdir = temppath.ensure("simple_api", dir=True) r = RestGen(ds, lg, DirWriter(tempdir)) r.write() basenames = [p.basename for p in tempdir.listdir('*.txt')] assert sorted(basenames) == [ 'class_SomeClass.txt', + 'class_SomeSubClass.txt', 'function_fun.txt', 'index.txt', 'method_SomeClass___init__.txt', 'method_SomeClass_method.txt', + 'method_SomeSubClass___init__.txt', + 'method_SomeSubClass_method.txt', ] # now we check out... self.check_rest(tempdir) def test_check_internal_links(self): - descs = {'SomeClass': SomeClass, - 'fun': fun} - ds = DocStorage().from_dict(descs) - t = self.get_some_trace(ds) - + ds = self.get_filled_docstorage() lg = DirectPaste() - tempdir = temppath.ensure('test_generation_internal_links', dir=True) + tempdir = temppath.ensure('internal_links', dir=True) r = RestGen(ds, lg, DirWriter(tempdir)) r.write() index = tempdir.join('index.txt') assert index.check(file=True) data = index.read() assert data.find('.. _`fun`: function_fun.html\n') > -1 - assert data.find('.. _`fun`: #fun\n') == -1 + assert data.find('.. _`fun`: #function-fun\n') == -1 - tempfile = temppath.ensure('test_generation_internal_links.txt', + tempfile = temppath.ensure('internal_links.txt', file=True) r = RestGen(ds, lg, FileWriter(tempfile)) r.write() data = tempfile.read() - print 'data:', data - assert data.find('.. _`fun`: #function_fun\n') > -1 + assert data.find('.. _`fun`: #function-fun\n') > -1 assert data.find('.. _`fun`: function_fun.html') == -1 + def test_check_section_order(self): + # we use the previous method's data + tempfile = temppath.join('internal_links.txt') + data = tempfile.read() + # index should be above the rest + assert data.find('Exported classes:') > -1 + assert data.find('Exported classes:') < data.find('Function: fun') + assert data.find('Exported classes:') < data.find('Class: SomeClass') + # function definitions should be above class ones + assert data.find('Function: fun') < data.find('Class: SomeClass') + # class method definitions should be below the class defs + assert data.find('Class: SomeClass') < data.find( + 'Function: SomeClass.method') + # __init__ should be above other methods + assert data.find('Function: SomeClass.__init__') > -1 + # XXX in the future... + # assert data.find('Function: SomeClass.__init__') < data.find( + # 'Function: SomeClass.method') ##def test_generation(): ## py.test.skip("WIP") From guido at codespeak.net Thu Oct 19 12:47:02 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Thu, 19 Oct 2006 12:47:02 +0200 (CEST) Subject: [py-svn] r33449 - py/dist/py/test/tracer Message-ID: <20061019104702.BC58610060@code0.codespeak.net> Author: guido Date: Thu Oct 19 12:47:00 2006 New Revision: 33449 Modified: py/dist/py/test/tracer/genrest.py Log: Removing 'write_index' methods from writers, they were a hack to make the index appear on top of the document which isn't required anymore (since the order of the write_section calls is improved so that the index is written first). Modified: py/dist/py/test/tracer/genrest.py ============================================================================== --- py/dist/py/test/tracer/genrest.py (original) +++ py/dist/py/test/tracer/genrest.py Thu Oct 19 12:47:00 2006 @@ -55,9 +55,6 @@ def __init__(self, output=sys.stdout): self.output = output - def write_index(self, data): - self.write_section('index', data) - def write_section(self, name, data): text = "Written file: %s.txt" % (name,) self.output.write(text + "\n") @@ -79,9 +76,6 @@ filename = '%s.txt' % (name,) self.directory.ensure(filename).write(data) - def write_index(self, data): - self.write_section('index', data) - def rewrite_link(self, type, targetname, targetfilename): # we assume the result will get converted to HTML... return '%s.html' % (targetfilename,) @@ -95,15 +89,6 @@ self.fp.write(data) self.fp.flush() - def write_index(self, data): - self.fp.seek(0) - curdata = self.fp.read() - data += curdata - self.fp.seek(0) - self.fp.truncate(0) - self.fp.write(data) - self.fp.flush() - _defined_targets = [] def rewrite_link(self, type, targetname, targetbasename): if targetname in self._defined_targets: @@ -131,7 +116,7 @@ funclst = self.write_function_list(indexlst) indexlst.append(Title("Exported classes:", belowchar="-")) classlst = self.write_class_list(indexlst) - self.writer.write_index(Rest(*indexlst).text()) + self.writer.write_section('index', Rest(*indexlst).text()) for sectionname, restitems in funclst: self.writer.write_section(sectionname, Rest(*restitems).text()) for sectionname, classdata in classlst: From guido at codespeak.net Thu Oct 19 16:20:11 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Thu, 19 Oct 2006 16:20:11 +0200 (CEST) Subject: [py-svn] r33460 - in py/dist/py/test/tracer: . testing Message-ID: <20061019142011.75BEB10074@code0.codespeak.net> Author: guido Date: Thu Oct 19 16:20:08 2006 New Revision: 33460 Modified: py/dist/py/test/tracer/genrest.py py/dist/py/test/tracer/testing/test_rest.py Log: Removed the 'write_interface()' method as there was no need for another layer of abstraction there. Enabled linking to files and refined link writers. Some rendering details (header sizes, etc.) and cleanups (removed more commented code, etc.). Modified: py/dist/py/test/tracer/genrest.py ============================================================================== --- py/dist/py/test/tracer/genrest.py (original) +++ py/dist/py/test/tracer/genrest.py Thu Oct 19 16:20:08 2006 @@ -49,20 +49,26 @@ """ No-link writer (inliner) """ def getlink(self, filename, lineno): - return (py.path.local(filename).open().readlines()[lineno-1], "") + return ('%s:%s' % (filename, lineno), "") + +class DirectFS(AbstractLinkWriter): + """ Creates links to the files on the file system (for local docs) + """ + def getlink(self, filename, lineno): + return ('%s:%s' % (filename, lineno), 'file://%s' % (filename,)) class PipeWriter(object): def __init__(self, output=sys.stdout): self.output = output def write_section(self, name, data): - text = "Written file: %s.txt" % (name,) + text = "Contents of file %s.txt:" % (name,) self.output.write(text + "\n") self.output.write("=" * len(text) + "\n") self.output.write("\n") self.output.write(data + "\n") - def rewrite_link(self, type, targetname, targetfilename): + def getlink(self, type, targetname, targetfilename): return '%s.txt' % (targetfilename,) class DirWriter(object): @@ -76,7 +82,7 @@ filename = '%s.txt' % (name,) self.directory.ensure(filename).write(data) - def rewrite_link(self, type, targetname, targetfilename): + def getlink(self, type, targetname, targetfilename): # we assume the result will get converted to HTML... return '%s.html' % (targetfilename,) @@ -84,13 +90,16 @@ def __init__(self, fpath): self.fpath = fpath self.fp = fpath.open('w+') + self._defined_targets = [] def write_section(self, name, data): self.fp.write(data) self.fp.flush() - _defined_targets = [] - def rewrite_link(self, type, targetname, targetbasename): + def getlink(self, type, targetname, targetbasename): + # XXX problem: because of docutils' named anchor generation scheme, + # a method Foo.__init__ would clash with Foo.init (underscores are + # removed) if targetname in self._defined_targets: return None self._defined_targets.append(targetname) @@ -104,11 +113,11 @@ self.writer = writer def write(self): - # first write down a file with all the exported interfaces, - # sorted by type - self.write_interface() - - def write_interface(self): + """write the data to the writer""" + # note that this builds up a list of Rest elements for the index as + # 'side effect', the list is passed along and filled, while the actual + # sections (also ReST elements) are returned by the write_* methods + # XXX this is quite icky! would be nice to have refactored indexlst = [Title("Module: %s" % self.dsa.get_module_name(), belowchar="="), Paragraph(self.dsa.get_module_info()), @@ -130,7 +139,7 @@ retlst = [] for name in self.dsa.get_function_names(): sectionname = 'function_%s' % (name,) - linktarget = self.writer.rewrite_link('function', name, + linktarget = self.writer.getlink('function', name, sectionname) indexlst.append(ListItem(Text("Function: "), Link(name, linktarget))) @@ -142,7 +151,7 @@ retlst = [] for name in self.dsa.get_class_names(): sectionname = 'class_%s' % (name,) - linktarget = self.writer.rewrite_link('class', name, sectionname) + linktarget = self.writer.getlink('class', name, sectionname) indexlst.append(ListItem(Text("Class: "), Link(name, linktarget))) retlst.append((sectionname, self.write_class(sectionname, name))) return retlst @@ -157,14 +166,14 @@ for method in self.dsa.get_class_methods(class_name): sectionname = 'method_%s_%s' % (class_name, method) linktext = '%s.%s' % (class_name, method) - linktarget = self.writer.rewrite_link('function', linktext, + linktarget = self.writer.getlink('function', linktext, sectionname) classlst.append(ListItem(Link(linktext, linktarget))) # XXX assuming a function is always part of a class section funclist.append((sectionname, self.write_function(sectionname, class_name + "." + method, - '+'))) + '^'))) return classlst, funclist def write_function(self, section_name, fun_name, belowchar='-'): @@ -175,7 +184,14 @@ BlockQuote(self.dsa.get_function_doc(fun_name)), BlockQuote(self.dsa.get_function_definition(fun_name))] - lst.append(Paragraph("Function source: XXX (to write a link here)")) + # XXX missing implementation of dsa.get_function_location() + #filename, lineno = self.dsa.get_function_location(fun_name) + #linkname, linktarget = self.linkgen.getlink(filename, lineno) + #if linktarget: + # lst.append(Paragraph("Function source: ", + # Link(linkname, linktarget))) + #else: + lst.append(Paragraph('Function source: ')) args, retval = self.dsa.get_function_signature(fun_name) # XXX: we just do "knowntype" here, but we should @@ -188,29 +204,28 @@ lst.append(arg_quote) # call sites.. - call_site_title = Title("Call sites:", belowchar=belowchar) + call_site_title = Title("Call sites:", belowchar='^') lst.append(call_site_title) call_sites = lst for call_site, frame in self.dsa.get_function_callpoints(fun_name): link_str = "File %s:%s" % (call_site.filename, call_site.lineno) - #link_str, link_target = self.linkgen.getlink(call_site.filename, - # call_site.lineno) - #if link_target: # otherwise it's just inline text - # call_sites.append(Paragraph(Link(link_str, link_target))) - #else: - # call_sites.append(Paragraph(link_str)) + link_str, link_target = self.linkgen.getlink(call_site.filename, + call_site.lineno) + if link_target: # otherwise it's just inline text + call_sites.append(Paragraph(Link(link_str, link_target))) + else: + call_sites.append(Paragraph(link_str)) #call_sites.append(BlockQuote(call_site.source)) # XXX: For now, we just paste here the filename of that - call_sites.append(Paragraph(link_str)) + #call_sites.append(Paragraph(link_str)) source = frame.code.source() lines = [] for num, line in enumerate(source): if num == call_site.lineno - frame.code.firstlineno - 1: m = re.match("^( *)(.*)", line) - lines.append("%s >%s%s" % (num, "-" * len(m.group(1)), - m.group(2))) + lines.append(">%s%s" % ("-" * len(m.group(1)), m.group(2))) else: lines.append(" " + line) call_sites.append(BlockQuote("\n".join(lines))) Modified: py/dist/py/test/tracer/testing/test_rest.py ============================================================================== --- py/dist/py/test/tracer/testing/test_rest.py (original) +++ py/dist/py/test/tracer/testing/test_rest.py Thu Oct 19 16:20:08 2006 @@ -3,13 +3,14 @@ """ import py +from StringIO import StringIO try: from py.__.test.tracer.genrest import ViewVC, RestGen, PipeWriter, \ - DirWriter, FileWriter, DirectPaste + DirWriter, FileWriter, \ + DirectPaste, DirectFS from py.__.test.tracer.tracer import DocStorage, Tracer - from StringIO import StringIO from py.__.test.tracer.testing.runtest import cut_pyc except ImportError, s: py.test.skip("Cannot import: %s" % str(s)) @@ -17,12 +18,6 @@ def setup_module(mod): mod.temppath = py.test.ensuretemp('restgen') -def test_links(): - vcview = ViewVC("http://codespeak.net/viewvc/") - _, linkname = vcview.getlink(cut_pyc(__file__), 0) - assert linkname == ('http://codespeak.net/viewvc/py/test/tracer/' - 'testing/test_rest.py?view=markup') - class SomeClass(object): """Some class definition""" @@ -52,19 +47,75 @@ """ return "d" -def test_dir_writer(): - p = StringIO() - dir = temppath.ensure("dirwriter", dir=True) - w = DirWriter(dir) - w.write_section("one", "one data") - w.write_section("two", "two data") - assert dir.join("one.txt").read() == "one data" - assert dir.join("two.txt").read() == "two data" - def test_direct_link(): - assert DirectPaste().getlink(cut_pyc(__file__), 2)[0] == ( - '""" tests document generation\n') - # C-c C-v .... + fname = cut_pyc(__file__) + title, link = DirectPaste().getlink(fname, 2) + assert title == '%s:%s' % (fname, 2) + assert link == '' + +def test_viewvc_link(): + vcview = ViewVC("http://codespeak.net/viewvc/") + fname = cut_pyc(__file__) + title, link = vcview.getlink(fname, 0) + assert title == '%s:%s' % (fname, 0) + assert link == ('http://codespeak.net/viewvc/py/test/tracer/' + 'testing/test_rest.py?view=markup') + +def test_fs_link(): + title, link = DirectFS().getlink('/foo/bar/baz.py', 100) + assert title == '/foo/bar/baz.py:100' + assert link == 'file:///foo/bar/baz.py' + +class WriterTest(object): + def get_filled_writer(self, writerclass, *args, **kwargs): + dw = writerclass(*args, **kwargs) + dw.write_section('foo', 'foo data') + dw.write_section('bar', 'bar data') + return dw + +class TestDirWriter(WriterTest): + def test_write_section(self): + tempdir = temppath.ensure('dirwriter', dir=True) + dw = self.get_filled_writer(DirWriter, tempdir) + fpaths = tempdir.listdir('*.txt') + assert len(fpaths) == 2 + assert sorted([f.basename for f in fpaths]) == ['bar.txt', 'foo.txt'] + assert tempdir.join('foo.txt').read() == 'foo data' + assert tempdir.join('bar.txt').read() == 'bar data' + + def test_getlink(self): + dw = DirWriter(temppath.join('dirwriter_getlink')) + link = dw.getlink('function', 'Foo.bar', 'method_foo_bar') + assert link == 'method_foo_bar.html' + +class TestFileWriter(WriterTest): + def test_write_section(self): + tempfile = temppath.ensure('filewriter', file=True) + fw = self.get_filled_writer(FileWriter, tempfile) + data = tempfile.read() + assert len(data) + + def test_getlink(self): + fw = FileWriter(temppath.join('filewriter_getlink')) + link = fw.getlink('function', 'Foo.bar', 'method_foo_bar') + assert link == '#function-foo-bar' + # only produce the same link target once... + link = fw.getlink('function', 'Foo.bar', 'method_foo_bar') + assert link is None + link = fw.getlink('function', 'Foo.__init__', 'method_foo___init__') + assert link == '#function-foo-init' + +class TestPipeWriter(WriterTest): + def test_write_section(self): + s = StringIO() + pw = self.get_filled_writer(PipeWriter, s) + data = s.getvalue() + assert len(data) + + def test_getlink(self): + pw = PipeWriter(StringIO()) + link = pw.getlink('function', 'Foo.bar', 'method_foo_bar') + assert link == 'method_foo_bar.txt' class TestRest(object): def get_filled_docstorage(self): @@ -76,7 +127,7 @@ t.start_tracing() s1 = SomeClass("a") fun(1, 2, s1) - s2 = SomeClass("b") + s2 = SomeSubClass("b") s2.method(1,2,3) fun(1, 3, s2) t.end_tracing() @@ -109,7 +160,7 @@ def test_check_internal_links(self): ds = self.get_filled_docstorage() - lg = DirectPaste() + lg = DirectFS() tempdir = temppath.ensure('internal_links', dir=True) r = RestGen(ds, lg, DirWriter(tempdir)) r.write() @@ -130,6 +181,8 @@ def test_check_section_order(self): # we use the previous method's data tempfile = temppath.join('internal_links.txt') + if not tempfile.check(): + py.test.skip('depends on previous test, which failed') data = tempfile.read() # index should be above the rest assert data.find('Exported classes:') > -1 @@ -146,20 +199,3 @@ # assert data.find('Function: SomeClass.__init__') < data.find( # 'Function: SomeClass.method') -##def test_generation(): -## py.test.skip("WIP") -## descs = {"fun":fun} -## ds = DocStorage().from_dict(descs) -## lg = ViewVC("http://codespeak.net/viewvc/") -## t = Tracer(ds) -## t.start_tracing() -## fun(1, ("g", 3), 8) -## fun(2., ("a", 1.), "a") -## t.end_tracing() -## s = StringIO() -## r = RestGen(ds, lg, output=s) -## r.write() -## # we cannot check the source, cause the rapidly changing -## # visual effect will break it, so we'll make assertion later -## # XXX: do that -## assert s.getvalue() From fijal at codespeak.net Thu Oct 19 16:31:32 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Thu, 19 Oct 2006 16:31:32 +0200 (CEST) Subject: [py-svn] r33461 - py/dist/py/documentation/apigen Message-ID: <20061019143132.86F9510074@code0.codespeak.net> Author: fijal Date: Thu Oct 19 16:28:36 2006 New Revision: 33461 Added: py/dist/py/documentation/apigen/first-implementation.txt (contents, props changed) Log: Added some notes. Added: py/dist/py/documentation/apigen/first-implementation.txt ============================================================================== --- (empty file) +++ py/dist/py/documentation/apigen/first-implementation.txt Thu Oct 19 16:28:36 2006 @@ -0,0 +1,34 @@ + +Automatic API documentation generation - first outcome +====================================================== + +After the first implementation of API generation tool, +I've realised that there are several shortcoming in current +attempt. + +First of all, we need to define our type system. The one presented +in PyPy is just to weak to our purpose. This will not be very easy +issue anyway. Basic ideas are very much like the PyPy one, but +from the beggining we want to support all rich python typesystem, not +only the subset of it. So we need to provide informations which are +valuable for the end user (quite rich type system) and can always +work. I don't think that actuall tracking of all possible values of +objects makes sense. User might see them in call sites if he really wants +to. + +Second thing is that we need some kind of structure (we do lack such +attempt now), which can group several classes/functions/objects into +a module (split by '.' or whatever). + +Another thing is that we need to support any possible object which +are actually exported (well, we might assume that objects which +are exported are to some extend constants). + +We need to track somehow several objects, which are not entirely +Python user-build objects. This means probably: builtin functions, +classes with builtin __init__, etc. etc. + +And as well implement stuff like c_call, c_return and such. + +I guess that keeping track of side effects might happen at some point +in the future, but it's not *now*. From guido at codespeak.net Thu Oct 19 17:49:56 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Thu, 19 Oct 2006 17:49:56 +0200 (CEST) Subject: [py-svn] r33467 - in py/dist/py/rst: . testing Message-ID: <20061019154956.70CDF10076@code0.codespeak.net> Author: guido Date: Thu Oct 19 17:49:54 2006 New Revision: 33467 Modified: py/dist/py/rst/rst.py py/dist/py/rst/testing/test_rst.py Log: Added some tests, removed some ugly hack (previous_paragraph stuff) to have the blockquote :: right behind the previous item, in favour of having a less nice looking result (\n\n:). Modified: py/dist/py/rst/rst.py ============================================================================== --- py/dist/py/rst/rst.py (original) +++ py/dist/py/rst/rst.py Thu Oct 19 17:49:54 2006 @@ -90,8 +90,6 @@ def text(self): outcome = [] for child in self.childs: - if child.previous_paragraph and len(outcome): - outcome[-1] += child.previous_paragraph outcome.append(child.text()) text = self.sep.join(outcome) + "\n" # trailing newline @@ -102,7 +100,6 @@ sep = " " indent = "" width = 80 - previous_paragraph = "" def __init__(self, *args, **kwargs): # make shortcut @@ -146,19 +143,17 @@ class BlockQuote(Paragraph): indent = " " - previous_paragraph = "\n::" sep = "" def text(self): all_txt = AbstractNode.text(self) all_txts = all_txt.split("\n") - return '%s\n' % ("\n".join([self.indent + i for i in all_txts]),) + return '::\n\n%s' % ("\n".join([self.indent + i for i in all_txts]),) class Title(Paragraph): parentclass = Rest belowchar = "" abovechar = "" - previous_paragraph = None def text(self): txt = Paragraph.text(self) Modified: py/dist/py/rst/testing/test_rst.py ============================================================================== --- py/dist/py/rst/testing/test_rst.py (original) +++ py/dist/py/rst/testing/test_rst.py Thu Oct 19 17:49:54 2006 @@ -4,37 +4,84 @@ from py.__.rst.rst import * -def test_textgen(): - assert Rest(Paragraph(Text("dupa"))).text() == "dupa\n" - assert Rest(Paragraph(Text("dupa"), Text("dupa"))).text() == "dupa dupa\n" - assert Rest(Paragraph(Text("a")), Paragraph(Text("b"))).text() == "a\n\nb\n" - assert Rest(Paragraph(Text("a"), indent=" ")).text() == " a\n" - -def test_join(): - txt = Rest(Paragraph(Text("a b c d e f"), width=3, indent=" ")).text() - assert txt == ' a\n b\n c\n d\n e\n f\n' +def test_illegal_parent(): + Rest(Paragraph(Text('spam'))) + py.test.raises(RestError, 'Rest(Text("spam"))') + py.test.raises(RestError, 'ListItem(Paragraph(Text("eggs")))') + +def test_text_basic(): + assert Text("dupa").text() == "dupa" + +def test_text_join(): + assert Paragraph(Text("dupa"), Text("dupa")).text() == "dupa dupa" + +def test_paragraph_basic(): + assert Paragraph(Text('spam')).text() == 'spam' + +def test_paragraph_string(): + assert Paragraph("eggs").text() == "eggs" + +def test_paragraph_join(): + assert Rest(Paragraph(Text("a")), Paragraph(Text("b"))).text() == ( + "a\n\nb\n") + +def test_paragraph_indent(): + assert Paragraph(Text("a"), indent=" ").text() == " a" + assert Paragraph(Text(" a "), indent=" ").text() == " a" + +def test_paragraph_width(): + txt = Paragraph(Text("a b c d e f"), width=3, indent=" ").text() + assert txt == ' a\n b\n c\n d\n e\n f' + text = """ +Lorem ipsum dolor sit amet, consectetuer +adipiscing elit. Vestibulum malesuada +eleifend leo. Sed faucibus commodo libero. +Mauris elementum fringilla velit. Ut +sem urna, aliquet sed, molestie at, viverra +id, justo. In ornare lacinia turpis. Etiam +et ipsum. Quisque at lacus. Etiam +pellentesque, enim porta pulvinar viverra, +libero elit iaculis justo, vitae convallis +pede purus vel arcu. Morbi aliquam lacus +et urna. Donec commodo pellentesque mi. +""" + expected = """\ +Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Vestibulum malesuada +eleifend leo. Sed faucibus commodo libero. Mauris elementum fringilla velit. Ut +sem urna, aliquet sed, molestie at, viverra id, justo. In ornare lacinia +turpis. Etiam et ipsum. Quisque at lacus. Etiam pellentesque, enim porta +pulvinar viverra, libero elit iaculis justo, vitae convallis pede purus vel +arcu. Morbi aliquam lacus et urna. Donec commodo pellentesque mi.""" + ret = Paragraph(text, width=80).text() + print repr(ret) + print repr(expected) + assert ret == expected + +def test_paragraph_stripping(): + txt = Paragraph(Text('\n foo bar\t')).text() + assert txt == 'foo bar' def test_blockquote(): expected = """\ Text + :: def fun(): some - Paragraph """ txt = Rest(Paragraph("Text"), BlockQuote("def fun():\n some"), \ - Paragraph("Paragraph")).text() + Paragraph("Paragraph")).text() print repr(txt) assert txt == expected def test_title(): - assert Rest(Title(Text("Some title"), belowchar="=")).text() == \ - "Some title\n==========\n" - assert Rest(Title(Text("Some title"), belowchar="#", abovechar="#")).text() \ - == "##########\nSome title\n##########\n" + txt = Title(Text("Some title"), belowchar="=").text() + assert txt == "Some title\n==========" + txt = Title(Text("Some title"), belowchar="#", abovechar="#").text() + assert txt == "##########\nSome title\n##########" def test_link(): expected = "`some link`_\n.. _`some link`: http://codespeak.net\n" From hpk at codespeak.net Thu Oct 19 20:02:08 2006 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 19 Oct 2006 20:02:08 +0200 (CEST) Subject: [py-svn] r33475 - py/dist/py/documentation Message-ID: <20061019180208.B687B1006F@code0.codespeak.net> Author: hpk Date: Thu Oct 19 20:02:07 2006 New Revision: 33475 Added: py/dist/py/documentation/maciej-implementation-notes.txt - copied unchanged from r33387, py/dist/py/documentation/roadmap.txt Removed: py/dist/py/documentation/roadmap.txt Log: i think this roadmap proposal is more a collection of implementation notes, sorry. Deleted: /py/dist/py/documentation/roadmap.txt ============================================================================== --- /py/dist/py/documentation/roadmap.txt Thu Oct 19 20:02:07 2006 +++ (empty file) @@ -1,99 +0,0 @@ - -Pylib roadmap, from my point of view -==================================== - -Author: Maciek Fijalkowski --------------------------- - -Date: 7.X.2006 - -Rough specifications of what needs to be done regarding py.test -(especially distributed version), py.api and others. - -So main targets (for near future) of pylib developement would be: - -* further py.test distributed developement, especially incorporating - some command line options (I think -x and -k are done). This might be: - - - DONE options transfer to clients (maybe whole conftest) to allow them - to perform special things. - - -s (how???) --view for pypy, etc. - - screen/--pdb (there is some work done in this area) - - If someone (merlinux?) set up test farm (few machines connected - together for running tests), provide convenient interface for - running at least nightly tests (web one? offline html?) or even - just running tests for branch, trunk, custom svn without need to - upload local changes every time (RSync daemon?). I'm not sure if - it does make any sense, but makes convinient if we provide - front machine with access, not all the nodes (XXX and what with - screen?) - - Make sure web frontend works when more than one client (webbrowser) - connects to it (actually it does not). - - XXX: anything else? - -* integration from py.test distributed into normal py.test, especially - cool reporting features as well as web frontend (and extend it a bit) - -* integrate some kind of TestRunner, which will run the tests and perform - some additional stuff. This is related to (but not limited): - - - benchmarks - - - py.api integration - -PY.API: -------- - -(actually lying down in py.test.tracer in branch, maybe not -too nice name for that, will go to py.api). - -So, todo list is (in random order): - -* Make interface for every possible single item on the end (function, - method, generator) to provide better coverage. Function/method DONE. - -* Make more... (frontend for API extraction, backend for output - generation, different source link generators, etc. etc.) - -* Provide some model of determining which methods are exposed as API - and which are internal (just called directly in unittest looks like - enough for now, altough we do not have method of determining that yet) - -* Provide some command line debugger-like tool (PDB hook?) which will expose - API information additionally to debugging info. - -* Make clean vision of pypy annotation typesystem, probably even - extend it a bit (it's still easier to use than providing all - the interface on our own). Possible extensions: - - - Provide SomeUnion instead of SomeObject which will behave like - gathering all possibilities together (this might be beneficient to - pypy as well). - - Provide some convinient way of getting common interface out of - objects that are contained in SomeObject. Like "what are the - common methods of both, even if they do not share a baseclass". - In this place, it should be quite easy to add different type - joining functions by user, because different projects might have - different needs at this point. - -* Test it on generating pylib documentation. - -* Make some API definition tool (object format) for defining API in - pypy. This will make a lot easier annotating the pypy source with - info (like "what the heck type comes here as an argument"....) - -* Add several different things to trace, mostly tracing of side effects - (track what method can change what attributes, as well as in child nodes. - Maybe global variables as well). - -Timeline: ---------- - -This is quite huge wishlist.... I don't know exactly what are priorities -on all the stuff, what I would go first is to make API tool usable and -easy to use as soon as possible, to show it at HHU on sprint in some -running fashion. It should be well tested and maybe not containing -all-the-possible features, but runnable. - -Next thing to do is to extend py.test distributed, especially with -screen functionality. From guido at codespeak.net Thu Oct 19 22:51:29 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Thu, 19 Oct 2006 22:51:29 +0200 (CEST) Subject: [py-svn] r33483 - in py/dist/py: rst rst/testing test/tracer Message-ID: <20061019205129.EC80910068@code0.codespeak.net> Author: guido Date: Thu Oct 19 22:51:27 2006 New Revision: 33483 Modified: py/dist/py/rst/rst.py py/dist/py/rst/testing/test_rst.py py/dist/py/test/tracer/genrest.py Log: Renamed 'BlockQuote' to 'LiteralBlock' (since blockquote in ReST has a different meaning), added some basic inline markup items, added escaping of markup characters. Modified: py/dist/py/rst/rst.py ============================================================================== --- py/dist/py/rst/rst.py (original) +++ py/dist/py/rst/rst.py Thu Oct 19 22:51:27 2006 @@ -6,6 +6,12 @@ import py +def escape(txt): + """escape ReST markup""" + for c in '*`[|': + txt = txt.replace(c, '\\%s' % (c,)) + return txt + class RestError(Exception): pass @@ -51,7 +57,7 @@ def add(self, child): self._add(child) - return child + return child def _add(self, child): if child.__class__ not in self.allowed_child: @@ -84,7 +90,7 @@ return "" link_texts = [] for link, target in self.links.iteritems(): - link_texts.append(".. _`%s`: %s" % (link, target)) + link_texts.append(".. _`%s`: %s" % (escape(link), escape(target))) return "\n" + "\n".join(link_texts) + "\n\n" def text(self): @@ -141,14 +147,15 @@ class SubParagraph(Paragraph): indent = " " -class BlockQuote(Paragraph): +class LiteralBlock(Paragraph): indent = " " sep = "" def text(self): all_txt = AbstractNode.text(self) all_txts = all_txt.split("\n") - return '::\n\n%s' % ("\n".join([self.indent + i for i in all_txts]),) + return '::\n\n%s' % ("\n".join([self.indent + i for i in + all_txts]),) class Title(Paragraph): parentclass = Rest @@ -173,27 +180,51 @@ self._text = _text def text(self): - return self.start + self._text + self.end + return self.start + escape(self._text) + self.end class Text(AbstractText): _reg_whitespace = py.std.re.compile('\s+') def wordlist(self): - return self._reg_whitespace.split(self._text) + return self._reg_whitespace.split(escape(self._text)) -class Emph(AbstractText): +class Em(AbstractText): start = "*" end = "*" +class Strong(AbstractText): + start = "**" + end = "**" + +class Quote(AbstractText): + start = '``' + end = '``' + +class Anchor(AbstractText): + start = '_`' + end = '`' + +class Footnote(AbstractText): + def __init__(self, note, symbol=False): + raise NotImplemented('XXX') + +class Citation(AbstractText): + def __init__(self, text, cite): + raise NotImplemented('XXX') + class ListItem(Paragraph): item_char = "*" def text(self): - self.indent = self.indent + " " + oldindent = self.indent + self.indent = oldindent + ' ' txt = Paragraph.text(self) txt = self.item_char + txt[1:] - del self.indent # XXX ? + self.indent = oldindent return txt +class OrderedListItem(ListItem): + item_char = "#" + class Link(AbstractText): start = '`' end = '`_' @@ -216,6 +247,7 @@ next = next.parent return next -class Quote(AbstractText): - start = '``' - end = '``' +class Substitution(AbstractText): + def __init__(self, text, **kwargs): + raise NotImplemented('XXX') + Modified: py/dist/py/rst/testing/test_rst.py ============================================================================== --- py/dist/py/rst/testing/test_rst.py (original) +++ py/dist/py/rst/testing/test_rst.py Thu Oct 19 22:51:27 2006 @@ -72,7 +72,7 @@ Paragraph """ - txt = Rest(Paragraph("Text"), BlockQuote("def fun():\n some"), \ + txt = Rest(Paragraph("Text"), LiteralBlock("def fun():\n some"), \ Paragraph("Paragraph")).text() print repr(txt) assert txt == expected @@ -174,3 +174,18 @@ """ txt = Rest(Paragraph("Foo, bar and ", Link("baz", "http://www.baz.com")), Title('Spam'), Paragraph('Spam, eggs and spam.')) + +def test_basic_inline(): + txt = Em('foo').text() + assert txt == '*foo*' + txt = Strong('bar').text() + assert txt == '**bar**' + +def test_escape(): + txt = Paragraph('*escape* ``test``').text() + assert txt == '\\*escape\\* \\`\\`test\\`\\`' + txt = Strong('*strong*').text() + assert txt == '**\\*strong\\***' + txt = Rest(Paragraph(Link('foo[bar]', 'foo[bar]'))).text() + assert txt == "`foo\\[bar]`_\n\n.. _`foo\\[bar]`: foo\\[bar]\n\n" + Modified: py/dist/py/test/tracer/genrest.py ============================================================================== --- py/dist/py/test/tracer/genrest.py (original) +++ py/dist/py/test/tracer/genrest.py Thu Oct 19 22:51:27 2006 @@ -158,7 +158,7 @@ def write_class(self, section_name, class_name): classlst = [Title("Class: %s" % class_name, belowchar='-'), - BlockQuote(self.dsa.get_function_doc(class_name))] + LiteralBlock(self.dsa.get_function_doc(class_name))] # write down exported methods classlst.append(Title("Exported methods:", belowchar="^")) @@ -181,8 +181,8 @@ # from indentation, or treated as ReST too (although this is obviously # dangerous for non-ReST docstrings)... lst = [Title("Function: %s" % fun_name, belowchar=belowchar), - BlockQuote(self.dsa.get_function_doc(fun_name)), - BlockQuote(self.dsa.get_function_definition(fun_name))] + LiteralBlock(self.dsa.get_function_doc(fun_name)), + LiteralBlock(self.dsa.get_function_definition(fun_name))] # XXX missing implementation of dsa.get_function_location() #filename, lineno = self.dsa.get_function_location(fun_name) @@ -217,7 +217,7 @@ call_sites.append(Paragraph(Link(link_str, link_target))) else: call_sites.append(Paragraph(link_str)) - #call_sites.append(BlockQuote(call_site.source)) + #call_sites.append(LiteralBlock(call_site.source)) # XXX: For now, we just paste here the filename of that #call_sites.append(Paragraph(link_str)) source = frame.code.source() @@ -228,7 +228,7 @@ lines.append(">%s%s" % ("-" * len(m.group(1)), m.group(2))) else: lines.append(" " + line) - call_sites.append(BlockQuote("\n".join(lines))) + call_sites.append(LiteralBlock("\n".join(lines))) return lst From guido at codespeak.net Fri Oct 20 13:35:45 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Fri, 20 Oct 2006 13:35:45 +0200 (CEST) Subject: [py-svn] r33495 - in py/dist/py: apigen apigen/rest apigen/tracer apigen/tracer/testing test/tracer Message-ID: <20061020113545.A1C3C10053@code0.codespeak.net> Author: guido Date: Fri Oct 20 13:35:43 2006 New Revision: 33495 Added: py/dist/py/apigen/ py/dist/py/apigen/__init__.py py/dist/py/apigen/rest/ py/dist/py/apigen/tracer/ - copied from r33482, py/dist/py/test/tracer/ py/dist/py/apigen/tracer/__init__.py - copied unchanged from r33494, py/dist/py/test/tracer/__init__.py py/dist/py/apigen/tracer/description.py - copied unchanged from r33494, py/dist/py/test/tracer/description.py py/dist/py/apigen/tracer/docstorage.py - copied, changed from r33494, py/dist/py/test/tracer/docstorage.py py/dist/py/apigen/tracer/genrest.py - copied, changed from r33494, py/dist/py/test/tracer/genrest.py py/dist/py/apigen/tracer/magic.py - copied, changed from r33494, py/dist/py/test/tracer/magic.py py/dist/py/apigen/tracer/testing/ - copied from r33494, py/dist/py/test/tracer/testing/ py/dist/py/apigen/tracer/tracer.py - copied, changed from r33494, py/dist/py/test/tracer/tracer.py Removed: py/dist/py/test/tracer/ Modified: py/dist/py/apigen/tracer/testing/test_docgen.py py/dist/py/apigen/tracer/testing/test_magic.py py/dist/py/apigen/tracer/testing/test_rest.py Log: Moving tracer out of the test package. Added: py/dist/py/apigen/__init__.py ============================================================================== Copied: py/dist/py/apigen/tracer/docstorage.py (from r33494, py/dist/py/test/tracer/docstorage.py) ============================================================================== --- py/dist/py/test/tracer/docstorage.py (original) +++ py/dist/py/apigen/tracer/docstorage.py Fri Oct 20 13:35:43 2006 @@ -7,8 +7,8 @@ import sys import types -from py.__.test.tracer.description import FunctionDesc, ClassDesc, MethodDesc, \ - Desc +from py.__.apigen.tracer.description import FunctionDesc, ClassDesc, \ + MethodDesc, Desc try: from pypy.annotation.policy import AnnotatorPolicy Copied: py/dist/py/apigen/tracer/genrest.py (from r33494, py/dist/py/test/tracer/genrest.py) ============================================================================== --- py/dist/py/test/tracer/genrest.py (original) +++ py/dist/py/apigen/tracer/genrest.py Fri Oct 20 13:35:43 2006 @@ -7,7 +7,7 @@ import sys import re -from py.__.test.tracer.docstorage import DocStorageAccessor +from py.__.apigen.tracer.docstorage import DocStorageAccessor from py.__.rst.rst import * # XXX Maybe we should list it here class AbstractLinkWriter(object): Copied: py/dist/py/apigen/tracer/magic.py (from r33494, py/dist/py/test/tracer/magic.py) ============================================================================== --- py/dist/py/test/tracer/magic.py (original) +++ py/dist/py/apigen/tracer/magic.py Fri Oct 20 13:35:43 2006 @@ -9,8 +9,8 @@ import weakref import py -from py.__.test.tracer.docstorage import DocStorage -from py.__.test.tracer.tracer import Tracer +from py.__.apigen.tracer.docstorage import DocStorage +from py.__.apigen.tracer.tracer import Tracer import sys class DocStorageKeeper(object): Modified: py/dist/py/apigen/tracer/testing/test_docgen.py ============================================================================== --- py/dist/py/test/tracer/testing/test_docgen.py (original) +++ py/dist/py/apigen/tracer/testing/test_docgen.py Fri Oct 20 13:35:43 2006 @@ -6,9 +6,9 @@ import sys try: - from py.__.test.tracer.tracer import DocStorage, Tracer - from py.__.test.tracer.testing.runtest import cut_pyc - from py.__.test.tracer.description import FunctionDesc + from py.__.apigen.tracer.tracer import DocStorage, Tracer + from py.__.apigen.tracer.testing.runtest import cut_pyc + from py.__.apigen.tracer.description import FunctionDesc from pypy.annotation import model except ImportError, s: py.test.skip("Cannot import: %s" % str(s)) Modified: py/dist/py/apigen/tracer/testing/test_magic.py ============================================================================== --- py/dist/py/test/tracer/testing/test_magic.py (original) +++ py/dist/py/apigen/tracer/testing/test_magic.py Fri Oct 20 13:35:43 2006 @@ -2,9 +2,9 @@ """ test magic abilities of tracer """ -from py.__.test.tracer.magic import trace, get_storage, stack_copier, \ +from py.__.apigen.tracer.magic import trace, get_storage, stack_copier, \ DocStorageKeeper -from py.__.test.tracer.docstorage import DocStorage +from py.__.apigen.tracer.docstorage import DocStorage from pypy.annotation import model #def setup_function(f): Modified: py/dist/py/apigen/tracer/testing/test_rest.py ============================================================================== --- py/dist/py/test/tracer/testing/test_rest.py (original) +++ py/dist/py/apigen/tracer/testing/test_rest.py Fri Oct 20 13:35:43 2006 @@ -6,12 +6,12 @@ from StringIO import StringIO try: - from py.__.test.tracer.genrest import ViewVC, RestGen, PipeWriter, \ + from py.__.apigen.tracer.genrest import ViewVC, RestGen, PipeWriter, \ DirWriter, FileWriter, \ DirectPaste, DirectFS - from py.__.test.tracer.tracer import DocStorage, Tracer + from py.__.apigen.tracer.tracer import DocStorage, Tracer - from py.__.test.tracer.testing.runtest import cut_pyc + from py.__.apigen.tracer.testing.runtest import cut_pyc except ImportError, s: py.test.skip("Cannot import: %s" % str(s)) @@ -58,7 +58,7 @@ fname = cut_pyc(__file__) title, link = vcview.getlink(fname, 0) assert title == '%s:%s' % (fname, 0) - assert link == ('http://codespeak.net/viewvc/py/test/tracer/' + assert link == ('http://codespeak.net/viewvc/py/apigen/tracer/' 'testing/test_rest.py?view=markup') def test_fs_link(): Copied: py/dist/py/apigen/tracer/tracer.py (from r33494, py/dist/py/test/tracer/tracer.py) ============================================================================== --- py/dist/py/test/tracer/tracer.py (original) +++ py/dist/py/apigen/tracer/tracer.py Fri Oct 20 13:35:43 2006 @@ -10,8 +10,8 @@ # from pypy's code. # BLAH BLAH BLAH -from py.__.test.tracer.description import FunctionDesc -from py.__.test.tracer.docstorage import DocStorage +from py.__.apigen.tracer.description import FunctionDesc +from py.__.apigen.tracer.docstorage import DocStorage class UnionError(Exception): pass From guido at codespeak.net Fri Oct 20 13:40:25 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Fri, 20 Oct 2006 13:40:25 +0200 (CEST) Subject: [py-svn] r33496 - in py/dist/py/apigen: . rest rest/testing tracer tracer/testing Message-ID: <20061020114025.C12F710053@code0.codespeak.net> Author: guido Date: Fri Oct 20 13:40:23 2006 New Revision: 33496 Added: py/dist/py/apigen/rest/__init__.py (contents, props changed) py/dist/py/apigen/rest/genrest.py - copied unchanged from r33495, py/dist/py/apigen/tracer/genrest.py py/dist/py/apigen/rest/testing/ (props changed) py/dist/py/apigen/rest/testing/__init__.py (contents, props changed) py/dist/py/apigen/rest/testing/test_rest.py - copied, changed from r33495, py/dist/py/apigen/tracer/testing/test_rest.py Removed: py/dist/py/apigen/tracer/genrest.py py/dist/py/apigen/tracer/testing/test_rest.py Modified: py/dist/py/apigen/ (props changed) py/dist/py/apigen/__init__.py (props changed) py/dist/py/apigen/rest/ (props changed) Log: Moved rest stuff to seperate dir. Added: py/dist/py/apigen/rest/__init__.py ============================================================================== Added: py/dist/py/apigen/rest/testing/__init__.py ============================================================================== Copied: py/dist/py/apigen/rest/testing/test_rest.py (from r33495, py/dist/py/apigen/tracer/testing/test_rest.py) ============================================================================== --- py/dist/py/apigen/tracer/testing/test_rest.py (original) +++ py/dist/py/apigen/rest/testing/test_rest.py Fri Oct 20 13:40:23 2006 @@ -6,7 +6,7 @@ from StringIO import StringIO try: - from py.__.apigen.tracer.genrest import ViewVC, RestGen, PipeWriter, \ + from py.__.apigen.rest.genrest import ViewVC, RestGen, PipeWriter, \ DirWriter, FileWriter, \ DirectPaste, DirectFS from py.__.apigen.tracer.tracer import DocStorage, Tracer @@ -58,7 +58,7 @@ fname = cut_pyc(__file__) title, link = vcview.getlink(fname, 0) assert title == '%s:%s' % (fname, 0) - assert link == ('http://codespeak.net/viewvc/py/apigen/tracer/' + assert link == ('http://codespeak.net/viewvc/py/apigen/rest/' 'testing/test_rest.py?view=markup') def test_fs_link(): Deleted: /py/dist/py/apigen/tracer/genrest.py ============================================================================== --- /py/dist/py/apigen/tracer/genrest.py Fri Oct 20 13:40:23 2006 +++ (empty file) @@ -1,234 +0,0 @@ - -""" Generating ReST output (raw, not python) -out of data that we know about function calls -""" - -import py -import sys -import re - -from py.__.apigen.tracer.docstorage import DocStorageAccessor -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): - # XXX: should try to guess from a working copy of svn - self.basepath = basepath - - 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 ('%s:%s' % (filename, lineno), - self.basepath + relname[1:] + '?view=markup') - -class DirectPaste(AbstractLinkWriter): - """ No-link writer (inliner) - """ - def getlink(self, filename, lineno): - return ('%s:%s' % (filename, lineno), "") - -class DirectFS(AbstractLinkWriter): - """ Creates links to the files on the file system (for local docs) - """ - def getlink(self, filename, lineno): - return ('%s:%s' % (filename, lineno), 'file://%s' % (filename,)) - -class PipeWriter(object): - def __init__(self, output=sys.stdout): - self.output = output - - def write_section(self, name, data): - text = "Contents of file %s.txt:" % (name,) - self.output.write(text + "\n") - self.output.write("=" * len(text) + "\n") - self.output.write("\n") - self.output.write(data + "\n") - - def getlink(self, type, targetname, targetfilename): - return '%s.txt' % (targetfilename,) - -class DirWriter(object): - def __init__(self, directory=None): - if directory is None: - self.directory = py.test.ensuretemp("rstoutput") - else: - self.directory = py.path.local(directory) - - def write_section(self, name, data): - filename = '%s.txt' % (name,) - self.directory.ensure(filename).write(data) - - def getlink(self, type, targetname, targetfilename): - # we assume the result will get converted to HTML... - return '%s.html' % (targetfilename,) - -class FileWriter(object): - def __init__(self, fpath): - self.fpath = fpath - self.fp = fpath.open('w+') - self._defined_targets = [] - - def write_section(self, name, data): - self.fp.write(data) - self.fp.flush() - - def getlink(self, type, targetname, targetbasename): - # XXX problem: because of docutils' named anchor generation scheme, - # a method Foo.__init__ would clash with Foo.init (underscores are - # removed) - if targetname in self._defined_targets: - return None - self._defined_targets.append(targetname) - targetname = targetname.lower().replace('.', '-').replace('_', '') - return '#%s-%s' % (type, targetname) - -class RestGen(object): - def __init__(self, ds, linkgen, writer=PipeWriter()): - self.dsa = DocStorageAccessor(ds) - self.linkgen = linkgen - self.writer = writer - - def write(self): - """write the data to the writer""" - # note that this builds up a list of Rest elements for the index as - # 'side effect', the list is passed along and filled, while the actual - # sections (also ReST elements) are returned by the write_* methods - # XXX this is quite icky! would be nice to have refactored - indexlst = [Title("Module: %s" % self.dsa.get_module_name(), - belowchar="="), - Paragraph(self.dsa.get_module_info()), - Title("Exported functions:", belowchar="-")] - funclst = self.write_function_list(indexlst) - indexlst.append(Title("Exported classes:", belowchar="-")) - classlst = self.write_class_list(indexlst) - self.writer.write_section('index', Rest(*indexlst).text()) - for sectionname, restitems in funclst: - self.writer.write_section(sectionname, Rest(*restitems).text()) - for sectionname, classdata in classlst: - restitems, classfunclst = classdata - self.writer.write_section(sectionname, Rest(*restitems).text()) - for fsectionname, frestitems in classfunclst: - self.writer.write_section(fsectionname, - Rest(*frestitems).text()) - - def write_function_list(self, indexlst): - retlst = [] - for name in self.dsa.get_function_names(): - sectionname = 'function_%s' % (name,) - linktarget = self.writer.getlink('function', name, - sectionname) - indexlst.append(ListItem(Text("Function: "), - Link(name, linktarget))) - retlst.append((sectionname, - self.write_function(sectionname, name))) - return retlst - - def write_class_list(self, indexlst): - retlst = [] - for name in self.dsa.get_class_names(): - sectionname = 'class_%s' % (name,) - linktarget = self.writer.getlink('class', name, sectionname) - indexlst.append(ListItem(Text("Class: "), Link(name, linktarget))) - retlst.append((sectionname, self.write_class(sectionname, name))) - return retlst - - def write_class(self, section_name, class_name): - classlst = [Title("Class: %s" % class_name, belowchar='-'), - LiteralBlock(self.dsa.get_function_doc(class_name))] - - # write down exported methods - classlst.append(Title("Exported methods:", belowchar="^")) - funclist = [] - for method in self.dsa.get_class_methods(class_name): - sectionname = 'method_%s_%s' % (class_name, method) - linktext = '%s.%s' % (class_name, method) - linktarget = self.writer.getlink('function', linktext, - sectionname) - classlst.append(ListItem(Link(linktext, linktarget))) - # XXX assuming a function is always part of a class section - funclist.append((sectionname, - self.write_function(sectionname, - class_name + "." + method, - '^'))) - return classlst, funclist - - def write_function(self, section_name, fun_name, belowchar='-'): - # XXX I think the docstring should either be split on \n\n and cleaned - # from indentation, or treated as ReST too (although this is obviously - # dangerous for non-ReST docstrings)... - lst = [Title("Function: %s" % fun_name, belowchar=belowchar), - LiteralBlock(self.dsa.get_function_doc(fun_name)), - LiteralBlock(self.dsa.get_function_definition(fun_name))] - - # XXX missing implementation of dsa.get_function_location() - #filename, lineno = self.dsa.get_function_location(fun_name) - #linkname, linktarget = self.linkgen.getlink(filename, lineno) - #if linktarget: - # lst.append(Paragraph("Function source: ", - # Link(linkname, linktarget))) - #else: - lst.append(Paragraph('Function source: ')) - - args, retval = self.dsa.get_function_signature(fun_name) - # XXX: we just do "knowntype" here, but we should - # present some more informative way, maybe even provide a link - # for the type declaration (if this is known) - arg_str = "(%s)" % (",".join([str(i.knowntype) for i in args])) - ret_str = str(retval.knowntype) - arg_quote = Paragraph("Function type:", Quote(arg_str), '->', - Quote(ret_str)) - lst.append(arg_quote) - - # call sites.. - call_site_title = Title("Call sites:", belowchar='^') - lst.append(call_site_title) - call_sites = lst - - for call_site, frame in self.dsa.get_function_callpoints(fun_name): - link_str = "File %s:%s" % (call_site.filename, - call_site.lineno) - link_str, link_target = self.linkgen.getlink(call_site.filename, - call_site.lineno) - if link_target: # otherwise it's just inline text - call_sites.append(Paragraph(Link(link_str, link_target))) - else: - call_sites.append(Paragraph(link_str)) - #call_sites.append(LiteralBlock(call_site.source)) - # XXX: For now, we just paste here the filename of that - #call_sites.append(Paragraph(link_str)) - source = frame.code.source() - lines = [] - for num, line in enumerate(source): - if num == call_site.lineno - frame.code.firstlineno - 1: - m = re.match("^( *)(.*)", line) - lines.append(">%s%s" % ("-" * len(m.group(1)), m.group(2))) - else: - lines.append(" " + line) - call_sites.append(LiteralBlock("\n".join(lines))) - - return lst - Deleted: /py/dist/py/apigen/tracer/testing/test_rest.py ============================================================================== --- /py/dist/py/apigen/tracer/testing/test_rest.py Fri Oct 20 13:40:23 2006 +++ (empty file) @@ -1,201 +0,0 @@ - -""" tests document generation -""" - -import py -from StringIO import StringIO - -try: - from py.__.apigen.tracer.genrest import ViewVC, RestGen, PipeWriter, \ - DirWriter, FileWriter, \ - DirectPaste, DirectFS - from py.__.apigen.tracer.tracer import DocStorage, Tracer - - from py.__.apigen.tracer.testing.runtest import cut_pyc -except ImportError, s: - py.test.skip("Cannot import: %s" % str(s)) - -def setup_module(mod): - mod.temppath = py.test.ensuretemp('restgen') - -class SomeClass(object): - """Some class definition""" - - def __init__(self, a): - self.a = a - - def method(self, a, b, c): - """method docstring""" - return a + b + c - -class SomeSubClass(SomeClass): - """Some subclass definition""" - -def fun(a, b, c): - """Some docstring - - Let's make it span a couple of lines to be interesting... - - Note: - - * rest - * should - * be - * supported - * or - * ignored... - """ - return "d" - -def test_direct_link(): - fname = cut_pyc(__file__) - title, link = DirectPaste().getlink(fname, 2) - assert title == '%s:%s' % (fname, 2) - assert link == '' - -def test_viewvc_link(): - vcview = ViewVC("http://codespeak.net/viewvc/") - fname = cut_pyc(__file__) - title, link = vcview.getlink(fname, 0) - assert title == '%s:%s' % (fname, 0) - assert link == ('http://codespeak.net/viewvc/py/apigen/tracer/' - 'testing/test_rest.py?view=markup') - -def test_fs_link(): - title, link = DirectFS().getlink('/foo/bar/baz.py', 100) - assert title == '/foo/bar/baz.py:100' - assert link == 'file:///foo/bar/baz.py' - -class WriterTest(object): - def get_filled_writer(self, writerclass, *args, **kwargs): - dw = writerclass(*args, **kwargs) - dw.write_section('foo', 'foo data') - dw.write_section('bar', 'bar data') - return dw - -class TestDirWriter(WriterTest): - def test_write_section(self): - tempdir = temppath.ensure('dirwriter', dir=True) - dw = self.get_filled_writer(DirWriter, tempdir) - fpaths = tempdir.listdir('*.txt') - assert len(fpaths) == 2 - assert sorted([f.basename for f in fpaths]) == ['bar.txt', 'foo.txt'] - assert tempdir.join('foo.txt').read() == 'foo data' - assert tempdir.join('bar.txt').read() == 'bar data' - - def test_getlink(self): - dw = DirWriter(temppath.join('dirwriter_getlink')) - link = dw.getlink('function', 'Foo.bar', 'method_foo_bar') - assert link == 'method_foo_bar.html' - -class TestFileWriter(WriterTest): - def test_write_section(self): - tempfile = temppath.ensure('filewriter', file=True) - fw = self.get_filled_writer(FileWriter, tempfile) - data = tempfile.read() - assert len(data) - - def test_getlink(self): - fw = FileWriter(temppath.join('filewriter_getlink')) - link = fw.getlink('function', 'Foo.bar', 'method_foo_bar') - assert link == '#function-foo-bar' - # only produce the same link target once... - link = fw.getlink('function', 'Foo.bar', 'method_foo_bar') - assert link is None - link = fw.getlink('function', 'Foo.__init__', 'method_foo___init__') - assert link == '#function-foo-init' - -class TestPipeWriter(WriterTest): - def test_write_section(self): - s = StringIO() - pw = self.get_filled_writer(PipeWriter, s) - data = s.getvalue() - assert len(data) - - def test_getlink(self): - pw = PipeWriter(StringIO()) - link = pw.getlink('function', 'Foo.bar', 'method_foo_bar') - assert link == 'method_foo_bar.txt' - -class TestRest(object): - def get_filled_docstorage(self): - descs = {'SomeClass': SomeClass, - 'SomeSubClass': SomeSubClass, - 'fun':fun} - ds = DocStorage().from_dict(descs) - t = Tracer(ds) - t.start_tracing() - s1 = SomeClass("a") - fun(1, 2, s1) - s2 = SomeSubClass("b") - s2.method(1,2,3) - fun(1, 3, s2) - t.end_tracing() - return ds - - def check_rest(self, tempdir): - from py.__.misc import rest - for path in tempdir.listdir('*.txt'): - rest.process(path) - - def test_generation_simple_api(self): - ds = self.get_filled_docstorage() - lg = DirectPaste() - tempdir = temppath.ensure("simple_api", dir=True) - r = RestGen(ds, lg, DirWriter(tempdir)) - r.write() - basenames = [p.basename for p in tempdir.listdir('*.txt')] - assert sorted(basenames) == [ - 'class_SomeClass.txt', - 'class_SomeSubClass.txt', - 'function_fun.txt', - 'index.txt', - 'method_SomeClass___init__.txt', - 'method_SomeClass_method.txt', - 'method_SomeSubClass___init__.txt', - 'method_SomeSubClass_method.txt', - ] - # now we check out... - self.check_rest(tempdir) - - def test_check_internal_links(self): - ds = self.get_filled_docstorage() - lg = DirectFS() - tempdir = temppath.ensure('internal_links', dir=True) - r = RestGen(ds, lg, DirWriter(tempdir)) - r.write() - index = tempdir.join('index.txt') - assert index.check(file=True) - data = index.read() - assert data.find('.. _`fun`: function_fun.html\n') > -1 - assert data.find('.. _`fun`: #function-fun\n') == -1 - - tempfile = temppath.ensure('internal_links.txt', - file=True) - r = RestGen(ds, lg, FileWriter(tempfile)) - r.write() - data = tempfile.read() - assert data.find('.. _`fun`: #function-fun\n') > -1 - assert data.find('.. _`fun`: function_fun.html') == -1 - - def test_check_section_order(self): - # we use the previous method's data - tempfile = temppath.join('internal_links.txt') - if not tempfile.check(): - py.test.skip('depends on previous test, which failed') - data = tempfile.read() - # index should be above the rest - assert data.find('Exported classes:') > -1 - assert data.find('Exported classes:') < data.find('Function: fun') - assert data.find('Exported classes:') < data.find('Class: SomeClass') - # function definitions should be above class ones - assert data.find('Function: fun') < data.find('Class: SomeClass') - # class method definitions should be below the class defs - assert data.find('Class: SomeClass') < data.find( - 'Function: SomeClass.method') - # __init__ should be above other methods - assert data.find('Function: SomeClass.__init__') > -1 - # XXX in the future... - # assert data.find('Function: SomeClass.__init__') < data.find( - # 'Function: SomeClass.method') - From guido at codespeak.net Fri Oct 20 14:14:15 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Fri, 20 Oct 2006 14:14:15 +0200 (CEST) Subject: [py-svn] r33498 - in py/dist/py/apigen: rest/testing tracer tracer/testing Message-ID: <20061020121415.C47BD10068@code0.codespeak.net> Author: guido Date: Fri Oct 20 14:14:13 2006 New Revision: 33498 Modified: py/dist/py/apigen/rest/testing/test_rest.py py/dist/py/apigen/tracer/testing/test_docgen.py py/dist/py/apigen/tracer/tracer.py Log: Removed useless import, whitespace, indentation, typo. Modified: py/dist/py/apigen/rest/testing/test_rest.py ============================================================================== --- py/dist/py/apigen/rest/testing/test_rest.py (original) +++ py/dist/py/apigen/rest/testing/test_rest.py Fri Oct 20 14:14:13 2006 @@ -9,7 +9,8 @@ from py.__.apigen.rest.genrest import ViewVC, RestGen, PipeWriter, \ DirWriter, FileWriter, \ DirectPaste, DirectFS - from py.__.apigen.tracer.tracer import DocStorage, Tracer + from py.__.apigen.tracer.tracer import Tracer + from py.__.apigen.tracer.docstorage import DocStorage from py.__.apigen.tracer.testing.runtest import cut_pyc except ImportError, s: Modified: py/dist/py/apigen/tracer/testing/test_docgen.py ============================================================================== --- py/dist/py/apigen/tracer/testing/test_docgen.py (original) +++ py/dist/py/apigen/tracer/testing/test_docgen.py Fri Oct 20 14:14:13 2006 @@ -6,7 +6,8 @@ import sys try: - from py.__.apigen.tracer.tracer import DocStorage, Tracer + from py.__.apigen.tracer.docstorage import DocStorage + from py.__.apigen.tracer.tracer import Tracer from py.__.apigen.tracer.testing.runtest import cut_pyc from py.__.apigen.tracer.description import FunctionDesc from pypy.annotation import model Modified: py/dist/py/apigen/tracer/tracer.py ============================================================================== --- py/dist/py/apigen/tracer/tracer.py (original) +++ py/dist/py/apigen/tracer/tracer.py Fri Oct 20 14:14:13 2006 @@ -10,9 +10,6 @@ # from pypy's code. # BLAH BLAH BLAH -from py.__.apigen.tracer.description import FunctionDesc -from py.__.apigen.tracer.docstorage import DocStorage - class UnionError(Exception): pass @@ -26,11 +23,12 @@ def _tracer(self, frame, event, arg): - # perform actuall tracing + # perform actual tracing frame = py.code.Frame(frame) if event == 'call': assert arg is None - self.docstorage.consider_call(frame, py.code.Frame(sys._getframe(2))) + self.docstorage.consider_call(frame, + py.code.Frame(sys._getframe(2))) elif event == 'return': self.docstorage.consider_return(frame, arg) From guido at codespeak.net Fri Oct 20 15:27:01 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Fri, 20 Oct 2006 15:27:01 +0200 (CEST) Subject: [py-svn] r33502 - in py/dist/py/rst: . testing Message-ID: <20061020132701.E9EFC10053@code0.codespeak.net> Author: guido Date: Fri Oct 20 15:26:58 2006 New Revision: 33502 Modified: py/dist/py/rst/rst.py py/dist/py/rst/testing/test_rst.py Log: Small cleanups, added comments, renamed 'childs' to 'children', added some simple node types. Modified: py/dist/py/rst/rst.py ============================================================================== --- py/dist/py/rst/rst.py (original) +++ py/dist/py/rst/rst.py Fri Oct 20 15:26:58 2006 @@ -1,5 +1,12 @@ -""" reStructuredText manipulation tools +""" reStructuredText generation tools + + provides an api to build a tree from nodes, which can be converted to + ReStructuredText on demand + + note that not all of ReST is supported, a usable subset is offered, but + certain features aren't supported, and also certain details (like how links + are generated, or how escaping is done) can not be controlled """ from __future__ import generators @@ -8,7 +15,7 @@ def escape(txt): """escape ReST markup""" - for c in '*`[|': + for c in '\\*`[|:': txt = txt.replace(c, '\\%s' % (c,)) return txt @@ -34,7 +41,7 @@ return obj class AbstractNode(object): - """ Basic class implementing writing rest code + """ Base class implementing rest generation """ sep = '' __metaclass__ = AbstractMetaclass @@ -45,17 +52,25 @@ def __init__(self, *args, **kwargs): self.parent = None - self.childs = [] + self.children = [] for child in args: self._add(child) for arg in kwargs: setattr(self, arg, kwargs[arg]) def join(self, child): + """ adds a child node + + returns a reference to self + """ self._add(child) return self def add(self, child): + """ adds a child node + + returns a reference to the child + """ self._add(child) return child @@ -63,19 +78,21 @@ if child.__class__ not in self.allowed_child: raise RestError("%r cannot be child of %r" % \ (child.__class__, self.__class__)) - self.childs.append(child) + self.children.append(child) child.parent = self def __getitem__(self, item): - return self.childs[item] + return self.children[item] def __setitem__(self, item, value): - self.childs[item] = value + self.children[item] = value def text(self): - return self.sep.join([child.text() for child in self.childs]) + """ return a ReST string representation of the node """ + return self.sep.join([child.text() for child in self.children]) def wordlist(self): + """ return a list of ReST strings for this node and its children """ return [self.text()] class Rest(AbstractNode): @@ -85,22 +102,35 @@ self.links = {} def render_links(self, check=False): + """render the link attachents of the document""" assert not check, "Link checking not implemented" if not self.links: return "" link_texts = [] + # XXX this could check for duplicates and remove them... for link, target in self.links.iteritems(): link_texts.append(".. _`%s`: %s" % (escape(link), escape(target))) return "\n" + "\n".join(link_texts) + "\n\n" def text(self): outcome = [] - for child in self.childs: + for child in self.children: outcome.append(child.text()) text = self.sep.join(outcome) + "\n" # trailing newline return text + self.render_links() +class Transition(AbstractNode): + parentclass = Rest + + def __init__(self, char='-', width=80, *args, **kwargs): + self.char = char + self.width = width + super(Transition, self).__init__(*args, **kwargs) + + def text(self): + return (self.width - 1) * self.char + class Paragraph(AbstractNode): parentclass = Rest sep = " " @@ -117,7 +147,7 @@ def text(self): texts = [] - for child in self.childs: + for child in self.children: texts += child.wordlist() buf = [] @@ -217,13 +247,21 @@ def text(self): oldindent = self.indent self.indent = oldindent + ' ' - txt = Paragraph.text(self) + try: + txt = Paragraph.text(self) + finally: + self.indent = oldindent txt = self.item_char + txt[1:] - self.indent = oldindent return txt class OrderedListItem(ListItem): - item_char = "#" + item_char = "#." + +class DListItem(ListItem): + item_char = None + def __init__(self, term, definition, *args, **kwargs): + self.item_char = '%s\n ' % (term,) + super(DListItem, self).__init__(definition, *args, **kwargs) class Link(AbstractText): start = '`' Modified: py/dist/py/rst/testing/test_rst.py ============================================================================== --- py/dist/py/rst/testing/test_rst.py (original) +++ py/dist/py/rst/testing/test_rst.py Fri Oct 20 15:26:58 2006 @@ -4,6 +4,14 @@ from py.__.rst.rst import * +def test_escape(): + txt = Paragraph('*escape* ``test``').text() + assert txt == '\\*escape\\* \\`\\`test\\`\\`' + txt = Strong('*strong*').text() + assert txt == '**\\*strong\\***' + txt = Rest(Paragraph(Link('foo[bar]', 'foo[bar]'))).text() + assert txt == "`foo\\[bar]`_\n\n.. _`foo\\[bar]`: foo\\[bar]\n\n" + def test_illegal_parent(): Rest(Paragraph(Text('spam'))) py.test.raises(RestError, 'Rest(Text("spam"))') @@ -12,6 +20,12 @@ def test_text_basic(): assert Text("dupa").text() == "dupa" +def test_basic_inline(): + txt = Em('foo').text() + assert txt == '*foo*' + txt = Strong('bar').text() + assert txt == '**bar**' + def test_text_join(): assert Paragraph(Text("dupa"), Text("dupa")).text() == "dupa dupa" @@ -67,7 +81,7 @@ :: - def fun(): + def fun()\\: some Paragraph @@ -161,6 +175,12 @@ 'commodo pellentesque mi.')).text() assert txt == expected +def test_ordered_list(): + expected = "#. foo\n\n#. bar\n\n#. baz\n" + txt = Rest(OrderedListItem('foo'), OrderedListItem('bar'), + OrderedListItem('baz')).text() + assert txt == expected + def test_title_following_links_empty_line(): expected = """\ Foo, bar and `baz`_. @@ -175,17 +195,20 @@ txt = Rest(Paragraph("Foo, bar and ", Link("baz", "http://www.baz.com")), Title('Spam'), Paragraph('Spam, eggs and spam.')) -def test_basic_inline(): - txt = Em('foo').text() - assert txt == '*foo*' - txt = Strong('bar').text() - assert txt == '**bar**' +def test_definition_list(): + expected = """\ +foo + bar, baz and qux! -def test_escape(): - txt = Paragraph('*escape* ``test``').text() - assert txt == '\\*escape\\* \\`\\`test\\`\\`' - txt = Strong('*strong*').text() - assert txt == '**\\*strong\\***' - txt = Rest(Paragraph(Link('foo[bar]', 'foo[bar]'))).text() - assert txt == "`foo\\[bar]`_\n\n.. _`foo\\[bar]`: foo\\[bar]\n\n" +spam + eggs, spam, spam, eggs, spam and spam... +""" + txt = Rest(DListItem("foo", "bar, baz and qux!"), + DListItem("spam", "eggs, spam, spam, eggs, spam and spam...") + ).text() + assert txt == expected + +def test_transition(): + assert Rest(Transition()).text() == '%s\n' % ('-' * 79,) + assert Rest(Transition('+')).text() == '%s\n' % ('+' * 79,) From fijal at codespeak.net Fri Oct 20 18:43:01 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Fri, 20 Oct 2006 18:43:01 +0200 (CEST) Subject: [py-svn] r33506 - in py/dist/py/apigen: rest tracer tracer/testing Message-ID: <20061020164301.4ADCB10070@code0.codespeak.net> Author: fijal Date: Fri Oct 20 18:41:30 2006 New Revision: 33506 Added: py/dist/py/apigen/tracer/model.py (contents, props changed) py/dist/py/apigen/tracer/testing/test_model.py (contents, props changed) Modified: py/dist/py/apigen/rest/genrest.py py/dist/py/apigen/tracer/description.py py/dist/py/apigen/tracer/docstorage.py py/dist/py/apigen/tracer/testing/test_docgen.py py/dist/py/apigen/tracer/testing/test_magic.py py/dist/py/apigen/tracer/tracer.py Log: Remove pypy dependency, added some stuff. Right now I would go for more objects to support. Modified: py/dist/py/apigen/rest/genrest.py ============================================================================== --- py/dist/py/apigen/rest/genrest.py (original) +++ py/dist/py/apigen/rest/genrest.py Fri Oct 20 18:41:30 2006 @@ -197,8 +197,8 @@ # XXX: we just do "knowntype" here, but we should # present some more informative way, maybe even provide a link # for the type declaration (if this is known) - arg_str = "(%s)" % (",".join([str(i.knowntype) for i in args])) - ret_str = str(retval.knowntype) + arg_str = "(%s)" % (",".join([str(i) for i in args])) + ret_str = str(retval) arg_quote = Paragraph("Function type:", Quote(arg_str), '->', Quote(ret_str)) lst.append(arg_quote) Modified: py/dist/py/apigen/tracer/description.py ============================================================================== --- py/dist/py/apigen/tracer/description.py (original) +++ py/dist/py/apigen/tracer/description.py Fri Oct 20 18:41:30 2006 @@ -1,8 +1,10 @@ -try: - from pypy.annotation import model -except ImportError: - pass +#try: +# from pypy.annotation import model +#except ImportError: +# pass + +from py.__.apigen.tracer import model import types @@ -117,7 +119,10 @@ # other variables encountered here def getcode(self): - return self.pyobj.__init__.im_func.func_code + try: + return self.pyobj.__init__.im_func.func_code + except AttributeError: + return self.pyobj.__name__ code = property(getcode) def consider_call(self, inputcells): Modified: py/dist/py/apigen/tracer/docstorage.py ============================================================================== --- py/dist/py/apigen/tracer/docstorage.py (original) +++ py/dist/py/apigen/tracer/docstorage.py Fri Oct 20 18:41:30 2006 @@ -7,23 +7,16 @@ import sys import types -from py.__.apigen.tracer.description import FunctionDesc, ClassDesc, \ - MethodDesc, Desc +from py.__.apigen.tracer.description import FunctionDesc, ClassDesc, MethodDesc, \ + Desc -try: - from pypy.annotation.policy import AnnotatorPolicy - from pypy.annotation.bookkeeper import Bookkeeper -except ImportError, i: - py.test.skip("No PyPy %s" % i) - -class DummyAnnotator(object): - policy = AnnotatorPolicy() +from py.__.apigen.tracer.model import guess_type class DocStorage(object): """ Class storing info about API """ - def __init__(self): - self.bookkeeper = Bookkeeper(DummyAnnotator()) + #def __init__(self): + #self.bookkeeper = Bookkeeper(DummyAnnotator()) #self.call_stack = [] #self.frames = [] @@ -37,10 +30,10 @@ def generalize_args(self, desc, frame): args = [arg for key, arg in frame.getargs()] #self.call_stack.append((desc, args)) - desc.consider_call([self.bookkeeper.immutablevalue(arg) for arg in args]) + desc.consider_call([guess_type(arg) for arg in args]) def generalize_retval(self, desc, arg): - desc.consider_return(self.bookkeeper.immutablevalue(arg)) + desc.consider_return(guess_type(arg)) def consider_return(self, frame, arg): assert isinstance(frame, py.code.Frame) @@ -64,7 +57,8 @@ self.descs = {} for key, val in _dict.iteritems(): to_key, to_val = self.make_desc(key, val) - self.descs[to_key] = to_val + if to_key: + self.descs[to_key] = to_val self.make_cache() return self @@ -86,7 +80,8 @@ desc = ClassDesc(key, value, **kwargs) for name in dir(value): field = getattr(value, name) - if isinstance(field, types.MethodType): + if isinstance(field, types.MethodType) and \ + isinstance(field.im_func, types.FunctionType): real_name = key + '.' + name md = MethodDesc(real_name, field) self.descs[real_name] = md @@ -101,7 +96,15 @@ def from_pkg(self, module): self.module = module - self.from_dict(module.__package__.__dict__) + keys = module.__package__.exportdefs.keys() + d = {} + for key in keys: + chain = key.split('.') + base = module + for elem in chain: + base = getattr(base, elem) + d[key] = base + self.from_dict(d) return self def from_module(self, func): Added: py/dist/py/apigen/tracer/model.py ============================================================================== --- (empty file) +++ py/dist/py/apigen/tracer/model.py Fri Oct 20 18:41:30 2006 @@ -0,0 +1,239 @@ + +""" model - type system model for apigen +""" + +# we implement all the types which are in the types.*, naming +# scheme after pypy's + +import types + +# __extend__ and pairtype? +class SomeObject(object): + typedef = types.ObjectType + + def __repr__(self): + return str(self.typedef)[7:-2] + + def unionof(self, other): + if isinstance(other, SomeImpossibleValue): + return self + if self.gettypedef() is other.gettypedef(): + return self + return SomeUnion([self, other]) + + def gettypedef(self): + return self.typedef + +class SomeUnion(object): + # empty typedef + def __init__(self, possibilities): + self.possibilities = possibilities + + def unionof(self, other): + return SomeUnion(self.possibilities + [other]) + + def __repr__(self): + return "AnyOf(%s)" % ",".join([str(i) for i in self.possibilities]) + + def gettypedef(self): + return (None, None) + +class SomeBoolean(SomeObject): + typedef = types.BooleanType + +class SomeBuffer(SomeObject): + typedef = types.BufferType + +class SomeBuiltinFunction(SomeObject): + typedef = types.BuiltinFunctionType + +class SomeBuiltinMethod(SomeObject): + typedef = types.BuiltinMethodType + +class SomeClass(SomeObject): + typedef = types.ClassType + +class SomeCode(SomeObject): + typedef = types.CodeType + +class SomeComplex(SomeObject): + typedef = types.ComplexType + +class SomeDictProxy(SomeObject): + typedef = types.DictProxyType + +class SomeDict(SomeObject): + typedef = types.DictType + +class SomeBoolean(SomeObject): + typedef = types.BooleanType + +class SomeDictionary(SomeObject): + typedef = types.DictionaryType + +class SomeEllipsis(SomeObject): + typedef = types.EllipsisType + +class SomeFile(SomeObject): + typedef = types.FileType + +class SomeFloat(SomeObject): + typedef = types.FloatType + +class SomeFrame(SomeObject): + typedef = types.FrameType + +class SomeFunction(SomeObject): + typedef = types.FunctionType + +class SomeGenerator(SomeObject): + typedef = types.GeneratorType + +class SomeInstance(SomeObject): + typedef = types.InstanceType + +class SomeInt(SomeObject): + typedef = types.IntType + +class SomeLambda(SomeObject): + typedef = types.LambdaType + +class SomeList(SomeObject): + typedef = types.ListType + +class SomeLong(SomeObject): + typedef = types.LongType + +class SomeMethod(SomeObject): + typedef = types.MethodType + +class SomeModule(SomeObject): + typedef = types.ModuleType + +class SomeNone(SomeObject): + typedef = types.NoneType + +class SomeNotImplemented(SomeObject): + typedef = types.NotImplementedType + +class SomeObject(SomeObject): + typedef = types.ObjectType + +class SomeSlice(SomeObject): + typedef = types.SliceType + +class SomeString(SomeObject): + typedef = types.StringType + +class SomeTraceback(SomeObject): + typedef = types.TracebackType + +class SomeTuple(SomeObject): + typedef = types.TupleType + +class SomeType(SomeObject): + typedef = types.TypeType + +class SomeUnboundMethod(SomeObject): + typedef = types.UnboundMethodType + +class SomeUnicode(SomeObject): + typedef = types.UnicodeType + +class SomeXRange(SomeObject): + typedef = types.XRangeType + +class SomeImpossibleValue(SomeObject): + def unionof(self, other): + return other + + def __repr__(self): + return "" + +s_ImpossibleValue = SomeImpossibleValue() +s_None = SomeNone() +s_Ellipsis = SomeEllipsis() + +def guess_type(x): + # this is mostly copy of immutablevalue + if hasattr(x, 'im_self') and x.im_self is None: + x = x.im_func + assert not hasattr(x, 'im_self') + tp = type(x) + if tp is bool: + result = SomeBool() + elif tp is int: + result = SomeInt() + elif issubclass(tp, str): + result = SomeString() + elif tp is unicode: + result = SomeUnicode() + elif tp is tuple: + result = SomeTuple() + #result = SomeTuple(items = [self.immutablevalue(e, need_const) for e in x]) + elif tp is float: + result = SomeFloat() + elif tp is list: + #else: + # listdef = ListDef(self, s_ImpossibleValue) + # for e in x: + # listdef.generalize(self.annotation_from_example(e)) + result = SomeList() + elif tp is dict: +## dictdef = DictDef(self, +## s_ImpossibleValue, +## s_ImpossibleValue, +## is_r_dict = tp is r_dict) +## if tp is r_dict: +## s_eqfn = self.immutablevalue(x.key_eq) +## s_hashfn = self.immutablevalue(x.key_hash) +## dictdef.dictkey.update_rdict_annotations(s_eqfn, +## s_hashfn) +## for ek, ev in x.iteritems(): +## dictdef.generalize_key(self.annotation_from_example(ek)) +## dictdef.generalize_value(self.annotation_from_example(ev)) + result = SomeDict() + elif callable(x): + #if hasattr(x, '__self__') and x.__self__ is not None: + # # for cases like 'l.append' where 'l' is a global constant list + # s_self = self.immutablevalue(x.__self__, need_const) + # result = s_self.find_method(x.__name__) + # if result is None: + # result = SomeObject() + #elif hasattr(x, 'im_self') and hasattr(x, 'im_func'): + # # on top of PyPy, for cases like 'l.append' where 'l' is a + # # global constant list, the find_method() returns non-None + # s_self = self.immutablevalue(x.im_self, need_const) + # result = s_self.find_method(x.im_func.__name__) + #else: + # result = None + #if result is None: + # if (self.annotator.policy.allow_someobjects + # and getattr(x, '__module__', None) == '__builtin__' + # # XXX note that the print support functions are __builtin__ + # and tp not in (types.FunctionType, types.MethodType)): + ## result = SomeObject() + # result.knowntype = tp # at least for types this needs to be correct + # else: + # result = SomePBC([self.getdesc(x)]) + if hasattr(x, 'im_func'): + result = SomeMethod() + else: + result = SomeFunction() + elif hasattr(x, '__class__'): + if x.__class__ is type: + result = SomeClass() + else: + result = SomeInstance() + elif tp is types.ClassType: + result = SomeClass() + elif x is None: + return s_None + else: + result = SomeObject() + # XXX here we might want to consider stuff like + # buffer, slice, etc. etc. Let's leave it for now + return result + +def unionof(first, other): + return first.unionof(other) Modified: py/dist/py/apigen/tracer/testing/test_docgen.py ============================================================================== --- py/dist/py/apigen/tracer/testing/test_docgen.py (original) +++ py/dist/py/apigen/tracer/testing/test_docgen.py Fri Oct 20 18:41:30 2006 @@ -5,14 +5,14 @@ import py import sys -try: - from py.__.apigen.tracer.docstorage import DocStorage - from py.__.apigen.tracer.tracer import Tracer - from py.__.apigen.tracer.testing.runtest import cut_pyc - from py.__.apigen.tracer.description import FunctionDesc - from pypy.annotation import model -except ImportError, s: - py.test.skip("Cannot import: %s" % str(s)) +#try: +from py.__.apigen.tracer.tracer import DocStorage, Tracer +from py.__.apigen.tracer.testing.runtest import cut_pyc +from py.__.apigen.tracer.description import FunctionDesc +from py.__.apigen.tracer import model +# from pypy.annotation import model +#except ImportError, s: +# py.test.skip("Cannot import: %s" % str(s)) #def setup_module(mod): # data_path = py.path.local(mod.__file__).dirpath().join("data") @@ -34,10 +34,10 @@ desc = ds.descs['fun'] inputcells = desc.inputcells assert len(inputcells) == 3 - assert isinstance(inputcells[0], model.SomeFloat) + assert isinstance(inputcells[0], model.SomeUnion) assert isinstance(inputcells[1], model.SomeTuple) - assert isinstance(inputcells[2], model.SomeObject) - assert isinstance(desc.retval, model.SomeChar) + assert isinstance(inputcells[2], model.SomeUnion) + assert isinstance(desc.retval, model.SomeString) cs = sorted(desc.call_sites.keys()) assert len(cs) == 2 f_name = cut_pyc(__file__) @@ -74,7 +74,8 @@ inputcells = desc.fields['__init__'].inputcells assert len(inputcells) == 2 assert isinstance(inputcells[0], model.SomeInstance) - assert inputcells[0].classdef.classdesc.pyobj is SomeClass + #assert inputcells[0].classdef.classdesc.pyobj is SomeClass + # XXX: should work assert isinstance(inputcells[1], model.SomeString) f_name = __file__ if f_name.endswith('.pyc'): @@ -88,11 +89,12 @@ 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 inputcells[0].classdef.classdesc.pyobj is SomeClass + # XXX should work + assert isinstance(inputcells[1], model.SomeInt) assert isinstance(inputcells[2], model.SomeFloat) assert isinstance(inputcells[3], model.SomeList) - assert isinstance(desc.fields['exposed_method'].retval, model.SomeChar) + assert isinstance(desc.fields['exposed_method'].retval, model.SomeString) def other_fun(): pass Modified: py/dist/py/apigen/tracer/testing/test_magic.py ============================================================================== --- py/dist/py/apigen/tracer/testing/test_magic.py (original) +++ py/dist/py/apigen/tracer/testing/test_magic.py Fri Oct 20 18:41:30 2006 @@ -5,7 +5,7 @@ from py.__.apigen.tracer.magic import trace, get_storage, stack_copier, \ DocStorageKeeper from py.__.apigen.tracer.docstorage import DocStorage -from pypy.annotation import model +from py.__.apigen.tracer import model #def setup_function(f): # DocStorageKeeper.set_storage(DocStorage().from_dict({})) @@ -22,10 +22,10 @@ assert len(ds.descs.keys()) == 2 desc = ds.descs['fun'] inputcells = desc.inputcells - assert isinstance(inputcells[0], model.SomeInteger) - assert isinstance(inputcells[1], model.SomeInteger) - assert isinstance(inputcells[2], model.SomeInteger) - assert isinstance(desc.retval, model.SomeChar) + assert isinstance(inputcells[0], model.SomeInt) + assert isinstance(inputcells[1], model.SomeInt) + assert isinstance(inputcells[2], model.SomeInt) + assert isinstance(desc.retval, model.SomeString) def g(x): return f(x) Added: py/dist/py/apigen/tracer/testing/test_model.py ============================================================================== --- (empty file) +++ py/dist/py/apigen/tracer/testing/test_model.py Fri Oct 20 18:41:30 2006 @@ -0,0 +1,16 @@ + +""" test_model - our (very simple) type system +model tests +""" + +from py.__.test.tracer import model + +import types +import py + +def test_basic(): + py.test.skip("Checks nothing") + for i in dir(types): + if i.endswith('Type'): + attr = getattr(types, i) + assert model.typedefs[attr].typedef is attr Modified: py/dist/py/apigen/tracer/tracer.py ============================================================================== --- py/dist/py/apigen/tracer/tracer.py (original) +++ py/dist/py/apigen/tracer/tracer.py Fri Oct 20 18:41:30 2006 @@ -6,9 +6,8 @@ import sys import types -# We have to steal as much as possible from union of types -# from pypy's code. -# BLAH BLAH BLAH +from py.__.apigen.tracer.description import FunctionDesc +from py.__.apigen.tracer.docstorage import DocStorage class UnionError(Exception): pass @@ -23,15 +22,14 @@ def _tracer(self, frame, event, arg): - # perform actual tracing + # perform actuall tracing frame = py.code.Frame(frame) if event == 'call': assert arg is None - self.docstorage.consider_call(frame, - py.code.Frame(sys._getframe(2))) + self.docstorage.consider_call(frame, py.code.Frame(sys._getframe(2))) elif event == 'return': self.docstorage.consider_return(frame, arg) - + return self._tracer def start_tracing(self): From fijal at codespeak.net Sat Oct 21 11:29:33 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Sat, 21 Oct 2006 11:29:33 +0200 (CEST) Subject: [py-svn] r33518 - in py/dist/py/apigen/tracer: . testing Message-ID: <20061021092933.9CEF310063@code0.codespeak.net> Author: fijal Date: Sat Oct 21 11:29:19 2006 New Revision: 33518 Added: py/dist/py/apigen/tracer/testing/test_desc.py (contents, props changed) Modified: py/dist/py/apigen/tracer/description.py py/dist/py/apigen/tracer/docstorage.py py/dist/py/apigen/tracer/model.py py/dist/py/apigen/tracer/testing/test_docgen.py py/dist/py/apigen/tracer/testing/test_model.py Log: Several improvements, more tests. Starts looking "quite-ok", ready for pylib tests. Modified: py/dist/py/apigen/tracer/description.py ============================================================================== --- py/dist/py/apigen/tracer/description.py (original) +++ py/dist/py/apigen/tracer/description.py Sat Oct 21 11:29:19 2006 @@ -119,15 +119,34 @@ # other variables encountered here def getcode(self): + # This is a hack. We're trying to return as much close to __init__ + # of us as possible, but still hashable object + if hasattr(self.pyobj, '__init__'): + if hasattr(self.pyobj.__init__, 'im_func'): + result = self.pyobj.__init__.im_func.func_code + else: + result = self.pyobj.__init__ + else: + result = self.pyobj try: - return self.pyobj.__init__.im_func.func_code - except AttributeError: - return self.pyobj.__name__ + hash(result) + except KeyboardInterrupt, SystemExit: + raise + except: # UUuuuu bare except here. What can it really rise??? + try: + hash(self.pyobj) + return self.pyobj + except: + return self + return result code = property(getcode) def consider_call(self, inputcells): - md = MethodDesc(self.name + '.__init__', self.pyobj.__init__) - self.fields['__init__'] = md + if '__init__' in self.fields: + md = self.fields['__init__'] + else: + md = MethodDesc(self.name + '.__init__', self.pyobj.__init__) + self.fields['__init__'] = md md.consider_call(inputcells) def consider_return(self, arg): Modified: py/dist/py/apigen/tracer/docstorage.py ============================================================================== --- py/dist/py/apigen/tracer/docstorage.py (original) +++ py/dist/py/apigen/tracer/docstorage.py Sat Oct 21 11:29:19 2006 @@ -62,6 +62,9 @@ self.make_cache() return self + # XXX: This function becomes slowly outdated and even might go away at some + # point. The question is whether we want to use tracer.magic or not + # at all def add_desc(self, name, value, **kwargs): key = name count = 1 @@ -69,10 +72,13 @@ key = "%s_%d" % (name, count) count += 1 key, desc = self.make_desc(key, value, **kwargs) - self.descs[key] = desc - self.desc_cache[desc] = desc - return desc - + if key: + self.descs[key] = desc + self.desc_cache[desc] = desc + return desc + else: + return None + def make_desc(self, key, value, **kwargs): if isinstance(value, types.FunctionType): desc = FunctionDesc(key, value, **kwargs) Modified: py/dist/py/apigen/tracer/model.py ============================================================================== --- py/dist/py/apigen/tracer/model.py (original) +++ py/dist/py/apigen/tracer/model.py Sat Oct 21 11:29:19 2006 @@ -12,6 +12,7 @@ typedef = types.ObjectType def __repr__(self): + return "<%s>" % self.__class__.__name__[4:] return str(self.typedef)[7:-2] def unionof(self, other): @@ -47,8 +48,8 @@ class SomeBuiltinFunction(SomeObject): typedef = types.BuiltinFunctionType -class SomeBuiltinMethod(SomeObject): - typedef = types.BuiltinMethodType +#class SomeBuiltinMethod(SomeObject): +# typedef = types.BuiltinMethodType class SomeClass(SomeObject): typedef = types.ClassType @@ -65,12 +66,6 @@ class SomeDict(SomeObject): typedef = types.DictType -class SomeBoolean(SomeObject): - typedef = types.BooleanType - -class SomeDictionary(SomeObject): - typedef = types.DictionaryType - class SomeEllipsis(SomeObject): typedef = types.EllipsisType @@ -161,7 +156,7 @@ assert not hasattr(x, 'im_self') tp = type(x) if tp is bool: - result = SomeBool() + result = SomeBoolean() elif tp is int: result = SomeInt() elif issubclass(tp, str): @@ -193,6 +188,8 @@ ## dictdef.generalize_key(self.annotation_from_example(ek)) ## dictdef.generalize_value(self.annotation_from_example(ev)) result = SomeDict() + elif tp is types.ModuleType: + result = SomeModule() elif callable(x): #if hasattr(x, '__self__') and x.__self__ is not None: # # for cases like 'l.append' where 'l' is a global constant list @@ -216,19 +213,23 @@ # result.knowntype = tp # at least for types this needs to be correct # else: # result = SomePBC([self.getdesc(x)]) - if hasattr(x, 'im_func'): + if tp is types.BuiltinFunctionType or tp is types.BuiltinMethodType: + result = SomeBuiltinFunction() + elif hasattr(x, 'im_func'): result = SomeMethod() - else: + elif hasattr(x, 'func_code'): result = SomeFunction() - elif hasattr(x, '__class__'): - if x.__class__ is type: + elif hasattr(x, '__class__'): + if x.__class__ is type: + result = SomeClass() + else: + result = SomeInstance() + elif tp is types.ClassType: result = SomeClass() - else: - result = SomeInstance() - elif tp is types.ClassType: - result = SomeClass() elif x is None: return s_None + elif hasattr(x, '__class__'): + result = SomeInstance() else: result = SomeObject() # XXX here we might want to consider stuff like Added: py/dist/py/apigen/tracer/testing/test_desc.py ============================================================================== --- (empty file) +++ py/dist/py/apigen/tracer/testing/test_desc.py Sat Oct 21 11:29:19 2006 @@ -0,0 +1,25 @@ + +""" Some additional tests about descriptions +""" + +from py.__.apigen.tracer.description import * + +class A: + pass + +class B(object): + def __init__(self): + pass + +class C(object): + pass + +class D: + def __init__(self): + pass + +def test_getcode(): + assert hash(ClassDesc("a", A).code) + assert hash(ClassDesc("b", B).code) + assert hash(ClassDesc("c", C).code) + assert hash(ClassDesc("d", D).code) Modified: py/dist/py/apigen/tracer/testing/test_docgen.py ============================================================================== --- py/dist/py/apigen/tracer/testing/test_docgen.py (original) +++ py/dist/py/apigen/tracer/testing/test_docgen.py Sat Oct 21 11:29:19 2006 @@ -46,7 +46,7 @@ assert cs[1].filename == f_name assert cs[1].lineno == test_basic.func_code.co_firstlineno + 6 -class SomeClass(object): +class AClass(object): """ Class docstring """ def __init__(self, b="blah"): @@ -63,14 +63,14 @@ return "z" def test_class(): - descs = {'SomeClass':SomeClass} + descs = {'AClass':AClass} ds = DocStorage().from_dict(descs) t = Tracer(ds) t.start_tracing() - s = SomeClass() + s = AClass() s.exposed_method(1, 2., [1,2,3]) t.end_tracing() - desc = ds.descs['SomeClass'] + desc = ds.descs['AClass'] inputcells = desc.fields['__init__'].inputcells assert len(inputcells) == 2 assert isinstance(inputcells[0], model.SomeInstance) @@ -121,3 +121,17 @@ desc = ds.descs["other_fun"] assert len(desc.call_sites.keys()) == 1 assert isinstance(desc.call_sites.values()[0], py.code.Frame) + +class A(object): + pass + +class B: + pass + +def test_without_init(): + ds = DocStorage().from_dict({'A':A, 'B':B}) + t = Tracer(ds) + t.start_tracing() + x = A() + y = B() + t.end_tracing() Modified: py/dist/py/apigen/tracer/testing/test_model.py ============================================================================== --- py/dist/py/apigen/tracer/testing/test_model.py (original) +++ py/dist/py/apigen/tracer/testing/test_model.py Sat Oct 21 11:29:19 2006 @@ -3,14 +3,49 @@ model tests """ -from py.__.test.tracer import model +from py.__.apigen.tracer.model import * import types import py def test_basic(): - py.test.skip("Checks nothing") - for i in dir(types): - if i.endswith('Type'): - attr = getattr(types, i) - assert model.typedefs[attr].typedef is attr + """ This tests checks every object that we might want + to track + """ + def check_guess(val, t): + assert isinstance(guess_type(val), t) + + check_guess(3, SomeInt) + check_guess(3., SomeFloat) + check_guess(True, SomeBoolean) + check_guess(lambda x: None, SomeFunction) + + class A: + pass + + check_guess(A, SomeClass) + check_guess(A(), SomeInstance) + + class B(object): + def meth(self): + pass + + class C(object): + def __call__(self): + pass + + check_guess(B, SomeClass) + check_guess(B.meth, SomeFunction) + check_guess(B(), SomeInstance) + check_guess(B().meth, SomeMethod) + check_guess([1], SomeList) + check_guess(None, SomeNone) + check_guess((1,), SomeTuple) + check_guess(C(), SomeInstance) + import sys + check_guess(sys, SomeModule) + check_guess({}, SomeDict) + check_guess(sys.exc_info, SomeBuiltinFunction) + +def test_union(): + py.test.skip("We're not thinking about type unions right now") From fijal at codespeak.net Sat Oct 21 12:29:39 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Sat, 21 Oct 2006 12:29:39 +0200 (CEST) Subject: [py-svn] r33519 - py/dist/py/apigen/rest/testing Message-ID: <20061021102939.9FA9010063@code0.codespeak.net> Author: fijal Date: Sat Oct 21 12:29:37 2006 New Revision: 33519 Modified: py/dist/py/apigen/rest/testing/test_rest.py Log: Added skipped failing test. Modified: py/dist/py/apigen/rest/testing/test_rest.py ============================================================================== --- py/dist/py/apigen/rest/testing/test_rest.py (original) +++ py/dist/py/apigen/rest/testing/test_rest.py Sat Oct 21 12:29:37 2006 @@ -19,6 +19,9 @@ def setup_module(mod): mod.temppath = py.test.ensuretemp('restgen') +def fun_(): + pass + class SomeClass(object): """Some class definition""" @@ -28,7 +31,7 @@ def method(self, a, b, c): """method docstring""" return a + b + c - + class SomeSubClass(SomeClass): """Some subclass definition""" @@ -200,3 +203,16 @@ # assert data.find('Function: SomeClass.__init__') < data.find( # 'Function: SomeClass.method') + def test_som_fun(self): + py.test.skip("Failing") + descs = {'fun_':fun_} + ds = DocStorage().from_dict(descs) + t = Tracer(ds) + t.start_tracing() + fun_() + t.end_tracing() + lg = DirectPaste() + tempdir = temppath.ensure("some_fun", dir=True) + r = RestGen(ds, lg, DirWriter(tempdir)) + r.write() + self.check_rest(tempdir) From hpk at codespeak.net Sun Oct 22 12:43:45 2006 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 22 Oct 2006 12:43:45 +0200 (CEST) Subject: [py-svn] r33530 - py/dist/py/documentation Message-ID: <20061022104345.79FB010070@code0.codespeak.net> Author: hpk Date: Sun Oct 22 12:43:43 2006 New Revision: 33530 Added: py/dist/py/documentation/releaseplan.txt (contents, props changed) Log: release related planning/task information Added: py/dist/py/documentation/releaseplan.txt ============================================================================== --- (empty file) +++ py/dist/py/documentation/releaseplan.txt Sun Oct 22 12:43:43 2006 @@ -0,0 +1,94 @@ +========================================================= +py lib release related tasks - notes and plans $Date$ +========================================================= + +Current time plan regarding releases: + +- 0.9 stable release end November/early December 2006 + +- 1.0 stable release Beginning February 2007 + incorporating apigen tool and fully featured + py.test tool, including distribution among + many hosts. + +py lib 0.9 release blockers: +======================================= + +* document and sort out py.log API approaches + +* have APIGEN generate nice Web documentation of the py lib API + +* support packaging for Debian and possibly other distros, + particularly regarding C modules + +* document and refine "py.*" helper tools. + +* go through Issue tracker and determine 0.9 relevant issues + +* get py.test (and distributed) to a sufficiently good/usable state. + +* refine and implement `releasescheme`_ + +.. _releasescheme: releasescheme.html + +Task planning +=================== + +APIGEN Tool (py.apigen) +------------------------------ + +main developers: Maciej, Guido: reviewers: Holger, Carl + +1st goal: have a clean abstractions on iterating a package's to-be-documented + namespaces (modules), classes and functions, being able + to extract information (docstrings, arguments) in a nice manner. + + Implement a ReST backend which represents information + extracted from apigen. For now, the new "apigen" namespace + should not be exported, but only be available via + py.__.apigen style imports. + + the ReST backend, the API info extraction tool, + (and extending py.test to collect additional information) + should all be completely separated tasks. py.test + is not involved in this first goal, though. + + important: be sure to make things configurable + and use a nice local Config() object from the + beginning so that variations (initpkg'ed versus + otherwise-specified APIs) can be incorporated + somewhat obviously later. + +2nd goal: incorporate information obtained from tests + XXX how should py.test write out information? Create an issue, + handle as separate task. + +3rd goal: maybe: write a pygame frontend, integrate it into PyPy :) + + +py.test distribution +--------------------------- + +main developer: Maciej, reviewer: Holger, Carl + +1st Goal: have basically all normal py.test features present in py.test distributed + merge current code into one approach (making "local" execution + a special case of distributed execution), be careful + that conftest.py config handling continues to work, + particularly for PyPy. + +py.test ReST / HTML / PDF backend +------------------------------------- + +main developer: Guido, reviewer: Carl, Maciej + +1st Goal: do a ReST backend (maybe about the py.test/distributed merge) + probably related to the above py.test distribution task + (the distributed testing approach is + better for having different reporters, so basing + it on the current py.test mainline approach does + not make too much sense). + Do some incremental steps, also to see if + the upcoming py.rest API (see py APIGEN) + is nice enough etc. + From hpk at codespeak.net Sun Oct 22 12:44:24 2006 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 22 Oct 2006 12:44:24 +0200 (CEST) Subject: [py-svn] r33531 - py/dist/py/documentation Message-ID: <20061022104424.5D32310070@code0.codespeak.net> Author: hpk Date: Sun Oct 22 12:44:19 2006 New Revision: 33531 Modified: py/dist/py/documentation/TODO.txt (props changed) py/dist/py/documentation/api.txt (props changed) py/dist/py/documentation/code_template.txt (props changed) py/dist/py/documentation/coding-style.txt (props changed) py/dist/py/documentation/contact.txt (props changed) py/dist/py/documentation/execnet.txt (props changed) py/dist/py/documentation/future.txt (props changed) py/dist/py/documentation/getting-started.txt (props changed) py/dist/py/documentation/greenlet.txt (props changed) py/dist/py/documentation/home.txt (props changed) py/dist/py/documentation/index.txt (props changed) py/dist/py/documentation/links.txt (props changed) py/dist/py/documentation/log.txt (props changed) py/dist/py/documentation/maciej-implementation-notes.txt (props changed) py/dist/py/documentation/misc.txt (props changed) py/dist/py/documentation/planning.txt (props changed) py/dist/py/documentation/releasescheme.txt (props changed) py/dist/py/documentation/test.txt (props changed) py/dist/py/documentation/why_py.txt (props changed) py/dist/py/documentation/xml.txt (props changed) Log: add Date, Id and Rev as svn:keywords From hpk at codespeak.net Sun Oct 22 12:55:16 2006 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 22 Oct 2006 12:55:16 +0200 (CEST) Subject: [py-svn] r33532 - py/dist/py/documentation Message-ID: <20061022105516.1511D10072@code0.codespeak.net> Author: hpk Date: Sun Oct 22 12:55:12 2006 New Revision: 33532 Modified: py/dist/py/documentation/releaseplan.txt Log: amending some items from py-dev/Issue32 in order to close that. Modified: py/dist/py/documentation/releaseplan.txt ============================================================================== --- py/dist/py/documentation/releaseplan.txt (original) +++ py/dist/py/documentation/releaseplan.txt Sun Oct 22 12:55:12 2006 @@ -18,8 +18,12 @@ * have APIGEN generate nice Web documentation of the py lib API -* support packaging for Debian and possibly other distros, - particularly regarding C modules +* support packaging with TARs and for Debian and possibly + other distros, particularly look into C module issues. + +* support both setup.py and setuptools (eggs?) installs + +* support for installs from pypi (and listing py lib there) * document and refine "py.*" helper tools. @@ -29,6 +33,9 @@ * refine and implement `releasescheme`_ +* ensure compatibility with Python 2.2 - 2.5 + (possibly update py/compat package) + .. _releasescheme: releasescheme.html Task planning From hpk at codespeak.net Sun Oct 22 12:56:43 2006 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 22 Oct 2006 12:56:43 +0200 (CEST) Subject: [py-svn] r33533 - py/dist/py/documentation Message-ID: <20061022105643.6F99110072@code0.codespeak.net> Author: hpk Date: Sun Oct 22 12:56:41 2006 New Revision: 33533 Modified: py/dist/py/documentation/releaseplan.txt Log: ah, and the win32/linux/osx issue Modified: py/dist/py/documentation/releaseplan.txt ============================================================================== --- py/dist/py/documentation/releaseplan.txt (original) +++ py/dist/py/documentation/releaseplan.txt Sun Oct 22 12:56:41 2006 @@ -36,6 +36,8 @@ * ensure compatibility with Python 2.2 - 2.5 (possibly update py/compat package) +* ensure that py.tests pass on win32, linux and OSX + .. _releasescheme: releasescheme.html Task planning From fijal at codespeak.net Sun Oct 22 15:46:00 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Sun, 22 Oct 2006 15:46:00 +0200 (CEST) Subject: [py-svn] r33536 - py/dist/py/documentation Message-ID: <20061022134600.AA17510070@code0.codespeak.net> Author: fijal Date: Sun Oct 22 15:45:58 2006 New Revision: 33536 Added: py/dist/py/documentation/test-distributed.txt (contents, props changed) Log: Added document describing distributed tests implementation details. Added: py/dist/py/documentation/test-distributed.txt ============================================================================== --- (empty file) +++ py/dist/py/documentation/test-distributed.txt Sun Oct 22 15:45:58 2006 @@ -0,0 +1,69 @@ +======================================================= +The ``py.test`` distributed features - developer's view +======================================================= + +.. contents:: +.. sectnum:: + +starting point: new session objects +=================================== + +There are two new session objects, both located in `rsession.py`_. `RSession` +and `LSession`. `RSession` is responsible for running tests distributedly, +while `LSession` runs tests locally, but with different implementation +details (as well as using details). Options available are varying, I'll try to +write them down later. + + +Abstraction layers: +=================== + +Main difference between normal session and `(L|R)Session` is split into +reporter and actual session. Reporter is an object or function (also defined +in `rsession.py`_) which gets all the events (listed in `report.py`_) and +represents them to the user. Default reporter is command line one (to be +precise, there are different reporters for L and R Sessions because of +different needs), but there is existing reporter which runs web server +(`web.py`_)and communicates over XMLHttp requests with the browser. + +Writing down new reporter is relatively easy, way easier than writing session +from a scratch, so one can write down GTK reporter and whatnot. + +Another abstraction layer (present only in `LSession`) is runner. Actual +object which runs tests. There are two existing runners: box_runner and +plain_runner, both defined in `local.py`_. box_runner can run tests +after fork, so signals are properly handled (you get error instead of +crash of all the program), plain_runner is running in local process, needed +for --pdb command line option for example. There are several different runners +in my mind, like exec_runner (for implementing --exec), apigen_runner, for +creating documentation, benchamrk_runner for benchmarks etc. etc. + +.. _`rsession.py`: http://codespeak.net/svn/py/dist/py/test/rsession/rsession.py +.. _`report.py`: http://codespeak.net/svn/py/dist/py/test/rsession/report.py +.. _`web.py`: http://codespeak.net/svn/py/dist/py/test/rsession/web.py +.. _`local.py`: http://codespeak.net/svn/py/dist/py/test/rsession/local.py + +Implemented features: +===================== + +Actually most of normal py.test features are implemented in distributed +version. These are command line options: -v -x -k --pdb (only for LSession). +Missing options are -s (makes sense for LSession, easy), --exec (moderatly +easy for L/R Sessions), --pdb for RSession (with screen, unsure if it'll +be implemented). Quite missing is testing LSession under OSX/Windows or other +machines than mine. + +Unique features: +================ + +Besides distribution (which is quite unique for testing framework I guess) +there is boxing code (`box.py`_) which makes possible catching SIGSEGV and +other strange stuff as well as separates memory usage between tests (ie +we're quite sure that memory is not kept allocated during test runs) as long +as it's not gathered during setup/teardown stuff. + +Another unique feature is web server which allows you to track down tests and +how they goes (but I'm not sure whether anyone really wants to use it - it's +just a cool feature). + +.. _`box.py`: http://codespeak.net/svn/py/dist/py/test/rsession/box.py From guido at codespeak.net Sun Oct 22 19:43:34 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Sun, 22 Oct 2006 19:43:34 +0200 (CEST) Subject: [py-svn] r33537 - py/dist/py/apigen/rest/testing Message-ID: <20061022174334.5FBA710070@code0.codespeak.net> Author: guido Date: Sun Oct 22 19:43:32 2006 New Revision: 33537 Modified: py/dist/py/apigen/rest/testing/test_rest.py Log: Fixed test that wasn't updated after adding code to escape colons. Modified: py/dist/py/apigen/rest/testing/test_rest.py ============================================================================== --- py/dist/py/apigen/rest/testing/test_rest.py (original) +++ py/dist/py/apigen/rest/testing/test_rest.py Sun Oct 22 19:43:32 2006 @@ -188,17 +188,19 @@ if not tempfile.check(): py.test.skip('depends on previous test, which failed') data = tempfile.read() + print data # index should be above the rest - assert data.find('Exported classes:') > -1 - assert data.find('Exported classes:') < data.find('Function: fun') - assert data.find('Exported classes:') < data.find('Class: SomeClass') + assert data.find('Exported classes\\:') > -1 + assert data.find('Exported classes\\:') < data.find('Function\\: fun') + assert data.find('Exported classes\\:') < data.find( + 'Class\\: SomeClass') # function definitions should be above class ones - assert data.find('Function: fun') < data.find('Class: SomeClass') + assert data.find('Function\\: fun') < data.find('Class\\: SomeClass') # class method definitions should be below the class defs - assert data.find('Class: SomeClass') < data.find( - 'Function: SomeClass.method') + assert data.find('Class\\: SomeClass') < data.find( + 'Function\\: SomeClass.method') # __init__ should be above other methods - assert data.find('Function: SomeClass.__init__') > -1 + assert data.find('Function\\: SomeClass.__init__') > -1 # XXX in the future... # assert data.find('Function: SomeClass.__init__') < data.find( # 'Function: SomeClass.method') From cfbolz at codespeak.net Sun Oct 22 22:03:12 2006 From: cfbolz at codespeak.net (cfbolz at codespeak.net) Date: Sun, 22 Oct 2006 22:03:12 +0200 (CEST) Subject: [py-svn] r33538 - py/dist/py/documentation Message-ID: <20061022200312.0A2D71006E@code0.codespeak.net> Author: cfbolz Date: Sun Oct 22 22:03:10 2006 New Revision: 33538 Modified: py/dist/py/documentation/future.txt py/dist/py/documentation/getting-started.txt py/dist/py/documentation/why_py.txt Log: updated some links: used newer link for zope 3 and armin's memoryfs. removed link to amk's blog since I cannot find his post (and I searched for a while). Modified: py/dist/py/documentation/future.txt ============================================================================== --- py/dist/py/documentation/future.txt (original) +++ py/dist/py/documentation/future.txt Sun Oct 22 22:03:10 2006 @@ -359,7 +359,7 @@ `reiserfs v4 features`_ at the Filesystem level but that is a can of subsequent worms). -.. _`memoryfs`: http://codespeak.net/svn/user/arigo/hack/pylufs/memoryfs.py +.. _`memoryfs`: http://codespeak.net/svn/user/arigo/hack/pyfuse/memoryfs.py .. _`dictfs`: http://codespeak.net/pipermail/py-dev/2005-January/000191.html .. _`pylufs`: http://codespeak.net/svn/user/arigo/hack/pylufs/ .. _`reiserfs v4 features`: http://www.namesys.com/v4/v4.html Modified: py/dist/py/documentation/getting-started.txt ============================================================================== --- py/dist/py/documentation/getting-started.txt (original) +++ py/dist/py/documentation/getting-started.txt Sun Oct 22 22:03:10 2006 @@ -102,7 +102,7 @@ some of which projects like twisted_ or zope3_ have adopted in similar ways. -.. _`zope3`: http://www.zope3.org +.. _`zope3`: http://zope3.zwiki.org/ .. _twisted: http://www.twistedmatrix.org .. _future: future.html Modified: py/dist/py/documentation/why_py.txt ============================================================================== --- py/dist/py/documentation/why_py.txt (original) +++ py/dist/py/documentation/why_py.txt Sun Oct 22 22:03:10 2006 @@ -49,8 +49,6 @@ can't solve them all at once but you will find that the above points currently drive the development of the py lib. -And it seems there are `related thoughts from AMK`_ about -standard library development. Experimenting with new days, appreciating the existing ones ----------------------------------------------------------- @@ -272,7 +270,6 @@ .. _future: future.html .. _`py.test tool and library`: test.html .. _`py API`: api.html -.. _`related thoughts from AMK`: http://www.amk.ca/diary/archives/cat_python.html .. _`great work that Fred L. Drake, Jr. is doing`: http://www.python.org/doc/2.3.4/lib/lib.html -- From fijal at codespeak.net Sun Oct 22 22:38:00 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Sun, 22 Oct 2006 22:38:00 +0200 (CEST) Subject: [py-svn] r33539 - py/dist/py/documentation Message-ID: <20061022203800.961CB1006E@code0.codespeak.net> Author: fijal Date: Sun Oct 22 22:37:43 2006 New Revision: 33539 Modified: py/dist/py/documentation/test.txt Log: added usefull note. Modified: py/dist/py/documentation/test.txt ============================================================================== --- py/dist/py/documentation/test.txt (original) +++ py/dist/py/documentation/test.txt Sun Oct 22 22:37:43 2006 @@ -182,7 +182,9 @@ Besides making it easier to compare test output this allows multi-stage tests where you can rely on your test to iteratively -build up a test structure. +build up a test structure. + +`Note: This is not true for distributed tests` useful tracebacks, recursion detection -------------------------------------- From hpk at codespeak.net Mon Oct 23 13:05:30 2006 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 23 Oct 2006 13:05:30 +0200 (CEST) Subject: [py-svn] r33548 - py/dist/py/documentation Message-ID: <20061023110530.29AE810063@code0.codespeak.net> Author: hpk Date: Mon Oct 23 13:05:28 2006 New Revision: 33548 Modified: py/dist/py/documentation/releaseplan.txt Log: fixed ReST problem with expanded svn keyword Modified: py/dist/py/documentation/releaseplan.txt ============================================================================== --- py/dist/py/documentation/releaseplan.txt (original) +++ py/dist/py/documentation/releaseplan.txt Mon Oct 23 13:05:28 2006 @@ -1,7 +1,9 @@ ========================================================= -py lib release related tasks - notes and plans $Date$ +py lib release related tasks - notes and plans ========================================================= +$Date$ + Current time plan regarding releases: - 0.9 stable release end November/early December 2006 From guido at codespeak.net Mon Oct 23 14:24:24 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Mon, 23 Oct 2006 14:24:24 +0200 (CEST) Subject: [py-svn] r33554 - in py/dist/py/rst: . testing Message-ID: <20061023122424.1E6BA1006E@code0.codespeak.net> Author: guido Date: Mon Oct 23 14:24:22 2006 New Revision: 33554 Modified: py/dist/py/rst/rst.py py/dist/py/rst/testing/test_rst.py Log: Literal block contents is no longer escaped. Modified: py/dist/py/rst/rst.py ============================================================================== --- py/dist/py/rst/rst.py (original) +++ py/dist/py/rst/rst.py Mon Oct 23 14:24:22 2006 @@ -50,6 +50,8 @@ allowed_child = {} defaults = {} + _reg_whitespace = py.std.re.compile('\s+') + def __init__(self, *args, **kwargs): self.parent = None self.children = [] @@ -87,13 +89,13 @@ def __setitem__(self, item, value): self.children[item] = value - def text(self): + def text(self, escape=True): """ return a ReST string representation of the node """ - return self.sep.join([child.text() for child in self.children]) + return self.sep.join([child.text(escape) for child in self.children]) - def wordlist(self): + def wordlist(self, escape=True): """ return a list of ReST strings for this node and its children """ - return [self.text()] + return [self.text(escape)] class Rest(AbstractNode): sep = "\n\n" @@ -112,10 +114,10 @@ link_texts.append(".. _`%s`: %s" % (escape(link), escape(target))) return "\n" + "\n".join(link_texts) + "\n\n" - def text(self): + def text(self, escape=True): outcome = [] for child in self.children: - outcome.append(child.text()) + outcome.append(child.text(escape)) text = self.sep.join(outcome) + "\n" # trailing newline return text + self.render_links() @@ -128,7 +130,7 @@ self.width = width super(Transition, self).__init__(*args, **kwargs) - def text(self): + def text(self, escape=True): return (self.width - 1) * self.char class Paragraph(AbstractNode): @@ -145,10 +147,10 @@ args[num] = Text(arg) super(Paragraph, self).__init__(*args, **kwargs) - def text(self): + def text(self, escape=True): texts = [] for child in self.children: - texts += child.wordlist() + texts += child.wordlist(escape) buf = [] outcome = [] @@ -181,9 +183,9 @@ indent = " " sep = "" - def text(self): - all_txt = AbstractNode.text(self) - all_txts = all_txt.split("\n") + def text(self, escape=True): + all_txt = AbstractNode.text(self, escape=False) + all_txts = all_txt.split('\n') return '::\n\n%s' % ("\n".join([self.indent + i for i in all_txts]),) @@ -192,8 +194,8 @@ belowchar = "" abovechar = "" - def text(self): - txt = Paragraph.text(self) + def text(self, escape=True): + txt = Paragraph.text(self, escape) lines = [] if self.abovechar: lines.append(self.abovechar * len(txt)) @@ -209,13 +211,18 @@ def __init__(self, _text): self._text = _text - def text(self): - return self.start + escape(self._text) + self.end + def text(self, escape=True): + text = self._text + if escape: + text = globals()['escape'](text) + return self.start + text + self.end class Text(AbstractText): - _reg_whitespace = py.std.re.compile('\s+') - def wordlist(self): - return self._reg_whitespace.split(escape(self._text)) + def wordlist(self, escape=True): + text = self._text + if escape: + text = globals()['escape'](text) + return self._reg_whitespace.split(text) class Em(AbstractText): start = "*" @@ -244,11 +251,11 @@ class ListItem(Paragraph): item_char = "*" - def text(self): + def text(self, escape=True): oldindent = self.indent self.indent = oldindent + ' ' try: - txt = Paragraph.text(self) + txt = Paragraph.text(self, escape) finally: self.indent = oldindent txt = self.item_char + txt[1:] @@ -272,11 +279,11 @@ self.target = target self.rest = None - def text(self): + def text(self, escape=True): if self.rest is None: self.rest = self.find_rest() self.rest.links[self._text] = self.target - return AbstractText.text(self) + return AbstractText.text(self, escape) def find_rest(self): # XXX little overkill, but who cares... Modified: py/dist/py/rst/testing/test_rst.py ============================================================================== --- py/dist/py/rst/testing/test_rst.py (original) +++ py/dist/py/rst/testing/test_rst.py Mon Oct 23 14:24:22 2006 @@ -7,11 +7,15 @@ def test_escape(): txt = Paragraph('*escape* ``test``').text() assert txt == '\\*escape\\* \\`\\`test\\`\\`' - txt = Strong('*strong*').text() + txt = Paragraph(Strong('*strong*')).text() assert txt == '**\\*strong\\***' txt = Rest(Paragraph(Link('foo[bar]', 'foo[bar]'))).text() assert txt == "`foo\\[bar]`_\n\n.. _`foo\\[bar]`: foo\\[bar]\n\n" +def test_escape_literal(): + txt = LiteralBlock('*escape* ``test``').text() + assert txt == '::\n\n *escape* ``test``' + def test_illegal_parent(): Rest(Paragraph(Text('spam'))) py.test.raises(RestError, 'Rest(Text("spam"))') @@ -81,7 +85,7 @@ :: - def fun()\\: + def fun(): some Paragraph From fijal at codespeak.net Mon Oct 23 17:30:25 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Mon, 23 Oct 2006 17:30:25 +0200 (CEST) Subject: [py-svn] r33605 - in py/dist/py/apigen: rest tracer tracer/testing Message-ID: <20061023153025.21B6810070@code0.codespeak.net> Author: fijal Date: Mon Oct 23 17:30:23 2006 New Revision: 33605 Modified: py/dist/py/apigen/rest/genrest.py py/dist/py/apigen/tracer/description.py py/dist/py/apigen/tracer/docstorage.py py/dist/py/apigen/tracer/model.py py/dist/py/apigen/tracer/testing/test_model.py Log: Some minor changes and fixes. Right now it seems that it needs some serious testing (it does not crash on pylib though). Modified: py/dist/py/apigen/rest/genrest.py ============================================================================== --- py/dist/py/apigen/rest/genrest.py (original) +++ py/dist/py/apigen/rest/genrest.py Mon Oct 23 17:30:23 2006 @@ -220,7 +220,12 @@ #call_sites.append(LiteralBlock(call_site.source)) # XXX: For now, we just paste here the filename of that #call_sites.append(Paragraph(link_str)) - source = frame.code.source() + try: + source = frame.code.source() + except KeyboardInterrupt, SystemError: + raise + except: + source = "*Cannot get source*" lines = [] for num, line in enumerate(source): if num == call_site.lineno - frame.code.firstlineno - 1: Modified: py/dist/py/apigen/tracer/description.py ============================================================================== --- py/dist/py/apigen/tracer/description.py (original) +++ py/dist/py/apigen/tracer/description.py Mon Oct 23 17:30:23 2006 @@ -122,7 +122,8 @@ # This is a hack. We're trying to return as much close to __init__ # of us as possible, but still hashable object if hasattr(self.pyobj, '__init__'): - if hasattr(self.pyobj.__init__, 'im_func'): + if hasattr(self.pyobj.__init__, 'im_func') and \ + hasattr(self.pyobj.__init__.im_func, 'func_code'): result = self.pyobj.__init__.im_func.func_code else: result = self.pyobj.__init__ Modified: py/dist/py/apigen/tracer/docstorage.py ============================================================================== --- py/dist/py/apigen/tracer/docstorage.py (original) +++ py/dist/py/apigen/tracer/docstorage.py Mon Oct 23 17:30:23 2006 @@ -100,7 +100,7 @@ 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): + def from_pkg(self, module, keep_frames=False): self.module = module keys = module.__package__.exportdefs.keys() d = {} @@ -110,13 +110,55 @@ for elem in chain: base = getattr(base, elem) d[key] = base - self.from_dict(d) + self.from_dict(d, keep_frames) return self def from_module(self, func): raise NotImplementedError("From module") -class DocStorageAccessor(object): +class AbstractDocStorageAccessor(object): + def __init__(self): + raise NotImplementedError("Purely virtual object") + + def get_function_names(self): + """ Returning names of all functions + """ + + def get_class_names(self): + """ Returning names of all classess + """ + + def get_function_doc(self, name): + """ Returning __doc__ of a function + """ + + def get_function_definition(self, name): + """ Returns definition of a function (source) + """ + + def get_function_signature(self, name): + """ Returns types of a function + """ + + def get_function_callpoints(self, name): + """ Returns list of all callpoints + """ + + def get_module_name(self): + pass + + def get_class_methods(self, name): + """ Returns all methods of a class + """ + + #def get_object_info(self, key): + # + + def get_module_info(self): + """ Returns module information + """ + +class DocStorageAccessor(AbstractDocStorageAccessor): """ Set of helper functions to access DocStorage, separated in different class to keep abstraction """ Modified: py/dist/py/apigen/tracer/model.py ============================================================================== --- py/dist/py/apigen/tracer/model.py (original) +++ py/dist/py/apigen/tracer/model.py Mon Oct 23 17:30:23 2006 @@ -18,12 +18,23 @@ def unionof(self, other): if isinstance(other, SomeImpossibleValue): return self + if isinstance(other, SomeUnion): + return other.unionof(self) if self.gettypedef() is other.gettypedef(): return self return SomeUnion([self, other]) def gettypedef(self): return self.typedef + + def __hash__(self): + return hash(self.__class__) + + def __eq__(self, other): + return self.__class__ == other.__class__ + + def __ne__(self, other): + return not self == other class SomeUnion(object): # empty typedef @@ -31,6 +42,16 @@ self.possibilities = possibilities def unionof(self, other): + if isinstance(other, SomeUnion): + poss = [] + our_poss = {} + for p in self.possibilities: + our_poss[p] = True + for p in other.possibilities: + if not p in our_poss: + poss.append(p) + poss += self.possibilities + return SomeUnion(poss) return SomeUnion(self.possibilities + [other]) def __repr__(self): Modified: py/dist/py/apigen/tracer/testing/test_model.py ============================================================================== --- py/dist/py/apigen/tracer/testing/test_model.py (original) +++ py/dist/py/apigen/tracer/testing/test_model.py Mon Oct 23 17:30:23 2006 @@ -8,13 +8,13 @@ import types import py +def check_guess(val, t): + assert isinstance(guess_type(val), t) + def test_basic(): """ This tests checks every object that we might want to track """ - def check_guess(val, t): - assert isinstance(guess_type(val), t) - check_guess(3, SomeInt) check_guess(3., SomeFloat) check_guess(True, SomeBoolean) @@ -47,5 +47,23 @@ check_guess({}, SomeDict) check_guess(sys.exc_info, SomeBuiltinFunction) +def test_anyof(): + def check_lst(lst): + a = guess_type(lst[0]) + for i in lst[1:]: + a = unionof(a, guess_type(i)) + d = dict([(i, True) for i in a.possibilities]) + assert len(a.possibilities) == len(d) + for i in a.possibilities: + assert not isinstance(i, SomeUnion) + return a + + ret = check_lst([3, 4, 3., "aa"]) + assert len(ret.possibilities) == 3 + ret = check_lst([3, 4, 3.]) + ret2 = check_lst([1, "aa"]) + ret3 = unionof(ret, ret2) + assert len(ret3.possibilities) == 3 + def test_union(): py.test.skip("We're not thinking about type unions right now") From fijal at codespeak.net Mon Oct 23 22:56:20 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Mon, 23 Oct 2006 22:56:20 +0200 (CEST) Subject: [py-svn] r33618 - in py/dist/py/apigen/tracer/testing: . package package/submodule package/submodule/pak Message-ID: <20061023205620.86EAF1006C@code0.codespeak.net> Author: fijal Date: Mon Oct 23 22:56:17 2006 New Revision: 33618 Added: py/dist/py/apigen/tracer/testing/package/ py/dist/py/apigen/tracer/testing/package/submodule/ (props changed) py/dist/py/apigen/tracer/testing/package/submodule/__init__.py (contents, props changed) py/dist/py/apigen/tracer/testing/package/submodule/pak/ (props changed) py/dist/py/apigen/tracer/testing/package/submodule/pak/__init__.py (contents, props changed) py/dist/py/apigen/tracer/testing/package/submodule/pak/mod.py (contents, props changed) py/dist/py/apigen/tracer/testing/test_package.py (contents, props changed) Modified: py/dist/py/apigen/tracer/testing/test_docgen.py Log: Added tests for from_pkg. Added: py/dist/py/apigen/tracer/testing/package/submodule/__init__.py ============================================================================== --- (empty file) +++ py/dist/py/apigen/tracer/testing/package/submodule/__init__.py Mon Oct 23 22:56:17 2006 @@ -0,0 +1,11 @@ +import py + +from py.__.initpkg import initpkg + +initpkg(__name__, + description="test package", + exportdefs = { + 'pak.mod.one': ('./pak/mod.py', 'one'), + 'pak.mod.two': ('./pak/mod.py', 'nottwo'), + 'notpak.notmod.notclass': ('./pak/mod.py', 'cls'), + }) Added: py/dist/py/apigen/tracer/testing/package/submodule/pak/__init__.py ============================================================================== Added: py/dist/py/apigen/tracer/testing/package/submodule/pak/mod.py ============================================================================== --- (empty file) +++ py/dist/py/apigen/tracer/testing/package/submodule/pak/mod.py Mon Oct 23 22:56:17 2006 @@ -0,0 +1,10 @@ +class cls(object): + def __init__(self, x): + self.x = x + +def one(x): + return x+3 + +def nottwo(): + pass + Modified: py/dist/py/apigen/tracer/testing/test_docgen.py ============================================================================== --- py/dist/py/apigen/tracer/testing/test_docgen.py (original) +++ py/dist/py/apigen/tracer/testing/test_docgen.py Mon Oct 23 22:56:17 2006 @@ -123,10 +123,12 @@ assert isinstance(desc.call_sites.values()[0], py.code.Frame) class A(object): - pass + def method(self, x): + self.x = x class B: - pass + def method(self, x): + self.x = x def test_without_init(): ds = DocStorage().from_dict({'A':A, 'B':B}) @@ -134,4 +136,8 @@ t.start_tracing() x = A() y = B() + x.method(3) + y.method(4) t.end_tracing() + assert isinstance(ds.descs['A'].fields['method'].inputcells[1], model.SomeInt) + assert isinstance(ds.descs['B'].fields['method'].inputcells[1], model.SomeInt) Added: py/dist/py/apigen/tracer/testing/test_package.py ============================================================================== --- (empty file) +++ py/dist/py/apigen/tracer/testing/test_package.py Mon Oct 23 22:56:17 2006 @@ -0,0 +1,47 @@ + +""" some tests for from_package +""" + +from py.__.apigen.tracer.docstorage import DocStorage +from py.__.apigen.tracer.tracer import Tracer +from py.__.apigen.tracer import model +import sys +import py + +def setup_module(mod): + sys.path.insert(0, str(py.path.local(__file__).dirpath().join("package"))) + import submodule + mod.submodule = submodule + +def teardown_module(mod): + sys.path = sys.path[1:] + +class TestFullModule(object): + def setup_class(cls): + cls.ds = DocStorage().from_pkg(submodule) + cls.tracer = Tracer(cls.ds) + + def test_init(self): + ds = self.ds + assert len(ds.descs) == 4 + assert sorted(ds.descs.keys()) == ["notpak.notmod.notclass", "notpak.notmod.notclass.__init__", \ + "pak.mod.one", "pak.mod.two"] + + def test_simple_call(self): + ds = self.ds + self.tracer.start_tracing() + submodule.pak.mod.one(3) + self.tracer.end_tracing() + desc = self.ds.descs['pak.mod.one'] + assert isinstance(desc.retval, model.SomeInt) + assert isinstance(desc.inputcells[0], model.SomeInt) + + def test_call_class(self): + ds = self.ds + self.tracer.start_tracing() + c = submodule.notpak.notmod.notclass(3) + self.tracer.end_tracing() + desc = self.ds.descs['notpak.notmod.notclass'] + methdesc = desc.fields['__init__'] + assert isinstance(methdesc.inputcells[0], model.SomeInstance) + assert isinstance(methdesc.inputcells[1], model.SomeInt) From fijal at codespeak.net Tue Oct 24 10:53:30 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Tue, 24 Oct 2006 10:53:30 +0200 (CEST) Subject: [py-svn] r33634 - py/dist/py/apigen/tracer Message-ID: <20061024085330.DDC0B10063@code0.codespeak.net> Author: fijal Date: Tue Oct 24 10:53:28 2006 New Revision: 33634 Modified: py/dist/py/apigen/tracer/docstorage.py Log: Added sorting. Modified: py/dist/py/apigen/tracer/docstorage.py ============================================================================== --- py/dist/py/apigen/tracer/docstorage.py (original) +++ py/dist/py/apigen/tracer/docstorage.py Tue Oct 24 10:53:28 2006 @@ -169,10 +169,10 @@ return [i for i, desc in self.ds.descs.iteritems() if filter(i, desc)] def get_function_names(self): - return self._get_names(lambda i, desc: type(desc) is FunctionDesc) + return sorted(self._get_names(lambda i, desc: type(desc) is FunctionDesc)) def get_class_names(self): - return self._get_names(lambda i, desc: isinstance(desc, ClassDesc)) + return sorted(self._get_names(lambda i, desc: isinstance(desc, ClassDesc))) #def get_function(self, name): # return self.ds.descs[name].pyobj From fijal at codespeak.net Tue Oct 24 10:54:20 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Tue, 24 Oct 2006 10:54:20 +0200 (CEST) Subject: [py-svn] r33635 - py/dist/py/apigen/tracer Message-ID: <20061024085420.D73321006C@code0.codespeak.net> Author: fijal Date: Tue Oct 24 10:54:19 2006 New Revision: 33635 Modified: py/dist/py/apigen/tracer/docstorage.py Log: And here as well. Modified: py/dist/py/apigen/tracer/docstorage.py ============================================================================== --- py/dist/py/apigen/tracer/docstorage.py (original) +++ py/dist/py/apigen/tracer/docstorage.py Tue Oct 24 10:54:19 2006 @@ -202,7 +202,7 @@ def get_class_methods(self, name): desc = self.ds.descs[name] assert isinstance(desc, ClassDesc) - return desc.getfields() + return sorted(desc.getfields()) #def get_object_info(self, key): # From fijal at codespeak.net Tue Oct 24 11:22:26 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Tue, 24 Oct 2006 11:22:26 +0200 (CEST) Subject: [py-svn] r33638 - in py/dist/py/test: . rsession rsession/testing Message-ID: <20061024092226.01EDA1006E@code0.codespeak.net> Author: fijal Date: Tue Oct 24 11:22:24 2006 New Revision: 33638 Modified: py/dist/py/test/defaultconftest.py py/dist/py/test/rsession/local.py py/dist/py/test/rsession/rsession.py py/dist/py/test/rsession/testing/test_lsession.py Log: Added some new runners. Warning, for apigen runner this is intermediate checkin (it can go away as well). Modified: py/dist/py/test/defaultconftest.py ============================================================================== --- py/dist/py/test/defaultconftest.py (original) +++ py/dist/py/test/defaultconftest.py Tue Oct 24 11:22:24 2006 @@ -22,9 +22,9 @@ Option('-s', '--nocapture', action="store_true", dest="nocapture", default=False, help="disable catching of sys.stdout/stderr output."), - Option('-k', + Option('-k', action="store", dest="keyword", default='', - help="only run test items matching the given (google-style) keyword expression"), + help="only run test items matching the given (google-style) keyword expression"), Option('-l', '--showlocals', action="store_true", dest="showlocals", default=False, help="show locals in tracebacks (disabled by default)"), @@ -40,13 +40,17 @@ help="don't cut any tracebacks (default is to cut)"), Option('', '--nomagic', action="store_true", dest="nomagic", default=False, - help="refrain from using magic as much as possible"), + help="refrain from using magic as much as possible"), Option('', '--collectonly', action="store_true", dest="collectonly", default=False, help="only collect tests, don't execute them. "), Option('', '--traceconfig', action="store_true", dest="traceconfig", default=False, - help="trace considerations of conftest.py files. "), + help="trace considerations of conftest.py files. "), + Option('', '--apigen', + action="store_true", dest="apigen", default=False, + help="Generated API docs out of tests. Needs to have defined"\ + "__package__ for module or overwritten in conftest") ) py.test.Config.addoptions('test-session related options', Option('', '--tkinter', Modified: py/dist/py/test/rsession/local.py ============================================================================== --- py/dist/py/test/rsession/local.py (original) +++ py/dist/py/test/rsession/local.py Tue Oct 24 11:22:24 2006 @@ -6,21 +6,44 @@ from py.__.test.rsession import report from py.__.test.rsession.outcome import ReprOutcome -def box_runner(item, config, reporter): +# XXX copied from session.py +def startcapture(session): + if not session.config.option.nocapture and not session.config.option.usepdb: + # XXX refactor integrate capturing + from py.__.misc.simplecapture import SimpleOutErrCapture + session._capture = SimpleOutErrCapture() + +def finishcapture(session): + if hasattr(session, '_capture'): + capture = session._capture + del session._capture + return capture.reset() + return "", "" + +def box_runner(item, session, reporter): r = BoxExecutor(item) return ReprOutcome(r.execute()) -def plain_runner(item, config, reporter): - r = RunExecutor(item, usepdb=config.option.usepdb, reporter=reporter) - return ReprOutcome(r.execute().make_repr()) +def plain_runner(item, session, reporter): + # box executor is doing stdout/err catching for us, let's do it here + startcapture(session) + r = RunExecutor(item, usepdb=session.config.option.usepdb, reporter=reporter) + outcome = r.execute() + outcome = ReprOutcome(outcome.make_repr()) + outcome.stdout, outcome.stderr = finishcapture(session) + return outcome -def benchmark_runner(item, config, reporter): +def benchmark_runner(item, session, reporter): raise NotImplementedError() -def apigen_runner(item, config, reporter): - raise NotImplementedError() +def apigen_runner(item, session, reporter): + r = RunExecutor(item, reporter=reporter) + session.tracer.start_tracing() + retval = plain_runner(item, session, reporter) + session.tracer.end_tracing() + return retval -def exec_runner(item, config, reporter): +def exec_runner(item, session, reporter): raise NotImplementedError() # runner interface is here to perform several different types of run @@ -30,15 +53,18 @@ #-3. exec_runner - for running under different interpreter #-4. benchmark_runner - for running with benchmarking #-5. apigen_runner - for running under apigen to generate api out of it. -def local_loop(reporter, itemgenerator, shouldstop, config, runner=None): +def local_loop(session, reporter, itemgenerator, shouldstop, config, runner=None): if runner is None: - runner = box_runner + if session.config.option.apigen: + runner = apigen_runner + else: + runner = box_runner while 1: try: item = itemgenerator.next() if shouldstop(): return - outcome = runner(item, config, reporter) + outcome = runner(item, session, reporter) reporter(report.ReceivedItemOutcome(None, item, outcome)) except StopIteration: break Modified: py/dist/py/test/rsession/rsession.py ============================================================================== --- py/dist/py/test/rsession/rsession.py (original) +++ py/dist/py/test/rsession/rsession.py Tue Oct 24 11:22:24 2006 @@ -42,6 +42,7 @@ self.failed = dict([(host, 0) for host in hosts]) self.skipped = dict([(host, 0) for host in hosts]) self.passed = dict([(host, 0) for host in hosts]) + # XXX: This is for tests to work self.count = 0 self.lgt = 1000 @@ -421,9 +422,11 @@ """ Local version of session """ def main(self, args, reporter=None, runner=None, shouldstop=None): + # check out if used options makes any sense + if not args: args = [py.path.local()] - + sshhosts = ['localhost'] # this is just an info to reporter if not self.config.option.nomagic: @@ -438,6 +441,15 @@ pkgdir = self.getpkgdir(args[0]) colitems = self.make_colitems(args, baseon=pkgdir.dirpath()) reporter(report.RsyncFinished()) + + if self.config.option.apigen: + from py.__.apigen.tracer.docstorage import DocStorage + from py.__.apigen.tracer.tracer import Tracer + # XXX + module = py + #module = __import__(str(pkgdir.join('__init__.py'))) + self.docstorage = DocStorage().from_pkg(module) + self.tracer = Tracer(self.docstorage) keyword = self.config.option.keyword @@ -450,9 +462,9 @@ #assert 0, "\n".join([",".join(x.listnames()) for x in # list(itemgenerator)]) # XXX: We have to decide which runner to use at this point - if runner is None and self.config.option.usepdb: + if runner is None and (self.config.option.usepdb or self.config.option.nocapture): runner = plain_runner - local_loop(reporter, itemgenerator, checkfun, self.config, runner=runner) + local_loop(self, reporter, itemgenerator, checkfun, self.config, runner=runner) reporter(report.TestFinished()) if startserverflag: @@ -462,3 +474,8 @@ #py.test.Function.state.teardown_all() if not self.config.option.nomagic: py.magic.revoke(assertion=1) + + if self.config.option.apigen: + from py.__.apigen.rest.genrest import DirectPaste, RestGen, DirWriter + RestGen(self.docstorage, DirectPaste(), DirWriter()).write() + Modified: py/dist/py/test/rsession/testing/test_lsession.py ============================================================================== --- py/dist/py/test/rsession/testing/test_lsession.py (original) +++ py/dist/py/test/rsession/testing/test_lsession.py Tue Oct 24 11:22:24 2006 @@ -243,3 +243,24 @@ assert len(failevents) == 1 assert len(testevents) == 1 assert failevents[0].outcome.excinfo.value == 'assert [0] == [1, 2]' + + def test_nocapture(self): + tmpdir = tmp + tmpdir.ensure("sub7", "__init__.py") + tmpdir.ensure("sub7", "test_nocap.py").write(py.code.Source(""" + def test_one(): + print 1 + print 2 + print 3 + """)) + args = [str(tmpdir.join("sub7"))] + config, args = py.test.Config.parse(args) + lsession = LSession(config) + allevents = [] + lsession.main(args, reporter=allevents.append, runner=plain_runner) + testevents = [x for x in allevents + if isinstance(x, report.ReceivedItemOutcome)] + assert len(testevents) == 1 + assert testevents[0].outcome.passed + assert testevents[0].outcome.stderr == "" + assert testevents[0].outcome.stdout == "1\n2\n3\n" From fijal at codespeak.net Tue Oct 24 11:42:25 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Tue, 24 Oct 2006 11:42:25 +0200 (CEST) Subject: [py-svn] r33639 - py/dist/py/test/rsession Message-ID: <20061024094225.39F611005A@code0.codespeak.net> Author: fijal Date: Tue Oct 24 11:42:23 2006 New Revision: 33639 Modified: py/dist/py/test/rsession/rsession.py Log: Dirty hack to allow display to work better, needs some tests. Modified: py/dist/py/test/rsession/rsession.py ============================================================================== --- py/dist/py/test/rsession/rsession.py (original) +++ py/dist/py/test/rsession/rsession.py Tue Oct 24 11:42:23 2006 @@ -33,8 +33,9 @@ remote_options = RemoteOptions({'we_are_remote':False}) class AbstractReporter(object): - def __init__(self, config, hosts): + def __init__(self, config, hosts, pkgdir=""): self.config = config + self.pkgdir = pkgdir self.failed_tests_outcome = [] self.skipped_tests_outcome = [] self.out = getout(py.std.sys.stdout) @@ -288,10 +289,20 @@ def report_ItemStart(self, event): item = event.item if isinstance(item, py.test.collect.Module): + # XXX This is a terrible hack, I don't like it + # and will rewrite it at some point self.count = 0 lgt = len(list(event.item.tryiter())) self.lgt = lgt - self.out.write("%s[%d] " % (item.name, lgt)) + # print names relative to current workdir + name = "/".join(item.listnames()) + local = str(py.path.local()) + d = str(self.pkgdir.dirpath().dirpath()) + if local.startswith(d): + local = local[len(d) + 1:] + if name.startswith(local): + name = name[len(local) + 1:] + self.out.write("%s[%d] " % (name, lgt)) def hangs(self): pass @@ -329,7 +340,7 @@ return pkgpath getpkgdir = staticmethod(getpkgdir) - def init_reporter(self, reporter, sshhosts, reporter_class): + def init_reporter(self, reporter, sshhosts, reporter_class, arg=""): try: # XXX: use it like a command line option, but how? startserverflag = self.config.getinitialvalue("startserver") @@ -343,7 +354,10 @@ reporter = exported_methods.report start_server() elif reporter is None: - reporter_instance = reporter_class(self.config, sshhosts) + if arg: + reporter_instance = reporter_class(self.config, sshhosts, self.getpkgdir(arg)) + else: + reporter_instance = reporter_class(self.config, sshhosts) reporter = reporter_instance.report checkfun = lambda : self.config.option.exitfirst and \ reporter_instance.is_failing() @@ -433,7 +447,7 @@ py.magic.invoke(assertion=1) reporter, checkfun, startserverflag = self.init_reporter(reporter, - sshhosts, LocalReporter) + sshhosts, LocalReporter, args[0]) if shouldstop: checkfun = shouldstop From fijal at codespeak.net Tue Oct 24 11:46:29 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Tue, 24 Oct 2006 11:46:29 +0200 (CEST) Subject: [py-svn] r33640 - py/dist/py/test/rsession Message-ID: <20061024094629.462391005A@code0.codespeak.net> Author: fijal Date: Tue Oct 24 11:46:27 2006 New Revision: 33640 Modified: py/dist/py/test/rsession/rsession.py Log: Convinient newline. Modified: py/dist/py/test/rsession/rsession.py ============================================================================== --- py/dist/py/test/rsession/rsession.py (original) +++ py/dist/py/test/rsession/rsession.py Tue Oct 24 11:46:27 2006 @@ -303,6 +303,8 @@ if name.startswith(local): name = name[len(local) + 1:] self.out.write("%s[%d] " % (name, lgt)) + if lgt == 0: + self.out.write("\n") def hangs(self): pass From fijal at codespeak.net Tue Oct 24 13:10:46 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Tue, 24 Oct 2006 13:10:46 +0200 (CEST) Subject: [py-svn] r33647 - in py/dist/py/apigen/tracer: . testing Message-ID: <20061024111046.8DC8010068@code0.codespeak.net> Author: fijal Date: Tue Oct 24 13:10:44 2006 New Revision: 33647 Modified: py/dist/py/apigen/tracer/model.py py/dist/py/apigen/tracer/testing/test_model.py Log: Fixed anyof to use set (which is not py2.2 friendly). Modified: py/dist/py/apigen/tracer/model.py ============================================================================== --- py/dist/py/apigen/tracer/model.py (original) +++ py/dist/py/apigen/tracer/model.py Tue Oct 24 13:10:44 2006 @@ -39,23 +39,16 @@ class SomeUnion(object): # empty typedef def __init__(self, possibilities): - self.possibilities = possibilities + self.possibilities = set(possibilities) def unionof(self, other): + print self.possibilities if isinstance(other, SomeUnion): - poss = [] - our_poss = {} - for p in self.possibilities: - our_poss[p] = True - for p in other.possibilities: - if not p in our_poss: - poss.append(p) - poss += self.possibilities - return SomeUnion(poss) - return SomeUnion(self.possibilities + [other]) + return SomeUnion(self.possibilities.union(other.possibilities)) + return SomeUnion(list(self.possibilities) + [other]) def __repr__(self): - return "AnyOf(%s)" % ",".join([str(i) for i in self.possibilities]) + return "AnyOf(%s)" % ",".join([str(i) for i in list(self.possibilities)]) def gettypedef(self): return (None, None) Modified: py/dist/py/apigen/tracer/testing/test_model.py ============================================================================== --- py/dist/py/apigen/tracer/testing/test_model.py (original) +++ py/dist/py/apigen/tracer/testing/test_model.py Tue Oct 24 13:10:44 2006 @@ -57,6 +57,9 @@ for i in a.possibilities: assert not isinstance(i, SomeUnion) return a + + class C(object): + pass ret = check_lst([3, 4, 3., "aa"]) assert len(ret.possibilities) == 3 @@ -64,6 +67,13 @@ ret2 = check_lst([1, "aa"]) ret3 = unionof(ret, ret2) assert len(ret3.possibilities) == 3 + ret = check_lst([3, 1.]) + ret = unionof(ret, guess_type("aa")) + ret = unionof(guess_type("aa"), ret) + ret = unionof(guess_type(C()), ret) + ret = unionof(ret, guess_type("aa")) + ret = unionof(ret, guess_type(C())) + assert len(ret.possibilities) == 4 def test_union(): py.test.skip("We're not thinking about type unions right now") From fijal at codespeak.net Tue Oct 24 14:22:25 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Tue, 24 Oct 2006 14:22:25 +0200 (CEST) Subject: [py-svn] r33648 - in py/dist/py: apigen/tracer test/rsession Message-ID: <20061024122225.49F2C1006C@code0.codespeak.net> Author: fijal Date: Tue Oct 24 14:22:22 2006 New Revision: 33648 Modified: py/dist/py/apigen/tracer/docstorage.py py/dist/py/apigen/tracer/model.py py/dist/py/test/rsession/rsession.py Log: Removed debug print. Added some fixes. Modified: py/dist/py/apigen/tracer/docstorage.py ============================================================================== --- py/dist/py/apigen/tracer/docstorage.py (original) +++ py/dist/py/apigen/tracer/docstorage.py Tue Oct 24 14:22:22 2006 @@ -84,8 +84,10 @@ desc = FunctionDesc(key, value, **kwargs) elif isinstance(value, (types.ObjectType, types.ClassType)): desc = ClassDesc(key, value, **kwargs) - for name in dir(value): - field = getattr(value, name) + # XXX: This is the special case when we do not have __init__ + # in dir(value) for uknown reason. Need to investigate it + for name in dir(value) + ['__init__']: + field = getattr(value, name, None) if isinstance(field, types.MethodType) and \ isinstance(field.im_func, types.FunctionType): real_name = key + '.' + name Modified: py/dist/py/apigen/tracer/model.py ============================================================================== --- py/dist/py/apigen/tracer/model.py (original) +++ py/dist/py/apigen/tracer/model.py Tue Oct 24 14:22:22 2006 @@ -42,7 +42,6 @@ self.possibilities = set(possibilities) def unionof(self, other): - print self.possibilities if isinstance(other, SomeUnion): return SomeUnion(self.possibilities.union(other.possibilities)) return SomeUnion(list(self.possibilities) + [other]) Modified: py/dist/py/test/rsession/rsession.py ============================================================================== --- py/dist/py/test/rsession/rsession.py (original) +++ py/dist/py/test/rsession/rsession.py Tue Oct 24 14:22:22 2006 @@ -493,5 +493,5 @@ if self.config.option.apigen: from py.__.apigen.rest.genrest import DirectPaste, RestGen, DirWriter - RestGen(self.docstorage, DirectPaste(), DirWriter()).write() + RestGen(self.docstorage, DirectPaste(), DirWriter("/tmp/output")).write() From fijal at codespeak.net Tue Oct 24 14:57:56 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Tue, 24 Oct 2006 14:57:56 +0200 (CEST) Subject: [py-svn] r33651 - in py/dist/py/apigen: rest rest/testing tracer Message-ID: <20061024125756.2940910070@code0.codespeak.net> Author: fijal Date: Tue Oct 24 14:57:54 2006 New Revision: 33651 Modified: py/dist/py/apigen/rest/genrest.py py/dist/py/apigen/rest/testing/test_rest.py py/dist/py/apigen/tracer/description.py py/dist/py/apigen/tracer/docstorage.py Log: Added function source. Modified: py/dist/py/apigen/rest/genrest.py ============================================================================== --- py/dist/py/apigen/rest/genrest.py (original) +++ py/dist/py/apigen/rest/genrest.py Tue Oct 24 14:57:54 2006 @@ -108,6 +108,7 @@ class RestGen(object): def __init__(self, ds, linkgen, writer=PipeWriter()): + #assert isinstance(linkgen, DirectPaste), "Cannot use different linkgen by now" self.dsa = DocStorageAccessor(ds) self.linkgen = linkgen self.writer = writer @@ -191,7 +192,8 @@ # lst.append(Paragraph("Function source: ", # Link(linkname, linktarget))) #else: - lst.append(Paragraph('Function source: ')) + lst.append(Paragraph('Function source:')) + lst.append(LiteralBlock(self.dsa.get_function_source(fun_name))) args, retval = self.dsa.get_function_signature(fun_name) # XXX: we just do "knowntype" here, but we should Modified: py/dist/py/apigen/rest/testing/test_rest.py ============================================================================== --- py/dist/py/apigen/rest/testing/test_rest.py (original) +++ py/dist/py/apigen/rest/testing/test_rest.py Tue Oct 24 14:57:54 2006 @@ -218,3 +218,22 @@ r = RestGen(ds, lg, DirWriter(tempdir)) r.write() self.check_rest(tempdir) + + def test_function_source(self): + def blah(): + a = 3 + b = 4 + return a + b + + descs = {'blah':blah} + ds = DocStorage().from_dict(descs) + t = Tracer(ds) + t.start_tracing() + blah() + t.end_tracing() + lg = DirectPaste() + tempdir = temppath.ensure("function_source", dir=True) + r = RestGen(ds, lg, DirWriter(tempdir)) + r.write() + assert tempdir.join("function_blah.txt").open().read().find("a = 3") != -1 + self.check_rest(tempdir) Modified: py/dist/py/apigen/tracer/description.py ============================================================================== --- py/dist/py/apigen/tracer/description.py (original) +++ py/dist/py/apigen/tracer/description.py Tue Oct 24 14:57:54 2006 @@ -1,9 +1,4 @@ -#try: -# from pypy.annotation import model -#except ImportError: -# pass - from py.__.apigen.tracer import model import types Modified: py/dist/py/apigen/tracer/docstorage.py ============================================================================== --- py/dist/py/apigen/tracer/docstorage.py (original) +++ py/dist/py/apigen/tracer/docstorage.py Tue Oct 24 14:57:54 2006 @@ -15,11 +15,6 @@ class DocStorage(object): """ Class storing info about API """ - #def __init__(self): - #self.bookkeeper = Bookkeeper(DummyAnnotator()) - #self.call_stack = [] - #self.frames = [] - def consider_call(self, frame, caller_frame): assert isinstance(frame, py.code.Frame) desc = self.find_desc(frame.code) @@ -191,6 +186,13 @@ def get_function_signature(self, name): desc = self.ds.descs[name] return desc.inputcells, desc.retval + + def get_function_source(self, name): + desc = self.ds.descs[name] + try: + return str(py.code.Source(desc.pyobj)) + except IOError: + return "Cannot get source" def get_function_callpoints(self, name): # return list of tuple (filename, fileline, frame) From fijal at codespeak.net Tue Oct 24 15:12:49 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Tue, 24 Oct 2006 15:12:49 +0200 (CEST) Subject: [py-svn] r33653 - in py/dist/py/apigen: rest rest/testing tracer Message-ID: <20061024131249.0666B1007C@code0.codespeak.net> Author: fijal Date: Tue Oct 24 15:12:48 2006 New Revision: 33653 Modified: py/dist/py/apigen/rest/genrest.py py/dist/py/apigen/rest/testing/test_rest.py py/dist/py/apigen/tracer/docstorage.py Log: Added cleaner arguments display. Modified: py/dist/py/apigen/rest/genrest.py ============================================================================== --- py/dist/py/apigen/rest/genrest.py (original) +++ py/dist/py/apigen/rest/genrest.py Tue Oct 24 15:12:48 2006 @@ -185,6 +185,13 @@ LiteralBlock(self.dsa.get_function_doc(fun_name)), LiteralBlock(self.dsa.get_function_definition(fun_name))] + + args, retval = self.dsa.get_function_signature(fun_name) + arg_str = "\n".join(["%s :: %s" % (str(name), str(type)) for name, type in args]) + arg_str += "\n" + "Return value :: %s" % str(retval) + lst.append(Paragraph("where:")) + lst.append(LiteralBlock(arg_str)) + # XXX missing implementation of dsa.get_function_location() #filename, lineno = self.dsa.get_function_location(fun_name) #linkname, linktarget = self.linkgen.getlink(filename, lineno) @@ -195,15 +202,11 @@ lst.append(Paragraph('Function source:')) lst.append(LiteralBlock(self.dsa.get_function_source(fun_name))) - args, retval = self.dsa.get_function_signature(fun_name) - # XXX: we just do "knowntype" here, but we should - # present some more informative way, maybe even provide a link - # for the type declaration (if this is known) - arg_str = "(%s)" % (",".join([str(i) for i in args])) - ret_str = str(retval) - arg_quote = Paragraph("Function type:", Quote(arg_str), '->', - Quote(ret_str)) - lst.append(arg_quote) + #arg_str = "(%s)" % (",".join([str(i) for i in args])) + #ret_str = str(retval) + #arg_quote = Paragraph("Function type:", Quote(arg_str), '->', + # Quote(ret_str)) + #lst.append(arg_quote) # call sites.. call_site_title = Title("Call sites:", belowchar='^') Modified: py/dist/py/apigen/rest/testing/test_rest.py ============================================================================== --- py/dist/py/apigen/rest/testing/test_rest.py (original) +++ py/dist/py/apigen/rest/testing/test_rest.py Tue Oct 24 15:12:48 2006 @@ -237,3 +237,26 @@ r.write() assert tempdir.join("function_blah.txt").open().read().find("a = 3") != -1 self.check_rest(tempdir) + + def test_function_arguments(self): + def blah(a, b, c): + return "axx" + + class C: + pass + + descs = {'blah':blah} + ds = DocStorage().from_dict(descs) + t = Tracer(ds) + t.start_tracing() + blah(3, "x", C()) + t.end_tracing() + lg = DirectPaste() + tempdir = temppath.ensure("function_args", dir=True) + r = RestGen(ds, lg, DirWriter(tempdir)) + r.write() + source = tempdir.join("function_blah.txt").open().read() + assert source.find("a :: ") != -1 + assert source.find("b :: ") != -1 + assert source.find("c :: ") != -1 + self.check_rest(tempdir) Modified: py/dist/py/apigen/tracer/docstorage.py ============================================================================== --- py/dist/py/apigen/tracer/docstorage.py (original) +++ py/dist/py/apigen/tracer/docstorage.py Tue Oct 24 15:12:48 2006 @@ -185,7 +185,10 @@ def get_function_signature(self, name): desc = self.ds.descs[name] - return desc.inputcells, desc.retval + # we return pairs of (name, type) here + names = desc.pyobj.func_code.co_varnames[:desc.pyobj.func_code.co_argcount] + types = desc.inputcells + return zip(names, types), desc.retval def get_function_source(self, name): desc = self.ds.descs[name] From fijal at codespeak.net Tue Oct 24 17:00:16 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Tue, 24 Oct 2006 17:00:16 +0200 (CEST) Subject: [py-svn] r33659 - in py/dist/py/apigen: rest/testing tracer tracer/testing Message-ID: <20061024150016.9B57E10063@code0.codespeak.net> Author: fijal Date: Tue Oct 24 17:00:14 2006 New Revision: 33659 Modified: py/dist/py/apigen/rest/testing/test_rest.py py/dist/py/apigen/tracer/model.py py/dist/py/apigen/tracer/testing/test_model.py Log: Really make the tests meaningfull, as well as extend model a bit. Modified: py/dist/py/apigen/rest/testing/test_rest.py ============================================================================== --- py/dist/py/apigen/rest/testing/test_rest.py (original) +++ py/dist/py/apigen/rest/testing/test_rest.py Tue Oct 24 17:00:14 2006 @@ -256,7 +256,38 @@ r = RestGen(ds, lg, DirWriter(tempdir)) r.write() source = tempdir.join("function_blah.txt").open().read() - assert source.find("a :: ") != -1 - assert source.find("b :: ") != -1 - assert source.find("c :: ") != -1 + call_point = source.find("Call sites\:") + assert call_point != -1 + assert source.find("a :: ") < call_point + assert source.find("b :: ") < call_point + assert source.find("c :: >") < call_point + self.check_rest(tempdir) + + def test_class_typedefs(self): + class A(object): + def __init__(self, x): + pass + + class B(object): + def __init__(self, y): + pass + + def xxx(x): + return x + + descs = {'A':A, 'B':B, 'xxx':xxx} + ds = DocStorage().from_dict(descs) + t = Tracer(ds) + t.start_tracing() + xxx(A(3)) + xxx(B("f")) + t.end_tracing() + lg = DirectPaste() + tempdir = temppath.ensure("classargs", dir=True) + r = RestGen(ds, lg, DirWriter(tempdir)) + r.write() + source = tempdir.join("function_xxx.txt").open().read() + call_point = source.find("Call sites\:") + assert call_point != -1 + assert source.find("x :: ,)>") < call_point self.check_rest(tempdir) Modified: py/dist/py/apigen/tracer/model.py ============================================================================== --- py/dist/py/apigen/tracer/model.py (original) +++ py/dist/py/apigen/tracer/model.py Tue Oct 24 17:00:14 2006 @@ -20,7 +20,7 @@ return self if isinstance(other, SomeUnion): return other.unionof(self) - if self.gettypedef() is other.gettypedef(): + if self == other: return self return SomeUnion([self, other]) @@ -46,6 +46,14 @@ return SomeUnion(self.possibilities.union(other.possibilities)) return SomeUnion(list(self.possibilities) + [other]) + def __eq__(self, other): + if type(other) is not SomeUnion: + return False + return self.possibilities == other.possibilities + + def __ne__(self, other): + return not self == other + def __repr__(self): return "AnyOf(%s)" % ",".join([str(i) for i in list(self.possibilities)]) @@ -66,7 +74,26 @@ class SomeClass(SomeObject): typedef = types.ClassType - + + def __init__(self, cls): + self.cls = cls + + def __hash__(self): + return hash("Class") ^ hash(self.cls) + + def __eq__(self, other): + if type(other) is not SomeClass: + return False + return self.cls == other.cls + + def unionof(self, other): + if type(other) is not SomeClass or self.cls is not other.cls: + return super(SomeClass, self).unionof(other) + return self + + def __repr__(self): + return "" % self.cls.__name__ + class SomeCode(SomeObject): typedef = types.CodeType @@ -98,6 +125,27 @@ typedef = types.GeneratorType class SomeInstance(SomeObject): + def __init__(self, classdef): + self.classdef = classdef + + def __hash__(self): + return hash("SomeInstance") ^ hash(self.classdef) + + def __eq__(self, other): + if type(other) is not SomeInstance: + return False + return other.classdef == self.classdef + + def unionof(self, other): + if type(other) is not SomeInstance: + return super(SomeInstance, self).unionof(other) + if self.classdef == other.classdef: + return self + return SomeInstance(unionof(self.classdef, other.classdef)) + + def __repr__(self): + return "" % str(self.classdef) + typedef = types.InstanceType class SomeInt(SomeObject): @@ -234,15 +282,15 @@ result = SomeFunction() elif hasattr(x, '__class__'): if x.__class__ is type: - result = SomeClass() + result = SomeClass(x) else: - result = SomeInstance() + result = SomeInstance(SomeClass(x.__class__)) elif tp is types.ClassType: - result = SomeClass() + result = SomeClass(x) elif x is None: return s_None elif hasattr(x, '__class__'): - result = SomeInstance() + result = SomeInstance(SomeClass(x.__class__)) else: result = SomeObject() # XXX here we might want to consider stuff like Modified: py/dist/py/apigen/tracer/testing/test_model.py ============================================================================== --- py/dist/py/apigen/tracer/testing/test_model.py (original) +++ py/dist/py/apigen/tracer/testing/test_model.py Tue Oct 24 17:00:14 2006 @@ -76,4 +76,24 @@ assert len(ret.possibilities) == 4 def test_union(): - py.test.skip("We're not thinking about type unions right now") + class A(object): + pass + + class B(object): + pass + + f = guess_type(A).unionof(guess_type(A)) + assert isinstance(f, SomeClass) + assert f.cls is A + f = guess_type(A).unionof(guess_type(B)) + assert isinstance(f, SomeUnion) + assert len(f.possibilities) == 2 + f = guess_type(A()).unionof(guess_type(A())) + assert isinstance(f, SomeInstance) + assert isinstance(f.classdef, SomeClass) + assert f.classdef.cls is A + f = guess_type(B()).unionof(guess_type(A())) + assert isinstance(f, SomeInstance) + assert isinstance(f.classdef, SomeUnion) + assert len(f.classdef.possibilities) == 2 + From fijal at codespeak.net Tue Oct 24 17:46:25 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Tue, 24 Oct 2006 17:46:25 +0200 (CEST) Subject: [py-svn] r33663 - in py/dist/py/apigen: rest rest/testing tracer/testing Message-ID: <20061024154625.4EAAA10070@code0.codespeak.net> Author: fijal Date: Tue Oct 24 17:46:21 2006 New Revision: 33663 Modified: py/dist/py/apigen/rest/genrest.py py/dist/py/apigen/rest/testing/test_rest.py py/dist/py/apigen/tracer/testing/test_package.py Log: Cleanup. Modified: py/dist/py/apigen/rest/genrest.py ============================================================================== --- py/dist/py/apigen/rest/genrest.py (original) +++ py/dist/py/apigen/rest/genrest.py Tue Oct 24 17:46:21 2006 @@ -230,7 +230,7 @@ except KeyboardInterrupt, SystemError: raise except: - source = "*Cannot get source*" + source = ["*Cannot get source*"] lines = [] for num, line in enumerate(source): if num == call_site.lineno - frame.code.firstlineno - 1: Modified: py/dist/py/apigen/rest/testing/test_rest.py ============================================================================== --- py/dist/py/apigen/rest/testing/test_rest.py (original) +++ py/dist/py/apigen/rest/testing/test_rest.py Tue Oct 24 17:46:21 2006 @@ -5,16 +5,13 @@ import py from StringIO import StringIO -try: - from py.__.apigen.rest.genrest import ViewVC, RestGen, PipeWriter, \ +from py.__.apigen.rest.genrest import ViewVC, RestGen, PipeWriter, \ DirWriter, FileWriter, \ DirectPaste, DirectFS - from py.__.apigen.tracer.tracer import Tracer - from py.__.apigen.tracer.docstorage import DocStorage +from py.__.apigen.tracer.tracer import Tracer +from py.__.apigen.tracer.docstorage import DocStorage - from py.__.apigen.tracer.testing.runtest import cut_pyc -except ImportError, s: - py.test.skip("Cannot import: %s" % str(s)) +from py.__.apigen.tracer.testing.runtest import cut_pyc def setup_module(mod): mod.temppath = py.test.ensuretemp('restgen') Modified: py/dist/py/apigen/tracer/testing/test_package.py ============================================================================== --- py/dist/py/apigen/tracer/testing/test_package.py (original) +++ py/dist/py/apigen/tracer/testing/test_package.py Tue Oct 24 17:46:21 2006 @@ -10,7 +10,10 @@ def setup_module(mod): sys.path.insert(0, str(py.path.local(__file__).dirpath().join("package"))) - import submodule + try: + import submodule + except ImportError: + py.test.skip("Importing submodule") mod.submodule = submodule def teardown_module(mod): From guido at codespeak.net Wed Oct 25 01:16:05 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Wed, 25 Oct 2006 01:16:05 +0200 (CEST) Subject: [py-svn] r33702 - in py/dist/py/rst: . testing Message-ID: <20061024231605.C9E6110063@code0.codespeak.net> Author: guido Date: Wed Oct 25 01:16:03 2006 New Revision: 33702 Modified: py/dist/py/rst/rst.py py/dist/py/rst/testing/test_rst.py Log: Not escaping link target anymore (can result in broken links), added _ to the list of to-be-escaped characters. Modified: py/dist/py/rst/rst.py ============================================================================== --- py/dist/py/rst/rst.py (original) +++ py/dist/py/rst/rst.py Wed Oct 25 01:16:03 2006 @@ -15,7 +15,9 @@ def escape(txt): """escape ReST markup""" - for c in '\\*`[|:': + # XXX this takes a very naive approach to escaping, but it seems to be + # sufficient... + for c in '\\*`[|:_': txt = txt.replace(c, '\\%s' % (c,)) return txt @@ -111,7 +113,7 @@ link_texts = [] # XXX this could check for duplicates and remove them... for link, target in self.links.iteritems(): - link_texts.append(".. _`%s`: %s" % (escape(link), escape(target))) + link_texts.append(".. _`%s`: %s" % (escape(link), target)) return "\n" + "\n".join(link_texts) + "\n\n" def text(self, escape=True): Modified: py/dist/py/rst/testing/test_rst.py ============================================================================== --- py/dist/py/rst/testing/test_rst.py (original) +++ py/dist/py/rst/testing/test_rst.py Wed Oct 25 01:16:03 2006 @@ -10,7 +10,7 @@ txt = Paragraph(Strong('*strong*')).text() assert txt == '**\\*strong\\***' txt = Rest(Paragraph(Link('foo[bar]', 'foo[bar]'))).text() - assert txt == "`foo\\[bar]`_\n\n.. _`foo\\[bar]`: foo\\[bar]\n\n" + assert txt == "`foo\\[bar]`_\n\n.. _`foo\\[bar]`: foo[bar]\n\n" def test_escape_literal(): txt = LiteralBlock('*escape* ``test``').text() From guido at codespeak.net Wed Oct 25 01:24:05 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Wed, 25 Oct 2006 01:24:05 +0200 (CEST) Subject: [py-svn] r33703 - in py/dist/py/apigen/rest: . testing Message-ID: <20061024232405.C8A6810063@code0.codespeak.net> Author: guido Date: Wed Oct 25 01:24:04 2006 New Revision: 33703 Added: py/dist/py/apigen/rest/testing/somemodule.py py/dist/py/apigen/rest/testing/someothermodule.py Modified: py/dist/py/apigen/rest/genrest.py py/dist/py/apigen/rest/testing/test_rest.py Log: Re-written the GenRest class, it's a bit cleaner now (but still quite a chunk unfortunately), don't replace dots in file names anymore (since that may cause name clashes), fixed underscore escaping. Modified: py/dist/py/apigen/rest/genrest.py ============================================================================== --- py/dist/py/apigen/rest/genrest.py (original) +++ py/dist/py/apigen/rest/genrest.py Wed Oct 25 01:24:04 2006 @@ -108,99 +108,188 @@ class RestGen(object): def __init__(self, ds, linkgen, writer=PipeWriter()): - #assert isinstance(linkgen, DirectPaste), "Cannot use different linkgen by now" + #assert isinstance(linkgen, DirectPaste), ( + # "Cannot use different linkgen by now") self.dsa = DocStorageAccessor(ds) self.linkgen = linkgen self.writer = writer - + def write(self): """write the data to the writer""" - # note that this builds up a list of Rest elements for the index as - # 'side effect', the list is passed along and filled, while the actual - # sections (also ReST elements) are returned by the write_* methods - # XXX this is quite icky! would be nice to have refactored - indexlst = [Title("Module: %s" % self.dsa.get_module_name(), - belowchar="="), - Paragraph(self.dsa.get_module_info()), - Title("Exported functions:", belowchar="-")] - funclst = self.write_function_list(indexlst) - indexlst.append(Title("Exported classes:", belowchar="-")) - classlst = self.write_class_list(indexlst) - self.writer.write_section('index', Rest(*indexlst).text()) - for sectionname, restitems in funclst: - self.writer.write_section(sectionname, Rest(*restitems).text()) - for sectionname, classdata in classlst: - restitems, classfunclst = classdata - self.writer.write_section(sectionname, Rest(*restitems).text()) - for fsectionname, frestitems in classfunclst: - self.writer.write_section(fsectionname, - Rest(*frestitems).text()) - - def write_function_list(self, indexlst): - retlst = [] - for name in self.dsa.get_function_names(): - sectionname = 'function_%s' % (name,) - linktarget = self.writer.getlink('function', name, - sectionname) - indexlst.append(ListItem(Text("Function: "), - Link(name, linktarget))) - retlst.append((sectionname, - self.write_function(sectionname, name))) - return retlst + modlist = self.get_module_list() + classlist = self.get_class_list(module='') + funclist = self.get_function_list() + + indexrest = self.build_index([t[0] for t in modlist], + [t[0] for t in classlist], + funclist) + self.writer.write_section('index', Rest(*indexrest).text()) + + self.build_modrest(modlist) + self.build_funcrest(funclist) + self.build_classrest(classlist) + + def build_modrest(self, modlist): + modrest = self.build_modules(modlist) + for name, rest, classlist, funclist in modrest: + self.writer.write_section('module_%s' % (name,), + Rest(*rest).text()) + for cname, crest, cfunclist in classlist: + self.writer.write_section('class_%s' % (cname,), + Rest(*crest).text()) + for fname, frest in cfunclist: + self.writer.write_section('method_%s' % (fname,), + Rest(*frest).text()) + for fname, frest in funclist: + self.writer.write_section('function_%s' % (fname,), + Rest(*frest).text()) - def write_class_list(self, indexlst): - retlst = [] - for name in self.dsa.get_class_names(): - sectionname = 'class_%s' % (name,) - linktarget = self.writer.getlink('class', name, sectionname) - indexlst.append(ListItem(Text("Class: "), Link(name, linktarget))) - retlst.append((sectionname, self.write_class(sectionname, name))) - return retlst + def build_classrest(self, classlist): + classrest = self.build_classes(classlist) + for cname, rest, cfunclist in classrest: + self.writer.write_section('class_%s' % (cname,), + Rest(*rest).text()) + for fname, rest in cfunclist: + self.writer.write_section('method_%s' % (fname,), + Rest(*rest).text()) + + def build_funcrest(self, funclist): + funcrest = self.build_functions(funclist) + for fname, rest in funcrest: + self.writer.write_section('function_%s' % (fname,), + Rest(*rest).text()) + + def build_index(self, modules, classes, functions): + rest = [Title('Index', abovechar='=', belowchar='=')] + rest.append(Title('Exported modules:', belowchar='=')) + for module in modules: + linktarget = self.writer.getlink('module', module, + 'module_%s' % (module,)) + rest.append(ListItem(Link(module, linktarget))) + rest.append(Title('Exported classes:', belowchar='=')) + for cls in classes: + linktarget = self.writer.getlink('class', cls, 'class_%s' % (cls,)) + rest.append(ListItem(Link(cls, linktarget))) + rest.append(Title('Exported functions:', belowchar='=')) + for func in functions: + linktarget = self.writer.getlink('function', func, + 'function_%s' % (func,)) + rest.append(ListItem(Link(func, linktarget))) + return rest + + def build_modules(self, modules): + ret = [] + for module, classes, functions in modules: + rest = [Title('Module: %s' % (module,), belowchar='-')] + rest.append(Title('Classes:', belowchar='^')) + for cls, cfunclist in classes: + linktarget = self.writer.getlink('class', cls, + 'class_%s' % (cls,)) + rest.append(ListItem(Link(cls, linktarget))) + classrest = self.build_classes(classes) + rest.append(Title('Functions:', belowchar='^')) + for func in functions: + linktarget = self.writer.getlink('function', + '%s.%s' % (module, func), + 'function_%s.%s' % (module, + func)) + rest.append(ListItem(Link(func, linktarget))) + funcrest = self.build_functions(functions, module) + ret.append((module, rest, classrest, funcrest)) + return ret - def write_class(self, section_name, class_name): - classlst = [Title("Class: %s" % class_name, belowchar='-'), - LiteralBlock(self.dsa.get_function_doc(class_name))] - - # write down exported methods - classlst.append(Title("Exported methods:", belowchar="^")) - funclist = [] - for method in self.dsa.get_class_methods(class_name): - sectionname = 'method_%s_%s' % (class_name, method) - linktext = '%s.%s' % (class_name, method) - linktarget = self.writer.getlink('function', linktext, - sectionname) - classlst.append(ListItem(Link(linktext, linktarget))) - # XXX assuming a function is always part of a class section - funclist.append((sectionname, - self.write_function(sectionname, - class_name + "." + method, - '^'))) - return classlst, funclist + def build_classes(self, classes): + ret = [] + for cls, functions in classes: + rest = [Title('Class: %s' % (cls,), belowchar='-'), + Title('Functions:', belowchar='^')] + for func in functions: + linktarget = self.writer.getlink('method', + '%s.%s' % (cls, func), + 'method_%s.%s' % (cls, func)) + rest.append(ListItem(Link(func, linktarget))) + funcrest = self.build_functions(functions, cls) + ret.append((cls, rest, funcrest)) + return ret - def write_function(self, section_name, fun_name, belowchar='-'): + def build_functions(self, functions, parent=''): + ret = [] + for function in functions: + if parent: + function = '%s.%s' % (parent, function) + rest = self.write_function(function) + ret.append((function, rest)) + return ret + + def get_module_list(self): + visited = [] + ret = [] + for name in self.dsa.get_class_names(): + if '.' in name: + module, classname = name.rsplit('.', 1) + if module in visited: + continue + visited.append(module) + ret.append((module, self.get_class_list(module), + self.get_function_list(module))) + return ret + + def get_class_list(self, module): + ret = [] + for name in self.dsa.get_class_names(): + classname = name + if '.' in name: + classmodule, classname = name.rsplit('.', 1) + if classmodule != module: + continue + elif module != '': + continue + print 'retrieving method list for', name + print 'method list:', self.get_method_list(name) + ret.append((name, self.get_method_list(name))) + return ret + + def get_function_list(self, module=''): + ret = [] + for name in self.dsa.get_function_names(): + funcname = name + if '.' in name: + funcpath, funcname = name.rsplit('.', 1) + if funcpath != module: + continue + elif module != '': + continue + ret.append(funcname) + return ret + + def get_method_list(self, classname): + return self.dsa.get_class_methods(classname) + + def write_function(self, functionname, belowchar='-'): # XXX I think the docstring should either be split on \n\n and cleaned # from indentation, or treated as ReST too (although this is obviously # dangerous for non-ReST docstrings)... - lst = [Title("Function: %s" % fun_name, belowchar=belowchar), - LiteralBlock(self.dsa.get_function_doc(fun_name)), - LiteralBlock(self.dsa.get_function_definition(fun_name))] + lst = [Title("Function: %s" % (functionname,), belowchar=belowchar), + LiteralBlock(self.dsa.get_function_doc(functionname)), + LiteralBlock(self.dsa.get_function_definition(functionname))] - args, retval = self.dsa.get_function_signature(fun_name) - arg_str = "\n".join(["%s :: %s" % (str(name), str(type)) for name, type in args]) + args, retval = self.dsa.get_function_signature(functionname) + arg_str = "\n".join(["%s :: %s" % (str(name), str(type)) + for name, type in args]) arg_str += "\n" + "Return value :: %s" % str(retval) lst.append(Paragraph("where:")) lst.append(LiteralBlock(arg_str)) # XXX missing implementation of dsa.get_function_location() - #filename, lineno = self.dsa.get_function_location(fun_name) + #filename, lineno = self.dsa.get_function_location(functionname) #linkname, linktarget = self.linkgen.getlink(filename, lineno) #if linktarget: # lst.append(Paragraph("Function source: ", # Link(linkname, linktarget))) #else: lst.append(Paragraph('Function source:')) - lst.append(LiteralBlock(self.dsa.get_function_source(fun_name))) + lst.append(LiteralBlock(self.dsa.get_function_source(functionname))) #arg_str = "(%s)" % (",".join([str(i) for i in args])) #ret_str = str(retval) @@ -213,7 +302,7 @@ lst.append(call_site_title) call_sites = lst - for call_site, frame in self.dsa.get_function_callpoints(fun_name): + for call_site, frame in self.dsa.get_function_callpoints(functionname): link_str = "File %s:%s" % (call_site.filename, call_site.lineno) link_str, link_target = self.linkgen.getlink(call_site.filename, @@ -230,7 +319,7 @@ except KeyboardInterrupt, SystemError: raise except: - source = ["*Cannot get source*"] + source = "*Cannot get source*" lines = [] for num, line in enumerate(source): if num == call_site.lineno - frame.code.firstlineno - 1: Added: py/dist/py/apigen/rest/testing/somemodule.py ============================================================================== --- (empty file) +++ py/dist/py/apigen/rest/testing/somemodule.py Wed Oct 25 01:24:04 2006 @@ -0,0 +1,10 @@ +class SomeClass(object): + """Some class definition""" + + def __init__(self, a): + self.a = a + + def method(self, a, b, c): + """method docstring""" + return a + b + c + Added: py/dist/py/apigen/rest/testing/someothermodule.py ============================================================================== --- (empty file) +++ py/dist/py/apigen/rest/testing/someothermodule.py Wed Oct 25 01:24:04 2006 @@ -0,0 +1,21 @@ +from somemodule import SomeClass + +class SomeSubClass(SomeClass): + """Some subclass definition""" + +def fun(a, b, c): + """Some docstring + + Let's make it span a couple of lines to be interesting... + + Note: + + * rest + * should + * be + * supported + * or + * ignored... + """ + return "d" + Modified: py/dist/py/apigen/rest/testing/test_rest.py ============================================================================== --- py/dist/py/apigen/rest/testing/test_rest.py (original) +++ py/dist/py/apigen/rest/testing/test_rest.py Wed Oct 25 01:24:04 2006 @@ -133,6 +133,25 @@ fun(1, 3, s2) t.end_tracing() return ds + + def get_filled_docstorage_modules(self): + import somemodule + import someothermodule + descs = { + 'somemodule.SomeClass': somemodule, + 'someothermodule.SomeOtherClass': someothermodule, + 'someothermodule.fun': someothermodule.fun, + } + ds = DocStorage().from_dict(descs) + t = Tracer(ds) + t.start_tracing() + s1 = somemodule.SomeClass("a") + someothermodule.fun(1, 2, s1) + s2 = someothermodule.SomeSubClass("b") + s2.method(1, 2, 3) + someothermodule.fun(1, 3, s2) + t.end_tracing() + return ds def check_rest(self, tempdir): from py.__.misc import rest @@ -151,14 +170,36 @@ 'class_SomeSubClass.txt', 'function_fun.txt', 'index.txt', - 'method_SomeClass___init__.txt', - 'method_SomeClass_method.txt', - 'method_SomeSubClass___init__.txt', - 'method_SomeSubClass_method.txt', + 'method_SomeClass.__init__.txt', + 'method_SomeClass.method.txt', + 'method_SomeSubClass.__init__.txt', + 'method_SomeSubClass.method.txt', ] # now we check out... self.check_rest(tempdir) + def test_generation_modules(self): + py.test.skip('borken - fijal, somehow the methods for classes in ' + 'modules don\'t get picked up... any idea?') + ds = self.get_filled_docstorage_modules() + lg = DirectPaste() + tempdir = temppath.ensure('module_api', dir=True) + r = RestGen(ds, lg, DirWriter(tempdir)) + r.write() + basenames = [p.basename for p in tempdir.listdir('*.txt')] + assert sorted(basenames) == [ + 'class_somemodule.SomeClass.txt', + 'class_someothermodule.SomeOtherClass.txt', + 'function_someothermodule.fun.txt', + 'index.txt', + 'method_somemodule.SomeClass.__init__.txt', + 'method_somemodule.SomeClass.method.txt', + 'method_somemodule.SomeSubClass.__init__.txt', + 'method_somemodule.SomeSubClass.method.txt', + 'module_somemodule.txt', + 'module_someothermodule.txt', + ] + def test_check_internal_links(self): ds = self.get_filled_docstorage() lg = DirectFS() @@ -185,7 +226,6 @@ if not tempfile.check(): py.test.skip('depends on previous test, which failed') data = tempfile.read() - print data # index should be above the rest assert data.find('Exported classes\\:') > -1 assert data.find('Exported classes\\:') < data.find('Function\\: fun') @@ -197,13 +237,11 @@ assert data.find('Class\\: SomeClass') < data.find( 'Function\\: SomeClass.method') # __init__ should be above other methods - assert data.find('Function\\: SomeClass.__init__') > -1 - # XXX in the future... - # assert data.find('Function: SomeClass.__init__') < data.find( - # 'Function: SomeClass.method') + assert data.find('Function\\: SomeClass.\\_\\_init\\_\\_') > -1 + assert data.find('Function\\: SomeClass.\\_\\_init\\_\\_') < data.find( + 'Function\\: SomeClass.method') def test_som_fun(self): - py.test.skip("Failing") descs = {'fun_':fun_} ds = DocStorage().from_dict(descs) t = Tracer(ds) @@ -232,7 +270,7 @@ tempdir = temppath.ensure("function_source", dir=True) r = RestGen(ds, lg, DirWriter(tempdir)) r.write() - assert tempdir.join("function_blah.txt").open().read().find("a = 3") != -1 + assert tempdir.join("function_blah.txt").read().find("a = 3") != -1 self.check_rest(tempdir) def test_function_arguments(self): @@ -252,7 +290,7 @@ tempdir = temppath.ensure("function_args", dir=True) r = RestGen(ds, lg, DirWriter(tempdir)) r.write() - source = tempdir.join("function_blah.txt").open().read() + source = tempdir.join("function_blah.txt").read() call_point = source.find("Call sites\:") assert call_point != -1 assert source.find("a :: ") < call_point From fijal at codespeak.net Wed Oct 25 10:39:30 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Wed, 25 Oct 2006 10:39:30 +0200 (CEST) Subject: [py-svn] r33709 - in py/dist/py/apigen: rest/testing tracer/testing Message-ID: <20061025083930.5826710050@code0.codespeak.net> Author: fijal Date: Wed Oct 25 10:39:27 2006 New Revision: 33709 Modified: py/dist/py/apigen/rest/testing/test_rest.py py/dist/py/apigen/tracer/testing/test_model.py Log: Fixed skipped test. Modified: py/dist/py/apigen/rest/testing/test_rest.py ============================================================================== --- py/dist/py/apigen/rest/testing/test_rest.py (original) +++ py/dist/py/apigen/rest/testing/test_rest.py Wed Oct 25 10:39:27 2006 @@ -138,8 +138,8 @@ import somemodule import someothermodule descs = { - 'somemodule.SomeClass': somemodule, - 'someothermodule.SomeOtherClass': someothermodule, + 'somemodule.SomeClass': somemodule.SomeClass, + 'someothermodule.SomeSubClass': someothermodule.SomeSubClass, 'someothermodule.fun': someothermodule.fun, } ds = DocStorage().from_dict(descs) @@ -179,26 +179,27 @@ self.check_rest(tempdir) def test_generation_modules(self): - py.test.skip('borken - fijal, somehow the methods for classes in ' - 'modules don\'t get picked up... any idea?') + #py.test.skip('borken - fijal, somehow the methods for classes in ' + # 'modules don\'t get picked up... any idea?') ds = self.get_filled_docstorage_modules() lg = DirectPaste() tempdir = temppath.ensure('module_api', dir=True) r = RestGen(ds, lg, DirWriter(tempdir)) r.write() basenames = [p.basename for p in tempdir.listdir('*.txt')] - assert sorted(basenames) == [ + expected = [ 'class_somemodule.SomeClass.txt', - 'class_someothermodule.SomeOtherClass.txt', + 'class_someothermodule.SomeSubClass.txt', 'function_someothermodule.fun.txt', 'index.txt', 'method_somemodule.SomeClass.__init__.txt', 'method_somemodule.SomeClass.method.txt', - 'method_somemodule.SomeSubClass.__init__.txt', - 'method_somemodule.SomeSubClass.method.txt', + 'method_someothermodule.SomeSubClass.__init__.txt', + 'method_someothermodule.SomeSubClass.method.txt', 'module_somemodule.txt', 'module_someothermodule.txt', ] + assert sorted(basenames) == expected def test_check_internal_links(self): ds = self.get_filled_docstorage() Modified: py/dist/py/apigen/tracer/testing/test_model.py ============================================================================== --- py/dist/py/apigen/tracer/testing/test_model.py (original) +++ py/dist/py/apigen/tracer/testing/test_model.py Wed Oct 25 10:39:27 2006 @@ -85,14 +85,14 @@ f = guess_type(A).unionof(guess_type(A)) assert isinstance(f, SomeClass) assert f.cls is A - f = guess_type(A).unionof(guess_type(B)) + f = guess_type(A).unionof(guess_type(B)).unionof(guess_type(A)) assert isinstance(f, SomeUnion) assert len(f.possibilities) == 2 f = guess_type(A()).unionof(guess_type(A())) assert isinstance(f, SomeInstance) assert isinstance(f.classdef, SomeClass) assert f.classdef.cls is A - f = guess_type(B()).unionof(guess_type(A())) + f = guess_type(B()).unionof(guess_type(A())).unionof(guess_type(B())) assert isinstance(f, SomeInstance) assert isinstance(f.classdef, SomeUnion) assert len(f.classdef.possibilities) == 2 From guido at codespeak.net Wed Oct 25 13:16:41 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Wed, 25 Oct 2006 13:16:41 +0200 (CEST) Subject: [py-svn] r33712 - py/dist/py/documentation Message-ID: <20061025111641.78A7110050@code0.codespeak.net> Author: guido Date: Wed Oct 25 13:16:36 2006 New Revision: 33712 Modified: py/dist/py/documentation/conftest.py Log: Sm Modified: py/dist/py/documentation/conftest.py ============================================================================== --- py/dist/py/documentation/conftest.py (original) +++ py/dist/py/documentation/conftest.py Wed Oct 25 13:16:36 2006 @@ -21,12 +21,15 @@ py.test.skip("docutils not importable") def restcheck(path): - _checkskip(path.localpath) + localpath = path + if hasattr(path, 'localpath'): + localpath = path.localpath + _checkskip(localpath) checkdocutils() import docutils.utils try: - cur = path.localpath + cur = localpath for x in cur.parts(reverse=True): confrest = x.dirpath('confrest.py') if confrest.check(file=1): From guido at codespeak.net Wed Oct 25 13:49:21 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Wed, 25 Oct 2006 13:49:21 +0200 (CEST) Subject: [py-svn] r33713 - in py/dist/py/rst: . testing Message-ID: <20061025114921.31D0A10063@code0.codespeak.net> Author: guido Date: Wed Oct 25 13:49:17 2006 New Revision: 33713 Modified: py/dist/py/rst/rst.py py/dist/py/rst/testing/test_rst.py Log: Fixed some escaping problems: [ is no longer escaped (not required, since it only has to be escaped for links and _ is already escaped), escaping inside markup is no longer done (not entirely okay yet, but we're getting close to something simple and solid, even though docutils' behaviour is totally broken in places). Modified: py/dist/py/rst/rst.py ============================================================================== --- py/dist/py/rst/rst.py (original) +++ py/dist/py/rst/rst.py Wed Oct 25 13:49:17 2006 @@ -17,7 +17,7 @@ """escape ReST markup""" # XXX this takes a very naive approach to escaping, but it seems to be # sufficient... - for c in '\\*`[|:_': + for c in '\\*`|:_': txt = txt.replace(c, '\\%s' % (c,)) return txt @@ -91,13 +91,13 @@ def __setitem__(self, item, value): self.children[item] = value - def text(self, escape=True): + def text(self): """ return a ReST string representation of the node """ - return self.sep.join([child.text(escape) for child in self.children]) + return self.sep.join([child.text() for child in self.children]) - def wordlist(self, escape=True): + def wordlist(self): """ return a list of ReST strings for this node and its children """ - return [self.text(escape)] + return [self.text()] class Rest(AbstractNode): sep = "\n\n" @@ -116,10 +116,14 @@ link_texts.append(".. _`%s`: %s" % (escape(link), target)) return "\n" + "\n".join(link_texts) + "\n\n" - def text(self, escape=True): + def text(self): outcome = [] + if (isinstance(self.children[0], Transition) or + isinstance(self.children[-1], Transition)): + raise ValueError, ('document must not begin or end with a ' + 'transition') for child in self.children: - outcome.append(child.text(escape)) + outcome.append(child.text()) text = self.sep.join(outcome) + "\n" # trailing newline return text + self.render_links() @@ -132,7 +136,7 @@ self.width = width super(Transition, self).__init__(*args, **kwargs) - def text(self, escape=True): + def text(self): return (self.width - 1) * self.char class Paragraph(AbstractNode): @@ -149,10 +153,10 @@ args[num] = Text(arg) super(Paragraph, self).__init__(*args, **kwargs) - def text(self, escape=True): + def text(self): texts = [] for child in self.children: - texts += child.wordlist(escape) + texts += child.wordlist() buf = [] outcome = [] @@ -185,8 +189,8 @@ indent = " " sep = "" - def text(self, escape=True): - all_txt = AbstractNode.text(self, escape=False) + def text(self): + all_txt = AbstractNode.text(self) all_txts = all_txt.split('\n') return '::\n\n%s' % ("\n".join([self.indent + i for i in all_txts]),) @@ -196,8 +200,8 @@ belowchar = "" abovechar = "" - def text(self, escape=True): - txt = Paragraph.text(self, escape) + def text(self): + txt = Paragraph.text(self) lines = [] if self.abovechar: lines.append(self.abovechar * len(txt)) @@ -213,17 +217,13 @@ def __init__(self, _text): self._text = _text - def text(self, escape=True): + def text(self): text = self._text - if escape: - text = globals()['escape'](text) return self.start + text + self.end class Text(AbstractText): - def wordlist(self, escape=True): - text = self._text - if escape: - text = globals()['escape'](text) + def wordlist(self): + text = escape(self._text) return self._reg_whitespace.split(text) class Em(AbstractText): @@ -238,6 +238,9 @@ start = '``' end = '``' + def text(self): + return super(Quote, self).text() + class Anchor(AbstractText): start = '_`' end = '`' @@ -253,11 +256,11 @@ class ListItem(Paragraph): item_char = "*" - def text(self, escape=True): + def text(self): oldindent = self.indent self.indent = oldindent + ' ' try: - txt = Paragraph.text(self, escape) + txt = Paragraph.text(self) finally: self.indent = oldindent txt = self.item_char + txt[1:] @@ -281,11 +284,11 @@ self.target = target self.rest = None - def text(self, escape=True): + def text(self): if self.rest is None: self.rest = self.find_rest() self.rest.links[self._text] = self.target - return AbstractText.text(self, escape) + return AbstractText.text(self) def find_rest(self): # XXX little overkill, but who cares... Modified: py/dist/py/rst/testing/test_rst.py ============================================================================== --- py/dist/py/rst/testing/test_rst.py (original) +++ py/dist/py/rst/testing/test_rst.py Wed Oct 25 13:49:17 2006 @@ -3,18 +3,53 @@ """ from py.__.rst.rst import * +from py.__.documentation.conftest import restcheck +import traceback + +tempdir = py.test.ensuretemp('rest') +def checkrest(rest): + fname = traceback.extract_stack()[-2][2] + i = 0 + while True: + if i == 0: + filename = '%s.txt' % (fname,) + else: + filename = '%s_%s.txt' % (fname, i) + tempfile = tempdir.join(filename) + if not tempfile.check(): + break + i += 1 + tempfile.write(rest) + restcheck(tempfile) def test_escape(): txt = Paragraph('*escape* ``test``').text() assert txt == '\\*escape\\* \\`\\`test\\`\\`' + checkrest(txt) txt = Paragraph(Strong('*strong*')).text() - assert txt == '**\\*strong\\***' - txt = Rest(Paragraph(Link('foo[bar]', 'foo[bar]'))).text() - assert txt == "`foo\\[bar]`_\n\n.. _`foo\\[bar]`: foo[bar]\n\n" + # docutils doesn't require escaping here (it's greedy) + assert txt == '***strong***' + checkrest(txt) + txt = Rest(Paragraph('foo [1]_')).text() + assert txt == "foo [1]\\_\n" + checkrest(txt) def test_escape_literal(): txt = LiteralBlock('*escape* ``test``').text() assert txt == '::\n\n *escape* ``test``' + checkrest(txt) + +def test_escape_markup(): + txt = Em('foo*bar').text() + # no escaping required here... (greedy behaviour) + assert txt == '*foo*bar*' + checkrest(txt) + txt = Paragraph(Em('foo'), "*bar").text() + assert txt == '*foo* \\*bar' + checkrest(txt) + txt = Paragraph(Em('foo'), "bar*").text() + assert txt == '*foo* bar\\*' + checkrest(txt) def test_illegal_parent(): Rest(Paragraph(Text('spam'))) @@ -22,34 +57,50 @@ py.test.raises(RestError, 'ListItem(Paragraph(Text("eggs")))') def test_text_basic(): - assert Text("dupa").text() == "dupa" + txt = Text("dupa").text() + assert txt == "dupa" + checkrest(txt) def test_basic_inline(): txt = Em('foo').text() assert txt == '*foo*' + checkrest(txt) txt = Strong('bar').text() assert txt == '**bar**' + checkrest(txt) def test_text_join(): - assert Paragraph(Text("dupa"), Text("dupa")).text() == "dupa dupa" + txt = Paragraph(Text("dupa"), Text("dupa")).text() + assert txt == "dupa dupa" + checkrest(txt) def test_paragraph_basic(): - assert Paragraph(Text('spam')).text() == 'spam' + txt = Paragraph(Text('spam')).text() + assert txt == 'spam' + checkrest(txt) def test_paragraph_string(): - assert Paragraph("eggs").text() == "eggs" + txt = Paragraph("eggs").text() + assert txt == "eggs" + checkrest(txt) def test_paragraph_join(): - assert Rest(Paragraph(Text("a")), Paragraph(Text("b"))).text() == ( - "a\n\nb\n") + txt = Rest(Paragraph(Text("a")), Paragraph(Text("b"))).text() + assert txt == "a\n\nb\n" + checkrest(txt) def test_paragraph_indent(): - assert Paragraph(Text("a"), indent=" ").text() == " a" - assert Paragraph(Text(" a "), indent=" ").text() == " a" + txt = Paragraph(Text("a"), indent=" ").text() + assert txt == " a" + checkrest(txt) + txt = Paragraph(Text(" a "), indent=" ").text() + assert txt == " a" + checkrest(txt) def test_paragraph_width(): txt = Paragraph(Text("a b c d e f"), width=3, indent=" ").text() assert txt == ' a\n b\n c\n d\n e\n f' + checkrest(txt) text = """ Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Vestibulum malesuada @@ -70,14 +121,16 @@ turpis. Etiam et ipsum. Quisque at lacus. Etiam pellentesque, enim porta pulvinar viverra, libero elit iaculis justo, vitae convallis pede purus vel arcu. Morbi aliquam lacus et urna. Donec commodo pellentesque mi.""" - ret = Paragraph(text, width=80).text() - print repr(ret) + txt = Paragraph(text, width=80).text() + print repr(txt) print repr(expected) - assert ret == expected + assert txt == expected + checkrest(txt) def test_paragraph_stripping(): txt = Paragraph(Text('\n foo bar\t')).text() assert txt == 'foo bar' + checkrest(txt) def test_blockquote(): expected = """\ @@ -94,21 +147,27 @@ Paragraph("Paragraph")).text() print repr(txt) assert txt == expected + checkrest(txt) def test_title(): txt = Title(Text("Some title"), belowchar="=").text() assert txt == "Some title\n==========" + checkrest(txt) txt = Title(Text("Some title"), belowchar="#", abovechar="#").text() assert txt == "##########\nSome title\n##########" + checkrest(txt) def test_link(): - expected = "`some link`_\n.. _`some link`: http://codespeak.net\n" + expected = "`some link`_\n\n.. _`some link`: http://codespeak.net\n\n" txt = Rest(Paragraph(Link("some link", "http://codespeak.net"))).text() + assert txt == expected + checkrest(txt) def test_text(): expected = "This is a test!\n" txt = Rest(Paragraph("This is a test!")).text() assert txt == expected + checkrest(txt) def test_text_multiline(): expected = """\ @@ -130,6 +189,7 @@ 'aliquam lacus et urna. Donec commodo pellentesque ' 'mi.')).text() assert txt == expected + checkrest(txt) def test_text_indented(): expected = """\ @@ -142,16 +202,19 @@ '\nThis is still part of the first paragraph.') ).text() assert txt == expected + checkrest(txt) def test_text_strip(): expected = "foo\n" txt = Rest(Paragraph(Text(' foo '))).text() assert txt == expected + checkrest(txt) def test_list(): expected = "* a\n\n* b\n" txt = Rest(ListItem("a"), ListItem("b")).text() assert txt == expected + checkrest(txt) def test_list_multiline(): expected = """\ @@ -178,12 +241,14 @@ 'purus vel arcu. Morbi aliquam lacus et urna. Donec ' 'commodo pellentesque mi.')).text() assert txt == expected + checkrest(txt) def test_ordered_list(): expected = "#. foo\n\n#. bar\n\n#. baz\n" txt = Rest(OrderedListItem('foo'), OrderedListItem('bar'), OrderedListItem('baz')).text() assert txt == expected + checkrest(txt) def test_title_following_links_empty_line(): expected = """\ @@ -198,6 +263,7 @@ """ txt = Rest(Paragraph("Foo, bar and ", Link("baz", "http://www.baz.com")), Title('Spam'), Paragraph('Spam, eggs and spam.')) + checkrest(txt) def test_definition_list(): expected = """\ @@ -211,8 +277,16 @@ DListItem("spam", "eggs, spam, spam, eggs, spam and spam...") ).text() assert txt == expected + checkrest(txt) def test_transition(): - assert Rest(Transition()).text() == '%s\n' % ('-' * 79,) - assert Rest(Transition('+')).text() == '%s\n' % ('+' * 79,) + txt = Rest(Paragraph('foo'), Transition(), Paragraph('bar')).text() + assert txt == 'foo\n\n%s\n\nbar\n' % ('-' * 79,) + checkrest(txt) + txt = Rest(Paragraph('foo'), Transition('+'), Paragraph('bar')).text() + assert txt == 'foo\n\n%s\n\nbar\n' % ('+' * 79,) + checkrest(txt) + + py.test.raises(ValueError, 'Rest(Transition(), Paragraph("foo")).text()') + py.test.raises(ValueError, 'Rest(Paragraph("foo"), Transition()).text()') From guido at codespeak.net Wed Oct 25 14:02:20 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Wed, 25 Oct 2006 14:02:20 +0200 (CEST) Subject: [py-svn] r33716 - in py/dist/py/rst: . testing Message-ID: <20061025120220.F249E10071@code0.codespeak.net> Author: guido Date: Wed Oct 25 14:02:17 2006 New Revision: 33716 Modified: py/dist/py/rst/rst.py py/dist/py/rst/testing/test_rst.py Log: Fixed escaping of the markup characters themselves (and only them) in the markup nodes. Modified: py/dist/py/rst/rst.py ============================================================================== --- py/dist/py/rst/rst.py (original) +++ py/dist/py/rst/rst.py Wed Oct 25 14:02:17 2006 @@ -218,8 +218,15 @@ self._text = _text def text(self): - text = self._text + text = self.escape(self._text) return self.start + text + self.end + + def escape(self, text): + if self.start: + text = text.replace(self.start, '\\%s' % (self.start,)) + if self.end and self.end != self.start: + text = text.replace(self.end, '\\%s' % (self.end,)) + return text class Text(AbstractText): def wordlist(self): @@ -238,9 +245,6 @@ start = '``' end = '``' - def text(self): - return super(Quote, self).text() - class Anchor(AbstractText): start = '_`' end = '`' Modified: py/dist/py/rst/testing/test_rst.py ============================================================================== --- py/dist/py/rst/testing/test_rst.py (original) +++ py/dist/py/rst/testing/test_rst.py Wed Oct 25 14:02:17 2006 @@ -26,13 +26,16 @@ txt = Paragraph('*escape* ``test``').text() assert txt == '\\*escape\\* \\`\\`test\\`\\`' checkrest(txt) - txt = Paragraph(Strong('*strong*')).text() + txt = Paragraph(Em('*strong*')).text() # docutils doesn't require escaping here (it's greedy) - assert txt == '***strong***' + assert txt == '*\\*strong\\**' checkrest(txt) txt = Rest(Paragraph('foo [1]_')).text() assert txt == "foo [1]\\_\n" checkrest(txt) + # hmmm... + txt = Rest(Paragraph(Em('foo *bar* baz'))).text() + assert txt == '*foo \\*bar\\* baz*\n' def test_escape_literal(): txt = LiteralBlock('*escape* ``test``').text() @@ -41,8 +44,7 @@ def test_escape_markup(): txt = Em('foo*bar').text() - # no escaping required here... (greedy behaviour) - assert txt == '*foo*bar*' + assert txt == '*foo\\*bar*' checkrest(txt) txt = Paragraph(Em('foo'), "*bar").text() assert txt == '*foo* \\*bar' From fijal at codespeak.net Wed Oct 25 15:38:55 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Wed, 25 Oct 2006 15:38:55 +0200 (CEST) Subject: [py-svn] r33718 - in py/dist/py/apigen: rest rest/testing tracer tracer/testing Message-ID: <20061025133855.60B6010053@code0.codespeak.net> Author: fijal Date: Wed Oct 25 15:38:52 2006 New Revision: 33718 Modified: py/dist/py/apigen/rest/genrest.py py/dist/py/apigen/rest/testing/test_rest.py py/dist/py/apigen/tracer/docstorage.py py/dist/py/apigen/tracer/model.py py/dist/py/apigen/tracer/testing/test_model.py Log: Added crosslinks. Modified: py/dist/py/apigen/rest/genrest.py ============================================================================== --- py/dist/py/apigen/rest/genrest.py (original) +++ py/dist/py/apigen/rest/genrest.py Wed Oct 25 15:38:52 2006 @@ -9,6 +9,7 @@ from py.__.apigen.tracer.docstorage import DocStorageAccessor from py.__.rst.rst import * # XXX Maybe we should list it here +from py.__.apigen.tracer import model class AbstractLinkWriter(object): """ Class implementing writing links to source code. @@ -244,8 +245,6 @@ continue elif module != '': continue - print 'retrieving method list for', name - print 'method list:', self.get_method_list(name) ret.append((name, self.get_method_list(name))) return ret @@ -265,6 +264,19 @@ def get_method_list(self, classname): return self.dsa.get_class_methods(classname) + def process_type_link(self, _type, lst): + # now we do simple type dispatching and provide a link in this case + data = self.dsa.get_type_desc(_type) + if not data: + for i in _type.striter(): + if isinstance(i, str): + lst.append(i) + else: + self.process_type_link(i, lst) + return + name, _desc_type = data + lst.append(Link(str(_type), _desc_type + "_" + name + ".html")) + def write_function(self, functionname, belowchar='-'): # XXX I think the docstring should either be split on \n\n and cleaned # from indentation, or treated as ReST too (although this is obviously @@ -273,13 +285,36 @@ LiteralBlock(self.dsa.get_function_doc(functionname)), LiteralBlock(self.dsa.get_function_definition(functionname))] + lst.append(Paragraph("where:")) args, retval = self.dsa.get_function_signature(functionname) - arg_str = "\n".join(["%s :: %s" % (str(name), str(type)) - for name, type in args]) - arg_str += "\n" + "Return value :: %s" % str(retval) - lst.append(Paragraph("where:")) - lst.append(LiteralBlock(arg_str)) + for name, _type in args + [('Return value', retval)]: + l = [] + self.process_type_link(_type, l) + items = [] + next = "%s :: " % name + for item in l: + if isinstance(item, str): + next += item + else: + if next: + items.append(Text(next)) + next = "" + items.append(item) + if next: + items.append(Text(next)) + lst.append(ListItem(*items)) + #if link: + # link_str, link_target = link + # lst.append(ListItem(Quote("%s :: %s" % (name, pre_str)), + # Link(link_str, link_target), Quote(post_str))) + #else: + # lst.append(ListItem(Quote("%s :: %s%s" % (name, _type)))) + + #arg_str = "\n".join(["%s :: %s" % (str(name), str(type)) + # for name, type in args]) + #arg_str += "\n" + "Return value :: %s" % str(retval) + #lst.append(LiteralBlock(arg_str)) # XXX missing implementation of dsa.get_function_location() #filename, lineno = self.dsa.get_function_location(functionname) Modified: py/dist/py/apigen/rest/testing/test_rest.py ============================================================================== --- py/dist/py/apigen/rest/testing/test_rest.py (original) +++ py/dist/py/apigen/rest/testing/test_rest.py Wed Oct 25 15:38:52 2006 @@ -294,9 +294,9 @@ source = tempdir.join("function_blah.txt").read() call_point = source.find("Call sites\:") assert call_point != -1 - assert source.find("a :: ") < call_point - assert source.find("b :: ") < call_point - assert source.find("c :: >") < call_point + assert source.find("a \:\: ") < call_point + assert source.find("b \:\: ") < call_point + assert source.find("c \:\: ") < call_point self.check_rest(tempdir) def test_class_typedefs(self): @@ -325,5 +325,5 @@ source = tempdir.join("function_xxx.txt").open().read() call_point = source.find("Call sites\:") assert call_point != -1 - assert source.find("x :: ,)>") < call_point + assert source.find("x \:\: ") < call_point self.check_rest(tempdir) Modified: py/dist/py/apigen/tracer/docstorage.py ============================================================================== --- py/dist/py/apigen/tracer/docstorage.py (original) +++ py/dist/py/apigen/tracer/docstorage.py Wed Oct 25 15:38:52 2006 @@ -10,7 +10,7 @@ from py.__.apigen.tracer.description import FunctionDesc, ClassDesc, MethodDesc, \ Desc -from py.__.apigen.tracer.model import guess_type +from py.__.apigen.tracer import model class DocStorage(object): """ Class storing info about API @@ -25,10 +25,10 @@ def generalize_args(self, desc, frame): args = [arg for key, arg in frame.getargs()] #self.call_stack.append((desc, args)) - desc.consider_call([guess_type(arg) for arg in args]) + desc.consider_call([model.guess_type(arg) for arg in args]) def generalize_retval(self, desc, arg): - desc.consider_return(guess_type(arg)) + desc.consider_return(model.guess_type(arg)) def consider_return(self, frame, arg): assert isinstance(frame, py.code.Frame) @@ -211,6 +211,16 @@ assert isinstance(desc, ClassDesc) return sorted(desc.getfields()) + def get_type_desc(self, _type): + # XXX We provice only classes here + if not isinstance(_type, model.SomeClass): + return None + # XXX we might want to cache it at some point + for key, desc in self.ds.descs.iteritems(): + if desc.pyobj == _type.cls: + return key, 'class' + return None + #def get_object_info(self, key): # Modified: py/dist/py/apigen/tracer/model.py ============================================================================== --- py/dist/py/apigen/tracer/model.py (original) +++ py/dist/py/apigen/tracer/model.py Wed Oct 25 15:38:52 2006 @@ -35,6 +35,10 @@ def __ne__(self, other): return not self == other + + # this is to provide possibility of eventually linking some stuff + def striter(self): + yield str(self) class SomeUnion(object): # empty typedef @@ -55,10 +59,18 @@ return not self == other def __repr__(self): - return "AnyOf(%s)" % ",".join([str(i) for i in list(self.possibilities)]) + return "AnyOf(%s)" % ", ".join([str(i) for i in list(self.possibilities)]) def gettypedef(self): return (None, None) + + def striter(self): + yield "AnyOf(" + for num, i in enumerate(self.possibilities): + yield i + if num != len(self.possibilities) - 1: + yield ", " + yield ")" class SomeBoolean(SomeObject): typedef = types.BooleanType @@ -92,7 +104,7 @@ return self def __repr__(self): - return "" % self.cls.__name__ + return "Class %s" % self.cls.__name__ class SomeCode(SomeObject): typedef = types.CodeType @@ -146,6 +158,11 @@ def __repr__(self): return "" % str(self.classdef) + def striter(self): + yield "" + typedef = types.InstanceType class SomeInt(SomeObject): Modified: py/dist/py/apigen/tracer/testing/test_model.py ============================================================================== --- py/dist/py/apigen/tracer/testing/test_model.py (original) +++ py/dist/py/apigen/tracer/testing/test_model.py Wed Oct 25 15:38:52 2006 @@ -97,3 +97,16 @@ assert isinstance(f.classdef, SomeUnion) assert len(f.classdef.possibilities) == 2 +def test_striter(): + class A(object): + pass + + class B(object): + pass + + g = guess_type(A).unionof(guess_type(A())) + l = list(g.striter()) + assert l[0] == "AnyOf(" + assert isinstance(l[1], SomeClass) + assert l[2] == ", " + assert isinstance(l[3], SomeInstance) From guido at codespeak.net Wed Oct 25 15:58:16 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Wed, 25 Oct 2006 15:58:16 +0200 (CEST) Subject: [py-svn] r33721 - in py/dist/py/rst: . testing Message-ID: <20061025135816.30F261006C@code0.codespeak.net> Author: guido Date: Wed Oct 25 15:58:11 2006 New Revision: 33721 Modified: py/dist/py/rst/rst.py py/dist/py/rst/testing/test_rst.py Log: Checking HTML results for certain elements now, added check for duplicate link names with different targets. Modified: py/dist/py/rst/rst.py ============================================================================== --- py/dist/py/rst/rst.py (original) +++ py/dist/py/rst/rst.py Wed Oct 25 15:58:11 2006 @@ -291,6 +291,8 @@ def text(self): if self.rest is None: self.rest = self.find_rest() + if self.rest.links.get(self._text, self.target) != self.target: + raise ValueError('link name already in use for a different target') self.rest.links[self._text] = self.target return AbstractText.text(self) Modified: py/dist/py/rst/testing/test_rst.py ============================================================================== --- py/dist/py/rst/testing/test_rst.py (original) +++ py/dist/py/rst/testing/test_rst.py Wed Oct 25 15:58:11 2006 @@ -21,37 +21,55 @@ i += 1 tempfile.write(rest) restcheck(tempfile) + return tempfile.new(ext='.html').read() -def test_escape(): +def test_escape_text(): txt = Paragraph('*escape* ``test``').text() assert txt == '\\*escape\\* \\`\\`test\\`\\`' - checkrest(txt) - txt = Paragraph(Em('*strong*')).text() - # docutils doesn't require escaping here (it's greedy) - assert txt == '*\\*strong\\**' - checkrest(txt) + html = checkrest(txt) + assert '*escape* ``test``' in html + +def test_escape_markup_simple(): + txt = Paragraph(Em('*em*')).text() + assert txt == '*\\*em\\**' + html = checkrest(txt) + assert '*em*' in html + +def test_escape_underscore(): txt = Rest(Paragraph('foo [1]_')).text() assert txt == "foo [1]\\_\n" - checkrest(txt) - # hmmm... + html = checkrest(txt) + assert 'foo [1]_' in html + +def test_escape_markup_spaces_docutils_nastyness(): txt = Rest(Paragraph(Em('foo *bar* baz'))).text() assert txt == '*foo \\*bar\\* baz*\n' + html = checkrest(txt) + assert 'foo *bar* baz' in html def test_escape_literal(): txt = LiteralBlock('*escape* ``test``').text() assert txt == '::\n\n *escape* ``test``' - checkrest(txt) + html = checkrest(txt) + assert '>\n*escape* ``test``\n' in html -def test_escape_markup(): +def test_escape_markup_obvious(): txt = Em('foo*bar').text() assert txt == '*foo\\*bar*' - checkrest(txt) + html = checkrest(txt) + assert 'foo*bar' in html + +def test_escape_markup_text_1(): txt = Paragraph(Em('foo'), "*bar").text() assert txt == '*foo* \\*bar' - checkrest(txt) + html = checkrest(txt) + assert 'foo *bar' in html + +def test_escape_markup_text_2(): txt = Paragraph(Em('foo'), "bar*").text() assert txt == '*foo* bar\\*' - checkrest(txt) + html = checkrest(txt) + assert 'foo bar*' in html def test_illegal_parent(): Rest(Paragraph(Text('spam'))) @@ -61,25 +79,22 @@ def test_text_basic(): txt = Text("dupa").text() assert txt == "dupa" - checkrest(txt) def test_basic_inline(): txt = Em('foo').text() assert txt == '*foo*' - checkrest(txt) + +def test_basic_inline_2(): txt = Strong('bar').text() assert txt == '**bar**' - checkrest(txt) def test_text_join(): txt = Paragraph(Text("dupa"), Text("dupa")).text() assert txt == "dupa dupa" - checkrest(txt) def test_paragraph_basic(): txt = Paragraph(Text('spam')).text() assert txt == 'spam' - checkrest(txt) def test_paragraph_string(): txt = Paragraph("eggs").text() @@ -89,7 +104,6 @@ def test_paragraph_join(): txt = Rest(Paragraph(Text("a")), Paragraph(Text("b"))).text() assert txt == "a\n\nb\n" - checkrest(txt) def test_paragraph_indent(): txt = Paragraph(Text("a"), indent=" ").text() @@ -97,7 +111,6 @@ checkrest(txt) txt = Paragraph(Text(" a "), indent=" ").text() assert txt == " a" - checkrest(txt) def test_paragraph_width(): txt = Paragraph(Text("a b c d e f"), width=3, indent=" ").text() @@ -157,19 +170,26 @@ checkrest(txt) txt = Title(Text("Some title"), belowchar="#", abovechar="#").text() assert txt == "##########\nSome title\n##########" - checkrest(txt) + html = checkrest(txt) + assert '>Some title' in html def test_link(): expected = "`some link`_\n\n.. _`some link`: http://codespeak.net\n\n" txt = Rest(Paragraph(Link("some link", "http://codespeak.net"))).text() assert txt == expected - checkrest(txt) + html = checkrest(txt) + assert ' href="http://codespeak.net">some link' in html -def test_text(): - expected = "This is a test!\n" - txt = Rest(Paragraph("This is a test!")).text() +def test_link_same_text_and_target(): + txt = Rest(Paragraph(Link('some link', 'bar'), 'foo', + Link('some link', 'bar')) + ).text() + expected = '`some link`_ foo `some link`_\n\n.. _`some link`: bar\n\n' assert txt == expected - checkrest(txt) + +def test_link_same_text_different_target(): + py.test.raises(ValueError, ("Rest(Paragraph(Link('some link', 'foo')," + "Link('some link', 'bar'))).text()")) def test_text_multiline(): expected = """\ From guido at codespeak.net Wed Oct 25 16:07:32 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Wed, 25 Oct 2006 16:07:32 +0200 (CEST) Subject: [py-svn] r33722 - in py/dist/py/apigen: rest tracer Message-ID: <20061025140732.2EE2210070@code0.codespeak.net> Author: guido Date: Wed Oct 25 16:07:30 2006 New Revision: 33722 Modified: py/dist/py/apigen/rest/genrest.py py/dist/py/apigen/tracer/docstorage.py Log: Renamed 'get_function_doc' (DocStorageAccessor) to 'get_doc' and using it to get class documentation inside the ReST. Modified: py/dist/py/apigen/rest/genrest.py ============================================================================== --- py/dist/py/apigen/rest/genrest.py (original) +++ py/dist/py/apigen/rest/genrest.py Wed Oct 25 16:07:30 2006 @@ -203,6 +203,7 @@ ret = [] for cls, functions in classes: rest = [Title('Class: %s' % (cls,), belowchar='-'), + LiteralBlock(self.dsa.get_doc(cls)), Title('Functions:', belowchar='^')] for func in functions: linktarget = self.writer.getlink('method', @@ -282,7 +283,7 @@ # from indentation, or treated as ReST too (although this is obviously # dangerous for non-ReST docstrings)... lst = [Title("Function: %s" % (functionname,), belowchar=belowchar), - LiteralBlock(self.dsa.get_function_doc(functionname)), + LiteralBlock(self.dsa.get_doc(functionname)), LiteralBlock(self.dsa.get_function_definition(functionname))] lst.append(Paragraph("where:")) Modified: py/dist/py/apigen/tracer/docstorage.py ============================================================================== --- py/dist/py/apigen/tracer/docstorage.py (original) +++ py/dist/py/apigen/tracer/docstorage.py Wed Oct 25 16:07:30 2006 @@ -124,8 +124,8 @@ def get_class_names(self): """ Returning names of all classess """ - - def get_function_doc(self, name): + + def get_doc(self, name): """ Returning __doc__ of a function """ @@ -174,7 +174,7 @@ #def get_function(self, name): # return self.ds.descs[name].pyobj - def get_function_doc(self, name): + def get_doc(self, name): return self.ds.descs[name].pyobj.__doc__ or "*Not documented*" def get_function_definition(self, name): From fijal at codespeak.net Wed Oct 25 18:50:00 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Wed, 25 Oct 2006 18:50:00 +0200 (CEST) Subject: [py-svn] r33725 - py/dist/py/test/rsession Message-ID: <20061025165000.6F16F10074@code0.codespeak.net> Author: fijal Date: Wed Oct 25 18:49:56 2006 New Revision: 33725 Modified: py/dist/py/test/rsession/local.py py/dist/py/test/rsession/rsession.py Log: Cleanup of runner, needs some tests. Modified: py/dist/py/test/rsession/local.py ============================================================================== --- py/dist/py/test/rsession/local.py (original) +++ py/dist/py/test/rsession/local.py Wed Oct 25 18:49:56 2006 @@ -54,11 +54,12 @@ #-4. benchmark_runner - for running with benchmarking #-5. apigen_runner - for running under apigen to generate api out of it. def local_loop(session, reporter, itemgenerator, shouldstop, config, runner=None): - if runner is None: - if session.config.option.apigen: - runner = apigen_runner - else: - runner = box_runner + assert runner is not None + #if runner is None: + # if session.config.option.apigen: + # runner = apigen_runner + # else: + # runner = box_runner while 1: try: item = itemgenerator.next() Modified: py/dist/py/test/rsession/rsession.py ============================================================================== --- py/dist/py/test/rsession/rsession.py (original) +++ py/dist/py/test/rsession/rsession.py Wed Oct 25 18:49:56 2006 @@ -16,7 +16,8 @@ from py.__.test.rsession.hostmanage import init_hosts, teardown_hosts from py.__.test.terminal.out import getout -from py.__.test.rsession.local import local_loop, plain_runner +from py.__.test.rsession.local import local_loop, plain_runner, apigen_runner,\ + box_runner class RemoteOptions(object): def __init__(self, d): @@ -458,7 +459,7 @@ colitems = self.make_colitems(args, baseon=pkgdir.dirpath()) reporter(report.RsyncFinished()) - if self.config.option.apigen: + if runner is None and self.config.option.apigen: from py.__.apigen.tracer.docstorage import DocStorage from py.__.apigen.tracer.tracer import Tracer # XXX @@ -466,6 +467,11 @@ #module = __import__(str(pkgdir.join('__init__.py'))) self.docstorage = DocStorage().from_pkg(module) self.tracer = Tracer(self.docstorage) + runner = apigen_runner + elif runner is None and (self.config.option.usepdb or self.config.option.nocapture): + runner = plain_runner + elif runner is None: + runner = box_runner keyword = self.config.option.keyword @@ -478,8 +484,6 @@ #assert 0, "\n".join([",".join(x.listnames()) for x in # list(itemgenerator)]) # XXX: We have to decide which runner to use at this point - if runner is None and (self.config.option.usepdb or self.config.option.nocapture): - runner = plain_runner local_loop(self, reporter, itemgenerator, checkfun, self.config, runner=runner) reporter(report.TestFinished()) From fijal at codespeak.net Thu Oct 26 12:55:01 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Thu, 26 Oct 2006 12:55:01 +0200 (CEST) Subject: [py-svn] r33753 - py/dist/py/test/rsession Message-ID: <20061026105501.54A7210050@code0.codespeak.net> Author: fijal Date: Thu Oct 26 12:54:58 2006 New Revision: 33753 Modified: py/dist/py/test/rsession/rsession.py Log: Remove nasty hack, but I think we need tests for that. Modified: py/dist/py/test/rsession/rsession.py ============================================================================== --- py/dist/py/test/rsession/rsession.py (original) +++ py/dist/py/test/rsession/rsession.py Thu Oct 26 12:54:58 2006 @@ -45,8 +45,8 @@ self.skipped = dict([(host, 0) for host in hosts]) self.passed = dict([(host, 0) for host in hosts]) # XXX: This is for tests to work - self.count = 0 - self.lgt = 1000 + #self.count = 0 + #self.lgt = 1000 def report(self, what): repfun = getattr(self, "report_" + what.__class__.__name__, @@ -249,10 +249,10 @@ "/".join(colitem.listnames()) def report_FailedTryiter(self, event): - self.out.line("FAILED TO LOAD MODULE: %s" % "/".join(event.item.listnames())) + self.out.line("FAILED TO LOAD MODULE: %s\n" % "/".join(event.item.listnames())) def report_SkippedTryiter(self, event): - self.out.line("Skipped (%s) %s" % (str(event.excinfo.value), "/". + self.out.line("Skipped (%s) %s\n" % (str(event.excinfo.value), "/". join(event.item.listnames()))) class LocalReporter(AbstractReporter): @@ -263,10 +263,12 @@ return "/".join(colitem.listnames()) def report_SkippedTryiter(self, event): - self.out.write("- skipped (%s)\n" % event.excinfo.value) + #self.show_item(event.item, False) + self.out.write("- skipped (%s)" % event.excinfo.value) def report_FailedTryiter(self, event): - self.out.write("- FAILED TO LOAD MODULE\n") + #self.show_item(event.item, False) + self.out.write("- FAILED TO LOAD MODULE") def report_ReceivedItemOutcome(self, event): if event.outcome.passed: @@ -281,20 +283,22 @@ self.failed_tests_outcome.append(event) self.out.write("F") # we'll take care of them later - self.count += 1 - if self.count >= self.lgt: - self.out.write("\n") + #self.count += 1 + #if self.count >= self.lgt: + # self.out.write("\n") #itempath = " ".join(event.item.listnames()[1:]) #print "%10s: %s %s" %(sshhost[:10], status, itempath) def report_ItemStart(self, event): - item = event.item + self.show_item(event.item) + + def show_item(self, item, count_elems = True): if isinstance(item, py.test.collect.Module): # XXX This is a terrible hack, I don't like it # and will rewrite it at some point - self.count = 0 - lgt = len(list(event.item.tryiter())) - self.lgt = lgt + #self.count = 0 + lgt = len(list(item.tryiter())) + #self.lgt = lgt # print names relative to current workdir name = "/".join(item.listnames()) local = str(py.path.local()) @@ -303,9 +307,7 @@ local = local[len(d) + 1:] if name.startswith(local): name = name[len(local) + 1:] - self.out.write("%s[%d] " % (name, lgt)) - if lgt == 0: - self.out.write("\n") + self.out.write("\n%s[%d] " % (name, lgt)) def hangs(self): pass From fijal at codespeak.net Thu Oct 26 14:12:37 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Thu, 26 Oct 2006 14:12:37 +0200 (CEST) Subject: [py-svn] r33764 - in py/dist/py/test/rsession: . testing Message-ID: <20061026121237.0EADC10034@code0.codespeak.net> Author: fijal Date: Thu Oct 26 14:12:35 2006 New Revision: 33764 Modified: py/dist/py/test/rsession/box.py py/dist/py/test/rsession/rsession.py py/dist/py/test/rsession/testing/test_reporter.py py/dist/py/test/rsession/testing/test_slave.py Log: Added more tests. Modified: py/dist/py/test/rsession/box.py ============================================================================== --- py/dist/py/test/rsession/box.py (original) +++ py/dist/py/test/rsession/box.py Thu Oct 26 14:12:35 2006 @@ -133,6 +133,8 @@ pass class FileBox(object): + count = 0 + def __init__(self, fun, args=None, kwargs=None): if args is None: args = [] @@ -143,7 +145,8 @@ self.kwargs = kwargs def run(self): - tempdir = py.test.ensuretemp("box") + tempdir = py.test.ensuretemp("box%d" % self.count) + self.count += 1 self.tempdir = tempdir self.PYTESTRETVAL = tempdir.join('retval') self.PYTESTSTDOUT = tempdir.join('stdout') @@ -196,6 +199,7 @@ self.signal = exitstat & 0x7f self.exitstat = exitstat & 0xff00 + if not exitstat: retval = self.PYTESTRETVAL.open() try: Modified: py/dist/py/test/rsession/rsession.py ============================================================================== --- py/dist/py/test/rsession/rsession.py (original) +++ py/dist/py/test/rsession/rsession.py Thu Oct 26 14:12:35 2006 @@ -34,7 +34,7 @@ remote_options = RemoteOptions({'we_are_remote':False}) class AbstractReporter(object): - def __init__(self, config, hosts, pkgdir=""): + def __init__(self, config, hosts, pkgdir=py.path.local(py.__file__)): self.config = config self.pkgdir = pkgdir self.failed_tests_outcome = [] Modified: py/dist/py/test/rsession/testing/test_reporter.py ============================================================================== --- py/dist/py/test/rsession/testing/test_reporter.py (original) +++ py/dist/py/test/rsession/testing/test_reporter.py Thu Oct 26 14:12:35 2006 @@ -3,19 +3,19 @@ test if it *works*, not if the output produced is what we really like """ -import py -from py.__.test.rsession.rsession import LocalReporter -from py.__.test.rsession.report import ReceivedItemOutcome +import py, os +from py.__.test.rsession.rsession import LocalReporter, AbstractSession +from py.__.test.rsession.report import ReceivedItemOutcome, ItemStart from py.__.test.rsession.outcome import ReprOutcome, Outcome -from py.__.test.rsession.testing.test_slave import funcpass_spec +from py.__.test.rsession.testing.test_slave import funcpass_spec, mod_spec +from py.__.test.rsession.box import Box +#from py.__.test. def setup_module(mod): mod.pkgdir = py.path.local(py.__file__).dirpath() class TestReporter(object): - def test_report_received_item_outcome(self): - config, args = py.test.Config.parse(["some_sub"]) - r = LocalReporter(config, ["localhost"]) + def prepare_outcomes(self): # possible outcomes try: 1/0 @@ -31,8 +31,69 @@ outcomes[3].signal = 11 outcomes[0].passed = False + return outcomes + + def test_report_received_item_outcome(self): + config, args = py.test.Config.parse(["some_sub"]) # we just go... rootcol = py.test.collect.Directory(pkgdir.dirpath()) item = rootcol.getitembynames(funcpass_spec) - for outcome in outcomes: - r.report(ReceivedItemOutcome(None, item, outcome)) + outcomes = self.prepare_outcomes() + + def boxfun(config, item, outcomes): + r = LocalReporter(config, ["localhost"]) + for outcome in outcomes: + r.report(ReceivedItemOutcome(None, item, outcome)) + + b = Box(boxfun, [config, item, outcomes]) + b.run() + assert b.stdoutrepr == 'FsF.' + + def test_module(self): + config, args = py.test.Config.parse(["some_sub"]) + # we just go... + rootcol = py.test.collect.Directory(pkgdir.dirpath()) + funcitem = rootcol.getitembynames(funcpass_spec) + moditem = rootcol.getitembynames(mod_spec) + outcomes = self.prepare_outcomes() + + def boxfun(pkgdir, config, item, funcitem, outcomes): + r = LocalReporter(config, ["localhost"]) + #r.pkgdir = pkdgir + r.report(ItemStart(item)) + for outcome in outcomes: + r.report(ReceivedItemOutcome(None, funcitem, outcome)) + + b = Box(boxfun, [pkgdir, config, moditem, funcitem, outcomes]) + b.run() + assert b.stdoutrepr.endswith("test_slave.py[8] FsF."),\ + b.stdoutrepr + assert not b.stderrrepr + + def test_full_module(self): + tmpdir = py.test.ensuretemp("repmod") + tmpdir.ensure("__init__.py") + tmpdir.ensure("test_one.py").write(py.code.Source(""" + def test_x(): + pass + """)) + tmpdir.ensure("test_two.py").write(py.code.Source(""" + import py + py.test.skip("reason") + """)) + tmpdir.ensure("test_three.py").write(py.code.Source(""" + sadsadsa + """)) + + def boxfun(): + config, args = py.test.Config.parse([str(tmpdir)]) + rootcol = py.test.collect.Directory(tmpdir) + r = LocalReporter(config, ["localhost"]) + list(rootcol.tryiter(reporterror=lambda x : AbstractSession.reporterror(r.report, x))) + + b = Box(boxfun) + b.run() + assert b.stdoutrepr == """ +repmod/test_one.py[1] +repmod/test_three.py[0] - FAILED TO LOAD MODULE +repmod/test_two.py[0] - skipped (reason)""" Modified: py/dist/py/test/rsession/testing/test_slave.py ============================================================================== --- py/dist/py/test/rsession/testing/test_slave.py (original) +++ py/dist/py/test/rsession/testing/test_slave.py Thu Oct 26 14:12:35 2006 @@ -45,6 +45,7 @@ funcprintfail_spec = (BASE + "funcprintfail").split("/") funcoption_spec = (BASE + "funcoption").split("/") funcoptioncustom_spec = (BASE + "funcoptioncustom").split("/") +mod_spec = BASE[:-1].split("/") # ---------------------------------------------------------------------- From fijal at codespeak.net Thu Oct 26 14:48:51 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Thu, 26 Oct 2006 14:48:51 +0200 (CEST) Subject: [py-svn] r33769 - in py/dist/py: documentation test test/rsession test/rsession/webdata Message-ID: <20061026124851.40F1D1005A@code0.codespeak.net> Author: fijal Date: Thu Oct 26 14:48:45 2006 New Revision: 33769 Modified: py/dist/py/documentation/test.txt py/dist/py/test/defaultconftest.py py/dist/py/test/rsession/rsession.py py/dist/py/test/rsession/web.py py/dist/py/test/rsession/webdata/source.js Log: Moved startsever from conftest to command line option, will integrate it with browser soon. Updated docs. Modified: py/dist/py/documentation/test.txt ============================================================================== --- py/dist/py/documentation/test.txt (original) +++ py/dist/py/documentation/test.txt Thu Oct 26 14:48:45 2006 @@ -737,15 +737,15 @@ should be different than the default: ``$HOME/pytestcache-hostname``) * distrsync_roots - a list of packages to copy to the remote machines. * dist_remotepython - the remote python to run. -* startserver - True or False - True to use the web based reporter listening - on ``localhost:8000`` (There is no command line option for this) Sample configuration:: disthosts = ['localhost', 'user at someserver:/tmp/somedir'] distrsync_roots = ['pypy', 'py'] dist_remotepython = 'python2.4' - startserver = True + +Running server is done by ``-w`` command line option or ``--startserver`` +(the former might change at some point due to conflicts). Development Notes ----------------- Modified: py/dist/py/test/defaultconftest.py ============================================================================== --- py/dist/py/test/defaultconftest.py (original) +++ py/dist/py/test/defaultconftest.py Thu Oct 26 14:48:45 2006 @@ -65,5 +65,9 @@ Option('', '--exec', action="store", dest="executable", default=None, help="python executable to run the tests with. "), + Option('-w', '--startserver', + action="store_true", dest="startserver", default=False, + help="Start HTTP server listening on localhost for test" + ), ) Modified: py/dist/py/test/rsession/rsession.py ============================================================================== --- py/dist/py/test/rsession/rsession.py (original) +++ py/dist/py/test/rsession/rsession.py Thu Oct 26 14:48:45 2006 @@ -346,11 +346,12 @@ getpkgdir = staticmethod(getpkgdir) def init_reporter(self, reporter, sshhosts, reporter_class, arg=""): - try: - # XXX: use it like a command line option, but how? - startserverflag = self.config.getinitialvalue("startserver") - except: - startserverflag = False + #try: + # # XXX: use it like a command line option, but how? + # startserverflag = self.config.getinitialvalue("startserver") + #except: + # startserverflag = False + startserverflag = self.config.option.startserver checkfun = lambda: None if startserverflag and reporter is None: Modified: py/dist/py/test/rsession/web.py ============================================================================== --- py/dist/py/test/rsession/web.py (original) +++ py/dist/py/test/rsession/web.py Thu Oct 26 14:48:45 2006 @@ -257,12 +257,14 @@ def run_jssource(self): js_name = py.path.local(__file__).dirpath("webdata").join("source.js") - if IMPORTED_PYPY: + web_name = py.path.local(__file__).dirpath().join("webjs.py") + if IMPORTED_PYPY and web_name.mtime() > js_name.mtime(): from py.__.test.rsession import webjs - javascript_source = rpython2javascript(webjs, + javascript_source = rpython2javascript(webjs, FUNCTION_LIST, Options) - #open(str(js_name), "w").write(javascript_source) + # XXX: This did not work for some reason, no idea why + open(str(js_name), "w").write(javascript_source) self.serve_data("text/javascript", javascript_source) else: js_source = open(str(js_name), "r").read() Modified: py/dist/py/test/rsession/webdata/source.js ============================================================================== Binary files. No diff available. From fijal at codespeak.net Thu Oct 26 14:53:38 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Thu, 26 Oct 2006 14:53:38 +0200 (CEST) Subject: [py-svn] r33771 - py/dist/py/test Message-ID: <20061026125338.5A76F10068@code0.codespeak.net> Author: fijal Date: Thu Oct 26 14:53:36 2006 New Revision: 33771 Modified: py/dist/py/test/cmdline.py Log: Added simple checks. Modified: py/dist/py/test/cmdline.py ============================================================================== --- py/dist/py/test/cmdline.py (original) +++ py/dist/py/test/cmdline.py Thu Oct 26 14:53:36 2006 @@ -4,6 +4,8 @@ # main entry point # +from py.__.test.rsession.rsession import AbstractSession + def main(args=None): warn_about_missing_assertion() if args is None: @@ -13,6 +15,15 @@ config, args = py.test.Config.parse(args) sessionclass = config.getsessionclass() session = sessionclass(config) + + # ok, some option checks + if config.option.startserver and not isinstance(session, AbstractSession): + print "Cannot use web server without (R|L)Session" + raise SystemExit, 2 + if config.option.apigen and not isinstance(session, AbstractSession): + print "Cannot generate API without (R|L)Session" + raise SystemExit, 2 + try: failures = session.main(args) if failures: From fijal at codespeak.net Thu Oct 26 16:40:24 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Thu, 26 Oct 2006 16:40:24 +0200 (CEST) Subject: [py-svn] r33775 - in py/dist/py/test: . rsession rsession/webdata Message-ID: <20061026144024.C996010068@code0.codespeak.net> Author: fijal Date: Thu Oct 26 16:40:18 2006 New Revision: 33775 Modified: py/dist/py/test/cmdline.py py/dist/py/test/defaultconftest.py py/dist/py/test/rsession/rsession.py py/dist/py/test/rsession/web.py py/dist/py/test/rsession/webdata/index.html py/dist/py/test/rsession/webdata/source.js py/dist/py/test/rsession/webjs.py Log: Minor fixes of web data. Handle TestFinished event, should catch at least TryiterFailed and TryiterSkipped. [Fijal cries for tests] Modified: py/dist/py/test/cmdline.py ============================================================================== --- py/dist/py/test/cmdline.py (original) +++ py/dist/py/test/cmdline.py Thu Oct 26 16:40:18 2006 @@ -23,6 +23,14 @@ if config.option.apigen and not isinstance(session, AbstractSession): print "Cannot generate API without (R|L)Session" raise SystemExit, 2 + if config.option.runbrowser and not config.option.startserver: + print "Cannot point browser when not starting server" + raise SystemExit, 2 + try: + if config.getinitialvalue('startserver'): + py.std.warnings.warn("Startserver flag in config is deprecated, use commandline option istead") + except ValueError: + pass try: failures = session.main(args) Modified: py/dist/py/test/defaultconftest.py ============================================================================== --- py/dist/py/test/defaultconftest.py (original) +++ py/dist/py/test/defaultconftest.py Thu Oct 26 16:40:18 2006 @@ -67,7 +67,11 @@ help="python executable to run the tests with. "), Option('-w', '--startserver', action="store_true", dest="startserver", default=False, - help="Start HTTP server listening on localhost for test" + help="Start HTTP server listening on localhost:8000 for test" + ), + Option('', '--runbrowser', + action="store_true", dest="runbrowser", default=False, + help="Run browser to point to your freshly started web server" ), ) Modified: py/dist/py/test/rsession/rsession.py ============================================================================== --- py/dist/py/test/rsession/rsession.py (original) +++ py/dist/py/test/rsession/rsession.py Thu Oct 26 16:40:18 2006 @@ -359,6 +359,9 @@ reporter = exported_methods.report start_server() + if self.config.option.runbrowser: + import webbrowser + webbrowser.open("localhost:8000") elif reporter is None: if arg: reporter_instance = reporter_class(self.config, sshhosts, self.getpkgdir(arg)) Modified: py/dist/py/test/rsession/web.py ============================================================================== --- py/dist/py/test/rsession/web.py (original) +++ py/dist/py/test/rsession/web.py Thu Oct 26 16:40:18 2006 @@ -18,7 +18,6 @@ from py.__.test.rsession.webdata import json - DATADIR = py.path.local(__file__).dirpath("webdata") FUNCTION_LIST = ["main", "show_skip", "show_traceback", "show_info", "hide_info"] @@ -246,6 +245,10 @@ args[key] = value return args + def log_message(self, format, *args): + # XXX just discard it + pass + do_POST = do_GET def run_(self): @@ -280,6 +283,7 @@ def start_server(server_address = ('', 8000)): httpd = HTTPServer(server_address, TestHandler) thread.start_new_thread(httpd.serve_forever, ()) + print "Server started, listening on %s" % (server_address,) def kill_server(): exported_methods.pending_events.put(None) Modified: py/dist/py/test/rsession/webdata/index.html ============================================================================== --- py/dist/py/test/rsession/webdata/index.html (original) +++ py/dist/py/test/rsession/webdata/index.html Thu Oct 26 16:40:18 2006 @@ -5,7 +5,7 @@ -

Tests

+

Tests

Modified: py/dist/py/test/rsession/webdata/source.js ============================================================================== Binary files. No diff available. Modified: py/dist/py/test/rsession/webjs.py ============================================================================== --- py/dist/py/test/rsession/webjs.py (original) +++ py/dist/py/test/rsession/webjs.py Thu Oct 26 16:40:18 2006 @@ -124,6 +124,9 @@ module_part.childNodes[-1].appendChild(td) except: dom.get_document().getElementById("testmain").innerHTML += "some error" + elif msg['type'] == 'TestFinished': + dom.get_document().title = "Py.test [FINISHED]" + dom.get_document().getElementById("Tests").childNodes[0].nodeValue = "Tests [FINISHED]" return True def show_skip(item_name="aa"): From fijal at codespeak.net Thu Oct 26 17:11:04 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Thu, 26 Oct 2006 17:11:04 +0200 (CEST) Subject: [py-svn] r33776 - in py/dist/py/test/rsession: . webdata Message-ID: <20061026151104.EB0AE10036@code0.codespeak.net> Author: fijal Date: Thu Oct 26 17:11:00 2006 New Revision: 33776 Modified: py/dist/py/test/rsession/web.py py/dist/py/test/rsession/webdata/source.js py/dist/py/test/rsession/webjs.py Log: Added FailedTryiter and SkippedTryiter to web server. Needs more explanation, but tests first. Modified: py/dist/py/test/rsession/web.py ============================================================================== --- py/dist/py/test/rsession/web.py (original) +++ py/dist/py/test/rsession/web.py Thu Oct 26 17:11:00 2006 @@ -6,6 +6,7 @@ import thread, threading import re +import time import random import Queue import os @@ -46,6 +47,22 @@ IMPORTED_PYPY = False +def add_item(event): + """ A little helper + """ + item = event.item + itemtype = item.__class__.__name__ + itemname = item.name + fullitemname = "/".join(item.listnames()) + d = {'fullitemname': fullitemname, 'itemtype':itemtype, + 'itemname':itemname} + if itemtype == 'Module': + try: + d['length'] = str(len(list(event.item.tryiter()))) + except: + d['length'] = "?" + return d + class ExportedMethods(BasicExternal): _render_xmlhttp = True def __init__(self): @@ -93,20 +110,6 @@ show_all_statuses = described(retval=[{"aa":"aa"}])(show_all_statuses) def show_status_change(self): - def add_item(event): - item = event.item - itemtype = item.__class__.__name__ - itemname = item.name - fullitemname = "/".join(item.listnames()) - d = {'fullitemname': fullitemname, 'itemtype':itemtype, - 'itemname':itemname} - if itemtype == 'Module': - try: - d['length'] = str(len(list(event.item.tryiter()))) - except: - d['length'] = "?" - return d - event = self.pending_events.get() if event is None: self.end_event.set() @@ -139,6 +142,11 @@ num += 1 self.ready_hosts[host] = True args = {'hostname' : event.hostname, 'hostkey' : host} + elif isinstance(event, report.FailedTryiter): + args = add_item(event) + elif isinstance(event, report.SkippedTryiter): + args = add_item(event) + args['reason'] = str(event.excinfo.value) else: args = {} args['event'] = escape(str(event)) @@ -287,4 +295,6 @@ def kill_server(): exported_methods.pending_events.put(None) + while not exported_methods.pending_events.empty(): + time.sleep(.1) exported_methods.end_event.wait() Modified: py/dist/py/test/rsession/webdata/source.js ============================================================================== Binary files. No diff available. Modified: py/dist/py/test/rsession/webjs.py ============================================================================== --- py/dist/py/test/rsession/webjs.py (original) +++ py/dist/py/test/rsession/webjs.py Thu Oct 26 17:11:00 2006 @@ -127,6 +127,28 @@ elif msg['type'] == 'TestFinished': dom.get_document().title = "Py.test [FINISHED]" dom.get_document().getElementById("Tests").childNodes[0].nodeValue = "Tests [FINISHED]" + elif msg['type'] == 'FailedTryiter': + module_part = get_elem(msg['fullitemname']) + if not module_part: + glob.pending.append(msg) + return True + tr = create_elem("tr") + td = create_elem("td") + txt = create_text_elem("- FAILED TO LOAD MODULE") + td.appendChild(txt) + tr.appendChild(td) + module_part.appendChild(tr) + elif msg['type'] == 'SkippedTryiter': + module_part = get_elem(msg['fullitemname']) + if not module_part: + glob.pending.append(msg) + return True + tr = create_elem("tr") + td = create_elem("td") + txt = create_text_elem("- skipped (%s)" % msg['reason']) + td.appendChild(txt) + tr.appendChild(td) + module_part.appendChild(tr) return True def show_skip(item_name="aa"): From fijal at codespeak.net Fri Oct 27 11:50:30 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Fri, 27 Oct 2006 11:50:30 +0200 (CEST) Subject: [py-svn] r33800 - in py/dist/py: apigen/tracer/testing test/rsession/testing Message-ID: <20061027095030.51F6310076@code0.codespeak.net> Author: fijal Date: Fri Oct 27 11:50:25 2006 New Revision: 33800 Modified: py/dist/py/apigen/tracer/testing/test_package.py py/dist/py/test/rsession/testing/test_reporter.py Log: Use less sophisticated testing methods. Modified: py/dist/py/apigen/tracer/testing/test_package.py ============================================================================== --- py/dist/py/apigen/tracer/testing/test_package.py (original) +++ py/dist/py/apigen/tracer/testing/test_package.py Fri Oct 27 11:50:25 2006 @@ -10,10 +10,7 @@ def setup_module(mod): sys.path.insert(0, str(py.path.local(__file__).dirpath().join("package"))) - try: - import submodule - except ImportError: - py.test.skip("Importing submodule") + import submodule mod.submodule = submodule def teardown_module(mod): Modified: py/dist/py/test/rsession/testing/test_reporter.py ============================================================================== --- py/dist/py/test/rsession/testing/test_reporter.py (original) +++ py/dist/py/test/rsession/testing/test_reporter.py Fri Oct 27 11:50:25 2006 @@ -10,6 +10,8 @@ from py.__.test.rsession.testing.test_slave import funcpass_spec, mod_spec from py.__.test.rsession.box import Box #from py.__.test. +import sys +from StringIO import StringIO def setup_module(mod): mod.pkgdir = py.path.local(py.__file__).dirpath() @@ -45,9 +47,13 @@ for outcome in outcomes: r.report(ReceivedItemOutcome(None, item, outcome)) - b = Box(boxfun, [config, item, outcomes]) - b.run() - assert b.stdoutrepr == 'FsF.' + s = StringIO() + stdoutcopy = sys.stdout + sys.stdout = s + boxfun(config, item, outcomes) + sys.stdoud = stdoutcopy + + assert s.getvalue() == 'FsF.' def test_module(self): config, args = py.test.Config.parse(["some_sub"]) @@ -64,11 +70,14 @@ for outcome in outcomes: r.report(ReceivedItemOutcome(None, funcitem, outcome)) - b = Box(boxfun, [pkgdir, config, moditem, funcitem, outcomes]) - b.run() - assert b.stdoutrepr.endswith("test_slave.py[8] FsF."),\ - b.stdoutrepr - assert not b.stderrrepr + s = StringIO() + stdoutcopy = sys.stdout + sys.stdout = s + boxfun(pkgdir, config, moditem, funcitem, outcomes) + sys.stdoud = stdoutcopy + + assert s.getvalue().endswith("test_slave.py[8] FsF."),\ + s.getvalue() def test_full_module(self): tmpdir = py.test.ensuretemp("repmod") @@ -91,9 +100,15 @@ r = LocalReporter(config, ["localhost"]) list(rootcol.tryiter(reporterror=lambda x : AbstractSession.reporterror(r.report, x))) - b = Box(boxfun) - b.run() - assert b.stdoutrepr == """ + #b = Box(boxfun) + #b.run() + s = StringIO() + stdoutcopy = sys.stdout + sys.stdout = s + boxfun() + sys.stdoud = stdoutcopy + + assert s.getvalue() == """ repmod/test_one.py[1] repmod/test_three.py[0] - FAILED TO LOAD MODULE repmod/test_two.py[0] - skipped (reason)""" From guido at codespeak.net Sat Oct 28 16:13:14 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Sat, 28 Oct 2006 16:13:14 +0200 (CEST) Subject: [py-svn] r33837 - in py/dist/py/apigen/rest: . testing Message-ID: <20061028141314.53098100A5@code0.codespeak.net> Author: guido Date: Sat Oct 28 16:13:10 2006 New Revision: 33837 Modified: py/dist/py/apigen/rest/genrest.py py/dist/py/apigen/rest/testing/test_rest.py Log: Fixed formatting a bit, made that methods now are marked as 'methods' rather than as 'functions', changed the order of the output a bit, made that the top-level module is now seperated from the index (and the index therefore contains only modules), fixed some header level issues. Modified: py/dist/py/apigen/rest/genrest.py ============================================================================== --- py/dist/py/apigen/rest/genrest.py (original) +++ py/dist/py/apigen/rest/genrest.py Sat Oct 28 16:13:10 2006 @@ -104,7 +104,13 @@ if targetname in self._defined_targets: return None self._defined_targets.append(targetname) - targetname = targetname.lower().replace('.', '-').replace('_', '') + targetname = targetname.lower().replace('.', '-').replace('_', '-') + while '--' in targetname: + targetname = targetname.replace('--', '-') + if targetname.startswith('-'): + targetname = targetname[1:] + if targetname.endswith('-'): + targetname = targetname[:-1] return '#%s-%s' % (type, targetname) class RestGen(object): @@ -120,20 +126,20 @@ modlist = self.get_module_list() classlist = self.get_class_list(module='') funclist = self.get_function_list() + modlist.insert(0, ['', classlist, funclist]) - indexrest = self.build_index([t[0] for t in modlist], - [t[0] for t in classlist], - funclist) + indexrest = self.build_index([t[0] for t in modlist]) self.writer.write_section('index', Rest(*indexrest).text()) self.build_modrest(modlist) - self.build_funcrest(funclist) - self.build_classrest(classlist) def build_modrest(self, modlist): modrest = self.build_modules(modlist) for name, rest, classlist, funclist in modrest: - self.writer.write_section('module_%s' % (name,), + mname = name + if mname == '': + mname = self.dsa.get_module_name() + self.writer.write_section('module_%s' % (mname,), Rest(*rest).text()) for cname, crest, cfunclist in classlist: self.writer.write_section('class_%s' % (cname,), @@ -160,28 +166,28 @@ self.writer.write_section('function_%s' % (fname,), Rest(*rest).text()) - def build_index(self, modules, classes, functions): + def build_index(self, modules): rest = [Title('Index', abovechar='=', belowchar='=')] rest.append(Title('Exported modules:', belowchar='=')) for module in modules: + mtitle = module + if module == '': + module = self.dsa.get_module_name() + mtitle = '%s (top-level)' % (module,) linktarget = self.writer.getlink('module', module, 'module_%s' % (module,)) - rest.append(ListItem(Link(module, linktarget))) - rest.append(Title('Exported classes:', belowchar='=')) - for cls in classes: - linktarget = self.writer.getlink('class', cls, 'class_%s' % (cls,)) - rest.append(ListItem(Link(cls, linktarget))) - rest.append(Title('Exported functions:', belowchar='=')) - for func in functions: - linktarget = self.writer.getlink('function', func, - 'function_%s' % (func,)) - rest.append(ListItem(Link(func, linktarget))) + rest.append(ListItem(Link(mtitle, linktarget))) return rest def build_modules(self, modules): ret = [] for module, classes, functions in modules: - rest = [Title('Module: %s' % (module,), belowchar='-')] + mname = module + if mname == '': + mname = self.dsa.get_module_name() + rest = [Title('Module: %s' % (mname,), abovechar='=', + belowchar='='), + Title('Index:', belowchar='=')] rest.append(Title('Classes:', belowchar='^')) for cls, cfunclist in classes: linktarget = self.writer.getlink('class', cls, @@ -190,36 +196,37 @@ classrest = self.build_classes(classes) rest.append(Title('Functions:', belowchar='^')) for func in functions: + if module: + func = '%s.%s' % (module, func) linktarget = self.writer.getlink('function', - '%s.%s' % (module, func), - 'function_%s.%s' % (module, - func)) + func, + 'function_%s' % (func,)) rest.append(ListItem(Link(func, linktarget))) - funcrest = self.build_functions(functions, module) + funcrest = self.build_functions(functions, module, False) ret.append((module, rest, classrest, funcrest)) return ret def build_classes(self, classes): ret = [] for cls, functions in classes: - rest = [Title('Class: %s' % (cls,), belowchar='-'), + rest = [Title('Class: %s' % (cls,), belowchar='='), LiteralBlock(self.dsa.get_doc(cls)), Title('Functions:', belowchar='^')] for func in functions: linktarget = self.writer.getlink('method', '%s.%s' % (cls, func), 'method_%s.%s' % (cls, func)) - rest.append(ListItem(Link(func, linktarget))) - funcrest = self.build_functions(functions, cls) + rest.append(ListItem(Link('%s.%s' % (cls, func), linktarget))) + funcrest = self.build_functions(functions, cls, True) ret.append((cls, rest, funcrest)) return ret - def build_functions(self, functions, parent=''): + def build_functions(self, functions, parent='', methods=False): ret = [] for function in functions: if parent: function = '%s.%s' % (parent, function) - rest = self.write_function(function) + rest = self.write_function(function, ismethod=methods) ret.append((function, rest)) return ret @@ -276,13 +283,18 @@ self.process_type_link(i, lst) return name, _desc_type = data + # XXX hard-coded HTML link! needs to be changed... lst.append(Link(str(_type), _desc_type + "_" + name + ".html")) - def write_function(self, functionname, belowchar='-'): + def write_function(self, functionname, ismethod=False, belowchar='-'): # XXX I think the docstring should either be split on \n\n and cleaned # from indentation, or treated as ReST too (although this is obviously # dangerous for non-ReST docstrings)... - lst = [Title("Function: %s" % (functionname,), belowchar=belowchar), + if ismethod: + title = 'Method: %s' % (functionname,) + else: + title = 'Function: %s' % (functionname,) + lst = [Title(title, belowchar=belowchar), LiteralBlock(self.dsa.get_doc(functionname)), LiteralBlock(self.dsa.get_function_definition(functionname))] @@ -334,7 +346,7 @@ #lst.append(arg_quote) # call sites.. - call_site_title = Title("Call sites:", belowchar='^') + call_site_title = Title("Call sites:", belowchar='+') lst.append(call_site_title) call_sites = lst Modified: py/dist/py/apigen/rest/testing/test_rest.py ============================================================================== --- py/dist/py/apigen/rest/testing/test_rest.py (original) +++ py/dist/py/apigen/rest/testing/test_rest.py Sat Oct 28 16:13:10 2006 @@ -174,6 +174,7 @@ 'method_SomeClass.method.txt', 'method_SomeSubClass.__init__.txt', 'method_SomeSubClass.method.txt', + 'module_Unknown module.txt', ] # now we check out... self.check_rest(tempdir) @@ -196,6 +197,7 @@ 'method_somemodule.SomeClass.method.txt', 'method_someothermodule.SomeSubClass.__init__.txt', 'method_someothermodule.SomeSubClass.method.txt', + 'module_Unknown module.txt', 'module_somemodule.txt', 'module_someothermodule.txt', ] @@ -207,7 +209,7 @@ tempdir = temppath.ensure('internal_links', dir=True) r = RestGen(ds, lg, DirWriter(tempdir)) r.write() - index = tempdir.join('index.txt') + index = tempdir.join('module_Unknown module.txt') assert index.check(file=True) data = index.read() assert data.find('.. _`fun`: function_fun.html\n') > -1 @@ -228,19 +230,20 @@ py.test.skip('depends on previous test, which failed') data = tempfile.read() # index should be above the rest - assert data.find('Exported classes\\:') > -1 - assert data.find('Exported classes\\:') < data.find('Function\\: fun') - assert data.find('Exported classes\\:') < data.find( + print data + assert data.find('Classes\\:') > -1 + assert data.find('Classes\\:') < data.find('Function\\: fun') + assert data.find('Classes\\:') < data.find( 'Class\\: SomeClass') # function definitions should be above class ones - assert data.find('Function\\: fun') < data.find('Class\\: SomeClass') + assert data.find('Function\\: fun') > data.find('Class\\: SomeClass') # class method definitions should be below the class defs assert data.find('Class\\: SomeClass') < data.find( - 'Function\\: SomeClass.method') + 'Method\\: SomeClass.method') # __init__ should be above other methods - assert data.find('Function\\: SomeClass.\\_\\_init\\_\\_') > -1 - assert data.find('Function\\: SomeClass.\\_\\_init\\_\\_') < data.find( - 'Function\\: SomeClass.method') + assert data.find('Method\\: SomeClass.\\_\\_init\\_\\_') > -1 + assert data.find('Method\\: SomeClass.\\_\\_init\\_\\_') < data.find( + 'Method\\: SomeClass.method') def test_som_fun(self): descs = {'fun_':fun_} From fijal at codespeak.net Sat Oct 28 22:17:20 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Sat, 28 Oct 2006 22:17:20 +0200 (CEST) Subject: [py-svn] r33839 - in py/dist/py: apigen/rest apigen/rest/testing apigen/tracer apigen/tracer/testing code documentation misc Message-ID: <20061028201720.4264810078@code0.codespeak.net> Author: fijal Date: Sat Oct 28 22:17:16 2006 New Revision: 33839 Modified: py/dist/py/apigen/rest/genrest.py py/dist/py/apigen/rest/testing/test_rest.py py/dist/py/apigen/tracer/description.py py/dist/py/apigen/tracer/docstorage.py py/dist/py/apigen/tracer/testing/test_docgen.py py/dist/py/apigen/tracer/testing/test_magic.py py/dist/py/apigen/tracer/tracer.py py/dist/py/code/code.py py/dist/py/code/traceback2.py py/dist/py/documentation/conftest.py py/dist/py/misc/rest.py Log: Added various improvements to traceback generation for call sites. Modified: py/dist/py/apigen/rest/genrest.py ============================================================================== --- py/dist/py/apigen/rest/genrest.py (original) +++ py/dist/py/apigen/rest/genrest.py Sat Oct 28 22:17:16 2006 @@ -120,6 +120,7 @@ self.dsa = DocStorageAccessor(ds) self.linkgen = linkgen self.writer = writer + self.traceback_no = 0 def write(self): """write the data to the writer""" @@ -348,34 +349,50 @@ # call sites.. call_site_title = Title("Call sites:", belowchar='+') lst.append(call_site_title) - call_sites = lst - for call_site, frame in self.dsa.get_function_callpoints(functionname): - link_str = "File %s:%s" % (call_site.filename, - call_site.lineno) - link_str, link_target = self.linkgen.getlink(call_site.filename, - call_site.lineno) - if link_target: # otherwise it's just inline text - call_sites.append(Paragraph(Link(link_str, link_target))) - else: - call_sites.append(Paragraph(link_str)) - #call_sites.append(LiteralBlock(call_site.source)) - # XXX: For now, we just paste here the filename of that - #call_sites.append(Paragraph(link_str)) - try: - source = frame.code.source() - except KeyboardInterrupt, SystemError: - raise - except: - source = "*Cannot get source*" - lines = [] - for num, line in enumerate(source): - if num == call_site.lineno - frame.code.firstlineno - 1: - m = re.match("^( *)(.*)", line) - lines.append(">%s%s" % ("-" * len(m.group(1)), m.group(2))) - else: - lines.append(" " + line) - call_sites.append(LiteralBlock("\n".join(lines))) + # we have to think differently here. I would go for: + # 1. A quick'n'dirty statement where call has appeared first (topmost) + # 2. Link to short traceback + # 3. Link to long traceback + for call_site, _ in self.dsa.get_function_callpoints(functionname): + self.write_call_site_link(call_site, lst) +## for call_site, frame in self.dsa.get_function_callpoints(functionname): +## link_str = "File %s:%s" % (call_site.filename, +## call_site.lineno) +## link_str, link_target = self.linkgen.getlink(call_site.filename, +## call_site.lineno) +## if link_target: # otherwise it's just inline text +## call_sites.append(Paragraph(Link(link_str, link_target))) +## else: +## call_sites.append(Paragraph(link_str)) +## #call_sites.append(LiteralBlock(call_site.source)) +## # XXX: For now, we just paste here the filename of that +## #call_sites.append(Paragraph(link_str)) +## try: +## source = frame.code.source() +## except KeyboardInterrupt, SystemError: +## raise +## except: +## source = "*Cannot get source*" +## lines = [] +## for num, line in enumerate(source): +## if num == call_site.lineno - frame.code.firstlineno - 1: +## m = re.match("^( *)(.*)", line) +## lines.append(">%s%s" % ("-" * len(m.group(1)), m.group(2))) +## else: +## lines.append(" " + line) +## call_sites.append(LiteralBlock("\n".join(lines))) return lst + def write_call_site_link(self, call_site, lst): + name = self.gen_traceback(call_site) + lst.append(Paragraph("Called in %s" % call_site[0].code.filename, Link\ + ("Full %s" % name, name + '.html'))) + + def gen_traceback(self, call_site): + name = "traceback_%d" % self.traceback_no + self.traceback_no += 1 + print name + self.writer.write_section(name, Rest(*[Title("Random traceback here")]).text()) + return name Modified: py/dist/py/apigen/rest/testing/test_rest.py ============================================================================== --- py/dist/py/apigen/rest/testing/test_rest.py (original) +++ py/dist/py/apigen/rest/testing/test_rest.py Sat Oct 28 22:17:16 2006 @@ -12,6 +12,8 @@ from py.__.apigen.tracer.docstorage import DocStorage from py.__.apigen.tracer.testing.runtest import cut_pyc +from py.__.documentation.conftest import genlinkchecks +# XXX: UUuuuuuuuuuuuuuuuuuuuuuuu, dangerous import def setup_module(mod): mod.temppath = py.test.ensuretemp('restgen') @@ -157,6 +159,9 @@ from py.__.misc import rest for path in tempdir.listdir('*.txt'): rest.process(path) + for path in tempdir.listdir('*.txt'): + for item, arg1, arg2, arg3 in genlinkchecks(path): + item(arg1, arg2, arg3) def test_generation_simple_api(self): ds = self.get_filled_docstorage() @@ -175,6 +180,11 @@ 'method_SomeSubClass.__init__.txt', 'method_SomeSubClass.method.txt', 'module_Unknown module.txt', + 'traceback_0.txt', + 'traceback_1.txt', + 'traceback_2.txt', + 'traceback_3.txt', + 'traceback_4.txt', ] # now we check out... self.check_rest(tempdir) @@ -200,6 +210,11 @@ 'module_Unknown module.txt', 'module_somemodule.txt', 'module_someothermodule.txt', + 'traceback_0.txt', + 'traceback_1.txt', + 'traceback_2.txt', + 'traceback_3.txt', + 'traceback_4.txt', ] assert sorted(basenames) == expected Modified: py/dist/py/apigen/tracer/description.py ============================================================================== --- py/dist/py/apigen/tracer/description.py (original) +++ py/dist/py/apigen/tracer/description.py Sat Oct 28 22:17:16 2006 @@ -1,35 +1,82 @@ +import py from py.__.apigen.tracer import model import types +import inspect -class CallSite(object): - def __init__(self, filename, lineno): - self.filename = filename - self.lineno = lineno +class CallStack(object): + def __init__(self, tb): + if isinstance(tb, py.code.Traceback): + self.tb = tb + else: + self.tb = py.code.Traceback(tb) - def get_tuple(self): - return self.filename, self.lineno + def _getval(self): + return [(frame.code.raw.co_filename, frame.lineno+1) for frame + in self] def __hash__(self): - return hash((self.filename, self.lineno)) + return hash(tuple(self._getval())) def __eq__(self, other): - return (self.filename, self.lineno) == (other.filename, other.lineno) + return self._getval() == other._getval() def __ne__(self, other): return not self == other + def __getattr__(self, attr): + return getattr(self.tb, attr) + + def __iter__(self): + return iter(self.tb) + + def __getitem__(self, item): + return self.tb[item] + + def __len__(self): + return len(self.tb) + def __cmp__(self, other): - if self.filename < other.filename: - return -1 - if self.filename > other.filename: - return 1 - if self.lineno < other.lineno: - return -1 - if self.lineno > other.lineno: - return 1 - return 0 + return cmp(self._getval(), other._getval()) + +def cut_stack(stack, frame, upward_frame=None): + if hasattr(frame, 'raw'): + frame = frame.raw + if upward_frame: + if hasattr(upward_frame, 'raw'): + upward_frame = upward_frame.raw + return CallStack([py.code.Frame(i) for i in stack[stack.index(frame):\ + stack.index(upward_frame)+1]]) + return CallStack([py.code.Frame(i) for i in stack[stack.index(frame):]]) + +##class CallSite(object): +## def __init__(self, filename, lineno): +## self.filename = filename +## self.lineno = lineno +## +## def get_tuple(self): +## return self.filename, self.lineno +## +## def __hash__(self): +## return hash((self.filename, self.lineno)) +## +## def __eq__(self, other): +## return (self.filename, self.lineno) == (other.filename, other.lineno) +## +## def __ne__(self, other): +## return not self == other +## +## def __cmp__(self, other): +## if self.filename < other.filename: +## return -1 +## if self.filename > other.filename: +## return 1 +## if self.lineno < other.lineno: +## return -1 +## if self.lineno > other.lineno: +## return 1 +## return 0 class NonHashableObject(object): def __init__(self, cls): @@ -77,16 +124,11 @@ for cell_num, cell in enumerate(inputcells): self.inputcells[cell_num] = model.unionof(cell, self.inputcells[cell_num]) - def consider_call_site(self, frame): - cs = CallSite(frame.code.raw.co_filename, frame.lineno+1) - if self.keep_frames: - if cs in self.call_sites: - self.call_sites[cs].append(self.frame_copier(frame)) - else: - self.call_sites[cs] = [self.frame_copier(frame)] - else: - # frame copier makes no sense if we want to keep only one - self.call_sites[cs] = frame + def consider_call_site(self, frame, cut_frame): + stack = [i[0] for i in inspect.stack()] + cs = cut_stack(stack, frame, cut_frame) + print cs, hash(cs), cs._getval() + self.call_sites[cs] = cs def get_call_sites(self): # convinient accessor for various data which we keep there @@ -148,8 +190,8 @@ def consider_return(self, arg): pass # we *know* what return value we do have - def consider_call_site(self, frame): - self.fields['__init__'].consider_call_site(frame) + def consider_call_site(self, frame, cut_frame): + self.fields['__init__'].consider_call_site(frame, cut_frame) def add_method_desc(self, name, methoddesc): self.fields[name] = methoddesc Modified: py/dist/py/apigen/tracer/docstorage.py ============================================================================== --- py/dist/py/apigen/tracer/docstorage.py (original) +++ py/dist/py/apigen/tracer/docstorage.py Sat Oct 28 22:17:16 2006 @@ -15,12 +15,12 @@ class DocStorage(object): """ Class storing info about API """ - def consider_call(self, frame, caller_frame): + def consider_call(self, frame, caller_frame, upward_cut_frame=None): assert isinstance(frame, py.code.Frame) desc = self.find_desc(frame.code) if desc: self.generalize_args(desc, frame) - desc.consider_call_site(caller_frame) + desc.consider_call_site(caller_frame, upward_cut_frame) def generalize_args(self, desc, frame): args = [arg for key, arg in frame.getargs()] Modified: py/dist/py/apigen/tracer/testing/test_docgen.py ============================================================================== --- py/dist/py/apigen/tracer/testing/test_docgen.py (original) +++ py/dist/py/apigen/tracer/testing/test_docgen.py Sat Oct 28 22:17:16 2006 @@ -41,10 +41,13 @@ cs = sorted(desc.call_sites.keys()) assert len(cs) == 2 f_name = cut_pyc(__file__) - assert cs[0].filename == f_name - assert cs[0].lineno == test_basic.func_code.co_firstlineno + 5 - assert cs[1].filename == f_name - assert cs[1].lineno == test_basic.func_code.co_firstlineno + 6 + assert len(cs[0]) == 1 + assert len(cs[1]) == 1 + assert cs[0][0].code.filename == f_name + # lines are counted from 0 + assert cs[0][0].lineno == test_basic.func_code.co_firstlineno + 4 + assert cs[1][0].code.filename == f_name + assert cs[1][0].lineno == test_basic.func_code.co_firstlineno + 5 class AClass(object): """ Class docstring @@ -82,8 +85,9 @@ f_name = f_name[:-1] cs = sorted(desc.fields['__init__'].call_sites.keys()) assert len(cs) == 1 - assert cs[0].filename == f_name - assert cs[0].lineno == test_class.func_code.co_firstlineno + 5 + assert len(cs[0]) == 1 + assert cs[0][0].code.filename == f_name + assert cs[0][0].lineno == test_class.func_code.co_firstlineno + 4 # method check assert sorted(desc.getfields()) == ['__init__', 'exposed_method'] inputcells = desc.fields['exposed_method'].inputcells @@ -120,7 +124,7 @@ t.end_tracing() desc = ds.descs["other_fun"] assert len(desc.call_sites.keys()) == 1 - assert isinstance(desc.call_sites.values()[0], py.code.Frame) + assert isinstance(desc.call_sites.values()[0][0], py.code.Frame) class A(object): def method(self, x): Modified: py/dist/py/apigen/tracer/testing/test_magic.py ============================================================================== --- py/dist/py/apigen/tracer/testing/test_magic.py (original) +++ py/dist/py/apigen/tracer/testing/test_magic.py Sat Oct 28 22:17:16 2006 @@ -2,6 +2,9 @@ """ test magic abilities of tracer """ +import py +py.test.skip("These features has been disabled") + from py.__.apigen.tracer.magic import trace, get_storage, stack_copier, \ DocStorageKeeper from py.__.apigen.tracer.docstorage import DocStorage Modified: py/dist/py/apigen/tracer/tracer.py ============================================================================== --- py/dist/py/apigen/tracer/tracer.py (original) +++ py/dist/py/apigen/tracer/tracer.py Sat Oct 28 22:17:16 2006 @@ -26,7 +26,7 @@ frame = py.code.Frame(frame) if event == 'call': assert arg is None - self.docstorage.consider_call(frame, py.code.Frame(sys._getframe(2))) + self.docstorage.consider_call(frame, py.code.Frame(sys._getframe(2)), self.frame) elif event == 'return': self.docstorage.consider_return(frame, arg) @@ -36,6 +36,7 @@ if self.tracing: return self.tracing = True + self.frame = py.code.Frame(sys._getframe(1)) sys.settrace(self._tracer) def end_tracing(self): Modified: py/dist/py/code/code.py ============================================================================== --- py/dist/py/code/code.py (original) +++ py/dist/py/code/code.py Sat Oct 28 22:17:16 2006 @@ -5,6 +5,7 @@ rawcode = getattr(rawcode, 'im_func', rawcode) rawcode = getattr(rawcode, 'func_code', rawcode) self.raw = rawcode + self.filename = rawcode.co_filename try: self.firstlineno = rawcode.co_firstlineno - 1 except AttributeError: Modified: py/dist/py/code/traceback2.py ============================================================================== --- py/dist/py/code/traceback2.py (original) +++ py/dist/py/code/traceback2.py Sat Oct 28 22:17:16 2006 @@ -133,7 +133,7 @@ return i l.append(entry.frame.f_locals) return None - + # def __str__(self): # for x in self # l = [] Modified: py/dist/py/documentation/conftest.py ============================================================================== --- py/dist/py/documentation/conftest.py (original) +++ py/dist/py/documentation/conftest.py Sat Oct 28 22:17:16 2006 @@ -108,7 +108,7 @@ pass def teardown(self): pass - def run(self): + def run(self): return [self.fspath.basename, 'checklinks', 'doctest'] def join(self, name): if name == self.fspath.basename: Modified: py/dist/py/misc/rest.py ============================================================================== --- py/dist/py/misc/rest.py (original) +++ py/dist/py/misc/rest.py Sat Oct 28 22:17:16 2006 @@ -39,8 +39,8 @@ def process(txtpath, encoding='latin1'): """ process a textfile """ - log("processing %s" % txtpath) - assert txtpath.check(ext='.txt') + log("processing %s" % txtpath) + assert txtpath.check(ext='.txt') if isinstance(txtpath, py.path.svnwc): txtpath = txtpath.localpath htmlpath = txtpath.new(ext='.html') @@ -48,12 +48,12 @@ style = txtpath.dirpath('style.css') if style.check(): - stylesheet = style.basename + stylesheet = style.basename else: stylesheet = None content = unicode(txtpath.read(), encoding) - doc = convert_rest_html(content, txtpath, stylesheet=stylesheet, encoding=encoding) - htmlpath.write(doc) + doc = convert_rest_html(content, txtpath, stylesheet=stylesheet, encoding=encoding) + htmlpath.write(doc) #log("wrote %r" % htmlpath) #if txtpath.check(svnwc=1, versioned=1): # info = txtpath.info() From fijal at codespeak.net Mon Oct 30 18:30:30 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Mon, 30 Oct 2006 18:30:30 +0100 (CET) Subject: [py-svn] r33920 - py/dist/py/test/rsession Message-ID: <20061030173030.BBD1910078@code0.codespeak.net> Author: fijal Date: Mon Oct 30 18:30:26 2006 New Revision: 33920 Added: py/dist/py/test/rsession/reporter.py (contents, props changed) Modified: py/dist/py/test/rsession/rsession.py Log: Splitted rsession.py into two different files. Added: py/dist/py/test/rsession/reporter.py ============================================================================== --- (empty file) +++ py/dist/py/test/rsession/reporter.py Mon Oct 30 18:30:26 2006 @@ -0,0 +1,292 @@ + +""" reporter - different reporter for different purposes ;-) + Still lacks: + + 1. Reporting of Failed to load module inside the module + 2. Tests for remote reporter + 3. Hanging nodes are not good done +""" + +import py + +from py.__.test.terminal.out import getout +from py.__.test.rsession import report + +class AbstractReporter(object): + def __init__(self, config, hosts, pkgdir=py.path.local(py.__file__)): + self.config = config + self.pkgdir = pkgdir + self.failed_tests_outcome = [] + self.skipped_tests_outcome = [] + self.out = getout(py.std.sys.stdout) + #assert hosts == ['localhost'] + self.failed = dict([(host, 0) for host in hosts]) + self.skipped = dict([(host, 0) for host in hosts]) + self.passed = dict([(host, 0) for host in hosts]) + # XXX: This is for tests to work + #self.count = 0 + #self.lgt = 1000 + + def report(self, what): + repfun = getattr(self, "report_" + what.__class__.__name__, + self.report_unknown) + try: + repfun(what) + except (KeyboardInterrupt, SystemExit): + raise + except: + print "Internal reporting problem" + excinfo = py.code.ExceptionInfo() + for i in excinfo.traceback: + print str(i)[2:-1] + print excinfo + + def report_unknown(self, what): + if self.config.option.verbose: + print "Unknown report: %s" % what + + def report_SendItem(self, item): + address = self.get_host(item) + if self.config.option.verbose: + print "Sending %s to %s" % (item.item, + address) + + def report_HostRSyncing(self, item): + print "%10s: RSYNC ==> %s" % (item.hostname[:10], + item.remoterootpath) + + def report_HostReady(self, item): + print "%10s: READY" % item.hostname[:10] + + def report_TestStarted(self, item): + txt = " Test started, hosts: %s " % ", ".join(item.hosts) + self.out.sep("=", txt) + self.timestart = item.timestart + + def report_RsyncFinished(self, item): + self.timersync = item.time + + def report_ImmediateFailure(self, event): + self.repr_failure(event.item, event.outcome) + + def report_TestFinished(self, item): + self.out.line() + assert hasattr(self, 'timestart') + self.timeend = item.timeend + self.skips() + self.failures() + self.hangs() + self.summary() + + def hangs(self): + h = [] + for node in self.nodes: + h += [(i, node.channel.gateway.sshaddress) for i in node.pending] + if h: + self.out.sep("=", " HANGING NODES ") + for i, node in h: + self.out.line("%s on %s" % (" ".join(i.listnames()), node)) + + def failures(self): + if self.failed_tests_outcome: + self.out.sep("=", " FAILURES ") + for event in self.failed_tests_outcome: + host = self.get_host(event) + self.out.sep('_', "%s on %s" % + (" ".join(event.item.listnames()), host)) + if event.outcome.signal: + self.repr_signal(event.item, event.outcome) + else: + self.repr_failure(event.item, event.outcome) + + def repr_failure(self, item, outcome): + excinfo = outcome.excinfo + traceback = excinfo.traceback + #if item and not self.config.option.fulltrace: + # path, firstlineno = item.getpathlineno() + if not traceback: + self.out.line("empty traceback from item %r" % (item,)) + return + #handler = getattr(self, 'repr_failure_tb%s' % self.config.option.tbstyle) + handler = self.repr_failure_tblong + handler(item, excinfo, traceback) + if outcome.stdout: + self.out.sep('-', " Captured process stdout: ") + self.out.write(outcome.stdout) + if outcome.stderr: + self.out.sep('-', " Captured process stderr: ") + self.out.write(outcome.stderr) + + def repr_signal(self, item, outcome): + signal = outcome.signal + self.out.line("Received signal: %d" % outcome.signal) + if outcome.stdout: + self.out.sep('-', " Captured process stdout: ") + self.out.write(outcome.stdout) + if outcome.stderr: + self.out.sep('-', " Captured process stderr: ") + self.out.write(outcome.stderr) + + def repr_failure_tblong(self, item, excinfo, traceback): + for index, entry in py.builtin.enumerate(traceback): + self.out.sep('-') + self.out.line("%s: %s" % (entry.path, entry.lineno)) + self.repr_source(entry.relline, str(entry.source)) + self.out.line("%s: %s" % (excinfo.typename, excinfo.value)) + + def repr_source(self, relline, source): + for num, line in enumerate(source.split("\n")): + if num == relline: + self.out.line(">>>>" + line) + else: + self.out.line(" " + line) + + def skips(self): + texts = {} + for event in self.skipped_tests_outcome: + colitem = event.item + if isinstance(event, report.ReceivedItemOutcome): + outcome = event.outcome + text = outcome.skipped + itemname = self.get_item_name(event, colitem) + elif isinstance(event, report.SkippedTryiter): + text = str(event.excinfo.value) + itemname = "/".join(colitem.listnames()) + if text not in texts: + texts[text] = [itemname] + else: + texts[text].append(itemname) + + if texts: + self.out.line() + self.out.sep('_', 'reasons for skipped tests') + for text, items in texts.items(): + for item in items: + self.out.line('Skipped in %s' % item) + self.out.line("reason: %s" % text) + + def summary(self): + def gather(dic): + total = 0 + for key, val in dic.iteritems(): + total += val + return total + + def create_str(name, count): + if count: + return ", %d %s" % (count, name) + return "" + + total_passed = gather(self.passed) + total_failed = gather(self.failed) + total_skipped = gather(self.skipped) + total = total_passed + total_failed + total_skipped + skipped_str = create_str("skipped", total_skipped) + failed_str = create_str("failed", total_failed) + self.out.sep("=", " %d test run%s%s in %.2fs (rsync: %.2f)" % + (total, skipped_str, failed_str, self.timeend - self.timestart, + self.timersync - self.timestart)) + + def report_SkippedTryiter(self, event): + #event.outcome.excinfo.source = + self.skipped_tests_outcome.append(event) + + def report_FailedTryiter(self, event): + pass + # XXX: right now we do not do anything with it + + def report_ReceivedItemOutcome(self, event): + host = self.get_host(event) + if event.outcome.passed: + status = "PASSED " + self.passed[host] += 1 + elif event.outcome.skipped: + status = "SKIPPED" + self.skipped_tests_outcome.append(event) + self.skipped[host] += 1 + else: + status = "FAILED " + self.failed[host] += 1 + self.failed_tests_outcome.append(event) + # we'll take care of them later + sshhost = self.get_host(event) + itempath = " ".join(event.item.listnames()[1:]) + print "%10s: %s %s" %(sshhost[:10], status, itempath) + + def is_failing(self): + return len(self.failed_tests_outcome) != 0 + + def report_Nodes(self, event): + self.nodes = event.nodes + +class RemoteReporter(AbstractReporter): + def get_host(self, item): + return item.channel.gateway.sshaddress + + def get_item_name(self, event, colitem): + return event.channel.gateway.sshaddress + ":" + \ + "/".join(colitem.listnames()) + + def report_FailedTryiter(self, event): + self.out.line("FAILED TO LOAD MODULE: %s\n" % "/".join(event.item.listnames())) + + def report_SkippedTryiter(self, event): + self.out.line("Skipped (%s) %s\n" % (str(event.excinfo.value), "/". + join(event.item.listnames()))) + +class LocalReporter(AbstractReporter): + def get_host(self, item): + return 'localhost' + + def get_item_name(self, event, colitem): + return "/".join(colitem.listnames()) + + def report_SkippedTryiter(self, event): + #self.show_item(event.item, False) + self.out.write("- skipped (%s)" % event.excinfo.value) + + def report_FailedTryiter(self, event): + #self.show_item(event.item, False) + self.out.write("- FAILED TO LOAD MODULE") + + def report_ReceivedItemOutcome(self, event): + if event.outcome.passed: + self.passed['localhost'] += 1 + self.out.write(".") + elif event.outcome.skipped: + self.skipped_tests_outcome.append(event) + self.skipped['localhost'] += 1 + self.out.write("s") + else: + self.failed['localhost'] += 1 + self.failed_tests_outcome.append(event) + self.out.write("F") + # we'll take care of them later + #self.count += 1 + #if self.count >= self.lgt: + # self.out.write("\n") + #itempath = " ".join(event.item.listnames()[1:]) + #print "%10s: %s %s" %(sshhost[:10], status, itempath) + + def report_ItemStart(self, event): + self.show_item(event.item) + + def show_item(self, item, count_elems = True): + if isinstance(item, py.test.collect.Module): + # XXX This is a terrible hack, I don't like it + # and will rewrite it at some point + #self.count = 0 + lgt = len(list(item.tryiter())) + #self.lgt = lgt + # print names relative to current workdir + name = "/".join(item.listnames()) + local = str(py.path.local()) + d = str(self.pkgdir.dirpath().dirpath()) + if local.startswith(d): + local = local[len(d) + 1:] + if name.startswith(local): + name = name[len(local) + 1:] + self.out.write("\n%s[%d] " % (name, lgt)) + + def hangs(self): + pass Modified: py/dist/py/test/rsession/rsession.py ============================================================================== --- py/dist/py/test/rsession/rsession.py (original) +++ py/dist/py/test/rsession/rsession.py Mon Oct 30 18:30:26 2006 @@ -15,9 +15,9 @@ setup_slave, MasterNode, dispatch_loop from py.__.test.rsession.hostmanage import init_hosts, teardown_hosts -from py.__.test.terminal.out import getout from py.__.test.rsession.local import local_loop, plain_runner, apigen_runner,\ box_runner +from py.__.test.rsession.reporter import LocalReporter, RemoteReporter class RemoteOptions(object): def __init__(self, d): @@ -33,285 +33,6 @@ remote_options = RemoteOptions({'we_are_remote':False}) -class AbstractReporter(object): - def __init__(self, config, hosts, pkgdir=py.path.local(py.__file__)): - self.config = config - self.pkgdir = pkgdir - self.failed_tests_outcome = [] - self.skipped_tests_outcome = [] - self.out = getout(py.std.sys.stdout) - #assert hosts == ['localhost'] - self.failed = dict([(host, 0) for host in hosts]) - self.skipped = dict([(host, 0) for host in hosts]) - self.passed = dict([(host, 0) for host in hosts]) - # XXX: This is for tests to work - #self.count = 0 - #self.lgt = 1000 - - def report(self, what): - repfun = getattr(self, "report_" + what.__class__.__name__, - self.report_unknown) - try: - repfun(what) - except (KeyboardInterrupt, SystemExit): - raise - except: - print "Internal reporting problem" - excinfo = py.code.ExceptionInfo() - for i in excinfo.traceback: - print str(i)[2:-1] - print excinfo - - def report_unknown(self, what): - if self.config.option.verbose: - print "Unknown report: %s" % what - - def report_SendItem(self, item): - address = self.get_host(item) - if self.config.option.verbose: - print "Sending %s to %s" % (item.item, - address) - - def report_HostRSyncing(self, item): - print "%10s: RSYNC ==> %s" % (item.hostname[:10], - item.remoterootpath) - - def report_HostReady(self, item): - print "%10s: READY" % item.hostname[:10] - - def report_TestStarted(self, item): - txt = " Test started, hosts: %s " % ", ".join(item.hosts) - self.out.sep("=", txt) - self.timestart = item.timestart - - def report_RsyncFinished(self, item): - self.timersync = item.time - - def report_ImmediateFailure(self, event): - self.repr_failure(event.item, event.outcome) - - def report_TestFinished(self, item): - self.out.line() - assert hasattr(self, 'timestart') - self.timeend = item.timeend - self.skips() - self.failures() - self.hangs() - self.summary() - - def hangs(self): - h = [] - for node in self.nodes: - h += [(i, node.channel.gateway.sshaddress) for i in node.pending] - if h: - self.out.sep("=", " HANGING NODES ") - for i, node in h: - self.out.line("%s on %s" % (" ".join(i.listnames()), node)) - - def failures(self): - if self.failed_tests_outcome: - self.out.sep("=", " FAILURES ") - for event in self.failed_tests_outcome: - host = self.get_host(event) - self.out.sep('_', "%s on %s" % - (" ".join(event.item.listnames()), host)) - if event.outcome.signal: - self.repr_signal(event.item, event.outcome) - else: - self.repr_failure(event.item, event.outcome) - - def repr_failure(self, item, outcome): - excinfo = outcome.excinfo - traceback = excinfo.traceback - #if item and not self.config.option.fulltrace: - # path, firstlineno = item.getpathlineno() - if not traceback: - self.out.line("empty traceback from item %r" % (item,)) - return - #handler = getattr(self, 'repr_failure_tb%s' % self.config.option.tbstyle) - handler = self.repr_failure_tblong - handler(item, excinfo, traceback) - if outcome.stdout: - self.out.sep('-', " Captured process stdout: ") - self.out.write(outcome.stdout) - if outcome.stderr: - self.out.sep('-', " Captured process stderr: ") - self.out.write(outcome.stderr) - - def repr_signal(self, item, outcome): - signal = outcome.signal - self.out.line("Received signal: %d" % outcome.signal) - if outcome.stdout: - self.out.sep('-', " Captured process stdout: ") - self.out.write(outcome.stdout) - if outcome.stderr: - self.out.sep('-', " Captured process stderr: ") - self.out.write(outcome.stderr) - - def repr_failure_tblong(self, item, excinfo, traceback): - for index, entry in py.builtin.enumerate(traceback): - self.out.sep('-') - self.out.line("%s: %s" % (entry.path, entry.lineno)) - self.repr_source(entry.relline, str(entry.source)) - self.out.line("%s: %s" % (excinfo.typename, excinfo.value)) - - def repr_source(self, relline, source): - for num, line in enumerate(source.split("\n")): - if num == relline: - self.out.line(">>>>" + line) - else: - self.out.line(" " + line) - - def skips(self): - texts = {} - for event in self.skipped_tests_outcome: - colitem = event.item - if isinstance(event, report.ReceivedItemOutcome): - outcome = event.outcome - text = outcome.skipped - itemname = self.get_item_name(event, colitem) - elif isinstance(event, report.SkippedTryiter): - text = str(event.excinfo.value) - itemname = "/".join(colitem.listnames()) - if text not in texts: - texts[text] = [itemname] - else: - texts[text].append(itemname) - - if texts: - self.out.line() - self.out.sep('_', 'reasons for skipped tests') - for text, items in texts.items(): - for item in items: - self.out.line('Skipped in %s' % item) - self.out.line("reason: %s" % text) - - def summary(self): - def gather(dic): - total = 0 - for key, val in dic.iteritems(): - total += val - return total - - def create_str(name, count): - if count: - return ", %d %s" % (count, name) - return "" - - total_passed = gather(self.passed) - total_failed = gather(self.failed) - total_skipped = gather(self.skipped) - total = total_passed + total_failed + total_skipped - skipped_str = create_str("skipped", total_skipped) - failed_str = create_str("failed", total_failed) - self.out.sep("=", " %d test run%s%s in %.2fs (rsync: %.2f)" % - (total, skipped_str, failed_str, self.timeend - self.timestart, - self.timersync - self.timestart)) - - def report_SkippedTryiter(self, event): - #event.outcome.excinfo.source = - self.skipped_tests_outcome.append(event) - - def report_FailedTryiter(self, event): - pass - # XXX: right now we do not do anything with it - - def report_ReceivedItemOutcome(self, event): - host = self.get_host(event) - if event.outcome.passed: - status = "PASSED " - self.passed[host] += 1 - elif event.outcome.skipped: - status = "SKIPPED" - self.skipped_tests_outcome.append(event) - self.skipped[host] += 1 - else: - status = "FAILED " - self.failed[host] += 1 - self.failed_tests_outcome.append(event) - # we'll take care of them later - sshhost = self.get_host(event) - itempath = " ".join(event.item.listnames()[1:]) - print "%10s: %s %s" %(sshhost[:10], status, itempath) - - def is_failing(self): - return len(self.failed_tests_outcome) != 0 - - def report_Nodes(self, event): - self.nodes = event.nodes - -class RemoteReporter(AbstractReporter): - def get_host(self, item): - return item.channel.gateway.sshaddress - - def get_item_name(self, event, colitem): - return event.channel.gateway.sshaddress + ":" + \ - "/".join(colitem.listnames()) - - def report_FailedTryiter(self, event): - self.out.line("FAILED TO LOAD MODULE: %s\n" % "/".join(event.item.listnames())) - - def report_SkippedTryiter(self, event): - self.out.line("Skipped (%s) %s\n" % (str(event.excinfo.value), "/". - join(event.item.listnames()))) - -class LocalReporter(AbstractReporter): - def get_host(self, item): - return 'localhost' - - def get_item_name(self, event, colitem): - return "/".join(colitem.listnames()) - - def report_SkippedTryiter(self, event): - #self.show_item(event.item, False) - self.out.write("- skipped (%s)" % event.excinfo.value) - - def report_FailedTryiter(self, event): - #self.show_item(event.item, False) - self.out.write("- FAILED TO LOAD MODULE") - - def report_ReceivedItemOutcome(self, event): - if event.outcome.passed: - self.passed['localhost'] += 1 - self.out.write(".") - elif event.outcome.skipped: - self.skipped_tests_outcome.append(event) - self.skipped['localhost'] += 1 - self.out.write("s") - else: - self.failed['localhost'] += 1 - self.failed_tests_outcome.append(event) - self.out.write("F") - # we'll take care of them later - #self.count += 1 - #if self.count >= self.lgt: - # self.out.write("\n") - #itempath = " ".join(event.item.listnames()[1:]) - #print "%10s: %s %s" %(sshhost[:10], status, itempath) - - def report_ItemStart(self, event): - self.show_item(event.item) - - def show_item(self, item, count_elems = True): - if isinstance(item, py.test.collect.Module): - # XXX This is a terrible hack, I don't like it - # and will rewrite it at some point - #self.count = 0 - lgt = len(list(item.tryiter())) - #self.lgt = lgt - # print names relative to current workdir - name = "/".join(item.listnames()) - local = str(py.path.local()) - d = str(self.pkgdir.dirpath().dirpath()) - if local.startswith(d): - local = local[len(d) + 1:] - if name.startswith(local): - name = name[len(local) + 1:] - self.out.write("\n%s[%d] " % (name, lgt)) - - def hangs(self): - pass - class AbstractSession(object): """ An abstract session executes collectors/items through a runner. From guido at codespeak.net Tue Oct 31 12:08:27 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Tue, 31 Oct 2006 12:08:27 +0100 (CET) Subject: [py-svn] r33942 - py/dist/py/apigen/rest Message-ID: <20061031110827.E067B1007B@code0.codespeak.net> Author: guido Date: Tue Oct 31 12:08:26 2006 New Revision: 33942 Modified: py/dist/py/apigen/rest/genrest.py Log: Some cleanups, getting rid of passing the ReST lists around, and of writing sections while walking through the trees (so now the traceback sections are written _after_ tree walking, just like the other sections), added first bits of a real traceback page (but not yet full ;). Modified: py/dist/py/apigen/rest/genrest.py ============================================================================== --- py/dist/py/apigen/rest/genrest.py (original) +++ py/dist/py/apigen/rest/genrest.py Tue Oct 31 12:08:26 2006 @@ -145,12 +145,19 @@ for cname, crest, cfunclist in classlist: self.writer.write_section('class_%s' % (cname,), Rest(*crest).text()) - for fname, frest in cfunclist: + for fname, frest, tbdata in cfunclist: + print 'frest:', frest self.writer.write_section('method_%s' % (fname,), Rest(*frest).text()) - for fname, frest in funclist: + for tbname, tbrest in tbdata: + self.writer.write_section('traceback_%s' % (tbname,), + Rest(*tbrest).text()) + for fname, frest, tbdata in funclist: self.writer.write_section('function_%s' % (fname,), Rest(*frest).text()) + for tbname, tbrest in tbdata: + self.writer.write_section('traceback_%s' % (tbname,), + Rest(*tbrest).text()) def build_classrest(self, classlist): classrest = self.build_classes(classlist) @@ -163,9 +170,12 @@ def build_funcrest(self, funclist): funcrest = self.build_functions(funclist) - for fname, rest in funcrest: + for fname, rest, tbdata in funcrest: self.writer.write_section('function_%s' % (fname,), Rest(*rest).text()) + for tbname, tbrest in tbdata: + self.writer.write_section('traceback_%s' % (tbname,), + Rest(*tbrest).text()) def build_index(self, modules): rest = [Title('Index', abovechar='=', belowchar='=')] @@ -227,8 +237,8 @@ for function in functions: if parent: function = '%s.%s' % (parent, function) - rest = self.write_function(function, ismethod=methods) - ret.append((function, rest)) + rest, tbrest = self.write_function(function, ismethod=methods) + ret.append((function, rest, tbrest)) return ret def get_module_list(self): @@ -288,6 +298,7 @@ lst.append(Link(str(_type), _desc_type + "_" + name + ".html")) def write_function(self, functionname, ismethod=False, belowchar='-'): + print 'function', functionname # XXX I think the docstring should either be split on \n\n and cleaned # from indentation, or treated as ReST too (although this is obviously # dangerous for non-ReST docstrings)... @@ -318,17 +329,6 @@ if next: items.append(Text(next)) lst.append(ListItem(*items)) - #if link: - # link_str, link_target = link - # lst.append(ListItem(Quote("%s :: %s" % (name, pre_str)), - # Link(link_str, link_target), Quote(post_str))) - #else: - # lst.append(ListItem(Quote("%s :: %s%s" % (name, _type)))) - - #arg_str = "\n".join(["%s :: %s" % (str(name), str(type)) - # for name, type in args]) - #arg_str += "\n" + "Return value :: %s" % str(retval) - #lst.append(LiteralBlock(arg_str)) # XXX missing implementation of dsa.get_function_location() #filename, lineno = self.dsa.get_function_location(functionname) @@ -340,12 +340,6 @@ lst.append(Paragraph('Function source:')) lst.append(LiteralBlock(self.dsa.get_function_source(functionname))) - #arg_str = "(%s)" % (",".join([str(i) for i in args])) - #ret_str = str(retval) - #arg_quote = Paragraph("Function type:", Quote(arg_str), '->', - # Quote(ret_str)) - #lst.append(arg_quote) - # call sites.. call_site_title = Title("Call sites:", belowchar='+') lst.append(call_site_title) @@ -354,45 +348,30 @@ # 1. A quick'n'dirty statement where call has appeared first (topmost) # 2. Link to short traceback # 3. Link to long traceback + tbrest = [] for call_site, _ in self.dsa.get_function_callpoints(functionname): - self.write_call_site_link(call_site, lst) -## for call_site, frame in self.dsa.get_function_callpoints(functionname): -## link_str = "File %s:%s" % (call_site.filename, -## call_site.lineno) -## link_str, link_target = self.linkgen.getlink(call_site.filename, -## call_site.lineno) -## if link_target: # otherwise it's just inline text -## call_sites.append(Paragraph(Link(link_str, link_target))) -## else: -## call_sites.append(Paragraph(link_str)) -## #call_sites.append(LiteralBlock(call_site.source)) -## # XXX: For now, we just paste here the filename of that -## #call_sites.append(Paragraph(link_str)) -## try: -## source = frame.code.source() -## except KeyboardInterrupt, SystemError: -## raise -## except: -## source = "*Cannot get source*" -## lines = [] -## for num, line in enumerate(source): -## if num == call_site.lineno - frame.code.firstlineno - 1: -## m = re.match("^( *)(.*)", line) -## lines.append(">%s%s" % ("-" * len(m.group(1)), m.group(2))) -## else: -## lines.append(" " + line) -## call_sites.append(LiteralBlock("\n".join(lines))) - - return lst - - def write_call_site_link(self, call_site, lst): - name = self.gen_traceback(call_site) - lst.append(Paragraph("Called in %s" % call_site[0].code.filename, Link\ - ("Full %s" % name, name + '.html'))) + fdata, tbdata = self.call_site_link(functionname, call_site) + lst += fdata + tbrest.append(tbdata) + + return lst, tbrest + + def call_site_link(self, functionname, call_site): + tbid, tbrest = self.gen_traceback(call_site) + tbname = '%s.%s' % (functionname, tbid) + linktarget = self.writer.getlink('traceback', + tbname, + 'traceback_%s' % (tbname,)) + frest = [Paragraph("Called in %s" % call_site[0].code.filename), + Paragraph(Link("Full traceback %s" % (tbname,), + linktarget))] + return frest, (tbname, tbrest) def gen_traceback(self, call_site): - name = "traceback_%d" % self.traceback_no + tbid = self.traceback_no self.traceback_no += 1 - print name - self.writer.write_section(name, Rest(*[Title("Random traceback here")]).text()) - return name + tbrest = [Title('Full traceback')] + for line in call_site: + tbrest.append(ListItem('%4s: %s' % (line.lineno + 1, + line.code.source()[line.lineno - line.code.firstlineno]))) + return tbid, tbrest From guido at codespeak.net Tue Oct 31 15:07:48 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Tue, 31 Oct 2006 15:07:48 +0100 (CET) Subject: [py-svn] r33955 - in py/dist/py/apigen/rest: . testing Message-ID: <20061031140748.24D9F10076@code0.codespeak.net> Author: guido Date: Tue Oct 31 15:07:47 2006 New Revision: 33955 Modified: py/dist/py/apigen/rest/genrest.py py/dist/py/apigen/rest/testing/test_rest.py Log: Improved traceback page names and content. Modified: py/dist/py/apigen/rest/genrest.py ============================================================================== --- py/dist/py/apigen/rest/genrest.py (original) +++ py/dist/py/apigen/rest/genrest.py Tue Oct 31 15:07:47 2006 @@ -120,7 +120,7 @@ self.dsa = DocStorageAccessor(ds) self.linkgen = linkgen self.writer = writer - self.traceback_no = 0 + self.tracebacks = {} def write(self): """write the data to the writer""" @@ -146,7 +146,6 @@ self.writer.write_section('class_%s' % (cname,), Rest(*crest).text()) for fname, frest, tbdata in cfunclist: - print 'frest:', frest self.writer.write_section('method_%s' % (fname,), Rest(*frest).text()) for tbname, tbrest in tbdata: @@ -283,22 +282,24 @@ def get_method_list(self, classname): return self.dsa.get_class_methods(classname) - def process_type_link(self, _type, lst): + def process_type_link(self, _type): # now we do simple type dispatching and provide a link in this case + lst = [] data = self.dsa.get_type_desc(_type) if not data: for i in _type.striter(): if isinstance(i, str): lst.append(i) else: - self.process_type_link(i, lst) - return + lst += self.process_type_link(i) + return lst name, _desc_type = data - # XXX hard-coded HTML link! needs to be changed... - lst.append(Link(str(_type), _desc_type + "_" + name + ".html")) + linktarget = self.writer.getlink(_desc_type, name, + '%s_%s' % (_desc_type, name)) + lst.append(Link(str(_type), linktarget)) + return lst def write_function(self, functionname, ismethod=False, belowchar='-'): - print 'function', functionname # XXX I think the docstring should either be split on \n\n and cleaned # from indentation, or treated as ReST too (although this is obviously # dangerous for non-ReST docstrings)... @@ -314,8 +315,7 @@ args, retval = self.dsa.get_function_signature(functionname) for name, _type in args + [('Return value', retval)]: - l = [] - self.process_type_link(_type, l) + l = self.process_type_link(_type) items = [] next = "%s :: " % name for item in l: @@ -357,7 +357,7 @@ return lst, tbrest def call_site_link(self, functionname, call_site): - tbid, tbrest = self.gen_traceback(call_site) + tbid, tbrest = self.gen_traceback(functionname, call_site) tbname = '%s.%s' % (functionname, tbid) linktarget = self.writer.getlink('traceback', tbname, @@ -367,11 +367,26 @@ linktarget))] return frest, (tbname, tbrest) - def gen_traceback(self, call_site): - tbid = self.traceback_no - self.traceback_no += 1 - tbrest = [Title('Full traceback')] - for line in call_site: - tbrest.append(ListItem('%4s: %s' % (line.lineno + 1, - line.code.source()[line.lineno - line.code.firstlineno]))) + def gen_traceback(self, funcname, call_site): + tbid = len(self.tracebacks.setdefault(funcname, [])) + self.tracebacks[funcname].append(call_site) + tbrest = [Title('Full traceback for %s' % (funcname,))] + for line in reversed(call_site): + lineno = line.lineno - line.code.firstlineno + linkname, linktarget = self.linkgen.getlink(line.code.filename, + line.lineno + 1) + if linktarget: + tbrest.append(Paragraph(Link(linkname, linktarget))) + else: + tbrest.append(Paragraph(linkname)) + source = line.code.source() + mangled = [] + for i, sline in enumerate(str(source).split('\n')): + if i == lineno: + line = '-> %s' % (sline,) + else: + line = ' %s' % (sline,) + mangled.append(line) + tbrest.append(LiteralBlock('\n'.join(mangled))) return tbid, tbrest + Modified: py/dist/py/apigen/rest/testing/test_rest.py ============================================================================== --- py/dist/py/apigen/rest/testing/test_rest.py (original) +++ py/dist/py/apigen/rest/testing/test_rest.py Tue Oct 31 15:07:47 2006 @@ -170,7 +170,7 @@ r = RestGen(ds, lg, DirWriter(tempdir)) r.write() basenames = [p.basename for p in tempdir.listdir('*.txt')] - assert sorted(basenames) == [ + expected = [ 'class_SomeClass.txt', 'class_SomeSubClass.txt', 'function_fun.txt', @@ -180,18 +180,18 @@ 'method_SomeSubClass.__init__.txt', 'method_SomeSubClass.method.txt', 'module_Unknown module.txt', - 'traceback_0.txt', - 'traceback_1.txt', - 'traceback_2.txt', - 'traceback_3.txt', - 'traceback_4.txt', + 'traceback_SomeClass.__init__.0.txt', + 'traceback_SomeClass.__init__.1.txt', + 'traceback_SomeClass.method.0.txt', + 'traceback_fun.0.txt', + 'traceback_fun.1.txt', ] + print sorted(basenames) + assert sorted(basenames) == expected # now we check out... self.check_rest(tempdir) def test_generation_modules(self): - #py.test.skip('borken - fijal, somehow the methods for classes in ' - # 'modules don\'t get picked up... any idea?') ds = self.get_filled_docstorage_modules() lg = DirectPaste() tempdir = temppath.ensure('module_api', dir=True) @@ -210,12 +210,13 @@ 'module_Unknown module.txt', 'module_somemodule.txt', 'module_someothermodule.txt', - 'traceback_0.txt', - 'traceback_1.txt', - 'traceback_2.txt', - 'traceback_3.txt', - 'traceback_4.txt', + 'traceback_somemodule.SomeClass.__init__.0.txt', + 'traceback_somemodule.SomeClass.__init__.1.txt', + 'traceback_someothermodule.SomeSubClass.method.0.txt', + 'traceback_someothermodule.fun.0.txt', + 'traceback_someothermodule.fun.1.txt', ] + print sorted(basenames) assert sorted(basenames) == expected def test_check_internal_links(self): From guido at codespeak.net Tue Oct 31 15:58:51 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Tue, 31 Oct 2006 15:58:51 +0100 (CET) Subject: [py-svn] r33960 - py/dist/py/apigen/tracer Message-ID: <20061031145851.264A410084@code0.codespeak.net> Author: guido Date: Tue Oct 31 15:58:44 2006 New Revision: 33960 Modified: py/dist/py/apigen/tracer/description.py py/dist/py/apigen/tracer/docstorage.py py/dist/py/apigen/tracer/tracer.py Log: Intermediate checkin: trying to display the differences between __dict__ after executing a method (or function). Currently not working. Modified: py/dist/py/apigen/tracer/description.py ============================================================================== --- py/dist/py/apigen/tracer/description.py (original) +++ py/dist/py/apigen/tracer/description.py Tue Oct 31 15:58:44 2006 @@ -119,6 +119,7 @@ self.keep_frames = kwargs.get('keep_frames', False) self.frame_copier = kwargs.get('frame_copier', lambda x:x) self.retval = model.s_ImpossibleValue + self.local_changes = {} def consider_call(self, inputcells): for cell_num, cell in enumerate(inputcells): @@ -127,7 +128,7 @@ def consider_call_site(self, frame, cut_frame): stack = [i[0] for i in inspect.stack()] cs = cut_stack(stack, frame, cut_frame) - print cs, hash(cs), cs._getval() + #print cs, hash(cs), cs._getval() self.call_sites[cs] = cs def get_call_sites(self): @@ -144,6 +145,9 @@ def consider_return(self, arg): self.retval = model.unionof(arg, self.retval) + def handle_local_changes(self, changeset): + self.local_changes = changeset + def getcode(self): return self.pyobj.func_code code = property(getcode) @@ -170,7 +174,7 @@ hash(result) except KeyboardInterrupt, SystemExit: raise - except: # UUuuuu bare except here. What can it really rise??? + except: # XXX UUuuuu bare except here. What can it really rise??? try: hash(self.pyobj) return self.pyobj Modified: py/dist/py/apigen/tracer/docstorage.py ============================================================================== --- py/dist/py/apigen/tracer/docstorage.py (original) +++ py/dist/py/apigen/tracer/docstorage.py Tue Oct 31 15:58:44 2006 @@ -21,6 +21,12 @@ if desc: self.generalize_args(desc, frame) desc.consider_call_site(caller_frame, upward_cut_frame) + + def handle_local_changes(self, frame, changes): + assert isinstance(frame, py.code.Frame) + desc = self.find_desc(frame.code) + if desc: + desc.handle_local_changes(changes) def generalize_args(self, desc, frame): args = [arg for key, arg in frame.getargs()] @@ -200,6 +206,9 @@ def get_function_callpoints(self, name): # return list of tuple (filename, fileline, frame) return self.ds.descs[name].get_call_sites() + + def get_function_local_changes(self, name): + return self.ds.descs[name].local_changes def get_module_name(self): if hasattr(self.ds, 'module'): Modified: py/dist/py/apigen/tracer/tracer.py ============================================================================== --- py/dist/py/apigen/tracer/tracer.py (original) +++ py/dist/py/apigen/tracer/tracer.py Tue Oct 31 15:58:44 2006 @@ -12,6 +12,9 @@ class UnionError(Exception): pass +class NoValue(object): + pass + class Tracer(object): """ Basic tracer object, used for gathering additional info about API functions @@ -20,16 +23,36 @@ self.docstorage = docstorage self.tracing = False + _locals = {} def _tracer(self, frame, event, arg): # perform actuall tracing frame = py.code.Frame(frame) + frameid = '%s:%s' % (frame.code.filename, frame.code.firstlineno) if event == 'call': assert arg is None - self.docstorage.consider_call(frame, py.code.Frame(sys._getframe(2)), self.frame) + self.docstorage.consider_call(frame, + py.code.Frame(sys._getframe(2)), + self.frame) + self._locals[frameid] = frame.f_locals.copy() elif event == 'return': self.docstorage.consider_return(frame, arg) - + changeset = {} + if frameid in self._locals: + oldlocals = self._locals[frameid] + newlocals = frame.f_locals + __marker__ = [] + for k, v in newlocals.items(): + oldvalue = oldlocals.get(k, __marker__) + if oldvalue is not v: + if oldvalue is __marker__: + changeset[k] = (NoValue, v) + else: + changeset[k] = (oldvalue, v) + for k, v in oldlocals.items(): + if k not in newlocals: + changeset[k] = (oldvalue, NoValue) + self.docstorage.handle_local_changes(frame, changeset) return self._tracer def start_tracing(self): From fijal at codespeak.net Tue Oct 31 16:00:40 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Tue, 31 Oct 2006 16:00:40 +0100 (CET) Subject: [py-svn] r33961 - in py/dist/py/apigen: rest tracer Message-ID: <20061031150040.02C0510088@code0.codespeak.net> Author: fijal Date: Tue Oct 31 16:00:30 2006 New Revision: 33961 Modified: py/dist/py/apigen/rest/genrest.py py/dist/py/apigen/tracer/description.py Log: Added max calls flag. Modified: py/dist/py/apigen/rest/genrest.py ============================================================================== --- py/dist/py/apigen/rest/genrest.py (original) +++ py/dist/py/apigen/rest/genrest.py Tue Oct 31 16:00:30 2006 @@ -389,4 +389,3 @@ mangled.append(line) tbrest.append(LiteralBlock('\n'.join(mangled))) return tbid, tbrest - Modified: py/dist/py/apigen/tracer/description.py ============================================================================== --- py/dist/py/apigen/tracer/description.py (original) +++ py/dist/py/apigen/tracer/description.py Tue Oct 31 16:00:30 2006 @@ -5,6 +5,8 @@ import types import inspect +MAX_CALL_SITES = 20 + class CallStack(object): def __init__(self, tb): if isinstance(tb, py.code.Traceback): @@ -126,9 +128,10 @@ self.inputcells[cell_num] = model.unionof(cell, self.inputcells[cell_num]) def consider_call_site(self, frame, cut_frame): + if len(self.call_sites) > MAX_CALL_SITES: + return stack = [i[0] for i in inspect.stack()] cs = cut_stack(stack, frame, cut_frame) - #print cs, hash(cs), cs._getval() self.call_sites[cs] = cs def get_call_sites(self): From guido at codespeak.net Tue Oct 31 17:09:38 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Tue, 31 Oct 2006 17:09:38 +0100 (CET) Subject: [py-svn] r33965 - in py/dist/py/apigen: rest tracer tracer/testing Message-ID: <20061031160938.A96DA1006E@code0.codespeak.net> Author: guido Date: Tue Oct 31 17:09:35 2006 New Revision: 33965 Modified: py/dist/py/apigen/rest/genrest.py py/dist/py/apigen/tracer/description.py py/dist/py/apigen/tracer/docstorage.py py/dist/py/apigen/tracer/testing/test_docgen.py py/dist/py/apigen/tracer/testing/test_package.py py/dist/py/apigen/tracer/tracer.py Log: Improved (and actually got it to work :) displaying of changes in self.__dict__ after method calls. Also some whitespace and reformatting. Modified: py/dist/py/apigen/rest/genrest.py ============================================================================== --- py/dist/py/apigen/rest/genrest.py (original) +++ py/dist/py/apigen/rest/genrest.py Tue Oct 31 17:09:35 2006 @@ -330,6 +330,17 @@ items.append(Text(next)) lst.append(ListItem(*items)) + local_changes = self.dsa.get_function_local_changes(functionname) + lst.append(Paragraph('Changes in __dict__:')) + from py.__.apigen.tracer.description import NoValue + for k, (oldvalue, newvalue) in local_changes.iteritems(): + description = 'value changed' + if oldvalue is NoValue: + description = 'newly added' + elif newvalue is NoValue: + description = 'deleted' + lst.append(ListItem('%s: %s' % (k, description))) + # XXX missing implementation of dsa.get_function_location() #filename, lineno = self.dsa.get_function_location(functionname) #linkname, linktarget = self.linkgen.getlink(filename, lineno) Modified: py/dist/py/apigen/tracer/description.py ============================================================================== --- py/dist/py/apigen/tracer/description.py (original) +++ py/dist/py/apigen/tracer/description.py Tue Oct 31 17:09:35 2006 @@ -42,6 +42,9 @@ def __cmp__(self, other): return cmp(self._getval(), other._getval()) +class NoValue(object): + """used in MethodDesc.get_local_changes() when there is no value""" + def cut_stack(stack, frame, upward_frame=None): if hasattr(frame, 'raw'): frame = frame.raw @@ -121,7 +124,6 @@ self.keep_frames = kwargs.get('keep_frames', False) self.frame_copier = kwargs.get('frame_copier', lambda x:x) self.retval = model.s_ImpossibleValue - self.local_changes = {} def consider_call(self, inputcells): for cell_num, cell in enumerate(inputcells): @@ -148,13 +150,19 @@ def consider_return(self, arg): self.retval = model.unionof(arg, self.retval) - def handle_local_changes(self, changeset): - self.local_changes = changeset + def consider_start_locals(self, frame): + pass + def consider_end_locals(self, frame): + pass + def getcode(self): return self.pyobj.func_code code = property(getcode) + def get_local_changes(self): + return {} + class ClassDesc(Desc): def __init__(self, *args, **kwargs): super(ClassDesc, self).__init__(*args, **kwargs) @@ -196,6 +204,12 @@ def consider_return(self, arg): pass # we *know* what return value we do have + + def consider_start_locals(self, frame): + pass + + def consider_end_locals(self, frame): + pass def consider_call_site(self, frame, cut_frame): self.fields['__init__'].consider_call_site(frame, cut_frame) @@ -217,9 +231,42 @@ ## ## class MethodDesc(FunctionDesc): + def __init__(self, *args, **kwargs): + super(MethodDesc, self).__init__(*args, **kwargs) + self.old_dict = {} + self.new_dict = {} + # 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 + + def consider_start_locals(self, frame): + # XXX recursion issues? + obj = frame.f_locals.get('self') + if not obj: + # static method + return + self.old_dict = obj.__dict__.copy() + + def consider_end_locals(self, frame): + obj = frame.f_locals.get('self') + if not obj: + # static method + return + self.new_dict = obj.__dict__.copy() + + def get_local_changes(self): + changeset = {} + for k, v in self.old_dict.iteritems(): + if k not in self.new_dict: + changeset[k] = (v, NoValue) + elif self.new_dict[k] != v: + changeset[k] = (v, self.new_dict[k]) + for k, v in self.new_dict.iteritems(): + if k not in self.old_dict: + changeset[k] = (NoValue, v) + return changeset + Modified: py/dist/py/apigen/tracer/docstorage.py ============================================================================== --- py/dist/py/apigen/tracer/docstorage.py (original) +++ py/dist/py/apigen/tracer/docstorage.py Tue Oct 31 17:09:35 2006 @@ -21,13 +21,8 @@ if desc: self.generalize_args(desc, frame) desc.consider_call_site(caller_frame, upward_cut_frame) + desc.consider_start_locals(frame) - def handle_local_changes(self, frame, changes): - assert isinstance(frame, py.code.Frame) - desc = self.find_desc(frame.code) - if desc: - desc.handle_local_changes(changes) - def generalize_args(self, desc, frame): args = [arg for key, arg in frame.getargs()] #self.call_stack.append((desc, args)) @@ -41,6 +36,7 @@ desc = self.find_desc(frame.code) if desc: self.generalize_retval(desc, arg) + desc.consider_end_locals(frame) def find_desc(self, code): return self.desc_cache.get(code.raw, None) @@ -208,7 +204,7 @@ return self.ds.descs[name].get_call_sites() def get_function_local_changes(self, name): - return self.ds.descs[name].local_changes + return self.ds.descs[name].get_local_changes() def get_module_name(self): if hasattr(self.ds, 'module'): Modified: py/dist/py/apigen/tracer/testing/test_docgen.py ============================================================================== --- py/dist/py/apigen/tracer/testing/test_docgen.py (original) +++ py/dist/py/apigen/tracer/testing/test_docgen.py Tue Oct 31 17:09:35 2006 @@ -145,3 +145,36 @@ t.end_tracing() assert isinstance(ds.descs['A'].fields['method'].inputcells[1], model.SomeInt) assert isinstance(ds.descs['B'].fields['method'].inputcells[1], model.SomeInt) + +def test_local_changes(): + class testclass(object): + def __init__(self): + self.foo = 0 + def bar(self, x): + self.foo = x + ds = DocStorage().from_dict({'testclass': testclass}) + t = Tracer(ds) + t.start_tracing() + c = testclass() + c.bar(1) + t.end_tracing() + desc = ds.descs['testclass'] + methdesc = desc.fields['bar'] + assert methdesc.old_dict != methdesc.new_dict + assert methdesc.get_local_changes() == {'foo': (0, 1)} + +def test_local_changes_nochange(): + class testclass(object): + def __init__(self): + self.foo = 0 + def bar(self, x): + self.foo = x + ds = DocStorage().from_dict({'testclass': testclass}) + t = Tracer(ds) + t.start_tracing() + c = testclass() + t.end_tracing() + desc = ds.descs['testclass'] + methdesc = desc.fields['bar'] + assert methdesc.get_local_changes() == {} + Modified: py/dist/py/apigen/tracer/testing/test_package.py ============================================================================== --- py/dist/py/apigen/tracer/testing/test_package.py (original) +++ py/dist/py/apigen/tracer/testing/test_package.py Tue Oct 31 17:09:35 2006 @@ -45,3 +45,4 @@ methdesc = desc.fields['__init__'] assert isinstance(methdesc.inputcells[0], model.SomeInstance) assert isinstance(methdesc.inputcells[1], model.SomeInt) + Modified: py/dist/py/apigen/tracer/tracer.py ============================================================================== --- py/dist/py/apigen/tracer/tracer.py (original) +++ py/dist/py/apigen/tracer/tracer.py Tue Oct 31 17:09:35 2006 @@ -28,31 +28,13 @@ # perform actuall tracing frame = py.code.Frame(frame) - frameid = '%s:%s' % (frame.code.filename, frame.code.firstlineno) if event == 'call': assert arg is None self.docstorage.consider_call(frame, py.code.Frame(sys._getframe(2)), self.frame) - self._locals[frameid] = frame.f_locals.copy() elif event == 'return': self.docstorage.consider_return(frame, arg) - changeset = {} - if frameid in self._locals: - oldlocals = self._locals[frameid] - newlocals = frame.f_locals - __marker__ = [] - for k, v in newlocals.items(): - oldvalue = oldlocals.get(k, __marker__) - if oldvalue is not v: - if oldvalue is __marker__: - changeset[k] = (NoValue, v) - else: - changeset[k] = (oldvalue, v) - for k, v in oldlocals.items(): - if k not in newlocals: - changeset[k] = (oldvalue, NoValue) - self.docstorage.handle_local_changes(frame, changeset) return self._tracer def start_tracing(self):