From fijal at codespeak.net Wed Nov 1 15:32:49 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Wed, 1 Nov 2006 15:32:49 +0100 (CET) Subject: [py-svn] r34030 - py/dist/py/apigen/tracer/testing Message-ID: <20061101143249.3113010077@code0.codespeak.net> Author: fijal Date: Wed Nov 1 15:32:48 2006 New Revision: 34030 Modified: py/dist/py/apigen/tracer/testing/test_model.py Log: (stakkars, fijal) - Fixed order. 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 Nov 1 15:32:48 2006 @@ -105,8 +105,8 @@ 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) + l = sorted(list(g.striter())) + assert l[4] == "AnyOf(" + assert isinstance(l[0], SomeClass) + assert l[3] == ", " + assert isinstance(l[1], SomeInstance) From fijal at codespeak.net Fri Nov 3 11:38:48 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Fri, 3 Nov 2006 11:38:48 +0100 (CET) Subject: [py-svn] r34089 - in py/dist/py/apigen: rest tracer tracer/testing Message-ID: <20061103103848.2B5091005A@code0.codespeak.net> Author: fijal Date: Fri Nov 3 11:38:47 2006 New Revision: 34089 Modified: py/dist/py/apigen/rest/genrest.py py/dist/py/apigen/tracer/description.py py/dist/py/apigen/tracer/testing/test_docgen.py Log: (guido, fijal) - Intermediate checkin of somehow changing semantics of __dict__ monitoring. Modified: py/dist/py/apigen/rest/genrest.py ============================================================================== --- py/dist/py/apigen/rest/genrest.py (original) +++ py/dist/py/apigen/rest/genrest.py Fri Nov 3 11:38:47 2006 @@ -390,7 +390,10 @@ tbrest.append(Paragraph(Link(linkname, linktarget))) else: tbrest.append(Paragraph(linkname)) - source = line.code.source() + try: + source = line.code.source() + except IOError: + source = "*Cannot get source*" mangled = [] for i, sline in enumerate(str(source).split('\n')): if i == lineno: Modified: py/dist/py/apigen/tracer/description.py ============================================================================== --- py/dist/py/apigen/tracer/description.py (original) +++ py/dist/py/apigen/tracer/description.py Fri Nov 3 11:38:47 2006 @@ -4,6 +4,7 @@ import types import inspect +import copy MAX_CALL_SITES = 20 @@ -42,9 +43,6 @@ 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 @@ -234,7 +232,7 @@ def __init__(self, *args, **kwargs): super(MethodDesc, self).__init__(*args, **kwargs) self.old_dict = {} - self.new_dict = {} + self.changeset = {} # right now it's not different than method desc, only code is different def getcode(self): @@ -245,28 +243,47 @@ def consider_start_locals(self, frame): # XXX recursion issues? - obj = frame.f_locals.get('self') - if not obj: - # static method + obj = frame.f_locals[self.pyobj.im_func.func_code.co_varnames[0]] + try: + if not obj: + # static method + return + except AttributeError: return - self.old_dict = obj.__dict__.copy() + #self.old_dict = self.perform_dict_copy(obj.__dict__) + self.old_dict = copy.deepcopy(obj.__dict__) def consider_end_locals(self, frame): - obj = frame.f_locals.get('self') - if not obj: - # static method + obj = frame.f_locals[self.pyobj.im_func.func_code.co_varnames[0]] + try: + if not obj: + # static method + return + except AttributeError: return - self.new_dict = obj.__dict__.copy() + # store the local changes + # update self.changeset + self.update_changeset(obj.__dict__) def get_local_changes(self): - changeset = {} + return self.changeset + + def set_changeset(changeset, key, value): + if key not in changeset: + changeset[key] = set([value]) + else: + changeset[key].add(value) + set_changeset = staticmethod(set_changeset) + + def update_changeset(self, new_dict): + changeset = 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 new_dict: + self.set_changeset(changeset, k, "deleted") + elif new_dict[k] != v: + self.set_changeset(changeset, k, "changed") + for k, v in new_dict.iteritems(): if k not in self.old_dict: - changeset[k] = (NoValue, v) + self.set_changeset(changeset, k, "created") return changeset 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 Nov 3 11:38:47 2006 @@ -160,8 +160,8 @@ 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)} + #assert methdesc.old_dict != methdesc.new_dict + assert methdesc.get_local_changes() == {'foo': set(['changed'])} def test_local_changes_nochange(): class testclass(object): @@ -177,4 +177,3 @@ desc = ds.descs['testclass'] methdesc = desc.fields['bar'] assert methdesc.get_local_changes() == {} - From guido at codespeak.net Fri Nov 3 14:08:18 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Fri, 3 Nov 2006 14:08:18 +0100 (CET) Subject: [py-svn] r34097 - in py/dist/py/apigen: rest tracer Message-ID: <20061103130818.92CC010079@code0.codespeak.net> Author: guido Date: Fri Nov 3 14:08:16 2006 New Revision: 34097 Modified: py/dist/py/apigen/rest/genrest.py py/dist/py/apigen/tracer/description.py Log: Fixed problems with deepcopy() not being able to copy everything, adjusted genrest to cope with the changes in tracer. Modified: py/dist/py/apigen/rest/genrest.py ============================================================================== --- py/dist/py/apigen/rest/genrest.py (original) +++ py/dist/py/apigen/rest/genrest.py Fri Nov 3 14:08:16 2006 @@ -332,14 +332,8 @@ 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))) + for k, changeset in local_changes.iteritems(): + lst.append(ListItem('%s: %s' % (k, ', '.join(changeset)))) # XXX missing implementation of dsa.get_function_location() #filename, lineno = self.dsa.get_function_location(functionname) Modified: py/dist/py/apigen/tracer/description.py ============================================================================== --- py/dist/py/apigen/tracer/description.py (original) +++ py/dist/py/apigen/tracer/description.py Fri Nov 3 14:08:16 2006 @@ -250,8 +250,19 @@ return except AttributeError: return - #self.old_dict = self.perform_dict_copy(obj.__dict__) - self.old_dict = copy.deepcopy(obj.__dict__) + self.old_dict = self.perform_dict_copy(obj.__dict__) + + def perform_dict_copy(self, d): + try: + c = copy.deepcopy(d) + except: + c = {} + for k, v in d.iteritems(): + try: + c[k] = copy.deepcopy(v) + except: + c[k] = v + return c def consider_end_locals(self, frame): obj = frame.f_locals[self.pyobj.im_func.func_code.co_varnames[0]] From arigo at codespeak.net Sun Nov 5 12:31:14 2006 From: arigo at codespeak.net (arigo at codespeak.net) Date: Sun, 5 Nov 2006 12:31:14 +0100 (CET) Subject: [py-svn] r34227 - in py/dist/py/test: . rsession Message-ID: <20061105113114.4478F10084@code0.codespeak.net> Author: arigo Date: Sun Nov 5 12:31:05 2006 New Revision: 34227 Modified: py/dist/py/test/cmdline.py py/dist/py/test/rsession/box.py py/dist/py/test/rsession/rsession.py py/dist/py/test/rsession/slave.py Log: (fijal, arigo) Kill some imports and dead code. Modified: py/dist/py/test/cmdline.py ============================================================================== --- py/dist/py/test/cmdline.py (original) +++ py/dist/py/test/cmdline.py Sun Nov 5 12:31:05 2006 @@ -4,8 +4,6 @@ # main entry point # -from py.__.test.rsession.rsession import AbstractSession - def main(args=None): warn_about_missing_assertion() if args is None: @@ -17,12 +15,16 @@ 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 + if config.option.startserver: + from py.__.test.rsession.rsession import AbstractSession + if not isinstance(session, AbstractSession): + print "Cannot use web server without (R|L)Session" + raise SystemExit, 2 + if config.option.apigen: + from py.__.test.rsession.rsession import AbstractSession + if 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 Modified: py/dist/py/test/rsession/box.py ============================================================================== --- py/dist/py/test/rsession/box.py (original) +++ py/dist/py/test/rsession/box.py Sun Nov 5 12:31:05 2006 @@ -7,7 +7,6 @@ import os import sys import marshal -import thread NICE_LEVEL = 0 # XXX make it a conftest option @@ -36,101 +35,6 @@ self.exitstat = 0 return 123 -class FifoBox(object): - def __init__(self, fun, args = [], kwargs = {}): - self.fun = fun - self.args = args - self.kwargs = kwargs - - def run(self): - dirname = tempfile.mkdtemp("pytest") - self.dirname = dirname - self.PYTESTRETVAL = os.path.join(dirname, PYTESTRETVAL) - self.PYTESTSTDERR = os.path.join(dirname, PYTESTSTDERR) - self.PYTESTSTDOUT = os.path.join(dirname, PYTESTSTDOUT) - os.mkfifo(self.PYTESTSTDOUT) - os.mkfifo(self.PYTESTSTDERR) - os.mkfifo(self.PYTESTRETVAL) - pid = os.fork() - if pid: - self.parent() - else: - try: - outcome = self.children() - except: - excinfo = py.code.ExceptionInfo() - print "Internal box error" - for i in excinfo.traceback: - print str(i)[2:-1] - print excinfo - os._exit(1) - os.close(1) - os.close(2) - os._exit(0) - return pid - - def children(self): - # right now we need to call a function, but first we need to - # map all IO that might happen - # make sure sys.stdout points to file descriptor one - fdstdout = os.open(self.PYTESTSTDOUT, os.O_WRONLY) - if fdstdout != 1: - os.dup2(fdstdout, 1) - fdstderr = os.open(self.PYTESTSTDERR, os.O_WRONLY) - if fdstderr != 2: - os.dup2(fdstderr, 2) - sys.stdout = os.fdopen(1, "w", 0) - sys.stderr = os.fdopen(2, "w", 0) - retvalf = open(self.PYTESTRETVAL, "w") - if NICE_LEVEL: - os.nice(NICE_LEVEL) - retval = self.fun(*self.args, **self.kwargs) - retvalf.write(marshal.dumps(retval)) - retvalf.close() - - def parent(self): - stdoutlock = thread.allocate_lock() - stdoutlock.acquire() - stderrlock = thread.allocate_lock() - stderrlock.acquire() - - def readsome(name, field, lock): - fd = open(name, "r") - setattr(self, field, fd.read()) - fd.close() - lock.release() - - thread.start_new_thread(readsome, (self.PYTESTSTDOUT, 'stdoutrepr', stdoutlock)) - thread.start_new_thread(readsome, (self.PYTESTSTDERR, 'stderrrepr', stderrlock)) - - retval = open(self.PYTESTRETVAL, "r") - pid, exitstat = os.wait() - self.signal = exitstat & 0x7f - self.exitstat = exitstat & 0xff00 - - if not exitstat: - retval_data = retval.read() - self.retval = marshal.loads(retval_data) - retval.close() - else: - self.retval = None - - stdoutlock.acquire() - stdoutlock.release() - stderrlock.acquire() - stderrlock.release() - - self.clear() - return self.stdoutrepr, self.stderrrepr - - def clear(self): - try: - os.unlink(self.PYTESTSTDOUT) - os.unlink(self.PYTESTSTDERR) - os.unlink(self.PYTESTRETVAL) - os.rmdir(self.dirname) - except OSError: - pass class FileBox(object): count = 0 Modified: py/dist/py/test/rsession/rsession.py ============================================================================== --- py/dist/py/test/rsession/rsession.py (original) +++ py/dist/py/test/rsession/rsession.py Sun Nov 5 12:31:05 2006 @@ -4,8 +4,6 @@ import os import py -import thread -import threading import sys import re import time Modified: py/dist/py/test/rsession/slave.py ============================================================================== --- py/dist/py/test/rsession/slave.py (original) +++ py/dist/py/test/rsession/slave.py Sun Nov 5 12:31:05 2006 @@ -56,14 +56,6 @@ 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(): default_options = {'nomagic':False} # XXX should come from somewhere else From arigo at codespeak.net Sun Nov 5 16:05:00 2006 From: arigo at codespeak.net (arigo at codespeak.net) Date: Sun, 5 Nov 2006 16:05:00 +0100 (CET) Subject: [py-svn] r34242 - py/dist/py/c-extension/greenlet Message-ID: <20061105150500.BB28210082@code0.codespeak.net> Author: arigo Date: Sun Nov 5 16:04:58 2006 New Revision: 34242 Modified: py/dist/py/c-extension/greenlet/greenlet.c Log: (tismer, arigo) A greenlet bug, as shown by an assert. On Linux the bug silently does nothing, but on Windows we get a crash. Modified: py/dist/py/c-extension/greenlet/greenlet.c ============================================================================== --- py/dist/py/c-extension/greenlet/greenlet.c (original) +++ py/dist/py/c-extension/greenlet/greenlet.c Sun Nov 5 16:04:58 2006 @@ -177,6 +177,7 @@ */ long sz1 = g->stack_saved; long sz2 = stop - g->stack_start; + assert(g->stack_start != NULL); if (sz2 > sz1) { char* c = PyMem_Realloc(g->stack_copy, sz2); if (!c) { @@ -378,7 +379,10 @@ /* start the greenlet */ ts_target->stack_start = NULL; ts_target->stack_stop = (char*) mark; - ts_target->stack_prev = ts_current; + if (ts_current->stack_start == NULL) /* ts_current is dying */ + ts_target->stack_prev = ts_current->stack_prev; + else + ts_target->stack_prev = ts_current; ts_target->top_frame = NULL; ts_target->recursion_depth = PyThreadState_GET()->recursion_depth; err = _PyGreen_switchstack(); From mwh at codespeak.net Sun Nov 5 16:38:12 2006 From: mwh at codespeak.net (mwh at codespeak.net) Date: Sun, 5 Nov 2006 16:38:12 +0100 (CET) Subject: [py-svn] r34244 - in py/dist/py: . documentation xmlobj xmlobj/testing Message-ID: <20061105153812.EE4A41007C@code0.codespeak.net> Author: mwh Date: Sun Nov 5 16:38:10 2006 New Revision: 34244 Modified: py/dist/py/__init__.py py/dist/py/documentation/confrest.py py/dist/py/xmlobj/testing/test_xml.py py/dist/py/xmlobj/visit.py py/dist/py/xmlobj/xml.py Log: add a py.xml.raw() object that can include html/xml directly in a py.xml.Tag() (i.e. without any escaping). use this in confrest.Project.process, which will hopefully unbreak http://codespeak.net/py/current/doc/ (my first py lib checkin, i think :-) (guido: this was your fault) Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Sun Nov 5 16:38:10 2006 @@ -109,6 +109,7 @@ # small and mean xml/html generation 'xml.html' : ('./xmlobj/html.py', 'html'), 'xml.Tag' : ('./xmlobj/xml.py', 'Tag'), + 'xml.raw' : ('./xmlobj/xml.py', 'raw'), 'xml.Namespace' : ('./xmlobj/xml.py', 'Namespace'), 'xml.escape' : ('./xmlobj/misc.py', 'escape'), Modified: py/dist/py/documentation/confrest.py ============================================================================== --- py/dist/py/documentation/confrest.py (original) +++ py/dist/py/documentation/confrest.py Sun Nov 5 16:38:10 2006 @@ -116,7 +116,7 @@ html.div(html.div(modified, style="float: right; font-style: italic;"), id = 'docinfoline')) - page.contentspace.append(content) + page.contentspace.append(py.xml.raw(content)) htmlpath = txtpath.new(ext='.html') htmlpath.write(page.unicode().encode(encoding)) 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 Sun Nov 5 16:38:10 2006 @@ -51,3 +51,7 @@ u = unicode(x) assert u == '' +def test_raw(): + x = ns.some(py.xml.raw("

literal

")) + u = unicode(x) + assert u == "

literal

" Modified: py/dist/py/xmlobj/visit.py ============================================================================== --- py/dist/py/xmlobj/visit.py (original) +++ py/dist/py/xmlobj/visit.py Sun Nov 5 16:38:10 2006 @@ -34,6 +34,9 @@ #self.write(obj) self.write(escape(unicode(obj))) + def raw(self, obj): + self.write(obj.uniobj) + def list(self, obj): assert id(obj) not in self.visited self.visited[id(obj)] = 1 Modified: py/dist/py/xmlobj/xml.py ============================================================================== --- py/dist/py/xmlobj/xml.py (original) +++ py/dist/py/xmlobj/xml.py Sun Nov 5 16:38:10 2006 @@ -24,6 +24,12 @@ name = self.__class__.__name__ return "<%r tag object %d>" % (name, id(self)) +class raw(object): + """just a box that can contain a unicode string that will be + included directly in the output""" + def __init__(self, uniobj): + self.uniobj = uniobj + # the generic xml namespace # provides Tag classes on the fly optionally checking for # a tagspecification From fijal at codespeak.net Thu Nov 9 15:19:39 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Thu, 9 Nov 2006 15:19:39 +0100 (CET) Subject: [py-svn] r34413 - py/dist/py/test Message-ID: <20061109141939.9501C10070@code0.codespeak.net> Author: fijal Date: Thu Nov 9 15:19:35 2006 New Revision: 34413 Modified: py/dist/py/test/config.py Log: Improved caching of configs. Modified: py/dist/py/test/config.py ============================================================================== --- py/dist/py/test/config.py (original) +++ py/dist/py/test/config.py Thu Nov 9 15:19:35 2006 @@ -172,19 +172,26 @@ Config._reset() return config +_config_paths_cache = {} def guessconfigpaths(*paths): """ return test configuration paths from skimming the args. """ + key = tuple(paths) + try: + return _config_paths_cache[key] + except KeyError: + pass d = {} l = [] - for anchor in paths: - if anchor: + for anchor in paths: + if anchor: for p in anchor.parts(): x = p.join(configbasename) if x not in d and x.check(file=1): d[x] = True l.append(x) l.reverse() + _config_paths_cache[key] = l return l def getanchorpaths(args): @@ -199,7 +206,7 @@ l = [current] return l -def importconfig(configpath): +def importconfig(configpath): if not configpath.dirpath('__init__.py').check(file=1): # HACK: we don't want a "globally" imported conftest.py, # prone to conflicts and subtle problems From fijal at codespeak.net Thu Nov 9 15:20:09 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Thu, 9 Nov 2006 15:20:09 +0100 (CET) Subject: [py-svn] r34414 - py/dist/py/path/local Message-ID: <20061109142009.73A8710070@code0.codespeak.net> Author: fijal Date: Thu Nov 9 15:20:05 2006 New Revision: 34414 Modified: py/dist/py/path/local/local.py Log: Make pyimport cache modules. This speeds up py.test --collectonly about 3 times. Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Thu Nov 9 15:20:05 2006 @@ -19,6 +19,8 @@ """ Local path implementation offering access/modification methods similar to os.path. """ + _path_cache = {} + sep = os.sep class Checkers(common.FSCheckers): def _stat(self): @@ -53,6 +55,10 @@ Note also that passing in a local path object will simply return the exact same path object. Use new() to get a new copy. """ + #try: + # return cls._path_cache[path] + #except KeyError: + # pass if isinstance(path, common.FSPathBase): if path.__class__ == cls: return path @@ -68,6 +74,7 @@ "can only pass None, Path instances " "or non-empty strings to LocalPath") assert isinstance(self.strpath, str) + #cls._path_cache[path] = self return self def __hash__(self): @@ -120,7 +127,7 @@ obj.strpath = os.path.normpath( "%(drive)s%(dirname)s%(sep)s%(basename)s" % kw) return obj - + def _getbyspec(self, spec): """ return a sequence of specified path parts. 'spec' is a comma separated string containing path part names. @@ -374,6 +381,11 @@ #print "trying to import", self pkgpath = None if modname is None: + if modname is None: + try: + return self.module + except AttributeError: + pass pkgpath = self.pypkgpath() if pkgpath is not None: if ensuresyspath: @@ -394,7 +406,9 @@ if ensuresyspath: self._prependsyspath(self.dirpath()) modname = self.purebasename - return __import__(modname, None, None, ['__doc__']) + mod = __import__(modname, None, None, ['__doc__']) + self.module = mod + return mod else: try: return sys.modules[modname] From fijal at codespeak.net Thu Nov 9 15:25:39 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Thu, 9 Nov 2006 15:25:39 +0100 (CET) Subject: [py-svn] r34415 - py/dist/py/path/local Message-ID: <20061109142539.7399B10050@code0.codespeak.net> Author: fijal Date: Thu Nov 9 15:25:34 2006 New Revision: 34415 Modified: py/dist/py/path/local/local.py Log: Removed debug code (commented anyway). Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Thu Nov 9 15:25:34 2006 @@ -55,10 +55,6 @@ Note also that passing in a local path object will simply return the exact same path object. Use new() to get a new copy. """ - #try: - # return cls._path_cache[path] - #except KeyError: - # pass if isinstance(path, common.FSPathBase): if path.__class__ == cls: return path @@ -74,7 +70,6 @@ "can only pass None, Path instances " "or non-empty strings to LocalPath") assert isinstance(self.strpath, str) - #cls._path_cache[path] = self return self def __hash__(self): From fijal at codespeak.net Thu Nov 9 16:36:10 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Thu, 9 Nov 2006 16:36:10 +0100 (CET) Subject: [py-svn] r34418 - in py/dist/py/test/rsession: . testing Message-ID: <20061109153610.781761008D@code0.codespeak.net> Author: fijal Date: Thu Nov 9 16:36:07 2006 New Revision: 34418 Modified: py/dist/py/test/rsession/conftest.py py/dist/py/test/rsession/hostmanage.py py/dist/py/test/rsession/rsession.py py/dist/py/test/rsession/testing/test_rsession.py Log: Refactored hostmanage a bit, fixing the tests, so they don't need to be run over Ssh. Modified: py/dist/py/test/rsession/conftest.py ============================================================================== --- py/dist/py/test/rsession/conftest.py (original) +++ py/dist/py/test/rsession/conftest.py Thu Nov 9 16:36:07 2006 @@ -4,9 +4,9 @@ defaultwait = 100.0 option = py.test.Config.addoptions("distributed testing options", - Option('-D', '--disthosts', - action="store", dest="disthosts", default=None, - help="comma separated list of testhosts"), +# Option('-D', '--disthosts', +# action="store", dest="disthosts", default=None, +# help="comma separated list of testhosts"), Option('', '--waittime', action="store", dest="waittime", default=defaultwait, help="How long (in seconds) to wait for hanging nodes" Modified: py/dist/py/test/rsession/hostmanage.py ============================================================================== --- py/dist/py/test/rsession/hostmanage.py (original) +++ py/dist/py/test/rsession/hostmanage.py Thu Nov 9 16:36:07 2006 @@ -27,24 +27,10 @@ else: return base in self.rsync_roots - -def init_hosts(reporter, sshhosts, relpath, pkgdir, rsync_roots=None, \ - remote_python=None, remote_options={}): - assert pkgdir.join("__init__.py").check(), ( - "%s probably wrong" %(pkgdir,)) - assert relpath, relpath - - nodes = [] - exc_info = [None] +def prepare_gateway(sshosts, relpath, rsync_roots, optimise_localhost, remote_python): hosts = [] - - for host in sshhosts: - - if host != 'localhost': - if remote_python is None: - gw = py.execnet.SshGateway(host) - else: - gw = py.execnet.SshGateway(host, remotepython=remote_python) + for host in sshosts: + if host != 'localhost' or not optimise_localhost: if isinstance(relpath, str): assert not os.path.isabs(relpath), relpath remoterootpath = relpath @@ -53,7 +39,18 @@ # XXX: because of NFS we do create different directories # otherwise, .pyc files overlap remoterootpath += "-" + host - + # for tests we want to use somtehing different + if host == 'localhost' and optimise_localhost is False: + from py.__.execnet.register import PopenCmdGateway + gw = PopenCmdGateway("cd ~/%s; python -u -c 'exec input()'" % remoterootpath) + if not remoterootpath.startswith("/"): + remoteroopath = "~/" + remoterootpath + else: + if remote_python is None: + gw = py.execnet.SshGateway(host) + else: + gw = py.execnet.SshGateway(host, remotepython=remote_python) + hosts.append((host, gw, remoterootpath)) else: if remote_python is None: @@ -62,9 +59,24 @@ gw = py.execnet.PopenGateway(remotepython=remote_python) gw.sshaddress = 'localhost' hosts.append((host, gw, str(pkgdir.dirpath()))) + return hosts +def init_hosts(reporter, sshhosts, relpath, pkgdir, rsync_roots=None, \ + remote_python=None, remote_options={}, optimise_localhost=True): + assert pkgdir.join("__init__.py").check(), ( + "%s probably wrong" %(pkgdir,)) + assert relpath, relpath + + nodes = [] + exc_info = [None] + hosts = prepare_gateway(sshhosts, relpath, rsync_roots, optimise_localhost, + remote_python) + # rsyncing - rsynced = {'localhost':True} + if optimise_localhost: + rsynced = {'localhost':True} + else: + rsynced = {} rsync = HostRSync(rsync_roots) for host, gw, remoterootpath in hosts: Modified: py/dist/py/test/rsession/rsession.py ============================================================================== --- py/dist/py/test/rsession/rsession.py (original) +++ py/dist/py/test/rsession/rsession.py Thu Nov 9 16:36:07 2006 @@ -36,8 +36,9 @@ An abstract session executes collectors/items through a runner. """ - def __init__(self, config): + def __init__(self, config, optimise_localhost=True): self.config = config + self.optimise_localhost = optimise_localhost def make_colitems(paths, baseon): # we presume that from the base we can simply get to @@ -139,7 +140,8 @@ remote_options['nomagic'] = self.config.option.nomagic nodes = init_hosts(reporter, sshhosts, directories, pkgdir, - rsync_roots, remotepython, remote_options=remote_options.d) + rsync_roots, remotepython, remote_options=remote_options.d, + optimise_localhost=self.optimise_localhost) reporter(report.RsyncFinished()) keyword = self.config.option.keyword 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 Thu Nov 9 16:36:07 2006 @@ -69,11 +69,11 @@ assert str(events[1][0].value) == "Reason" class TestWithRealSshHosts: - def setup_class(cls): - from py.__.test.rsession.conftest import option - if not option.disthosts: - py.test.skip("no test distribution ssh hosts specified") - cls.hosts = option.disthosts.split(",") + #def setup_class(cls): + # from py.__.test.rsession.conftest import option + # if not option.disthosts: + # py.test.skip("no test distribution ssh hosts specified") + # cls.hosts = option.disthosts.split(",") ## def test_rsync_does_what_it_should(self): ## host = self.hosts[0] @@ -112,9 +112,9 @@ # XXX find a better way for the below tmpdir = py.path.local(py.__file__).dirpath().dirpath() tmpdir.ensure("sub", "conftest.py").write(py.code.Source(""" - disthosts = %r + disthosts = [%r] distrsync_roots = ["sub", "py"] - """ % self.hosts[:1])) + """ % 'localhost')) tmpdir.ensure("sub", "__init__.py") tmpdir.ensure("sub", "test_one.py").write(py.code.Source(""" def test_1(): @@ -128,7 +128,7 @@ """)) args = [str(tmpdir.join("sub"))] config, args = py.test.Config.parse(args) - rsession = RSession(config) + rsession = RSession(config, optimise_localhost=False) allevents = [] rsession.main(args, reporter=allevents.append) testevents = [x for x in allevents @@ -154,21 +154,21 @@ assert tb[0].source.find("execute") != -1 def test_setup_teardown_ssh(self): - hosts = self.hosts + hosts = ['localhost'] setup_events = [] teardown_events = [] nodes = init_hosts(setup_events.append, hosts, 'pytesttest', pkgdir, - rsync_roots=["py"]) + rsync_roots=["py"], optimise_localhost=False) teardown_hosts(teardown_events.append, [node.channel for node in nodes], nodes) count_rsyn_calls = [i for i in setup_events if isinstance(i, report.HostRSyncing)] - assert len(count_rsyn_calls) == len([i for i in hosts if i != 'localhost']) + assert len(count_rsyn_calls) == len([i for i in hosts]) count_ready_calls = [i for i in setup_events if isinstance(i, report.HostReady)] - assert len(count_ready_calls) == len([i for i in hosts if i != 'localhost']) + assert len(count_ready_calls) == len([i for i in hosts]) # same for teardown events teardown_wait_starts = [i for i in teardown_events @@ -179,11 +179,11 @@ assert len(teardown_wait_ends) == len(hosts) def test_setup_teardown_run_ssh(self): - hosts = self.hosts + hosts = ['localhost'] allevents = [] nodes = init_hosts(allevents.append, hosts, 'pytesttest', pkgdir, - rsync_roots=["py"]) + rsync_roots=["py"], optimise_localhost=False) from py.__.test.rsession.testing.test_executor \ import ItemTestPassing, ItemTestFailing, ItemTestSkipping @@ -220,9 +220,10 @@ """ Tests options object passing master -> server """ allevents = [] - hosts = self.hosts + hosts = ['localhost'] nodes = init_hosts(allevents.append, hosts, 'pytesttest', pkgdir, - rsync_roots=["py"], remote_options={'custom':'custom'}) + rsync_roots=["py"], remote_options={'custom':'custom'}, + optimise_localhost=False) rootcol = py.test.collect.Directory(pkgdir.dirpath()) itempass = rootcol.getitembynames(funcoption_spec) From fijal at codespeak.net Thu Nov 9 17:24:40 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Thu, 9 Nov 2006 17:24:40 +0100 (CET) Subject: [py-svn] r34424 - in py/dist/py/test/rsession: . testing Message-ID: <20061109162440.5D80D10094@code0.codespeak.net> Author: fijal Date: Thu Nov 9 17:24:38 2006 New Revision: 34424 Modified: py/dist/py/test/rsession/reporter.py py/dist/py/test/rsession/testing/test_reporter.py Log: Reporter tests refactoring. Now FailedTryiter works for both reporters (and is tested) Modified: py/dist/py/test/rsession/reporter.py ============================================================================== --- py/dist/py/test/rsession/reporter.py (original) +++ py/dist/py/test/rsession/reporter.py Thu Nov 9 17:24:38 2006 @@ -11,6 +11,7 @@ from py.__.test.terminal.out import getout from py.__.test.rsession import report +from py.__.test.rsession import outcome class AbstractReporter(object): def __init__(self, config, hosts, pkgdir=py.path.local(py.__file__)): @@ -75,7 +76,8 @@ self.timeend = item.timeend self.skips() self.failures() - self.hangs() + if hasattr(self, 'nodes'): # XXX: Testing + self.hangs() self.summary() def hangs(self): @@ -91,13 +93,18 @@ 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) + if isinstance(event, report.ReceivedItemOutcome): + 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) else: - self.repr_failure(event.item, event.outcome) + self.out.sep('_', " ".join(event.item.listnames())) + out = outcome.Outcome(excinfo=event.excinfo) + self.repr_failure(event.item, outcome.ReprOutcome(out.make_repr())) def repr_failure(self, item, outcome): excinfo = outcome.excinfo @@ -221,14 +228,18 @@ class RemoteReporter(AbstractReporter): def get_host(self, item): - return item.channel.gateway.sshaddress + if item.channel: + return item.channel.gateway.sshaddress + # XXX: Testing purposes only + return 'localhost' def get_item_name(self, event, colitem): - return event.channel.gateway.sshaddress + ":" + \ + return self.get_host(event) + ":" + \ "/".join(colitem.listnames()) - + def report_FailedTryiter(self, event): self.out.line("FAILED TO LOAD MODULE: %s\n" % "/".join(event.item.listnames())) + self.failed_tests_outcome.append(event) def report_SkippedTryiter(self, event): self.out.line("Skipped (%s) %s\n" % (str(event.excinfo.value), "/". @@ -248,6 +259,7 @@ def report_FailedTryiter(self, event): #self.show_item(event.item, False) self.out.write("- FAILED TO LOAD MODULE") + self.failed_tests_outcome.append(event) def report_ReceivedItemOutcome(self, event): if event.outcome.passed: 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 Nov 9 17:24:38 2006 @@ -4,8 +4,9 @@ """ import py, os -from py.__.test.rsession.rsession import LocalReporter, AbstractSession -from py.__.test.rsession.report import ReceivedItemOutcome, ItemStart +from py.__.test.rsession.rsession import LocalReporter, AbstractSession,\ + RemoteReporter +from py.__.test.rsession import report from py.__.test.rsession.outcome import ReprOutcome, Outcome from py.__.test.rsession.testing.test_slave import funcpass_spec, mod_spec from py.__.test.rsession.box import Box @@ -16,7 +17,7 @@ def setup_module(mod): mod.pkgdir = py.path.local(py.__file__).dirpath() -class TestReporter(object): +class AbstractTestReporter(object): def prepare_outcomes(self): # possible outcomes try: @@ -35,7 +36,7 @@ return outcomes - def test_report_received_item_outcome(self): + def report_received_item_outcome(self): config, args = py.test.Config.parse(["some_sub"]) # we just go... rootcol = py.test.collect.Directory(pkgdir.dirpath()) @@ -43,19 +44,19 @@ outcomes = self.prepare_outcomes() def boxfun(config, item, outcomes): - r = LocalReporter(config, ["localhost"]) + r = self.reporter(config, ["localhost"]) for outcome in outcomes: - r.report(ReceivedItemOutcome(None, item, outcome)) + r.report(report.ReceivedItemOutcome(None, item, outcome)) s = StringIO() stdoutcopy = sys.stdout sys.stdout = s boxfun(config, item, outcomes) - sys.stdoud = stdoutcopy + sys.stdout = stdoutcopy - assert s.getvalue() == 'FsF.' + return s.getvalue() - def test_module(self): + def _test_module(self): config, args = py.test.Config.parse(["some_sub"]) # we just go... rootcol = py.test.collect.Directory(pkgdir.dirpath()) @@ -64,22 +65,22 @@ outcomes = self.prepare_outcomes() def boxfun(pkgdir, config, item, funcitem, outcomes): - r = LocalReporter(config, ["localhost"]) + r = self.reporter(config, ["localhost"]) #r.pkgdir = pkdgir - r.report(ItemStart(item)) + r.report(report.ItemStart(item)) for outcome in outcomes: - r.report(ReceivedItemOutcome(None, funcitem, outcome)) + r.report(report.ReceivedItemOutcome(None, funcitem, outcome)) s = StringIO() stdoutcopy = sys.stdout sys.stdout = s boxfun(pkgdir, config, moditem, funcitem, outcomes) - sys.stdoud = stdoutcopy + sys.stdout = stdoutcopy + + return s.getvalue() - assert s.getvalue().endswith("test_slave.py[8] FsF."),\ - s.getvalue() - def test_full_module(self): + def _test_full_module(self): tmpdir = py.test.ensuretemp("repmod") tmpdir.ensure("__init__.py") tmpdir.ensure("test_one.py").write(py.code.Source(""" @@ -97,7 +98,7 @@ def boxfun(): config, args = py.test.Config.parse([str(tmpdir)]) rootcol = py.test.collect.Directory(tmpdir) - r = LocalReporter(config, ["localhost"]) + r = self.reporter(config, ["localhost"]) list(rootcol.tryiter(reporterror=lambda x : AbstractSession.reporterror(r.report, x))) #b = Box(boxfun) @@ -106,9 +107,71 @@ stdoutcopy = sys.stdout sys.stdout = s boxfun() - sys.stdoud = stdoutcopy + sys.stdout = stdoutcopy - assert s.getvalue() == """ + return s.getvalue() + + def test_failed_to_load(self): + tmpdir = py.test.ensuretemp("failedtoload") + tmpdir.ensure("__init__.py") + 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 = self.reporter(config, ["localhost"]) + r.report(report.TestStarted(['localhost'])) + r.report(report.RsyncFinished()) + list(rootcol.tryiter(reporterror=lambda x : AbstractSession.reporterror(r.report, x))) + r.report(report.TestFinished()) + + s = StringIO() + stdoutcopy = sys.stdout + sys.stdout = s + boxfun() + sys.stdout = stdoutcopy + assert s.getvalue().find("NameError: name 'sadsadsa' is not defined") != -1 + +class TestLocalReporter(AbstractTestReporter): + reporter = LocalReporter + + def test_report_received_item_outcome(self): + assert self.report_received_item_outcome() == 'FsF.' + + def test_module(self): + assert self._test_module().endswith("test_slave.py[8] FsF."),\ + self._test_module() + + def test_full_module(self): + assert self._test_full_module() == """ repmod/test_one.py[1] repmod/test_three.py[0] - FAILED TO LOAD MODULE repmod/test_two.py[0] - skipped (reason)""" + +class TestRemoteReporter(AbstractTestReporter): + reporter = RemoteReporter + + def test_report_received_item_outcome(self): + val = self.report_received_item_outcome() + expected = """ localhost: FAILED py test rsession testing test_slave.py funcpass + localhost: SKIPPED py test rsession testing test_slave.py funcpass + localhost: FAILED py test rsession testing test_slave.py funcpass + localhost: PASSED py test rsession testing test_slave.py funcpass +""" + assert val == expected + + def test_module(self): + val = self._test_module() + print val + expected = """ localhost: FAILED py test rsession testing test_slave.py funcpass + localhost: SKIPPED py test rsession testing test_slave.py funcpass + localhost: FAILED py test rsession testing test_slave.py funcpass + localhost: PASSED py test rsession testing test_slave.py funcpass +""" + assert val == expected + + def test_full_module(self): + val = self._test_full_module() + assert val == 'FAILED TO LOAD MODULE: repmod/test_three.py\n'\ + '\nSkipped (reason) repmod/test_two.py\n\n' From guido at codespeak.net Fri Nov 10 11:33:37 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Fri, 10 Nov 2006 11:33:37 +0100 (CET) Subject: [py-svn] r34451 - in py/dist/py/test/rsession: . testing webdata Message-ID: <20061110103337.C9C93100BE@code0.codespeak.net> Author: guido Date: Fri Nov 10 11:33:34 2006 New Revision: 34451 Added: py/dist/py/test/rsession/testing/test_webjs.py Modified: py/dist/py/test/rsession/webdata/index.html py/dist/py/test/rsession/webdata/source.js py/dist/py/test/rsession/webjs.py Log: Added first tests for code that is translated to JS (using the mock DOM API from PyPy), added missing end tag in index.html. Added: py/dist/py/test/rsession/testing/test_webjs.py ============================================================================== --- (empty file) +++ py/dist/py/test/rsession/testing/test_webjs.py Fri Nov 10 11:33:34 2006 @@ -0,0 +1,28 @@ +import py +try: + import pypy +except ImportError: + py.test.skip('missing PyPy') +from pypy.translator.js.modules import dom +from py.__.test.rsession import webjs + +here = py.magic.autopath().dirpath() + +def setup_module(mod): + # load HTML into window object + html = here.join('../webdata/index.html').read() + dom.window = dom.Window(html) + +def test_html_loaded(): + body = dom.window.document.getElementsByTagName('body')[0] + assert len(body.childNodes) > 0 + assert str(body.childNodes[1].nodeName) == 'A' + +def test_set_msgbox(): + msgbox = dom.window.document.getElementById('messagebox') + assert len(msgbox.childNodes) == 0 + webjs.set_msgbox('foo', 'bar') + assert len(msgbox.childNodes) == 1 + assert msgbox.childNodes[0].nodeName == 'PRE' + assert msgbox.childNodes[0].childNodes[0].nodeValue == 'foo\nbar' + 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 Fri Nov 10 11:33:34 2006 @@ -2,6 +2,7 @@ Py.test 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 Fri Nov 10 11:33:34 2006 @@ -5,7 +5,7 @@ import py from py.__.test.rsession.web import exported_methods try: - from pypy.translator.js.modules import _dom as dom + from pypy.translator.js.modules import dom from pypy.translator.js.helper import __show_traceback from pypy.translator.transformer.debug import traceback_handler except ImportError: From fijal at codespeak.net Sat Nov 11 14:03:57 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Sat, 11 Nov 2006 14:03:57 +0100 (CET) Subject: [py-svn] r34484 - py/dist/py/test/rsession Message-ID: <20061111130357.1970410105@code0.codespeak.net> Author: fijal Date: Sat Nov 11 14:03:56 2006 New Revision: 34484 Modified: py/dist/py/test/rsession/hostmanage.py py/dist/py/test/rsession/web.py Log: Ooops, forgotten checkin. Modified: py/dist/py/test/rsession/hostmanage.py ============================================================================== --- py/dist/py/test/rsession/hostmanage.py (original) +++ py/dist/py/test/rsession/hostmanage.py Sat Nov 11 14:03:56 2006 @@ -27,7 +27,7 @@ else: return base in self.rsync_roots -def prepare_gateway(sshosts, relpath, rsync_roots, optimise_localhost, remote_python): +def prepare_gateway(sshosts, relpath, rsync_roots, optimise_localhost, remote_python, pkgdir): hosts = [] for host in sshosts: if host != 'localhost' or not optimise_localhost: @@ -70,7 +70,7 @@ nodes = [] exc_info = [None] hosts = prepare_gateway(sshhosts, relpath, rsync_roots, optimise_localhost, - remote_python) + remote_python, pkgdir) # rsyncing if optimise_localhost: Modified: py/dist/py/test/rsession/web.py ============================================================================== --- py/dist/py/test/rsession/web.py (original) +++ py/dist/py/test/rsession/web.py Sat Nov 11 14:03:56 2006 @@ -33,7 +33,7 @@ from pypy.translator.js.main import rpython2javascript, Options from pypy.translator.js import commproxy - Options.debug_transform = True + Options.debug_transform = False commproxy.USE_MOCHIKIT = False IMPORTED_PYPY = True except ImportError: From fijal at codespeak.net Sat Nov 11 14:30:36 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Sat, 11 Nov 2006 14:30:36 +0100 (CET) Subject: [py-svn] r34487 - py/dist/py/test/rsession Message-ID: <20061111133036.243CE1010D@code0.codespeak.net> Author: fijal Date: Sat Nov 11 14:30:34 2006 New Revision: 34487 Modified: py/dist/py/test/rsession/hostmanage.py py/dist/py/test/rsession/rsession.py py/dist/py/test/rsession/slave.py Log: Make easier teardown when using -x. Modified: py/dist/py/test/rsession/hostmanage.py ============================================================================== --- py/dist/py/test/rsession/hostmanage.py (original) +++ py/dist/py/test/rsession/hostmanage.py Sat Nov 11 14:30:34 2006 @@ -97,11 +97,12 @@ return nodes -def teardown_hosts(reporter, channels, nodes, waiter=lambda : time.sleep(.1)): +def teardown_hosts(reporter, channels, nodes, waiter=lambda : time.sleep(.1), + exitfirst=False): for channel in channels: channel.send(None) - clean = False + clean = exitfirst while not clean: clean = True for node in nodes: Modified: py/dist/py/test/rsession/rsession.py ============================================================================== --- py/dist/py/test/rsession/rsession.py (original) +++ py/dist/py/test/rsession/rsession.py Sat Nov 11 14:30:34 2006 @@ -139,6 +139,7 @@ remotepython = None remote_options['nomagic'] = self.config.option.nomagic + remote_options['exitfirst'] = self.config.option.exitfirst nodes = init_hosts(reporter, sshhosts, directories, pkgdir, rsync_roots, remotepython, remote_options=remote_options.d, optimise_localhost=self.optimise_localhost) @@ -155,7 +156,8 @@ #assert 0, "\n".join([",".join(x.listnames()) for x in # list(itemgenerator)]) dispatch_loop(nodes, itemgenerator, checkfun) - teardown_hosts(reporter, [node.channel for node in nodes], nodes) + teardown_hosts(reporter, [node.channel for node in nodes], nodes, + exitfirst=self.config.option.exitfirst) reporter(report.Nodes(nodes)) reporter(report.TestFinished()) if startserverflag: Modified: py/dist/py/test/rsession/slave.py ============================================================================== --- py/dist/py/test/rsession/slave.py (original) +++ py/dist/py/test/rsession/slave.py Sat Nov 11 14:30:34 2006 @@ -54,8 +54,14 @@ excinfo = py.code.ExceptionInfo() send(Outcome(excinfo=excinfo, is_critical=True).make_repr()) else: + if not res[0] and py.test.remote.exitfirst: + # we're finished, but need to eat what we can + send(res) + break send(res) - + + while nextitem is not None: + nextitem = receive() def setup(): default_options = {'nomagic':False} # XXX should come from somewhere else From fijal at codespeak.net Sat Nov 11 20:42:02 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Sat, 11 Nov 2006 20:42:02 +0100 (CET) Subject: [py-svn] r34506 - in py/dist/py/test/rsession: . testing Message-ID: <20061111194202.2F6D91011E@code0.codespeak.net> Author: fijal Date: Sat Nov 11 20:42:00 2006 New Revision: 34506 Modified: py/dist/py/test/rsession/rsession.py py/dist/py/test/rsession/testing/test_rsession.py Log: Test for -x on RSession. Modified: py/dist/py/test/rsession/rsession.py ============================================================================== --- py/dist/py/test/rsession/rsession.py (original) +++ py/dist/py/test/rsession/rsession.py Sat Nov 11 20:42:00 2006 @@ -138,10 +138,11 @@ except: remotepython = None - remote_options['nomagic'] = self.config.option.nomagic - remote_options['exitfirst'] = self.config.option.exitfirst + rem_options = RemoteOptions({}) + rem_options['nomagic'] = self.config.option.nomagic + rem_options['exitfirst'] = self.config.option.exitfirst nodes = init_hosts(reporter, sshhosts, directories, pkgdir, - rsync_roots, remotepython, remote_options=remote_options.d, + rsync_roots, remotepython, remote_options=rem_options.d, optimise_localhost=self.optimise_localhost) reporter(report.RsyncFinished()) 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 Sat Nov 11 20:42:00 2006 @@ -107,7 +107,34 @@ ## channel.send(None) ## res = channel.receive() ## assert res == "ok" - + + def test_example_distribution_minus_x(self): + # XXX find a better way for the below + tmpdir = py.path.local(py.__file__).dirpath().dirpath() + tmpdir.ensure("sub", "conftest.py").write(py.code.Source(""" + disthosts = [%r] + distrsync_roots = ["sub", "py"] + """ % 'localhost')) + 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")), "-x"] + config, args = py.test.Config.parse(args) + rsession = RSession(config) + allevents = [] + rsession.main(args, reporter=allevents.append) + testevents = [x for x in allevents + if isinstance(x, report.ReceivedItemOutcome)] + assert len(testevents) == 2 + def test_example_distribution(self): # XXX find a better way for the below tmpdir = py.path.local(py.__file__).dirpath().dirpath() @@ -159,7 +186,7 @@ teardown_events = [] nodes = init_hosts(setup_events.append, hosts, 'pytesttest', pkgdir, - rsync_roots=["py"], optimise_localhost=False) + rsync_roots=["py"], optimise_localhost=False, remote_options={'exitfirst':False}) teardown_hosts(teardown_events.append, [node.channel for node in nodes], nodes) @@ -183,7 +210,7 @@ allevents = [] nodes = init_hosts(allevents.append, hosts, 'pytesttest', pkgdir, - rsync_roots=["py"], optimise_localhost=False) + rsync_roots=["py"], optimise_localhost=False, remote_options={'exitfirst':False}) from py.__.test.rsession.testing.test_executor \ import ItemTestPassing, ItemTestFailing, ItemTestSkipping @@ -222,7 +249,7 @@ allevents = [] hosts = ['localhost'] nodes = init_hosts(allevents.append, hosts, 'pytesttest', pkgdir, - rsync_roots=["py"], remote_options={'custom':'custom'}, + rsync_roots=["py"], remote_options={'custom':'custom', 'exitfirst':False}, optimise_localhost=False) rootcol = py.test.collect.Directory(pkgdir.dirpath()) From fijal at codespeak.net Sat Nov 11 21:08:05 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Sat, 11 Nov 2006 21:08:05 +0100 (CET) Subject: [py-svn] r34510 - py/dist/py/test/rsession Message-ID: <20061111200805.B7EB010130@code0.codespeak.net> Author: fijal Date: Sat Nov 11 21:08:04 2006 New Revision: 34510 Modified: py/dist/py/test/rsession/rsession.py Log: Ooops, forgotten to commit that one. Modified: py/dist/py/test/rsession/rsession.py ============================================================================== --- py/dist/py/test/rsession/rsession.py (original) +++ py/dist/py/test/rsession/rsession.py Sat Nov 11 21:08:04 2006 @@ -29,7 +29,8 @@ def __setitem__(self, item, val): self.d[item] = val -remote_options = RemoteOptions({'we_are_remote':False}) +# XXX: Must be initialised somehow +remote_options = RemoteOptions({'we_are_remote':False, 'exitfirst':False}) class AbstractSession(object): """ From fijal at codespeak.net Sun Nov 12 00:25:20 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Sun, 12 Nov 2006 00:25:20 +0100 (CET) Subject: [py-svn] r34517 - py/dist/py/test Message-ID: <20061111232520.3009D10135@code0.codespeak.net> Author: fijal Date: Sun Nov 12 00:25:18 2006 New Revision: 34517 Modified: py/dist/py/test/config.py Log: Deholgerizing. Modified: py/dist/py/test/config.py ============================================================================== --- py/dist/py/test/config.py (original) +++ py/dist/py/test/config.py Sun Nov 12 00:25:18 2006 @@ -162,7 +162,7 @@ # access to the resulting config values? config = Config._config - # trigger loading conftest files which might add options! + # trigger loading conftest files which might add options! for configpath in configpaths: config.loadconfig(configpath) config.loadconfig(defaultconfig).adddefaultoptions() From fijal at codespeak.net Sun Nov 12 00:27:06 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Sun, 12 Nov 2006 00:27:06 +0100 (CET) Subject: [py-svn] r34518 - in py/dist/py/test/rsession: . testing Message-ID: <20061111232706.5696610136@code0.codespeak.net> Author: fijal Date: Sun Nov 12 00:27:04 2006 New Revision: 34518 Modified: py/dist/py/test/rsession/hostmanage.py py/dist/py/test/rsession/rsession.py py/dist/py/test/rsession/testing/test_rsession.py Log: Major refactoring of rsession rsync attempt. Now you can specify multiple times the same host and control how many times you would like to rsync. And test for the above ;-) Modified: py/dist/py/test/rsession/hostmanage.py ============================================================================== --- py/dist/py/test/rsession/hostmanage.py (original) +++ py/dist/py/test/rsession/hostmanage.py Sun Nov 12 00:27:04 2006 @@ -29,13 +29,13 @@ def prepare_gateway(sshosts, relpath, rsync_roots, optimise_localhost, remote_python, pkgdir): hosts = [] - for host in sshosts: + for num, host in enumerate(sshosts): if host != 'localhost' or not optimise_localhost: if isinstance(relpath, str): assert not os.path.isabs(relpath), relpath remoterootpath = relpath else: - remoterootpath = relpath[host] + remoterootpath = relpath[(num, host)] # XXX: because of NFS we do create different directories # otherwise, .pyc files overlap remoterootpath += "-" + host @@ -51,46 +51,53 @@ else: gw = py.execnet.SshGateway(host, remotepython=remote_python) - hosts.append((host, gw, remoterootpath)) + hosts.append((num, host, gw, remoterootpath)) else: 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()))) + hosts.append((num, host, gw, str(pkgdir.dirpath()))) return hosts +# XXX: Options has grown a bit too much, but most of them is just for tests def init_hosts(reporter, sshhosts, relpath, pkgdir, rsync_roots=None, \ - remote_python=None, remote_options={}, optimise_localhost=True): + remote_python=None, remote_options={}, optimise_localhost=True,\ + do_sync=True): assert pkgdir.join("__init__.py").check(), ( "%s probably wrong" %(pkgdir,)) assert relpath, relpath - nodes = [] exc_info = [None] hosts = prepare_gateway(sshhosts, relpath, rsync_roots, optimise_localhost, remote_python, pkgdir) # rsyncing - if optimise_localhost: - rsynced = {'localhost':True} - else: - rsynced = {} - - rsync = HostRSync(rsync_roots) - for host, gw, remoterootpath in hosts: - if host in rsynced: + rsynced = {} + + if do_sync: + rsync = HostRSync(rsync_roots) + for num, host, gw, remoterootpath in hosts: + if (host, remoterootpath) in rsynced or (host == 'localhost' \ + and optimise_localhost): continue - rsynced[host] = True + rsynced[(host, remoterootpath)] = True def done(host=host): reporter(report.HostReady(host)) reporter(report.HostRSyncing(host, remoterootpath)) - rsync.add_target(gw, remoterootpath, done) + if do_sync: + rsync.add_target(gw, remoterootpath, done) + if not do_sync: + return # for testing only rsync.send(pkgdir.dirpath()) # hosts ready - for host, gw, remoterootpath in hosts: + return setup_nodes(hosts, pkgdir, remote_options, reporter) + +def setup_nodes(hosts, pkgdir, remote_options, reporter): + nodes = [] + for num, host, gw, remoterootpath in hosts: ch = setup_slave(gw, os.path.join(remoterootpath, pkgdir.basename), remote_options) nodes.append(MasterNode(ch, reporter)) Modified: py/dist/py/test/rsession/rsession.py ============================================================================== --- py/dist/py/test/rsession/rsession.py (original) +++ py/dist/py/test/rsession/rsession.py Sun Nov 12 00:27:04 2006 @@ -106,6 +106,19 @@ reporter(report.FailedTryiter(excinfo, item)) reporterror = staticmethod(reporterror) +def parse_directories(sshhosts): + # dictionary containing directories for hosts + # XXX: should be class with some info like key, etc. in future + directories = {} + for num, host in enumerate(sshhosts): + m = re.match("^(.*?):(.*)$", host) + if m: + directories[(num, m.group(1))] = m.group(2) + sshhosts[num] = m.group(1) + else: + directories[(num, host)] = "pytestcache" + return directories + class RSession(AbstractSession): """ Remote version of session """ @@ -114,16 +127,7 @@ if not args: args = [py.path.local()] sshhosts = self.config.getinitialvalue("disthosts") - # dictionary containing directories for hosts - # XXX: should be class with some info like key, etc. in future - directories = {} - for num, host in enumerate(sshhosts): - m = re.match("^(.*?):(.*)$", host) - if m: - directories[m.group(1)] = m.group(2) - sshhosts[num] = m.group(1) - else: - directories[host] = "pytestcache" + directories = parse_directories(sshhosts) try: rsync_roots = self.config.getinitialvalue("distrsync_roots") except: @@ -155,8 +159,6 @@ yield y itemgenerator = itemgen() - #assert 0, "\n".join([",".join(x.listnames()) for x in - # list(itemgenerator)]) dispatch_loop(nodes, itemgenerator, checkfun) teardown_hosts(reporter, [node.channel for node in nodes], nodes, exitfirst=self.config.option.exitfirst) 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 Sun Nov 12 00:27:04 2006 @@ -4,7 +4,7 @@ import py from py.__.test.rsession import report -from py.__.test.rsession.rsession import RSession +from py.__.test.rsession.rsession import RSession, parse_directories 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, \ @@ -270,3 +270,39 @@ assert len(passed) == 2 * len(nodes) assert len(skipped) == 0 assert len(events) == len(passed) + +class TestDirectories(object): + def test_simple_parse(self): + sshhosts = ['h1', 'h2', 'h3'] + dirs = parse_directories(sshhosts) + assert len(set(dirs.values())) == 1 + assert sorted(dirs.keys()) == [(0, 'h1'), (1, 'h2'), (2, 'h3')] + + def test_sophisticated_parse(self): + sshhosts = ['a at h1:/tmp', 'h2:tmp', 'h3'] + dirs = parse_directories(sshhosts) + assert sorted(dirs.values()) == ['/tmp', 'pytestcache', 'tmp'] + + def test_parse_multiple_hosts(self): + hosts = ['h1', 'h1', 'h1:/tmp'] + dirs = parse_directories(hosts) + assert dirs == {(0, 'h1'): 'pytestcache', (1, 'h1'): 'pytestcache', + (2, 'h1'):'/tmp'} + +class TestInithosts(object): + def test_inithosts(self): + testevents = [] + hosts = ['h1:/tmp', 'h1:/tmp', 'h1:/other', 'h2', 'h2:home'] + dirs = parse_directories(hosts) + init_hosts(testevents.append, hosts, dirs, pkgdir, do_sync=False) + events = [i for i in testevents if isinstance(i, report.HostRSyncing)] + assert len(events) == 4 + assert events[0].hostname == 'h1' + assert events[0].remoterootpath == '/tmp-h1' + assert events[1].hostname == 'h1' + assert events[1].remoterootpath == '/other-h1' + assert events[2].hostname == 'h2' + assert events[2].remoterootpath == 'pytestcache-h2' + assert events[3].hostname == 'h2' + assert events[3].remoterootpath == 'home-h2' + From fijal at codespeak.net Sun Nov 12 00:29:29 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Sun, 12 Nov 2006 00:29:29 +0100 (CET) Subject: [py-svn] r34519 - py/dist/py/test/rsession Message-ID: <20061111232929.C360D1013C@code0.codespeak.net> Author: fijal Date: Sun Nov 12 00:29:28 2006 New Revision: 34519 Modified: py/dist/py/test/rsession/rsession.py Log: Changed default LSession runner. Modified: py/dist/py/test/rsession/rsession.py ============================================================================== --- py/dist/py/test/rsession/rsession.py (original) +++ py/dist/py/test/rsession/rsession.py Sun Nov 12 00:29:28 2006 @@ -201,10 +201,12 @@ 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 and (self.config.option.usepdb or self.config.option.nocapture): + # runner = plain_runner + #elif runner is None: + # runner = box_runner elif runner is None: - runner = box_runner + runner = plain_runner keyword = self.config.option.keyword From fijal at codespeak.net Sun Nov 12 21:24:16 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Sun, 12 Nov 2006 21:24:16 +0100 (CET) Subject: [py-svn] r34534 - py/dist/py/test Message-ID: <20061112202416.16D661015D@code0.codespeak.net> Author: fijal Date: Sun Nov 12 21:24:15 2006 New Revision: 34534 Modified: py/dist/py/test/collect.py Log: Add a bit of heuristic for collection order (it's better than it was before). Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Sun Nov 12 21:24:15 2006 @@ -120,8 +120,8 @@ return property(fget, fset, None, "underlying object") obj = obj() - def _getobj(self): - return getattr(self.parent.obj, self.name) + def _getobj(self): + return getattr(self.parent.obj, self.name) def multijoin(self, namelist): """ return a list of colitems for the given namelist. """ @@ -421,8 +421,16 @@ teardown_class = getattr(teardown_class, 'im_func', teardown_class) teardown_class(self.obj) - def getsortvalue(self): - for x in self.tryiter((py.test.collect.Generator, py.test.Item)): + def getsortvalue(self): + try: + for key, val in self.obj.__dict__.iteritems(): + import types + if type(val) is (types.FunctionType, types.GeneratorType): + return val.func_code.co_firstlineno + except AttributeError: + pass + # fall back... + for x in self.tryiter((py.test.collect.Generator, py.test.Item)): return x.getsortvalue() class Instance(PyCollectorMixin, Collector): From fijal at codespeak.net Mon Nov 13 12:21:50 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Mon, 13 Nov 2006 12:21:50 +0100 (CET) Subject: [py-svn] r34550 - py/dist/py/test/rsession/testing Message-ID: <20061113112150.ECB1810182@code0.codespeak.net> Author: fijal Date: Mon Nov 13 12:21:48 2006 New Revision: 34550 Modified: py/dist/py/test/rsession/testing/test_web.py Log: Skip the test till guido fix it. 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 Mon Nov 13 12:21:48 2006 @@ -15,6 +15,7 @@ py.test.skip("No PyPy detected") def test_js_generate(): + py.test.skip("XXX") from py.__.test.rsession import webjs from py.__.test.rsession.web import FUNCTION_LIST From fijal at codespeak.net Mon Nov 13 12:22:16 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Mon, 13 Nov 2006 12:22:16 +0100 (CET) Subject: [py-svn] r34551 - in py/dist/py/test/rsession: . testing Message-ID: <20061113112216.4FF4310184@code0.codespeak.net> Author: fijal Date: Mon Nov 13 12:22:13 2006 New Revision: 34551 Added: py/dist/py/test/rsession/testing/test_config.py (contents, props changed) Modified: py/dist/py/test/rsession/local.py py/dist/py/test/rsession/master.py py/dist/py/test/rsession/rsession.py Log: Refactored session options (starting point) for having central point for those. Modified: py/dist/py/test/rsession/local.py ============================================================================== --- py/dist/py/test/rsession/local.py (original) +++ py/dist/py/test/rsession/local.py Mon Nov 13 12:22:13 2006 @@ -33,6 +33,11 @@ outcome.stdout, outcome.stderr = finishcapture(session) return outcome +RunnerPolicy = { + 'plain_runner':plain_runner, + 'box_runner':box_runner +} + def benchmark_runner(item, session, reporter): raise NotImplementedError() Modified: py/dist/py/test/rsession/master.py ============================================================================== --- py/dist/py/test/rsession/master.py (original) +++ py/dist/py/test/rsession/master.py Mon Nov 13 12:22:13 2006 @@ -5,8 +5,6 @@ from py.__.test.rsession.outcome import ReprOutcome from py.__.test.rsession import report -MAX_TASKS_PER_NODE = 15 - class MasterNode(object): def __init__(self, channel, reporter): self.channel = channel @@ -29,12 +27,14 @@ self.reporter(report.SendItem(self.channel, item)) def dispatch_loop(masternodes, itemgenerator, shouldstop, - waiter = lambda: py.std.time.sleep(0.1), - maxtasks_per_node = MAX_TASKS_PER_NODE): + waiter = lambda: py.std.time.sleep(0.1)): + from py.__.test.rsession.rsession import session_options + + max_tasks_per_node = session_options.max_tasks_per_node while 1: try: for node in masternodes: - if len(node.pending) < maxtasks_per_node: + if len(node.pending) < max_tasks_per_node: item = itemgenerator.next() if shouldstop(): return Modified: py/dist/py/test/rsession/rsession.py ============================================================================== --- py/dist/py/test/rsession/rsession.py (original) +++ py/dist/py/test/rsession/rsession.py Mon Nov 13 12:22:13 2006 @@ -14,7 +14,7 @@ from py.__.test.rsession.hostmanage import init_hosts, teardown_hosts from py.__.test.rsession.local import local_loop, plain_runner, apigen_runner,\ - box_runner + box_runner, RunnerPolicy from py.__.test.rsession.reporter import LocalReporter, RemoteReporter class RemoteOptions(object): @@ -31,6 +31,36 @@ # XXX: Must be initialised somehow remote_options = RemoteOptions({'we_are_remote':False, 'exitfirst':False}) + +class SessionOptions: + defaults = { + 'max_tasks_per_node' : 15, + 'runner_policy' : 'plain_runner' + } + + config = None + + def getvalue(self, opt): + try: + return getattr(self.config.getinitialvalue('SessionOptions'), opt) + except (ValueError, AttributeError): + try: + return self.defaults[opt] + except KeyError: + raise AttributeError("Option %s undeclared" % opt) + + def bind_config(self, config): + self.config = config + + def __repr__(self): + return "" % self.config + + def __getattr__(self, attr): + if self.config is None: + raise NotImplementedError("Need to set up config first") + return self.getvalue(attr) + +session_options = SessionOptions() class AbstractSession(object): """ @@ -158,7 +188,8 @@ for y in x.tryiter(reporterror = lambda x: self.reporterror(reporter, x), keyword = keyword): yield y - itemgenerator = itemgen() + itemgenerator = itemgen() + session_options.bind_config(self.config) dispatch_loop(nodes, itemgenerator, checkfun) teardown_hosts(reporter, [node.channel for node in nodes], nodes, exitfirst=self.config.option.exitfirst) @@ -191,6 +222,7 @@ pkgdir = self.getpkgdir(args[0]) colitems = self.make_colitems(args, baseon=pkgdir.dirpath()) reporter(report.RsyncFinished()) + session_options.bind_config(self.config) if runner is None and self.config.option.apigen: from py.__.apigen.tracer.docstorage import DocStorage @@ -206,7 +238,7 @@ #elif runner is None: # runner = box_runner elif runner is None: - runner = plain_runner + runner = RunnerPolicy[session_options.runner_policy] keyword = self.config.option.keyword Added: py/dist/py/test/rsession/testing/test_config.py ============================================================================== --- (empty file) +++ py/dist/py/test/rsession/testing/test_config.py Mon Nov 13 12:22:13 2006 @@ -0,0 +1,21 @@ + +""" test session config options +""" + +import py +from py.__.test.rsession.rsession import session_options, SessionOptions + +def test_session_opts(): + tmpdir = py.test.ensuretemp("sessionopts") + tmpdir.ensure("conftest.py").write("""class SessionOptions: + max_tasks_per_node = 5 + """) + tmp2 = py.test.ensuretemp("xxx") + args = [str(tmpdir)] + config, args = py.test.Config.parse(args) + session_options.bind_config(config) + assert session_options.max_tasks_per_node == 5 + config, args = py.test.Config.parse([str(tmp2)]) + session_options.bind_config(config) + assert session_options.max_tasks_per_node == \ + SessionOptions.defaults['max_tasks_per_node'] From fijal at codespeak.net Mon Nov 13 13:20:16 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Mon, 13 Nov 2006 13:20:16 +0100 (CET) Subject: [py-svn] r34555 - in py/dist/py/test/rsession: . testing Message-ID: <20061113122016.3383310190@code0.codespeak.net> Author: fijal Date: Mon Nov 13 13:20:13 2006 New Revision: 34555 Modified: py/dist/py/test/rsession/box.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_boxing.py py/dist/py/test/rsession/testing/test_master.py py/dist/py/test/rsession/testing/test_rsession.py py/dist/py/test/rsession/testing/test_slave.py Log: A lot of refactoring of SessionOptions. Now nice level should work. Modified: py/dist/py/test/rsession/box.py ============================================================================== --- py/dist/py/test/rsession/box.py (original) +++ py/dist/py/test/rsession/box.py Mon Nov 13 13:20:13 2006 @@ -8,7 +8,7 @@ import sys import marshal -NICE_LEVEL = 0 # XXX make it a conftest option +#NICE_LEVEL = 0 # XXX make it a conftest option PYTESTSTDOUT = "pyteststdout" PYTESTSTDERR = "pyteststderr" @@ -55,12 +55,14 @@ self.PYTESTRETVAL = tempdir.join('retval') self.PYTESTSTDOUT = tempdir.join('stdout') self.PYTESTSTDERR = tempdir.join('stderr') + + nice_level = py.test.remote.nice_level pid = os.fork() if pid: self.parent() else: try: - outcome = self.children() + outcome = self.children(nice_level) except: excinfo = py.code.ExceptionInfo() print "Internal box error" @@ -73,7 +75,7 @@ os._exit(0) return pid - def children(self): + def children(self, nice_level): # right now we need to call a function, but first we need to # map all IO that might happen # make sure sys.stdout points to file descriptor one @@ -88,8 +90,9 @@ os.dup2(fdstderr, 2) retvalf = self.PYTESTRETVAL.open("w") try: - if NICE_LEVEL: - os.nice(NICE_LEVEL) + from py.__.test.rsession.rsession import remote_options + if nice_level: + os.nice(nice_level) retval = self.fun(*self.args, **self.kwargs) retvalf.write(marshal.dumps(retval)) finally: Modified: py/dist/py/test/rsession/master.py ============================================================================== --- py/dist/py/test/rsession/master.py (original) +++ py/dist/py/test/rsession/master.py Mon Nov 13 13:20:13 2006 @@ -43,7 +43,7 @@ break waiter() -def setup_slave(gateway, pkgpath, options={}): +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()"))) Modified: py/dist/py/test/rsession/rsession.py ============================================================================== --- py/dist/py/test/rsession/rsession.py (original) +++ py/dist/py/test/rsession/rsession.py Mon Nov 13 13:20:13 2006 @@ -30,12 +30,13 @@ self.d[item] = val # XXX: Must be initialised somehow -remote_options = RemoteOptions({'we_are_remote':False, 'exitfirst':False}) - +remote_options = RemoteOptions({'we_are_remote':False}) + class SessionOptions: defaults = { 'max_tasks_per_node' : 15, - 'runner_policy' : 'plain_runner' + 'runner_policy' : 'plain_runner', + 'nice_level' : 0, } config = None @@ -51,6 +52,20 @@ def bind_config(self, config): self.config = config + # copy to remote all options + for item, val in config.option.__dict__.items(): + remote_options[item] = val + # as well as some options from us + try: + ses_opt = self.config.getinitialvalue('SessionOptions').__dict__ + except ValueError: + ses_opt = self.defaults + for key in self.defaults: + try: + val = getattr(ses_opt, key) + except AttributeError: + val = self.defaults[key] + remote_options[key] = val def __repr__(self): return "" % self.config @@ -173,11 +188,9 @@ except: remotepython = None - rem_options = RemoteOptions({}) - rem_options['nomagic'] = self.config.option.nomagic - rem_options['exitfirst'] = self.config.option.exitfirst + session_options.bind_config(self.config) nodes = init_hosts(reporter, sshhosts, directories, pkgdir, - rsync_roots, remotepython, remote_options=rem_options.d, + rsync_roots, remotepython, remote_options=remote_options.d, optimise_localhost=self.optimise_localhost) reporter(report.RsyncFinished()) @@ -189,7 +202,6 @@ yield y itemgenerator = itemgen() - session_options.bind_config(self.config) dispatch_loop(nodes, itemgenerator, checkfun) teardown_hosts(reporter, [node.channel for node in nodes], nodes, exitfirst=self.config.option.exitfirst) Modified: py/dist/py/test/rsession/slave.py ============================================================================== --- py/dist/py/test/rsession/slave.py (original) +++ py/dist/py/test/rsession/slave.py Mon Nov 13 13:20:13 2006 @@ -64,9 +64,6 @@ nextitem = receive() 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 @@ -76,9 +73,6 @@ sys.path.insert(0, basedir) import py 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 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 Mon Nov 13 13:20:13 2006 @@ -11,6 +11,10 @@ from py.__.test.rsession.testing import example2 from py.__.test.rsession.conftest import option +def setup_module(mod): + from py.__.test.rsession.rsession import remote_options + remote_options['nice_level'] = 0 + def test_basic_boxing(): # XXX: because we do not have option transfer ## if not hasattr(option, 'nocapture') or not option.nocapture: Modified: py/dist/py/test/rsession/testing/test_master.py ============================================================================== --- py/dist/py/test/rsession/testing/test_master.py (original) +++ py/dist/py/test/rsession/testing/test_master.py Mon Nov 13 13:20:13 2006 @@ -13,8 +13,13 @@ from py.__.test.rsession.outcome import ReprOutcome, Outcome from py.__.test.rsession.testing.test_slave import funcpass_spec, funcfail_spec from py.__.test.rsession import report +from py.__.test.rsession.rsession import session_options, remote_options def setup_module(mod): + # bind an empty config + config, args = py.test.Config.parse([]) + session_options.bind_config(config) + #assert not remote_options.exitfirst mod.pkgdir = py.path.local(py.__file__).dirpath() class DummyChannel(object): @@ -70,7 +75,7 @@ def test_slave_setup(): gw = py.execnet.PopenGateway() - channel = setup_slave(gw, pkgdir) + channel = setup_slave(gw, pkgdir, remote_options.d) channel.send(funcpass_spec) output = ReprOutcome(channel.receive()) assert output.passed @@ -90,7 +95,7 @@ def open_gw(): gw = py.execnet.PopenGateway() - channel = setup_slave(gw, pkgdir) + channel = setup_slave(gw, pkgdir, remote_options.d) mn = MasterNode(channel, simple_report) return mn 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 Mon Nov 13 13:20:13 2006 @@ -4,7 +4,8 @@ import py from py.__.test.rsession import report -from py.__.test.rsession.rsession import RSession, parse_directories +from py.__.test.rsession.rsession import RSession, parse_directories,\ + session_options, remote_options 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, \ @@ -185,8 +186,10 @@ setup_events = [] teardown_events = [] + config, args = py.test.Config.parse([]) + session_options.bind_config(config) nodes = init_hosts(setup_events.append, hosts, 'pytesttest', pkgdir, - rsync_roots=["py"], optimise_localhost=False, remote_options={'exitfirst':False}) + rsync_roots=["py"], optimise_localhost=False, remote_options=remote_options.d) teardown_hosts(teardown_events.append, [node.channel for node in nodes], nodes) @@ -209,8 +212,10 @@ hosts = ['localhost'] allevents = [] + config, args = py.test.Config.parse([]) + session_options.bind_config(config) nodes = init_hosts(allevents.append, hosts, 'pytesttest', pkgdir, - rsync_roots=["py"], optimise_localhost=False, remote_options={'exitfirst':False}) + rsync_roots=["py"], optimise_localhost=False, remote_options=remote_options.d) from py.__.test.rsession.testing.test_executor \ import ItemTestPassing, ItemTestFailing, ItemTestSkipping @@ -248,8 +253,12 @@ """ allevents = [] hosts = ['localhost'] + config, args = py.test.Config.parse([]) + session_options.bind_config(config) + d = remote_options.d.copy() + d['custom'] = 'custom' nodes = init_hosts(allevents.append, hosts, 'pytesttest', pkgdir, - rsync_roots=["py"], remote_options={'custom':'custom', 'exitfirst':False}, + rsync_roots=["py"], remote_options=d, optimise_localhost=False) rootcol = py.test.collect.Directory(pkgdir.dirpath()) 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 Mon Nov 13 13:20:13 2006 @@ -11,7 +11,10 @@ py.test.skip("rsession is unsupported on Windows.") def setup_module(module): + from py.__.test.rsession.rsession import session_options module.rootdir = py.path.local(py.__file__).dirpath().dirpath() + config, args = py.test.Config.parse([]) + session_options.bind_config(config) # ---------------------------------------------------------------------- # inlined testing functions used below @@ -109,8 +112,7 @@ 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 and res_repr[1].passed) or \ - (not res_repr[1].passed and res_repr[0].passed) + assert not res_repr[0].passed and res_repr[1].passed def test_slave_run_different_stuff(): node = gettestnode() @@ -127,7 +129,8 @@ if self.count == 0: retval = str(tmp) elif self.count == 1: - retval = {} + from py.__.test.rsession.rsession import remote_options + retval = remote_options.d else: raise NotImplementedError("mora data") self.count += 1 @@ -152,7 +155,8 @@ if self.count == 0: retval = str(x.dirpath()) elif self.count == 1: - retval = {} + from py.__.test.rsession.rsession import remote_options + retval = remote_options.d else: raise NotImplementedError("mora data") self.count += 1 From fijal at codespeak.net Mon Nov 13 13:53:18 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Mon, 13 Nov 2006 13:53:18 +0100 (CET) Subject: [py-svn] r34557 - in py/dist/py/test/rsession: . testing Message-ID: <20061113125318.BCB8D1018E@code0.codespeak.net> Author: fijal Date: Mon Nov 13 13:53:16 2006 New Revision: 34557 Modified: py/dist/py/test/rsession/rsession.py py/dist/py/test/rsession/slave.py py/dist/py/test/rsession/testing/test_config.py py/dist/py/test/rsession/testing/test_master.py py/dist/py/test/rsession/testing/test_rsession.py Log: Some fixes of options and some 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 Mon Nov 13 13:53:16 2006 @@ -62,8 +62,8 @@ ses_opt = self.defaults for key in self.defaults: try: - val = getattr(ses_opt, key) - except AttributeError: + val = ses_opt[key] + except KeyError: val = self.defaults[key] remote_options[key] = val Modified: py/dist/py/test/rsession/slave.py ============================================================================== --- py/dist/py/test/rsession/slave.py (original) +++ py/dist/py/test/rsession/slave.py Mon Nov 13 13:53:16 2006 @@ -43,7 +43,6 @@ while 1: nextitem = receive() if nextitem is None: - #py.test.Function.state.teardown_all() break try: node = getnode(nextitem) Modified: py/dist/py/test/rsession/testing/test_config.py ============================================================================== --- py/dist/py/test/rsession/testing/test_config.py (original) +++ py/dist/py/test/rsession/testing/test_config.py Mon Nov 13 13:53:16 2006 @@ -3,18 +3,21 @@ """ import py -from py.__.test.rsession.rsession import session_options, SessionOptions +from py.__.test.rsession.rsession import session_options, SessionOptions,\ + remote_options def test_session_opts(): tmpdir = py.test.ensuretemp("sessionopts") tmpdir.ensure("conftest.py").write("""class SessionOptions: max_tasks_per_node = 5 + nice_level = 10 """) tmp2 = py.test.ensuretemp("xxx") args = [str(tmpdir)] config, args = py.test.Config.parse(args) session_options.bind_config(config) assert session_options.max_tasks_per_node == 5 + assert remote_options.nice_level == 10 config, args = py.test.Config.parse([str(tmp2)]) session_options.bind_config(config) assert session_options.max_tasks_per_node == \ Modified: py/dist/py/test/rsession/testing/test_master.py ============================================================================== --- py/dist/py/test/rsession/testing/test_master.py (original) +++ py/dist/py/test/rsession/testing/test_master.py Mon Nov 13 13:53:16 2006 @@ -18,6 +18,7 @@ def setup_module(mod): # bind an empty config config, args = py.test.Config.parse([]) + config.options.max_tasks_per_node = 10 session_options.bind_config(config) #assert not remote_options.exitfirst mod.pkgdir = py.path.local(py.__file__).dirpath() 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 Mon Nov 13 13:53:16 2006 @@ -279,6 +279,29 @@ assert len(passed) == 2 * len(nodes) assert len(skipped) == 0 assert len(events) == len(passed) + + def test_nice_level(self): + """ Tests if nice level behaviour is ok + """ + allevents = [] + hosts = ['localhost'] + tmpdir = py.test.ensuretemp("nice") + tmpdir.ensure("__init__.py") + tmpdir.ensure("conftest.py").write("""disthosts = ['localhost']""") + tmpdir.ensure("test_one.py").write("""def test_nice(): + import os + assert os.nice(0) == 10 + """) + + config, args = py.test.Config.parse([str(tmpdir)]) + config.option.nice_level = 10 + rsession = RSession(config) + allevents = [] + rsession.main(args, reporter=allevents.append) + testevents = [x for x in allevents + if isinstance(x, report.ReceivedItemOutcome)] + passevents = [x for x in testevents if x.outcome.passed] + assert len(passevents) == 1 class TestDirectories(object): def test_simple_parse(self): From fijal at codespeak.net Mon Nov 13 14:00:19 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Mon, 13 Nov 2006 14:00:19 +0100 (CET) Subject: [py-svn] r34558 - py/dist/py/documentation Message-ID: <20061113130019.06DA410193@code0.codespeak.net> Author: fijal Date: Mon Nov 13 14:00:17 2006 New Revision: 34558 Modified: py/dist/py/documentation/test.txt Log: Updated documentation a bit. Modified: py/dist/py/documentation/test.txt ============================================================================== --- py/dist/py/documentation/test.txt (original) +++ py/dist/py/documentation/test.txt Mon Nov 13 14:00:17 2006 @@ -733,20 +733,29 @@ The options are: -* disthosts - a list of ssh addresses (including a specific path if it +* `disthosts` - a list of ssh addresses (including a specific path if it 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. +* `distrsync_roots` - a list of packages to copy to the remote machines. +* `dist_remotepython` - the remote python to run. +* `SessionOptions` - containing some specific tuning options Sample configuration:: disthosts = ['localhost', 'user at someserver:/tmp/somedir'] distrsync_roots = ['pypy', 'py'] dist_remotepython = 'python2.4' + class SessionOptions: + nice_level = 10 Running server is done by ``-w`` command line option or ``--startserver`` (the former might change at some point due to conflicts). +Possible `SessionOptions` arguments: + +* `nice_level` - Level of nice under which tests are run +* `runner_policy` - (for `LSession` only) - contains policy for the test boxig. + `"plain_runner"` means no boxing, `"box_runner"` means boxing. + Development Notes ----------------- From fijal at codespeak.net Mon Nov 13 17:02:17 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Mon, 13 Nov 2006 17:02:17 +0100 (CET) Subject: [py-svn] r34572 - py/dist/py/test/rsession/testing Message-ID: <20061113160217.C1D09101B0@code0.codespeak.net> Author: fijal Date: Mon Nov 13 17:02:15 2006 New Revision: 34572 Modified: py/dist/py/test/rsession/testing/test_master.py py/dist/py/test/rsession/testing/test_web.py Log: Fixed tests. Modified: py/dist/py/test/rsession/testing/test_master.py ============================================================================== --- py/dist/py/test/rsession/testing/test_master.py (original) +++ py/dist/py/test/rsession/testing/test_master.py Mon Nov 13 17:02:15 2006 @@ -18,7 +18,7 @@ def setup_module(mod): # bind an empty config config, args = py.test.Config.parse([]) - config.options.max_tasks_per_node = 10 + config.option.max_tasks_per_node = 10 session_options.bind_config(config) #assert not remote_options.exitfirst mod.pkgdir = py.path.local(py.__file__).dirpath() 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 Mon Nov 13 17:02:15 2006 @@ -15,8 +15,8 @@ py.test.skip("No PyPy detected") def test_js_generate(): - py.test.skip("XXX") from py.__.test.rsession import webjs from py.__.test.rsession.web import FUNCTION_LIST source = rpython2javascript(webjs, FUNCTION_LIST, Options) + assert source From fijal at codespeak.net Mon Nov 13 17:02:43 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Mon, 13 Nov 2006 17:02:43 +0100 (CET) Subject: [py-svn] r34574 - py/dist/py/test/rsession Message-ID: <20061113160243.170CA101B7@code0.codespeak.net> Author: fijal Date: Mon Nov 13 17:02:41 2006 New Revision: 34574 Modified: py/dist/py/test/rsession/outcome.py py/dist/py/test/rsession/reporter.py Log: Added short/no traceback outcome. Modified: py/dist/py/test/rsession/outcome.py ============================================================================== --- py/dist/py/test/rsession/outcome.py (original) +++ py/dist/py/test/rsession/outcome.py Mon Nov 13 17:02:41 2006 @@ -7,6 +7,7 @@ # internal bug. import sys +import py class Outcome(object): def __init__(self, setupfailure=False, excinfo=None, skipped=None, @@ -31,7 +32,10 @@ relline = lineno - tb_entry.frame.code.firstlineno path = str(tb_entry.path) try: - source = str(tb_entry.getsource()) + if py.test.remote.tbstyle == 'long': + source = str(tb_entry.getsource()) + else: + source = str(tb_entry.getsource()).split("\n")[relline] except: source = "" return (relline, lineno, source, path) Modified: py/dist/py/test/rsession/reporter.py ============================================================================== --- py/dist/py/test/rsession/reporter.py (original) +++ py/dist/py/test/rsession/reporter.py Mon Nov 13 17:02:41 2006 @@ -115,8 +115,7 @@ 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) + self.repr_traceback(item, excinfo, traceback) if outcome.stdout: self.out.sep('-', " Captured process stdout: ") self.out.write(outcome.stdout) @@ -134,11 +133,16 @@ 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)) + def repr_traceback(self, item, excinfo, traceback): + if self.config.option.tbstyle == 'long': + 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)) + elif self.config.option.tbstyle == 'short': + for index, entry in py.builtin.enumerate(traceback): + self.out.line("%s: %s" % (entry.path, entry.lineno)) + self.out.line(entry.source) self.out.line("%s: %s" % (excinfo.typename, excinfo.value)) def repr_source(self, relline, source): From fijal at codespeak.net Mon Nov 13 19:34:34 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Mon, 13 Nov 2006 19:34:34 +0100 (CET) Subject: [py-svn] r34577 - py/dist/py/test Message-ID: <20061113183434.716DD101AD@code0.codespeak.net> Author: fijal Date: Mon Nov 13 19:34:32 2006 New Revision: 34577 Modified: py/dist/py/test/collect.py Log: Add generic __ne__. Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Mon Nov 13 19:34:32 2006 @@ -101,6 +101,9 @@ return self.name == other.name and self.parent == other.parent except AttributeError: return False + + def __ne__(self, other): + return not self == other def __cmp__(self, other): s1 = self.getsortvalue() From fijal at codespeak.net Mon Nov 13 19:55:08 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Mon, 13 Nov 2006 19:55:08 +0100 (CET) Subject: [py-svn] r34578 - in py/dist/py: . apigen/tracer path test/rsession Message-ID: <20061113185508.9325D101B5@code0.codespeak.net> Author: fijal Date: Mon Nov 13 19:55:04 2006 New Revision: 34578 Modified: py/dist/py/apigen/tracer/description.py py/dist/py/conftest.py py/dist/py/path/common.py py/dist/py/test/rsession/rsession.py Log: Moved rest generation out of py.test into conftest. This is now local to pylib. Modified: py/dist/py/apigen/tracer/description.py ============================================================================== --- py/dist/py/apigen/tracer/description.py (original) +++ py/dist/py/apigen/tracer/description.py Mon Nov 13 19:55:04 2006 @@ -253,15 +253,23 @@ self.old_dict = self.perform_dict_copy(obj.__dict__) def perform_dict_copy(self, d): + #try: + # c = copy.deepcopy(d) + #except: + # c = {} + # for k, v in d.iteritems(): + # try: + # c[k] = copy.deepcopy(v) + # except: + # c[k] = v + #return c try: - c = copy.deepcopy(d) - except: c = {} for k, v in d.iteritems(): - try: - c[k] = copy.deepcopy(v) - except: - c[k] = v + c[k] = v + except: + # cannot perform this + c = d return c def consider_end_locals(self, frame): Modified: py/dist/py/conftest.py ============================================================================== --- py/dist/py/conftest.py (original) +++ py/dist/py/conftest.py Mon Nov 13 19:55:04 2006 @@ -24,3 +24,14 @@ action="store", dest="sshtarget", default=None, help="target to run tests requiring ssh, e.g. user at codespeak.net"), ) + +class ApiGen: + def get_doc_storage(): + from py.__.apigen.tracer.docstorage import DocStorage + return DocStorage().from_pkg(py) + get_doc_storage = staticmethod(get_doc_storage) + + def write_docs(ds): + from py.__.apigen.rest.genrest import DirectPaste, RestGen, DirWriter + RestGen(ds, DirectPaste(), DirWriter("/tmp/output")).write() + write_docs = staticmethod(write_docs) Modified: py/dist/py/path/common.py ============================================================================== --- py/dist/py/path/common.py (original) +++ py/dist/py/path/common.py Mon Nov 13 19:55:04 2006 @@ -124,10 +124,12 @@ """ if not isinstance(relpath, (str, PathBase)): raise TypeError("%r: not a string or path object" %(relpath,)) - strrelpath = str(relpath) - if strrelpath and strrelpath[-1] != self.sep: - strrelpath += self.sep - strself = str(self) + strrelpath = str(relpath) + if strrelpath and strrelpath[-1] != self.sep: + strrelpath += self.sep + #assert strrelpath[-1] == self.sep + #assert strrelpath[-2] != self.sep + strself = str(self) if strself.startswith(strrelpath): return strself[len(strrelpath):] return "" Modified: py/dist/py/test/rsession/rsession.py ============================================================================== --- py/dist/py/test/rsession/rsession.py (original) +++ py/dist/py/test/rsession/rsession.py Mon Nov 13 19:55:04 2006 @@ -237,12 +237,15 @@ session_options.bind_config(self.config) if runner is None and 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) + try: + self.docstorage = self.config.getinitialvalue('ApiGen').get_doc_storage() + except (ValueError, AttributeError): + raise NotImplementedError("Need to provide conftest " + "way of generating DocStorage") self.tracer = Tracer(self.docstorage) runner = apigen_runner #elif runner is None and (self.config.option.usepdb or self.config.option.nocapture): @@ -275,6 +278,8 @@ py.magic.revoke(assertion=1) if self.config.option.apigen: - from py.__.apigen.rest.genrest import DirectPaste, RestGen, DirWriter - RestGen(self.docstorage, DirectPaste(), DirWriter("/tmp/output")).write() - + try: + self.config.getinitialvalue('ApiGen').write_docs(self.docstorage) + except ValueError: + raise NotImplementedError("Cannot create docs - didn't " + "provided way of doing that in conftest") From fijal at codespeak.net Mon Nov 13 19:59:39 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Mon, 13 Nov 2006 19:59:39 +0100 (CET) Subject: [py-svn] r34579 - py/dist/py/apigen/tracer Message-ID: <20061113185939.90981101B9@code0.codespeak.net> Author: fijal Date: Mon Nov 13 19:59:38 2006 New Revision: 34579 Modified: py/dist/py/apigen/tracer/description.py Log: Get rid of very-bare except. Modified: py/dist/py/apigen/tracer/description.py ============================================================================== --- py/dist/py/apigen/tracer/description.py (original) +++ py/dist/py/apigen/tracer/description.py Mon Nov 13 19:59:38 2006 @@ -267,6 +267,8 @@ c = {} for k, v in d.iteritems(): c[k] = v + except (KeyboardInterrupt, SystemExit): + raise except: # cannot perform this c = d From fijal at codespeak.net Mon Nov 13 20:45:17 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Mon, 13 Nov 2006 20:45:17 +0100 (CET) Subject: [py-svn] r34580 - in py/dist/py/apigen/tracer: . testing Message-ID: <20061113194517.1B7CD101BC@code0.codespeak.net> Author: fijal Date: Mon Nov 13 20:45:15 2006 New Revision: 34580 Modified: py/dist/py/apigen/tracer/description.py py/dist/py/apigen/tracer/docstorage.py py/dist/py/apigen/tracer/testing/test_docgen.py Log: Added some heuristics which generate a bit more acurate info. Modified: py/dist/py/apigen/tracer/description.py ============================================================================== --- py/dist/py/apigen/tracer/description.py (original) +++ py/dist/py/apigen/tracer/description.py Mon Nov 13 20:45:15 2006 @@ -106,6 +106,8 @@ return self.code is other.code if isinstance(other, types.CodeType): return self.code is other + if isinstance(other, tuple) and len(other) == 2: + return self.code == other return False def __ne__(self, other): @@ -186,10 +188,10 @@ except: # XXX UUuuuu bare except here. What can it really rise??? try: hash(self.pyobj) - return self.pyobj + result = self.pyobj except: - return self - return result + result = self + return (result, self.pyobj) code = property(getcode) def consider_call(self, inputcells): @@ -204,10 +206,14 @@ pass # we *know* what return value we do have def consider_start_locals(self, frame): - pass + if '__init__' in self.fields: + md = self.fields['__init__'] + md.consider_start_locals(frame) def consider_end_locals(self, frame): - pass + if '__init__' in self.fields: + md = self.fields['__init__'] + md.consider_end_locals(frame) def consider_call_site(self, frame, cut_frame): self.fields['__init__'].consider_call_site(frame, cut_frame) Modified: py/dist/py/apigen/tracer/docstorage.py ============================================================================== --- py/dist/py/apigen/tracer/docstorage.py (original) +++ py/dist/py/apigen/tracer/docstorage.py Mon Nov 13 20:45:15 2006 @@ -17,7 +17,7 @@ """ def consider_call(self, frame, caller_frame, upward_cut_frame=None): assert isinstance(frame, py.code.Frame) - desc = self.find_desc(frame.code) + desc = self.find_desc(frame.code, frame.raw.f_locals) if desc: self.generalize_args(desc, frame) desc.consider_call_site(caller_frame, upward_cut_frame) @@ -33,13 +33,18 @@ def consider_return(self, frame, arg): assert isinstance(frame, py.code.Frame) - desc = self.find_desc(frame.code) + desc = self.find_desc(frame.code, frame.raw.f_locals) 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) + def find_desc(self, code, locals): + if code.name == '__init__': + # argh, very fragile specialcasing + key = (code.raw, locals[code.raw.co_varnames[0]].__class__) + else: + key = code.raw + return self.desc_cache.get(key, None) #for desc in self.descs.values(): # if desc.has_code(frame.code.raw): # return desc 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 Nov 13 20:45:15 2006 @@ -177,3 +177,20 @@ desc = ds.descs['testclass'] methdesc = desc.fields['bar'] assert methdesc.get_local_changes() == {} + +def test_multiple_classes_with_same_init(): + class A: + def __init__(self, x): + self.x = x + + class B(A): + pass + + ds = DocStorage().from_dict({'A':A, 'B':B}) + t = Tracer(ds) + t.start_tracing() + c = A(3) + d = B(4) + t.end_tracing() + assert len(ds.descs['A'].fields['__init__'].call_sites) == 1 + assert len(ds.descs['B'].fields['__init__'].call_sites) == 1 From fijal at codespeak.net Mon Nov 13 22:37:51 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Mon, 13 Nov 2006 22:37:51 +0100 (CET) Subject: [py-svn] r34588 - py/dist/py/apigen/tracer Message-ID: <20061113213751.8F061101C8@code0.codespeak.net> Author: fijal Date: Mon Nov 13 22:37:49 2006 New Revision: 34588 Modified: py/dist/py/apigen/tracer/tracer.py Log: Fixed stuff for greenlets. Modified: py/dist/py/apigen/tracer/tracer.py ============================================================================== --- py/dist/py/apigen/tracer/tracer.py (original) +++ py/dist/py/apigen/tracer/tracer.py Mon Nov 13 22:37:49 2006 @@ -30,9 +30,12 @@ frame = py.code.Frame(frame) if event == 'call': assert arg is None - self.docstorage.consider_call(frame, + try: + self.docstorage.consider_call(frame, py.code.Frame(sys._getframe(2)), self.frame) + except ValueError: + self.docstorage.consider_call(frame, None, self.frame) elif event == 'return': self.docstorage.consider_return(frame, arg) return self._tracer From fijal at codespeak.net Tue Nov 14 00:30:02 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Tue, 14 Nov 2006 00:30:02 +0100 (CET) Subject: [py-svn] r34590 - py/dist/py/apigen/rest/testing Message-ID: <20061113233002.3AC59100CC@code0.codespeak.net> Author: fijal Date: Tue Nov 14 00:30:01 2006 New Revision: 34590 Modified: py/dist/py/apigen/rest/testing/test_rest.py Log: Fixed test, which was actually broken. 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 Nov 14 00:30:01 2006 @@ -181,8 +181,8 @@ 'method_SomeSubClass.method.txt', 'module_Unknown module.txt', 'traceback_SomeClass.__init__.0.txt', - 'traceback_SomeClass.__init__.1.txt', 'traceback_SomeClass.method.0.txt', + 'traceback_SomeSubClass.__init__.0.txt', 'traceback_fun.0.txt', 'traceback_fun.1.txt', ] @@ -211,7 +211,7 @@ 'module_somemodule.txt', 'module_someothermodule.txt', 'traceback_somemodule.SomeClass.__init__.0.txt', - 'traceback_somemodule.SomeClass.__init__.1.txt', + 'traceback_someothermodule.SomeSubClass.__init__.0.txt', 'traceback_someothermodule.SomeSubClass.method.0.txt', 'traceback_someothermodule.fun.0.txt', 'traceback_someothermodule.fun.1.txt', From fijal at codespeak.net Tue Nov 14 10:50:06 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Tue, 14 Nov 2006 10:50:06 +0100 (CET) Subject: [py-svn] r34591 - in py/dist/py/apigen/tracer: . testing Message-ID: <20061114095006.3B11D101D0@code0.codespeak.net> Author: fijal Date: Tue Nov 14 10:50:03 2006 New Revision: 34591 Modified: 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/tracer.py Log: Added exception handling code. Modified: py/dist/py/apigen/tracer/description.py ============================================================================== --- py/dist/py/apigen/tracer/description.py (original) +++ py/dist/py/apigen/tracer/description.py Tue Nov 14 10:50:03 2006 @@ -124,6 +124,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.exceptions = {} def consider_call(self, inputcells): for cell_num, cell in enumerate(inputcells): @@ -136,6 +137,9 @@ cs = cut_stack(stack, frame, cut_frame) self.call_sites[cs] = cs + def consider_exception(self, exc, value): + self.exceptions[exc] = True + def get_call_sites(self): # convinient accessor for various data which we keep there if not self.keep_frames: @@ -204,6 +208,14 @@ def consider_return(self, arg): pass # we *know* what return value we do have + + def consider_exception(self, exc, value): + if '__init__' in self.fields: + md = self.fields['__init__'] + else: + md = MethodDesc(self.name + '.__init__', self.pyobj.__init__) + self.fields['__init__'] = md + md.consider_exception(exc, value) def consider_start_locals(self, frame): if '__init__' in self.fields: Modified: py/dist/py/apigen/tracer/docstorage.py ============================================================================== --- py/dist/py/apigen/tracer/docstorage.py (original) +++ py/dist/py/apigen/tracer/docstorage.py Tue Nov 14 10:50:03 2006 @@ -37,6 +37,12 @@ if desc: self.generalize_retval(desc, arg) desc.consider_end_locals(frame) + + def consider_exception(self, frame, arg): + desc = self.find_desc(frame.code, frame.raw.f_locals) + if desc: + exc_class, value, _ = arg + desc.consider_exception(exc_class, value) def find_desc(self, code, locals): if code.name == '__init__': 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 Nov 14 10:50:03 2006 @@ -194,3 +194,25 @@ t.end_tracing() assert len(ds.descs['A'].fields['__init__'].call_sites) == 1 assert len(ds.descs['B'].fields['__init__'].call_sites) == 1 + +def test_exception_raise(): + def x(): + 1/0 + + def y(): + try: + x() + except ZeroDivisionError: + pass + + def z(): + y() + + ds = DocStorage().from_dict({'x':x, 'y':y, 'z':z}) + t = Tracer(ds) + t.start_tracing() + z() + t.end_tracing() + assert ds.descs['x'].exceptions.keys() == [ZeroDivisionError] + assert ds.descs['y'].exceptions.keys() == [ZeroDivisionError] + assert ds.descs['z'].exceptions.keys() == [] Modified: py/dist/py/apigen/tracer/tracer.py ============================================================================== --- py/dist/py/apigen/tracer/tracer.py (original) +++ py/dist/py/apigen/tracer/tracer.py Tue Nov 14 10:50:03 2006 @@ -38,6 +38,8 @@ self.docstorage.consider_call(frame, None, self.frame) elif event == 'return': self.docstorage.consider_return(frame, arg) + elif event == 'exception': + self.docstorage.consider_exception(frame, arg) return self._tracer def start_tracing(self): From fijal at codespeak.net Tue Nov 14 10:58:03 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Tue, 14 Nov 2006 10:58:03 +0100 (CET) Subject: [py-svn] r34592 - in py/dist/py/apigen: rest rest/testing tracer Message-ID: <20061114095803.02C39101D2@code0.codespeak.net> Author: fijal Date: Tue Nov 14 10:58:01 2006 New Revision: 34592 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 generation of exception info into rest. Modified: py/dist/py/apigen/rest/genrest.py ============================================================================== --- py/dist/py/apigen/rest/genrest.py (original) +++ py/dist/py/apigen/rest/genrest.py Tue Nov 14 10:58:01 2006 @@ -335,6 +335,11 @@ for k, changeset in local_changes.iteritems(): lst.append(ListItem('%s: %s' % (k, ', '.join(changeset)))) + lst.append(Paragraph('Exceptions that might appear in function body:')) + for exc in self.dsa.get_function_exceptions(functionname): + lst.append(ListItem(exc.__name__)) + # XXX: right now we leave it alone + # 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/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 Nov 14 10:58:01 2006 @@ -346,3 +346,23 @@ assert call_point != -1 assert source.find("x \:\: ") < call_point self.check_rest(tempdir) + + def test_exc_raising(self): + def x(): + try: + 1/0 + except: + pass + + descs = {'x':x} + ds = DocStorage().from_dict(descs) + t = Tracer(ds) + t.start_tracing() + x() + t.end_tracing() + lg = DirectPaste() + tempdir = temppath.ensure("exc_raising", dir=True) + r = RestGen(ds, lg, DirWriter(tempdir)) + r.write() + source = tempdir.join('function_x.txt').open().read() + assert source.find('ZeroDivisionError') < source.find('Call sites\:') Modified: py/dist/py/apigen/tracer/docstorage.py ============================================================================== --- py/dist/py/apigen/tracer/docstorage.py (original) +++ py/dist/py/apigen/tracer/docstorage.py Tue Nov 14 10:58:01 2006 @@ -251,3 +251,6 @@ except AttributeError: pass return retval + + def get_function_exceptions(self, name): + return sorted(self.ds.descs[name].exceptions.keys()) From guido at codespeak.net Tue Nov 14 13:54:05 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Tue, 14 Nov 2006 13:54:05 +0100 (CET) Subject: [py-svn] r34595 - in py/dist/py/rst: . testing Message-ID: <20061114125405.33F95101B6@code0.codespeak.net> Author: guido Date: Tue Nov 14 13:54:03 2006 New Revision: 34595 Modified: py/dist/py/rst/rst.py py/dist/py/rst/testing/test_rst.py Log: Added support for ReST directives (code I wrote on the sprint and during the train ride back), changed LiteralBlock so it subclasses from AbstractText (no need to allow anything but plain text in there, only makes it harder), added support for nested lists. Modified: py/dist/py/rst/rst.py ============================================================================== --- py/dist/py/rst/rst.py (original) +++ py/dist/py/rst/rst.py Tue Nov 14 13:54:03 2006 @@ -34,6 +34,8 @@ class_list = [parent_cls] else: class_list = parent_cls + if obj.allow_nesting: + class_list.append(obj) for _class in class_list: if not _class.allowed_child: @@ -49,6 +51,7 @@ __metaclass__ = AbstractMetaclass parentclass = None # this exists to allow parent to know what # children can exist + allow_nesting = False allowed_child = {} defaults = {} @@ -185,16 +188,6 @@ class SubParagraph(Paragraph): indent = " " -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]),) - class Title(Paragraph): parentclass = Rest belowchar = "" @@ -233,6 +226,18 @@ text = escape(self._text) return self._reg_whitespace.split(text) +class LiteralBlock(AbstractText): + parentclass = Rest + start = '::\n\n' + + def text(self): + text = self.escape(self._text).split('\n') + print text + for i, line in enumerate(text): + if line.strip(): + text[i] = ' %s' % (line,) + return self.start + '\n'.join(text) + class Em(AbstractText): start = "*" end = "*" @@ -258,25 +263,43 @@ raise NotImplemented('XXX') class ListItem(Paragraph): - item_char = "*" + allow_nesting = True + item_chars = '*+-' def text(self): - oldindent = self.indent - self.indent = oldindent + ' ' - try: - txt = Paragraph.text(self) - finally: - self.indent = oldindent - txt = self.item_char + txt[1:] - return txt + idepth = self.get_indent_depth() + indent = self.indent + (idepth + 1) * ' ' + txt = [] + for child in self.children: + if isinstance(child, AbstractText): + p = Paragraph(child, indent=indent) + txt.append(p.text()) + else: + txt.append(child.text()) + txt = '\n'.join(txt) + ret = [] + if idepth: + ret.append('\n') + item_char = self.item_chars[idepth] + ret += [indent[2:], item_char, ' ', txt[len(indent):]] + print repr(ret) + return ''.join(ret) + + def get_indent_depth(self): + depth = 0 + current = self + while current.parent is not None: + depth += 1 + current = current.parent + return depth - 1 class OrderedListItem(ListItem): - item_char = "#." + item_chars = ("#.",) class DListItem(ListItem): - item_char = None + item_chars = None def __init__(self, term, definition, *args, **kwargs): - self.item_char = '%s\n ' % (term,) + self.item_chars = ('%s\n ' % (term,),) super(DListItem, self).__init__(definition, *args, **kwargs) class Link(AbstractText): @@ -307,3 +330,33 @@ def __init__(self, text, **kwargs): raise NotImplemented('XXX') +class Directive(Paragraph): + indent = ' ' + def __init__(self, name, *args, **options): + self.name = name + self.content = options.pop('content', []) + children = list(args) + super(Directive, self).__init__(*children) + self.options = options + + def text(self): + # XXX not very pretty... + namechunksize = len(self.name) + 2 + self.children.insert(0, Text('X' * namechunksize)) + txt = super(Directive, self).text() + txt = '.. %s::%s' % (self.name, txt[namechunksize + 3:],) + options = '\n'.join([' :%s: %s' % (k, v) for (k, v) in + self.options.iteritems()]) + if options: + txt += '\n%s' % (options,) + + if self.content: + txt += '\n' + for item in self.content: + assert item.parentclass == Rest, 'only top-level items allowed' + assert not item.indent + item.indent = ' ' + txt += '\n' + item.text() + + return txt + 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 Tue Nov 14 13:54:03 2006 @@ -49,7 +49,7 @@ def test_escape_literal(): txt = LiteralBlock('*escape* ``test``').text() - assert txt == '::\n\n *escape* ``test``' + assert txt == '::\n\n *escape* ``test``' html = checkrest(txt) assert '>\n*escape* ``test``\n' in html @@ -153,8 +153,8 @@ :: - def fun(): - some + def fun(): + some Paragraph """ @@ -272,6 +272,18 @@ assert txt == expected checkrest(txt) +def test_nested_lists(): + expected = """\ +* foo + +* bar + + + baz +""" + txt = Rest(ListItem('foo'), ListItem('bar', ListItem('baz'))).text() + assert txt == expected + checkrest(txt) + def test_title_following_links_empty_line(): expected = """\ Foo, bar and `baz`_. @@ -312,3 +324,33 @@ py.test.raises(ValueError, 'Rest(Transition(), Paragraph("foo")).text()') py.test.raises(ValueError, 'Rest(Paragraph("foo"), Transition()).text()') +def test_directive_simple(): + txt = Rest(Directive('image', 'images/foo.png')).text() + assert txt == '.. image:: images/foo.png\n' + +def test_directive_metadata(): + txt = Rest(Directive('image', 'images/foo.png', + width=200, height=150)).text() + assert txt == ('.. image:: images/foo.png\n :width: 200\n' + ' :height: 150\n') + +def test_directive_multiline(): + txt = Rest(Directive('note', ('This is some string that is too long to ' + 'fit on a single line, so it should be ' + 'broken up.'))).text() + assert txt == """\ +.. note:: This is some string that is too long to fit on a single line, so it + should be broken up. +""" + +def test_directive_content(): + txt = Rest(Directive('image', 'foo.png', width=200, height=100, + content=[Paragraph('Some paragraph content.')])).text() + assert txt == """\ +.. image:: foo.png + :width: 200 + :height: 100 + + Some paragraph content. +""" + From guido at codespeak.net Tue Nov 14 13:56:23 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Tue, 14 Nov 2006 13:56:23 +0100 (CET) Subject: [py-svn] r34596 - py/dist/py/rst Message-ID: <20061114125623.E7742101DF@code0.codespeak.net> Author: guido Date: Tue Nov 14 13:56:22 2006 New Revision: 34596 Modified: py/dist/py/rst/rst.py Log: Fixed problem with finding the tree root, removed debug prints. Modified: py/dist/py/rst/rst.py ============================================================================== --- py/dist/py/rst/rst.py (original) +++ py/dist/py/rst/rst.py Tue Nov 14 13:56:22 2006 @@ -232,7 +232,6 @@ def text(self): text = self.escape(self._text).split('\n') - print text for i, line in enumerate(text): if line.strip(): text[i] = ' %s' % (line,) @@ -273,6 +272,7 @@ for child in self.children: if isinstance(child, AbstractText): p = Paragraph(child, indent=indent) + p.parent = self.parent txt.append(p.text()) else: txt.append(child.text()) @@ -282,7 +282,6 @@ ret.append('\n') item_char = self.item_chars[idepth] ret += [indent[2:], item_char, ' ', txt[len(indent):]] - print repr(ret) return ''.join(ret) def get_indent_depth(self): From guido at codespeak.net Tue Nov 14 14:49:06 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Tue, 14 Nov 2006 14:49:06 +0100 (CET) Subject: [py-svn] r34597 - in py/dist/py/rst: . testing Message-ID: <20061114134906.328C5101E0@code0.codespeak.net> Author: guido Date: Tue Nov 14 14:49:04 2006 New Revision: 34597 Added: py/dist/py/rst/testing/test_transform.py py/dist/py/rst/transform.py Modified: py/dist/py/rst/rst.py py/dist/py/rst/testing/test_rst.py Log: Added first bits of an event based Rest tree transformation thingie (a la SAX but then for converting an rst.Rest tree to e.g. HTML), fixed support for nested dlist items. Modified: py/dist/py/rst/rst.py ============================================================================== --- py/dist/py/rst/rst.py (original) +++ py/dist/py/rst/rst.py Tue Nov 14 14:49:04 2006 @@ -268,6 +268,15 @@ def text(self): idepth = self.get_indent_depth() indent = self.indent + (idepth + 1) * ' ' + txt = '\n'.join(self.render_children(indent)) + ret = [] + if idepth: + ret.append('\n') + item_char = self.item_chars[idepth] + ret += [indent[len(item_char)+1:], item_char, ' ', txt[len(indent):]] + return ''.join(ret) + + def render_children(self, indent): txt = [] for child in self.children: if isinstance(child, AbstractText): @@ -276,13 +285,7 @@ txt.append(p.text()) else: txt.append(child.text()) - txt = '\n'.join(txt) - ret = [] - if idepth: - ret.append('\n') - item_char = self.item_chars[idepth] - ret += [indent[2:], item_char, ' ', txt[len(indent):]] - return ''.join(ret) + return txt def get_indent_depth(self): depth = 0 @@ -293,14 +296,24 @@ return depth - 1 class OrderedListItem(ListItem): - item_chars = ("#.",) + item_chars = ["#."] * 5 class DListItem(ListItem): item_chars = None def __init__(self, term, definition, *args, **kwargs): - self.item_chars = ('%s\n ' % (term,),) + self.term = term super(DListItem, self).__init__(definition, *args, **kwargs) + def text(self): + idepth = self.get_indent_depth() + indent = self.indent + (idepth + 1) * ' ' + txt = '\n'.join(self.render_children(indent)) + ret = [] + if idepth: + ret.append('\n') + ret += [indent[2:], self.term, '\n', txt] + return ''.join(ret) + class Link(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 Tue Nov 14 14:49:04 2006 @@ -284,21 +284,6 @@ assert txt == expected checkrest(txt) -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.')) - checkrest(txt) - def test_definition_list(): expected = """\ foo @@ -313,6 +298,27 @@ assert txt == expected checkrest(txt) +def test_nested_dlists(): + expected = """\ +foo + bar baz + + qux + quux +""" + txt = Rest(DListItem('foo', 'bar baz', DListItem('qux', 'quux'))).text() + assert txt == expected + +def test_nested_list_dlist(): + expected = """\ +* foo + + foobar + baz +""" + txt = Rest(ListItem('foo', DListItem('foobar', 'baz'))).text() + assert txt == expected + def test_transition(): txt = Rest(Paragraph('foo'), Transition(), Paragraph('bar')).text() assert txt == 'foo\n\n%s\n\nbar\n' % ('-' * 79,) @@ -354,3 +360,18 @@ Some paragraph content. """ +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.')) + checkrest(txt) + Added: py/dist/py/rst/testing/test_transform.py ============================================================================== --- (empty file) +++ py/dist/py/rst/testing/test_transform.py Tue Nov 14 14:49:04 2006 @@ -0,0 +1,38 @@ +import py +from py.__.rst.rst import * +from py.__.rst.transform import * + +def convert_to_html(tree): + handler = HTMLHandler() + t = RestTransformer(tree) + t.parse(handler) + return handler.html + +class HTMLHandler(py.__.rst.transform.HTMLHandler): + def startDocument(self): + pass + endDocument = startDocument + +def test_transform_basic_html(): + for rest, expected in ((Rest(Title('foo')), '

foo

'), + (Rest(Paragraph('foo')), '

foo

'), + (Rest(SubParagraph('foo')), + '

foo

'), + (Rest(LiteralBlock('foo\nbar')), + '
foo\nbar
'), + (Rest(Paragraph(Link('foo', + 'http://www.foo.com/'))), + '

foo

')): + html = convert_to_html(rest) + assert html == expected + +def test_transform_list_simple(): + rest = Rest(ListItem('foo'), ListItem('bar')) + html = convert_to_html(rest) + assert html == '' + +def test_transform_list_nested(): + rest = Rest(ListItem('foo'), ListItem('bar', ListItem('baz'))) + html = convert_to_html(rest) + assert html == '' + Added: py/dist/py/rst/transform.py ============================================================================== --- (empty file) +++ py/dist/py/rst/transform.py Tue Nov 14 14:49:04 2006 @@ -0,0 +1,163 @@ +from py.__.rst import rst + +class RestTransformer(object): + def __init__(self, tree): + self.tree = tree + self._titledepths = {} + self._listmarkers = [] + + def parse(self, handler): + handler.startDocument() + self.parse_nodes(self.tree.children, handler) + handler.endDocument() + + def parse_nodes(self, nodes, handler): + for node in nodes: + name = node.__class__.__name__ + if name == 'Rest': + continue + try: + getattr(self, 'handle_%s' % (name,))(node, handler) + except AttributeError: + # caused by the handler not implementing something (well, let's + # assume that at least ;) + py.std.sys.stderr.write('Warning: error handling node %s\n' % ( + node,)) + def handle_Title(self, node, handler): + depthkey = (node.abovechar, node.belowchar) + if depthkey not in self._titledepths: + if not self._titledepths: + depth = 1 + else: + depth = max(self._titledepths.values()) + 1 + self._titledepths[depthkey] = depth + else: + depth = self._titledepths[depthkey] + handler.startTitle(depth) + self.parse_nodes(node.children, handler) + handler.endTitle(depth) + + def handle_ListItem(self, node, handler): + # XXX oomph... + startlist = False + c = node.parent.children + nodeindex = c.index(node) + if nodeindex == 0: + startlist = True + else: + prev = c[nodeindex - 1] + if not isinstance(prev, rst.ListItem): + startlist = True + elif prev.indent < node.indent: + startlist = True + endlist = False + if nodeindex == len(c) - 1: + endlist = True + else: + next = c[nodeindex + 1] + if not isinstance(next, rst.ListItem): + endlist = True + elif next.indent < node.indent: + endlist = True + type = isinstance(node, rst.OrderedListItem) and 'o' or 'u' + handler.startListItem('u', startlist) + self.parse_nodes(node.children, handler) + handler.endListItem('u', endlist) + + def handle_Transition(self, node, handler): + handler.handleTransition() + + def handle_Paragraph(self, node, handler): + handler.startParagraph() + self.parse_nodes(node.children, handler) + handler.endParagraph() + + def handle_SubParagraph(self, node, handler): + handler.startSubParagraph() + self.parse_nodes(node.children, handler) + handler.endSubParagraph() + + def handle_LiteralBlock(self, node, handler): + handler.handleLiteralBlock(node._text) + + def handle_Text(self, node, handler): + handler.handleText(node._text) + + def handle_Em(self, node, handler): + handler.handleEm(node._text) + + def handle_Strong(self, node, handler): + handler.handleStrong(node._text) + + def handle_Quote(self, node, handler): + handler.handleQuote(node._text) + + def handle_Link(self, node, handler): + handler.handleLink(node._text, node.target) + +def entitize(txt): + for char, repl in (('&', 'amp'), ('>', 'gt'), ('<', 'lt'), ('"', 'quot'), + ("'", 'apos')): + txt = txt.replace(char, '&%s;' % (repl,)) + return txt + +class HTMLHandler(object): + def __init__(self, title='untitled rest document'): + self.title = title + self._data = [] + self._listdepth = 0 + + def startDocument(self): + self._data += ['', '', '%s' % (self.title,), + '', ''] + + def endDocument(self): + self._data += ['', ''] + + def startTitle(self, depth): + self._data.append('' % (depth,)) + + def endTitle(self, depth): + self._data.append('' % (depth,)) + + def startParagraph(self): + self._data.append('

') + + def endParagraph(self): + self._data.append('

') + + def startSubParagraph(self): + self._data.append('

') + + def endSubParagraph(self): + self._data.append('

') + + def handleLiteralBlock(self, text): + self._data.append('
%s
' % (entitize(text),)) + + def handleText(self, text): + self._data.append(entitize(text)) + + def handleEm(self, text): + self._data.append('%s' % (entitize(text),)) + + def startListItem(self, type, startlist): + if startlist: + nodename = type == 'o' and 'ol' or 'ul' + self._data.append('<%s>' % (nodename,)) + self._data.append('
  • ') + + def endListItem(self, type, closelist): + self._data.append('
  • ') + if closelist: + nodename = type == 'o' and 'ol' or 'ul' + self._data.append('' % (nodename,)) + + def handleLink(self, text, target): + self._data.append('%s' % (entitize(target), + entitize(text))) + + def _html(self): + return ''.join(self._data) + html = property(_html) + From arigo at codespeak.net Tue Nov 14 18:57:42 2006 From: arigo at codespeak.net (arigo at codespeak.net) Date: Tue, 14 Nov 2006 18:57:42 +0100 (CET) Subject: [py-svn] r34606 - py/dist/py/c-extension/greenlet Message-ID: <20061114175742.4CE2D101D5@code0.codespeak.net> Author: arigo Date: Tue Nov 14 18:57:34 2006 New Revision: 34606 Modified: py/dist/py/c-extension/greenlet/README.txt Log: Fix the README.txt of greenlets. Modified: py/dist/py/c-extension/greenlet/README.txt ============================================================================== --- py/dist/py/c-extension/greenlet/README.txt (original) +++ py/dist/py/c-extension/greenlet/README.txt Tue Nov 14 18:57:34 2006 @@ -9,9 +9,5 @@ Christian. In other words, if it works it is thanks to Christian's work, and if it crashes it is because of a bug of mine :-) -This code is going to be integrated in the 'py' lib. The test file only runs -if you got greenlets from there! See http://codespeak.net/py/ and try -'from py.magic import greenlet'. - -- Armin From arigo at codespeak.net Tue Nov 14 18:58:46 2006 From: arigo at codespeak.net (arigo at codespeak.net) Date: Tue, 14 Nov 2006 18:58:46 +0100 (CET) Subject: [py-svn] r34608 - py/dist/py/magic Message-ID: <20061114175846.BDC3A101E6@code0.codespeak.net> Author: arigo Date: Tue Nov 14 18:58:45 2006 New Revision: 34608 Modified: py/dist/py/magic/greenlet.py Log: Add support for using 'py.magic.greenlet' on programs that run on top of a stackless pypy (you just get _stackless.greenlet, then). Modified: py/dist/py/magic/greenlet.py ============================================================================== --- py/dist/py/magic/greenlet.py (original) +++ py/dist/py/magic/greenlet.py Tue Nov 14 18:58:45 2006 @@ -1,5 +1,10 @@ - -import py -gdir = py.path.local(py.__file__).dirpath() -path = gdir.join('c-extension', 'greenlet', 'greenlet.c') -greenlet = path.getpymodule().greenlet +import sys +if '_stackless' in sys.builtin_module_names: + # when running on top of a pypy with stackless support + from _stackless import greenlet +else: + # regular CPython (or pypy without stackless support, and then crash :-) + import py + gdir = py.path.local(py.__file__).dirpath() + path = gdir.join('c-extension', 'greenlet', 'greenlet.c') + greenlet = path.getpymodule().greenlet From guido at codespeak.net Wed Nov 15 09:54:56 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Wed, 15 Nov 2006 09:54:56 +0100 (CET) Subject: [py-svn] r34616 - in py/dist/py/rst: . testing Message-ID: <20061115085456.8324E10083@code0.codespeak.net> Author: guido Date: Wed Nov 15 09:54:54 2006 New Revision: 34616 Modified: py/dist/py/rst/testing/test_transform.py py/dist/py/rst/transform.py Log: Adding newlines. Modified: py/dist/py/rst/testing/test_transform.py ============================================================================== --- py/dist/py/rst/testing/test_transform.py (original) +++ py/dist/py/rst/testing/test_transform.py Wed Nov 15 09:54:54 2006 @@ -14,25 +14,26 @@ endDocument = startDocument def test_transform_basic_html(): - for rest, expected in ((Rest(Title('foo')), '

    foo

    '), - (Rest(Paragraph('foo')), '

    foo

    '), + for rest, expected in ((Rest(Title('foo')), '

    foo

    \n'), + (Rest(Paragraph('foo')), '

    foo

    \n'), (Rest(SubParagraph('foo')), - '

    foo

    '), + '

    foo

    \n'), (Rest(LiteralBlock('foo\nbar')), - '
    foo\nbar
    '), + '
    foo\nbar
    \n'), (Rest(Paragraph(Link('foo', 'http://www.foo.com/'))), - '

    foo

    ')): + '

    foo

    \n')): html = convert_to_html(rest) assert html == expected def test_transform_list_simple(): rest = Rest(ListItem('foo'), ListItem('bar')) html = convert_to_html(rest) - assert html == '' + assert html == '\n' def test_transform_list_nested(): rest = Rest(ListItem('foo'), ListItem('bar', ListItem('baz'))) html = convert_to_html(rest) - assert html == '' + assert html == ('\n') Modified: py/dist/py/rst/transform.py ============================================================================== --- py/dist/py/rst/transform.py (original) +++ py/dist/py/rst/transform.py Wed Nov 15 09:54:54 2006 @@ -108,32 +108,33 @@ self._listdepth = 0 def startDocument(self): - self._data += ['', '', '%s' % (self.title,), - '', ''] + self._data += ['\n', '\n', + '%s\n' % (self.title,), + '\n', '\n'] def endDocument(self): - self._data += ['', ''] + self._data += ['\n', '\n'] def startTitle(self, depth): self._data.append('' % (depth,)) def endTitle(self, depth): - self._data.append('' % (depth,)) + self._data.append('\n' % (depth,)) def startParagraph(self): self._data.append('

    ') def endParagraph(self): - self._data.append('

    ') + self._data.append('

    \n') def startSubParagraph(self): self._data.append('

    ') def endSubParagraph(self): - self._data.append('

    ') + self._data.append('

    \n') def handleLiteralBlock(self, text): - self._data.append('
    %s
    ' % (entitize(text),)) + self._data.append('
    %s
    \n' % (entitize(text),)) def handleText(self, text): self._data.append(entitize(text)) @@ -148,10 +149,10 @@ self._data.append('
  • ') def endListItem(self, type, closelist): - self._data.append('
  • ') + self._data.append('\n') if closelist: nodename = type == 'o' and 'ol' or 'ul' - self._data.append('' % (nodename,)) + self._data.append('\n' % (nodename,)) def handleLink(self, text, target): self._data.append('%s' % (entitize(target), From guido at codespeak.net Wed Nov 15 10:49:53 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Wed, 15 Nov 2006 10:49:53 +0100 (CET) Subject: [py-svn] r34617 - in py/dist/py: . apigen apigen/rest apigen/rest/testing rst Message-ID: <20061115094953.32FE110087@code0.codespeak.net> Author: guido Date: Wed Nov 15 10:49:47 2006 New Revision: 34617 Added: py/dist/py/apigen/rest/htmlhandlers.py py/dist/py/apigen/style.css Modified: py/dist/py/apigen/rest/genrest.py py/dist/py/apigen/rest/testing/test_rest.py py/dist/py/conftest.py py/dist/py/rst/transform.py Log: Integrated the HTML transformer from py.rst into apigen. Added simple stylesheet. Modified: py/dist/py/apigen/rest/genrest.py ============================================================================== --- py/dist/py/apigen/rest/genrest.py (original) +++ py/dist/py/apigen/rest/genrest.py Wed Nov 15 10:49:47 2006 @@ -62,12 +62,12 @@ def __init__(self, output=sys.stdout): self.output = output - def write_section(self, name, data): + def write_section(self, name, rest): 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") + self.output.write(rest.text() + "\n") def getlink(self, type, targetname, targetfilename): return '%s.txt' % (targetfilename,) @@ -79,9 +79,9 @@ else: self.directory = py.path.local(directory) - def write_section(self, name, data): + def write_section(self, name, rest): filename = '%s.txt' % (name,) - self.directory.ensure(filename).write(data) + self.directory.ensure(filename).write(rest.text()) def getlink(self, type, targetname, targetfilename): # we assume the result will get converted to HTML... @@ -93,8 +93,8 @@ self.fp = fpath.open('w+') self._defined_targets = [] - def write_section(self, name, data): - self.fp.write(data) + def write_section(self, name, rest): + self.fp.write(rest.text()) self.fp.flush() def getlink(self, type, targetname, targetbasename): @@ -113,6 +113,29 @@ targetname = targetname[:-1] return '#%s-%s' % (type, targetname) +class HTMLDirWriter(object): + def __init__(self, indexhandler, filehandler, directory=None): + self.indexhandler = indexhandler + self.filehandler = filehandler + if directory is None: + self.directory = py.test.ensuretemp('dirwriter') + else: + self.directory = py.path.local(directory) + + def write_section(self, name, rest): + from py.__.rst.transform import RestTransformer + if name == 'index': + handler = self.indexhandler + else: + handler = self.filehandler + h = handler(name) + t = RestTransformer(rest) + t.parse(h) + self.directory.ensure('%s.html' % (name,)).write(h.html) + + def getlink(self, type, targetname, targetfilename): + return '%s.html' % (targetfilename,) + class RestGen(object): def __init__(self, ds, linkgen, writer=PipeWriter()): #assert isinstance(linkgen, DirectPaste), ( @@ -130,7 +153,7 @@ modlist.insert(0, ['', classlist, funclist]) indexrest = self.build_index([t[0] for t in modlist]) - self.writer.write_section('index', Rest(*indexrest).text()) + self.writer.write_section('index', Rest(*indexrest)) self.build_modrest(modlist) @@ -141,40 +164,40 @@ if mname == '': mname = self.dsa.get_module_name() self.writer.write_section('module_%s' % (mname,), - Rest(*rest).text()) + Rest(*rest)) for cname, crest, cfunclist in classlist: self.writer.write_section('class_%s' % (cname,), - Rest(*crest).text()) + Rest(*crest)) for fname, frest, tbdata in cfunclist: self.writer.write_section('method_%s' % (fname,), - Rest(*frest).text()) + Rest(*frest)) for tbname, tbrest in tbdata: self.writer.write_section('traceback_%s' % (tbname,), - Rest(*tbrest).text()) + Rest(*tbrest)) for fname, frest, tbdata in funclist: self.writer.write_section('function_%s' % (fname,), - Rest(*frest).text()) + Rest(*frest)) for tbname, tbrest in tbdata: self.writer.write_section('traceback_%s' % (tbname,), - Rest(*tbrest).text()) + Rest(*tbrest)) 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()) + Rest(*rest)) for fname, rest in cfunclist: self.writer.write_section('method_%s' % (fname,), - Rest(*rest).text()) + Rest(*rest)) def build_funcrest(self, funclist): funcrest = self.build_functions(funclist) for fname, rest, tbdata in funcrest: self.writer.write_section('function_%s' % (fname,), - Rest(*rest).text()) + Rest(*rest)) for tbname, tbrest in tbdata: self.writer.write_section('traceback_%s' % (tbname,), - Rest(*tbrest).text()) + Rest(*tbrest)) def build_index(self, modules): rest = [Title('Index', abovechar='=', belowchar='=')] Added: py/dist/py/apigen/rest/htmlhandlers.py ============================================================================== --- (empty file) +++ py/dist/py/apigen/rest/htmlhandlers.py Wed Nov 15 10:49:47 2006 @@ -0,0 +1,39 @@ +from py.__.rst.transform import HTMLHandler, entitize + +class PageHandler(HTMLHandler): + def startDocument(self): + self._data += ['\n', '\n', + '%s\n' % (self.title,), + '', + (''), + '\n', '\n'] + +class IndexHandler(PageHandler): + ignore_text = False + + def startDocument(self): + super(IndexHandler, self).startDocument() + self._data += ['', '
    ', + ('', + '
    '] + + def startTitle(self, depth): + self.ignore_text = True + + def endTitle(self, depth): + self.ignore_text = False + + def handleText(self, text): + if self.ignore_text: + return + super(IndexHandler, self).handleText(text) + + def handleLink(self, text, target): + self._data.append('%s' % ( + entitize(target), entitize(text))) 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 Nov 15 10:49:47 2006 @@ -7,12 +7,14 @@ from py.__.apigen.rest.genrest import ViewVC, RestGen, PipeWriter, \ DirWriter, FileWriter, \ - DirectPaste, DirectFS + DirectPaste, DirectFS, \ + HTMLDirWriter from py.__.apigen.tracer.tracer import Tracer from py.__.apigen.tracer.docstorage import DocStorage from py.__.apigen.tracer.testing.runtest import cut_pyc from py.__.documentation.conftest import genlinkchecks +from py.__.rst.rst import Rest, Paragraph # XXX: UUuuuuuuuuuuuuuuuuuuuuuuu, dangerous import def setup_module(mod): @@ -72,8 +74,8 @@ 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') + dw.write_section('foo', Rest(Paragraph('foo data'))) + dw.write_section('bar', Rest(Paragraph('bar data'))) return dw class TestDirWriter(WriterTest): @@ -83,8 +85,8 @@ 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' + assert tempdir.join('foo.txt').read() == 'foo data\n' + assert tempdir.join('bar.txt').read() == 'bar data\n' def test_getlink(self): dw = DirWriter(temppath.join('dirwriter_getlink')) @@ -120,6 +122,16 @@ link = pw.getlink('function', 'Foo.bar', 'method_foo_bar') assert link == 'method_foo_bar.txt' +class TestHTMLDirWriter(WriterTest): + def test_write_section(self): + from py.__.rst.transform import HTMLHandler + tempdir = temppath.ensure('htmldirwriter', dir=1) + hdw = self.get_filled_writer(HTMLDirWriter, HTMLHandler, HTMLHandler, + tempdir) + assert tempdir.join('foo.html').check(file=1) + assert tempdir.join('bar.html').check(file=1) + assert tempdir.join('foo.html').read().startswith('') + class TestRest(object): def get_filled_docstorage(self): descs = {'SomeClass': SomeClass, Added: py/dist/py/apigen/style.css ============================================================================== --- (empty file) +++ py/dist/py/apigen/style.css Wed Nov 15 10:49:47 2006 @@ -0,0 +1,31 @@ +#sidebar { + width: 9em; + float: left; +} + +#main { + margin-left: 9em; +} + +#content { + border: 0px; +} + +body, div, p, h1, h2, h3, h4 { + font-family: Trebuchet MS, Verdana, Arial; + background-color: #FFE; + color: black; +} + +a { + color: #006; + text-decoration: none; +} + +ul { + padding-left: 2em; +} + +ul li { + list-style-type: katakana; +} Modified: py/dist/py/conftest.py ============================================================================== --- py/dist/py/conftest.py (original) +++ py/dist/py/conftest.py Wed Nov 15 10:49:47 2006 @@ -16,13 +16,14 @@ showlocals = False nomagic = False -import py -Option = py.test.Config.Option +import py +Option = py.test.Config.Option -option = py.test.Config.addoptions("execnet options", +option = py.test.Config.addoptions("execnet options", Option('-S', '', action="store", dest="sshtarget", default=None, - help="target to run tests requiring ssh, e.g. user at codespeak.net"), + help=("target to run tests requiring ssh, e.g. " + "user at codespeak.net")), ) class ApiGen: @@ -32,6 +33,12 @@ get_doc_storage = staticmethod(get_doc_storage) def write_docs(ds): - from py.__.apigen.rest.genrest import DirectPaste, RestGen, DirWriter - RestGen(ds, DirectPaste(), DirWriter("/tmp/output")).write() + from py.__.apigen.rest.genrest import DirectPaste, RestGen, \ + HTMLDirWriter + from py.__.apigen.rest.htmlhandlers import IndexHandler, PageHandler + outdir = py.path.local('/tmp/output') + RestGen(ds, DirectPaste(), + HTMLDirWriter(IndexHandler, PageHandler, outdir)).write() + if not outdir.join('style.css').check(): + py.magic.autopath().dirpath().join('apigen/style.css').copy(outdir) write_docs = staticmethod(write_docs) Modified: py/dist/py/rst/transform.py ============================================================================== --- py/dist/py/rst/transform.py (original) +++ py/dist/py/rst/transform.py Wed Nov 15 10:49:47 2006 @@ -1,3 +1,4 @@ +import py from py.__.rst import rst class RestTransformer(object): @@ -16,13 +17,7 @@ name = node.__class__.__name__ if name == 'Rest': continue - try: - getattr(self, 'handle_%s' % (name,))(node, handler) - except AttributeError: - # caused by the handler not implementing something (well, let's - # assume that at least ;) - py.std.sys.stderr.write('Warning: error handling node %s\n' % ( - node,)) + getattr(self, 'handle_%s' % (name,))(node, handler) def handle_Title(self, node, handler): depthkey = (node.abovechar, node.belowchar) if depthkey not in self._titledepths: From fijal at codespeak.net Wed Nov 15 11:59:28 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Wed, 15 Nov 2006 11:59:28 +0100 (CET) Subject: [py-svn] r34619 - py/dist/py/test Message-ID: <20061115105928.2DD2C1005A@code0.codespeak.net> Author: fijal Date: Wed Nov 15 11:59:23 2006 New Revision: 34619 Modified: py/dist/py/test/cmdline.py Log: Added more relaxed option handling. Modified: py/dist/py/test/cmdline.py ============================================================================== --- py/dist/py/test/cmdline.py (original) +++ py/dist/py/test/cmdline.py Wed Nov 15 11:59:23 2006 @@ -12,22 +12,24 @@ args = args.split(" ") config, args = py.test.Config.parse(args) sessionclass = config.getsessionclass() - session = sessionclass(config) # ok, some option checks - if config.option.startserver: - from py.__.test.rsession.rsession import AbstractSession - if not isinstance(session, AbstractSession): - print "Cannot use web server without (R|L)Session" - raise SystemExit, 2 + if config.option.startserver or config.option.runbrowser: + from py.__.test.rsession.rsession import AbstractSession, LSession + if not issubclass(sessionclass, AbstractSession): + print "Cannot use web server without (R|L)Session, using lsession" + sessionclass = LSession if config.option.apigen: - from py.__.test.rsession.rsession import AbstractSession - if not isinstance(session, AbstractSession): - print "Cannot generate API without (R|L)Session" - raise SystemExit, 2 + from py.__.test.rsession.rsession import AbstractSession, LSession + if not issubclass(sessionclass, AbstractSession): + sessionclass = LSession + print "Cannot generate API without (R|L)Session, using lsession" + + session = sessionclass(config) + if config.option.runbrowser and not config.option.startserver: print "Cannot point browser when not starting server" - raise SystemExit, 2 + config.option.startserver = True try: if config.getinitialvalue('startserver'): py.std.warnings.warn("Startserver flag in config is deprecated, use commandline option istead") From fijal at codespeak.net Wed Nov 15 11:59:38 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Wed, 15 Nov 2006 11:59:38 +0100 (CET) Subject: [py-svn] r34620 - py/dist/py/test Message-ID: <20061115105938.B4FE110072@code0.codespeak.net> Author: fijal Date: Wed Nov 15 11:59:36 2006 New Revision: 34620 Modified: py/dist/py/test/collect.py Log: Deholgerising Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Wed Nov 15 11:59:36 2006 @@ -454,8 +454,8 @@ name = "[%d]" % i #XXX name = "%s(%r)" %(call.__name__, str(args and args[0] or i)) # # XXX d[name] = self.Function(name, self, args, obj=call, sort_value = i) - return d - + return d + def getcallargs(self, obj): if isinstance(obj, (tuple, list)): call, args = obj[0], obj[1:] From fijal at codespeak.net Wed Nov 15 11:59:57 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Wed, 15 Nov 2006 11:59:57 +0100 (CET) Subject: [py-svn] r34621 - py/dist/py/test/rsession/testing Message-ID: <20061115105957.668FF1006F@code0.codespeak.net> Author: fijal Date: Wed Nov 15 11:59:54 2006 New Revision: 34621 Modified: py/dist/py/test/rsession/testing/test_web.py Log: Don't use pdb by default 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 Wed Nov 15 11:59:54 2006 @@ -18,5 +18,5 @@ from py.__.test.rsession import webjs from py.__.test.rsession.web import FUNCTION_LIST - source = rpython2javascript(webjs, FUNCTION_LIST, Options) + source = rpython2javascript(webjs, FUNCTION_LIST, Options, use_pdb=False) assert source From fijal at codespeak.net Wed Nov 15 12:00:17 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Wed, 15 Nov 2006 12:00:17 +0100 (CET) Subject: [py-svn] r34622 - py/dist/py/test Message-ID: <20061115110017.8281110075@code0.codespeak.net> Author: fijal Date: Wed Nov 15 12:00:13 2006 New Revision: 34622 Modified: py/dist/py/test/config.py Log: Cache config paths. Modified: py/dist/py/test/config.py ============================================================================== --- py/dist/py/test/config.py (original) +++ py/dist/py/test/config.py Wed Nov 15 12:00:13 2006 @@ -26,6 +26,8 @@ class Config(object): """ central hub for dealing with configuration/initialization data. """ Option = optparse.Option + configs_cache = {} + values_cache = {} def __init__(self): self.option = optparse.Values() @@ -41,13 +43,25 @@ """ return 'name' value looked up from the first conftest file found up the path (including the path itself). """ + #try: + # return cls.values_cache[name] + #except KeyError: + # pass configpaths = guessconfigpaths(path) - if trydefaultconfig: + if trydefaultconfig: configpaths.append(defaultconfig) - for p in configpaths: - mod = importconfig(p) - if hasattr(mod, name): - return getattr(mod, name) + for p in configpaths: + try: + mod = cls.configs_cache[p] + except KeyError: + mod = importconfig(p) + cls.configs_cache[p] = mod + try: + retval = getattr(mod, name) + cls.values_cache[name] = retval + return retval + except AttributeError: + pass if default is not dummy: return default raise ValueError("config value not found: %r, path=%r" % (name, path)) From fijal at codespeak.net Wed Nov 15 12:27:57 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Wed, 15 Nov 2006 12:27:57 +0100 (CET) Subject: [py-svn] r34623 - py/dist/py/test Message-ID: <20061115112757.4FD0710092@code0.codespeak.net> Author: fijal Date: Wed Nov 15 12:27:42 2006 New Revision: 34623 Modified: py/dist/py/test/collect.py Log: Speedup generator test collection. Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Wed Nov 15 12:27:42 2006 @@ -445,6 +445,11 @@ Function = property(Function) class Generator(Collector): + def run(self): + self._prepare() + itemlist = self._name2items + return [itemlist["[%d]" % num].name for num in xrange(len(itemlist))] + def buildname2items(self): d = {} for i, x in py.builtin.enumerate(self.obj()): From guido at codespeak.net Wed Nov 15 13:19:43 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Wed, 15 Nov 2006 13:19:43 +0100 (CET) Subject: [py-svn] r34624 - in py/dist/py/apigen/rest: . testing Message-ID: <20061115121943.2F38C10092@code0.codespeak.net> Author: guido Date: Wed Nov 15 13:19:41 2006 New Revision: 34624 Modified: py/dist/py/apigen/rest/genrest.py py/dist/py/apigen/rest/htmlhandlers.py py/dist/py/apigen/rest/testing/test_rest.py Log: Removing headers of empty sections, made casing more consistently applied, improved title in index.html (a bit). Modified: py/dist/py/apigen/rest/genrest.py ============================================================================== --- py/dist/py/apigen/rest/genrest.py (original) +++ py/dist/py/apigen/rest/genrest.py Wed Nov 15 13:19:41 2006 @@ -200,8 +200,8 @@ Rest(*tbrest)) def build_index(self, modules): - rest = [Title('Index', abovechar='=', belowchar='=')] - rest.append(Title('Exported modules:', belowchar='=')) + rest = [Title('index', abovechar='=', belowchar='=')] + rest.append(Title('exported modules:', belowchar='=')) for module in modules: mtitle = module if module == '': @@ -218,23 +218,25 @@ mname = module if mname == '': mname = self.dsa.get_module_name() - rest = [Title('Module: %s' % (mname,), abovechar='=', + 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, - 'class_%s' % (cls,)) - rest.append(ListItem(Link(cls, linktarget))) + Title('index:', belowchar='=')] + if classes: + 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: - if module: - func = '%s.%s' % (module, func) - linktarget = self.writer.getlink('function', - func, - 'function_%s' % (func,)) - rest.append(ListItem(Link(func, linktarget))) + if functions: + rest.append(Title('functions:', belowchar='^')) + for func in functions: + if module: + func = '%s.%s' % (module, func) + linktarget = self.writer.getlink('function', + func, + 'function_%s' % (func,)) + rest.append(ListItem(Link(func, linktarget))) funcrest = self.build_functions(functions, module, False) ret.append((module, rest, classrest, funcrest)) return ret @@ -242,14 +244,17 @@ def build_classes(self, classes): 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', - '%s.%s' % (cls, func), - 'method_%s.%s' % (cls, func)) - rest.append(ListItem(Link('%s.%s' % (cls, func), linktarget))) + rest = [Title('class: %s' % (cls,), belowchar='='), + LiteralBlock(self.dsa.get_doc(cls))] + if functions: + rest.append(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('%s.%s' % (cls, func), + linktarget))) funcrest = self.build_functions(functions, cls, True) ret.append((cls, rest, funcrest)) return ret @@ -327,17 +332,16 @@ # from indentation, or treated as ReST too (although this is obviously # dangerous for non-ReST docstrings)... if ismethod: - title = 'Method: %s' % (functionname,) + title = 'method: %s' % (functionname,) else: - title = 'Function: %s' % (functionname,) + title = 'function: %s' % (functionname,) lst = [Title(title, belowchar=belowchar), LiteralBlock(self.dsa.get_doc(functionname)), LiteralBlock(self.dsa.get_function_definition(functionname))] lst.append(Paragraph("where:")) - args, retval = self.dsa.get_function_signature(functionname) - for name, _type in args + [('Return value', retval)]: + for name, _type in args + [('return value', retval)]: l = self.process_type_link(_type) items = [] next = "%s :: " % name @@ -354,14 +358,18 @@ lst.append(ListItem(*items)) local_changes = self.dsa.get_function_local_changes(functionname) - lst.append(Paragraph('Changes in __dict__:')) - for k, changeset in local_changes.iteritems(): - lst.append(ListItem('%s: %s' % (k, ', '.join(changeset)))) - - lst.append(Paragraph('Exceptions that might appear in function body:')) - for exc in self.dsa.get_function_exceptions(functionname): - lst.append(ListItem(exc.__name__)) - # XXX: right now we leave it alone + if local_changes: + lst.append(Paragraph('changes in __dict__ after execution:')) + for k, changeset in local_changes.iteritems(): + lst.append(ListItem('%s: %s' % (k, ', '.join(changeset)))) + + exceptions = self.dsa.get_function_exceptions(functionname) + if exceptions: + lst.append(Paragraph('exceptions that might appear during ' + 'execution:')) + for exc in exceptions: + lst.append(ListItem(exc.__name__)) + # XXX: right now we leave it alone # XXX missing implementation of dsa.get_function_location() #filename, lineno = self.dsa.get_function_location(functionname) @@ -370,22 +378,27 @@ # lst.append(Paragraph("Function source: ", # Link(linkname, linktarget))) #else: - lst.append(Paragraph('Function source:')) - lst.append(LiteralBlock(self.dsa.get_function_source(functionname))) + source = self.dsa.get_function_source(functionname) + if source: + lst.append(Paragraph('function source:')) + lst.append(LiteralBlock(source)) # call sites.. - call_site_title = Title("Call sites:", belowchar='+') - lst.append(call_site_title) - - # 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 + call_sites = self.dsa.get_function_callpoints(functionname) tbrest = [] - for call_site, _ in self.dsa.get_function_callpoints(functionname): - fdata, tbdata = self.call_site_link(functionname, call_site) - lst += fdata - tbrest.append(tbdata) + if call_sites: + call_site_title = Title("call sites:", belowchar='+') + lst.append(call_site_title) + + # 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 call_sites: + fdata, tbdata = self.call_site_link(functionname, call_site) + lst += fdata + tbrest.append(tbdata) return lst, tbrest @@ -395,15 +408,15 @@ 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,), + frest = [Paragraph("called in %s" % call_site[0].code.filename), + Paragraph(Link("traceback %s" % (tbname,), linktarget))] return frest, (tbname, tbrest) 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,))] + tbrest = [Title('traceback for %s' % (funcname,))] for line in reversed(call_site): lineno = line.lineno - line.code.firstlineno linkname, linktarget = self.linkgen.getlink(line.code.filename, @@ -415,7 +428,7 @@ try: source = line.code.source() except IOError: - source = "*Cannot get source*" + source = "*cannot get source*" mangled = [] for i, sline in enumerate(str(source).split('\n')): if i == lineno: @@ -425,3 +438,4 @@ mangled.append(line) tbrest.append(LiteralBlock('\n'.join(mangled))) return tbid, tbrest + Modified: py/dist/py/apigen/rest/htmlhandlers.py ============================================================================== --- py/dist/py/apigen/rest/htmlhandlers.py (original) +++ py/dist/py/apigen/rest/htmlhandlers.py Wed Nov 15 13:19:41 2006 @@ -3,7 +3,7 @@ class PageHandler(HTMLHandler): def startDocument(self): self._data += ['\n', '\n', - '%s\n' % (self.title,), + 'api reference\n', '', (' -1 - assert data.find('Classes\\:') < data.find('Function\\: fun') - assert data.find('Classes\\:') < data.find( - 'Class\\: SomeClass') + 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( - 'Method\\: SomeClass.method') + assert data.find('class\\: SomeClass') < data.find( + 'method\\: SomeClass.method') # __init__ should be above other methods - assert data.find('Method\\: SomeClass.\\_\\_init\\_\\_') > -1 - assert data.find('Method\\: SomeClass.\\_\\_init\\_\\_') < data.find( - 'Method\\: 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_} + descs = {'fun_': fun_} ds = DocStorage().from_dict(descs) t = Tracer(ds) t.start_tracing() @@ -292,7 +292,7 @@ b = 4 return a + b - descs = {'blah':blah} + descs = {'blah': blah} ds = DocStorage().from_dict(descs) t = Tracer(ds) t.start_tracing() @@ -323,7 +323,7 @@ r = RestGen(ds, lg, DirWriter(tempdir)) r.write() source = tempdir.join("function_blah.txt").read() - call_point = source.find("Call sites\:") + call_point = source.find("call sites\:") assert call_point != -1 assert source.find("a \:\: ") < call_point assert source.find("b \:\: ") < call_point @@ -354,9 +354,10 @@ r = RestGen(ds, lg, DirWriter(tempdir)) r.write() source = tempdir.join("function_xxx.txt").open().read() - call_point = source.find("Call sites\:") + 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) def test_exc_raising(self): @@ -377,4 +378,5 @@ r = RestGen(ds, lg, DirWriter(tempdir)) r.write() source = tempdir.join('function_x.txt').open().read() - assert source.find('ZeroDivisionError') < source.find('Call sites\:') + assert source.find('ZeroDivisionError') < source.find('call sites\:') + From fijal at codespeak.net Wed Nov 15 14:56:11 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Wed, 15 Nov 2006 14:56:11 +0100 (CET) Subject: [py-svn] r34628 - py/dist/py/apigen/rest Message-ID: <20061115135611.09FB010094@code0.codespeak.net> Author: fijal Date: Wed Nov 15 14:56:10 2006 New Revision: 34628 Modified: py/dist/py/apigen/rest/genrest.py Log: Fix order of traceback Modified: py/dist/py/apigen/rest/genrest.py ============================================================================== --- py/dist/py/apigen/rest/genrest.py (original) +++ py/dist/py/apigen/rest/genrest.py Wed Nov 15 14:56:10 2006 @@ -417,7 +417,7 @@ tbid = len(self.tracebacks.setdefault(funcname, [])) self.tracebacks[funcname].append(call_site) tbrest = [Title('traceback for %s' % (funcname,))] - for line in reversed(call_site): + for line in call_site: lineno = line.lineno - line.code.firstlineno linkname, linktarget = self.linkgen.getlink(line.code.filename, line.lineno + 1) From fijal at codespeak.net Wed Nov 15 14:56:29 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Wed, 15 Nov 2006 14:56:29 +0100 (CET) Subject: [py-svn] r34629 - in py/dist/py/apigen/tracer: . testing Message-ID: <20061115135629.BCD4410098@code0.codespeak.net> Author: fijal Date: Wed Nov 15 14:56:28 2006 New Revision: 34629 Modified: py/dist/py/apigen/tracer/docstorage.py py/dist/py/apigen/tracer/testing/test_docgen.py Log: Add simple idea how to iterate over base classes. Modified: py/dist/py/apigen/tracer/docstorage.py ============================================================================== --- py/dist/py/apigen/tracer/docstorage.py (original) +++ py/dist/py/apigen/tracer/docstorage.py Wed Nov 15 14:56:28 2006 @@ -254,3 +254,11 @@ def get_function_exceptions(self, name): return sorted(self.ds.descs[name].exceptions.keys()) + + def get_possible_base_classes(self, cls): + retval = [] + for base in cls.__bases__: + for desc in self.ds.descs.values(): + if isinstance(desc, ClassDesc) and desc.pyobj is base: + retval.append(desc) + return retval 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 Wed Nov 15 14:56:28 2006 @@ -7,6 +7,7 @@ #try: from py.__.apigen.tracer.tracer import DocStorage, Tracer +from py.__.apigen.tracer.docstorage import DocStorageAccessor from py.__.apigen.tracer.testing.runtest import cut_pyc from py.__.apigen.tracer.description import FunctionDesc from py.__.apigen.tracer import model @@ -216,3 +217,17 @@ assert ds.descs['x'].exceptions.keys() == [ZeroDivisionError] assert ds.descs['y'].exceptions.keys() == [ZeroDivisionError] assert ds.descs['z'].exceptions.keys() == [] + +def test_bases(): + class A: + pass + + class B: + pass + + class C(A,B): + pass + + ds = DocStorage().from_dict({'C':C, 'B':B}) + dsa = DocStorageAccessor(ds) + assert dsa.get_possible_base_classes(C) == [ds.descs['B']] From fijal at codespeak.net Wed Nov 15 18:43:06 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Wed, 15 Nov 2006 18:43:06 +0100 (CET) Subject: [py-svn] r34638 - py/dist/py/test Message-ID: <20061115174306.8E7A21009D@code0.codespeak.net> Author: fijal Date: Wed Nov 15 18:43:04 2006 New Revision: 34638 Modified: py/dist/py/test/cmdline.py py/dist/py/test/defaultconftest.py Log: Typos, spotted by Pierre Rouleau Modified: py/dist/py/test/cmdline.py ============================================================================== --- py/dist/py/test/cmdline.py (original) +++ py/dist/py/test/cmdline.py Wed Nov 15 18:43:04 2006 @@ -32,7 +32,7 @@ config.option.startserver = True try: if config.getinitialvalue('startserver'): - py.std.warnings.warn("Startserver flag in config is deprecated, use commandline option istead") + py.std.warnings.warn("Startserver flag in config is deprecated, use commandline option instead") except ValueError: pass Modified: py/dist/py/test/defaultconftest.py ============================================================================== --- py/dist/py/test/defaultconftest.py (original) +++ py/dist/py/test/defaultconftest.py Wed Nov 15 18:43:04 2006 @@ -15,7 +15,7 @@ py.test.Config.addoptions('general options', Option('-v', '--verbose', action="count", dest="verbose", default=0, - help="increase verbosity"), + help="increase verbosity."), Option('-x', '--exitfirst', action="store_true", dest="exitfirst", default=False, help="exit instantly on first error or failed test."), @@ -24,33 +24,33 @@ help="disable catching of sys.stdout/stderr output."), 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)"), + help="show locals in tracebacks (disabled by default)."), Option('', '--pdb', action="store_true", dest="usepdb", default=False, help="start pdb (the Python debugger) on errors."), Option('', '--tb', action="store", dest="tbstyle", default='long', type="choice", choices=['long', 'short', 'no'], - help="traceback verboseness (long/short/no)"), + help="traceback verboseness (long/short/no)."), Option('', '--fulltrace', action="store_true", dest="fulltrace", default=False, - help="don't cut any tracebacks (default is to cut)"), + 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. "), + 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") + "__package__ for module or overwritten in conftest.") ) py.test.Config.addoptions('test-session related options', Option('', '--tkinter', @@ -64,14 +64,14 @@ help="use given sessionclass, default is terminal."), Option('', '--exec', action="store", dest="executable", default=None, - help="python executable to run the tests with. "), + 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:8000 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" + help="Run browser to point to your freshly started web server." ), ) From fijal at codespeak.net Thu Nov 16 09:54:21 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Thu, 16 Nov 2006 09:54:21 +0100 (CET) Subject: [py-svn] r34650 - py/dist/py/test/testing Message-ID: <20061116085421.17F38100AB@code0.codespeak.net> Author: fijal Date: Thu Nov 16 09:54:19 2006 New Revision: 34650 Modified: py/dist/py/test/testing/test_collect.py Log: Added simple test for inequality. Modified: py/dist/py/test/testing/test_collect.py ============================================================================== --- py/dist/py/test/testing/test_collect.py (original) +++ py/dist/py/test/testing/test_collect.py Thu Nov 16 09:54:19 2006 @@ -376,3 +376,16 @@ """)) col = py.test.collect.Directory(tmp) py.test.raises(KeyboardInterrupt, list, col.tryiter()) + +def test_check_random_inequality(): + tmp = py.test.ensuretemp("ineq") + tmp.ensure("test_x.py").write(py.code.Source("""def test_one(): + pass + """)) + col = py.test.collect.Directory(tmp) + fn = col.tryiter().next() + assert fn != 3 + assert fn != col + assert fn != [1,2,3] + assert [1,2,3] != fn + assert col != fn From fijal at codespeak.net Thu Nov 16 10:04:08 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Thu, 16 Nov 2006 10:04:08 +0100 (CET) Subject: [py-svn] r34651 - py/dist/py/test Message-ID: <20061116090408.CD3E2100B5@code0.codespeak.net> Author: fijal Date: Thu Nov 16 10:04:05 2006 New Revision: 34651 Modified: py/dist/py/test/defaultconftest.py Log: Typo. Modified: py/dist/py/test/defaultconftest.py ============================================================================== --- py/dist/py/test/defaultconftest.py (original) +++ py/dist/py/test/defaultconftest.py Thu Nov 16 10:04:05 2006 @@ -49,7 +49,7 @@ 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"\ + 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', From guido at codespeak.net Thu Nov 16 10:29:08 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Thu, 16 Nov 2006 10:29:08 +0100 (CET) Subject: [py-svn] r34652 - in py/dist/py/apigen/tracer: . testing Message-ID: <20061116092908.03684100BE@code0.codespeak.net> Author: guido Date: Thu Nov 16 10:29:03 2006 New Revision: 34652 Modified: py/dist/py/apigen/tracer/description.py py/dist/py/apigen/tracer/docstorage.py py/dist/py/apigen/tracer/testing/test_desc.py py/dist/py/apigen/tracer/testing/test_docgen.py py/dist/py/apigen/tracer/tracer.py Log: Added support for finding out a method's origin (class on which it was defined or overriden), improved base classes handling a bit, fixed too long lines. Modified: py/dist/py/apigen/tracer/description.py ============================================================================== --- py/dist/py/apigen/tracer/description.py (original) +++ py/dist/py/apigen/tracer/description.py Thu Nov 16 10:29:03 2006 @@ -235,9 +235,21 @@ 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('__'))] + l = [i for i, v in self.fields.iteritems() if (not i.startswith('_') + or i.startswith('__'))] return l + + def getbases(self): + bases = [] + tovisit = [self.pyobj] + while tovisit: + current = tovisit.pop() + if current is not self.pyobj: + bases.append(current) + tovisit += [b for b in current.__bases__ if b not in bases] + return bases + bases = property(getbases) + ## def has_code(self, code): ## # check __init__ method ## return self.pyobj.__init__.im_func.func_code is code Modified: py/dist/py/apigen/tracer/docstorage.py ============================================================================== --- py/dist/py/apigen/tracer/docstorage.py (original) +++ py/dist/py/apigen/tracer/docstorage.py Thu Nov 16 10:29:03 2006 @@ -7,8 +7,8 @@ import sys import types -from py.__.apigen.tracer.description import FunctionDesc, ClassDesc, MethodDesc, \ - Desc +from py.__.apigen.tracer.description import FunctionDesc, ClassDesc, \ + MethodDesc, Desc from py.__.apigen.tracer import model @@ -179,10 +179,12 @@ return [i for i, desc in self.ds.descs.iteritems() if filter(i, desc)] def get_function_names(self): - return sorted(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 sorted(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 @@ -199,7 +201,8 @@ def get_function_signature(self, name): desc = self.ds.descs[name] # we return pairs of (name, type) here - names = desc.pyobj.func_code.co_varnames[:desc.pyobj.func_code.co_argcount] + names = desc.pyobj.func_code.co_varnames[ + :desc.pyobj.func_code.co_argcount] types = desc.inputcells return zip(names, types), desc.retval @@ -255,10 +258,38 @@ def get_function_exceptions(self, name): return sorted(self.ds.descs[name].exceptions.keys()) - def get_possible_base_classes(self, cls): + def get_method_origin(self, name): + method = self.ds.descs[name].pyobj + cls = method.im_class + if not cls.__bases__: + return self.desc_from_pyobj(cls) + curr = cls + while curr: + for base in curr.__bases__: + basefunc = getattr(base, method.im_func.func_name, None) + if (basefunc is not None and hasattr(basefunc, 'im_func') and + hasattr(basefunc.im_func, 'func_code') and + basefunc.im_func.func_code is + method.im_func.func_code): + curr = base + break + else: + break + return self.desc_from_pyobj(curr) + + def get_possible_base_classes(self, name): + cls = self.ds.descs[name].pyobj + if not hasattr(cls, '__bases__'): + return [] retval = [] for base in cls.__bases__: - for desc in self.ds.descs.values(): - if isinstance(desc, ClassDesc) and desc.pyobj is base: - retval.append(desc) + desc = self.desc_from_pyobj(base) + if desc is not None: + retval.append(desc) return retval + + def desc_from_pyobj(self, pyobj): + for desc in self.ds.descs.values(): + if isinstance(desc, ClassDesc) and desc.pyobj is pyobj: + return desc + Modified: py/dist/py/apigen/tracer/testing/test_desc.py ============================================================================== --- py/dist/py/apigen/tracer/testing/test_desc.py (original) +++ py/dist/py/apigen/tracer/testing/test_desc.py Thu Nov 16 10:29:03 2006 @@ -23,3 +23,7 @@ assert hash(ClassDesc("b", B).code) assert hash(ClassDesc("c", C).code) assert hash(ClassDesc("d", D).code) + +def test_eq(): + py.test.skip('fijal, please fix ;)') + assert ClassDesc('a', A) == ClassDesc('a', A) 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 Thu Nov 16 10:29:03 2006 @@ -66,6 +66,11 @@ """ return "z" +class ANotherClass(AClass): + def another_exposed_method(self, a): + # no docstring + return a + def test_class(): descs = {'AClass':AClass} ds = DocStorage().from_dict(descs) @@ -144,8 +149,10 @@ 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) + 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): @@ -218,6 +225,23 @@ assert ds.descs['y'].exceptions.keys() == [ZeroDivisionError] assert ds.descs['z'].exceptions.keys() == [] +def test_subclass(): + descs = {'ANotherClass': ANotherClass} + ds = DocStorage().from_dict(descs) + t = Tracer(ds) + t.start_tracing() + s = ANotherClass('blah blah') + s.another_exposed_method(1) + t.end_tracing() + desc = ds.descs['ANotherClass'] + assert len(desc.fields) == 4 + inputcells = desc.fields['__init__'].inputcells + assert len(inputcells) == 2 + inputcells = desc.fields['another_exposed_method'].inputcells + assert len(inputcells) == 2 + bases = desc.bases + assert len(bases) == 2 + def test_bases(): class A: pass @@ -230,4 +254,34 @@ ds = DocStorage().from_dict({'C':C, 'B':B}) dsa = DocStorageAccessor(ds) - assert dsa.get_possible_base_classes(C) == [ds.descs['B']] + assert dsa.get_possible_base_classes('C') == [ds.descs['B']] + +def test_desc_from_pyobj(): + class A: + pass + + class B(A): + pass + + ds = DocStorage().from_dict({'A': A, 'B': B}) + dsa = DocStorageAccessor(ds) + assert dsa.desc_from_pyobj(A) is ds.descs['A'] + +def test_method_origin(): + class A: + def foo(self): + pass + + class B(A): + def bar(self): + pass + + class C(B): + pass + + ds = DocStorage().from_dict({'C': C, 'B': B}) + dsa = DocStorageAccessor(ds) + origin = dsa.get_method_origin('C.bar') + assert origin is ds.descs['B'] + assert dsa.get_method_origin('C.foo') is None + Modified: py/dist/py/apigen/tracer/tracer.py ============================================================================== --- py/dist/py/apigen/tracer/tracer.py (original) +++ py/dist/py/apigen/tracer/tracer.py Thu Nov 16 10:29:03 2006 @@ -52,3 +52,4 @@ def end_tracing(self): self.tracing = False sys.settrace(None) + From fijal at codespeak.net Thu Nov 16 10:39:28 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Thu, 16 Nov 2006 10:39:28 +0100 (CET) Subject: [py-svn] r34653 - in py/dist/py/apigen/tracer: . testing Message-ID: <20061116093928.75ABD100B9@code0.codespeak.net> Author: fijal Date: Thu Nov 16 10:39:26 2006 New Revision: 34653 Modified: py/dist/py/apigen/tracer/description.py py/dist/py/apigen/tracer/testing/test_desc.py Log: Fix for guido's test. Modified: py/dist/py/apigen/tracer/description.py ============================================================================== --- py/dist/py/apigen/tracer/description.py (original) +++ py/dist/py/apigen/tracer/description.py Thu Nov 16 10:39:26 2006 @@ -103,9 +103,9 @@ def __eq__(self, other): if isinstance(other, Desc): - return self.code is other.code + return self.code == other.code if isinstance(other, types.CodeType): - return self.code is other + return self.code == other if isinstance(other, tuple) and len(other) == 2: return self.code == other return False Modified: py/dist/py/apigen/tracer/testing/test_desc.py ============================================================================== --- py/dist/py/apigen/tracer/testing/test_desc.py (original) +++ py/dist/py/apigen/tracer/testing/test_desc.py Thu Nov 16 10:39:26 2006 @@ -25,5 +25,4 @@ assert hash(ClassDesc("d", D).code) def test_eq(): - py.test.skip('fijal, please fix ;)') assert ClassDesc('a', A) == ClassDesc('a', A) From guido at codespeak.net Thu Nov 16 12:24:39 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Thu, 16 Nov 2006 12:24:39 +0100 (CET) Subject: [py-svn] r34657 - in py/dist/py/rst: . testing Message-ID: <20061116112439.5A40B100BF@code0.codespeak.net> Author: guido Date: Thu Nov 16 12:24:33 2006 New Revision: 34657 Modified: py/dist/py/rst/rst.py py/dist/py/rst/testing/test_rst.py Log: Fixed newline handling in nested lists. Modified: py/dist/py/rst/rst.py ============================================================================== --- py/dist/py/rst/rst.py (original) +++ py/dist/py/rst/rst.py Thu Nov 16 12:24:33 2006 @@ -268,23 +268,31 @@ def text(self): idepth = self.get_indent_depth() indent = self.indent + (idepth + 1) * ' ' - txt = '\n'.join(self.render_children(indent)) + txt = '\n\n'.join(self.render_children(indent)) ret = [] - if idepth: - ret.append('\n') item_char = self.item_chars[idepth] ret += [indent[len(item_char)+1:], item_char, ' ', txt[len(indent):]] return ''.join(ret) def render_children(self, indent): txt = [] + buffer = [] + def render_buffer(fro, to): + if not fro: + return + p = Paragraph(indent=indent, *fro) + p.parent = self.parent + to.append(p.text()) for child in self.children: if isinstance(child, AbstractText): - p = Paragraph(child, indent=indent) - p.parent = self.parent - txt.append(p.text()) + buffer.append(child) else: + if buffer: + render_buffer(buffer, txt) + buffer = [] txt.append(child.text()) + + render_buffer(buffer, txt) return txt def get_indent_depth(self): @@ -307,10 +315,8 @@ def text(self): idepth = self.get_indent_depth() indent = self.indent + (idepth + 1) * ' ' - txt = '\n'.join(self.render_children(indent)) + txt = '\n\n'.join(self.render_children(indent)) ret = [] - if idepth: - ret.append('\n') ret += [indent[2:], self.term, '\n', txt] return ''.join(ret) 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 Nov 16 12:24:33 2006 @@ -238,6 +238,12 @@ assert txt == expected checkrest(txt) +def test_list_item_multiple_args(): + expected = "* foo bar baz\n" + txt = Rest(ListItem('foo', 'bar', 'baz')).text() + assert txt == expected + checkrest(txt) + def test_list_multiline(): expected = """\ * Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Vestibulum @@ -284,6 +290,24 @@ assert txt == expected checkrest(txt) +def test_nested_nested_lists(): + expected = """\ +* foo + + + bar + + - baz + + + qux + +* quux +""" + txt = Rest(ListItem('foo', ListItem('bar', ListItem('baz')), + ListItem('qux')), ListItem('quux')).text() + print txt + assert txt == expected + checkrest(txt) + def test_definition_list(): expected = """\ foo From guido at codespeak.net Thu Nov 16 12:30:16 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Thu, 16 Nov 2006 12:30:16 +0100 (CET) Subject: [py-svn] r34659 - in py/dist/py/apigen/rest: . testing Message-ID: <20061116113016.213AC100C6@code0.codespeak.net> Author: guido Date: Thu Nov 16 12:30:12 2006 New Revision: 34659 Modified: py/dist/py/apigen/rest/genrest.py py/dist/py/apigen/rest/testing/test_rest.py Log: Displaying method origin (class where a method is defined) and base classes (if known). Modified: py/dist/py/apigen/rest/genrest.py ============================================================================== --- py/dist/py/apigen/rest/genrest.py (original) +++ py/dist/py/apigen/rest/genrest.py Thu Nov 16 12:30:12 2006 @@ -223,7 +223,7 @@ Title('index:', belowchar='=')] if classes: rest.append(Title('classes:', belowchar='^')) - for cls, cfunclist in classes: + for cls, bases, cfunclist in classes: linktarget = self.writer.getlink('class', cls, 'class_%s' % (cls,)) rest.append(ListItem(Link(cls, linktarget))) @@ -243,12 +243,18 @@ def build_classes(self, classes): ret = [] - for cls, functions in classes: + for cls, bases, functions in classes: rest = [Title('class: %s' % (cls,), belowchar='='), LiteralBlock(self.dsa.get_doc(cls))] + if bases: + rest.append(Title('base classes:', belowchar='^')), + for base in bases: + linktarget = self.writer.getlink('class', base.name, + 'class_%s' % (base.name,)) + rest.append(ListItem(Link(base.name, linktarget))) if functions: rest.append(Title('functions:', belowchar='^')) - for func in functions: + for (func, origin) in functions: linktarget = self.writer.getlink('method', '%s.%s' % (cls, func), 'method_%s.%s' % (cls, @@ -262,9 +268,13 @@ def build_functions(self, functions, parent='', methods=False): ret = [] for function in functions: + origin = None + if methods: + function, origin = function if parent: function = '%s.%s' % (parent, function) - rest, tbrest = self.write_function(function, ismethod=methods) + rest, tbrest = self.write_function(function, origin=origin, + ismethod=methods) ret.append((function, rest, tbrest)) return ret @@ -291,7 +301,8 @@ continue elif module != '': continue - ret.append((name, self.get_method_list(name))) + bases = self.dsa.get_possible_base_classes(name) + ret.append((name, bases, self.get_method_list(name))) return ret def get_function_list(self, module=''): @@ -308,7 +319,9 @@ return ret def get_method_list(self, classname): - return self.dsa.get_class_methods(classname) + methodnames = self.dsa.get_class_methods(classname) + return [(mn, self.dsa.get_method_origin('%s.%s' % (classname, mn))) + for mn in methodnames] def process_type_link(self, _type): # now we do simple type dispatching and provide a link in this case @@ -327,18 +340,28 @@ lst.append(Link(str(_type), linktarget)) return lst - def write_function(self, functionname, ismethod=False, belowchar='-'): + def write_function(self, functionname, origin=None, 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)... if ismethod: - title = 'method: %s' % (functionname,) + title = Title('method: %s' % (functionname,), belowchar=belowchar) else: - title = 'function: %s' % (functionname,) - lst = [Title(title, belowchar=belowchar), - LiteralBlock(self.dsa.get_doc(functionname)), + title = Title('function: %s' % (functionname,), + belowchar=belowchar) + lst = [title, LiteralBlock(self.dsa.get_doc(functionname)), LiteralBlock(self.dsa.get_function_definition(functionname))] + opar = Paragraph('origin: ') + if origin: + linktarget = self.writer.getlink('class', origin.name, + 'class_%s' % (origin.name,)) + opar.add(Link(origin.name, linktarget)) + else: + opar.add(Text('')) + lst.append(opar) + lst.append(Paragraph("where:")) args, retval = self.dsa.get_function_signature(functionname) for name, _type in args + [('return value', retval)]: 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 Thu Nov 16 12:30:12 2006 @@ -272,6 +272,11 @@ assert data.find('method\\: SomeClass.\\_\\_init\\_\\_') > -1 assert data.find('method\\: SomeClass.\\_\\_init\\_\\_') < data.find( 'method\\: SomeClass.method') + # base class info + assert py.std.re.search(r'class\\\: SomeSubClass.*' + r'base classes\\\:\n\^+[\n ]+\* `SomeClass`_.*' + r'`SomeSubClass.__init__', + data, py.std.re.S) def test_som_fun(self): descs = {'fun_': fun_} @@ -334,15 +339,18 @@ class A(object): def __init__(self, x): pass + + def a(self): + pass - class B(object): + class B(A): def __init__(self, y): pass def xxx(x): return x - descs = {'A':A, 'B':B, 'xxx':xxx} + descs = {'A': A, 'B': B, 'xxx':xxx} ds = DocStorage().from_dict(descs) t = Tracer(ds) t.start_tracing() @@ -353,11 +361,14 @@ tempdir = temppath.ensure("classargs", dir=True) r = RestGen(ds, lg, DirWriter(tempdir)) r.write() - source = tempdir.join("function_xxx.txt").open().read() + source = tempdir.join("function_xxx.txt").read() call_point = source.find("call sites\:") assert call_point != -1 - assert source.find("x \:\: ") < call_point + print source + assert -1 < source.find("x \:\: ") < call_point + source = tempdir.join('method_B.a.txt').read() + assert source.find('origin\: `A`_') > -1 self.check_rest(tempdir) def test_exc_raising(self): From guido at codespeak.net Thu Nov 16 14:32:30 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Thu, 16 Nov 2006 14:32:30 +0100 (CET) Subject: [py-svn] r34667 - py/dist/py/documentation Message-ID: <20061116133230.CE887100CE@code0.codespeak.net> Author: guido Date: Thu Nov 16 14:32:28 2006 New Revision: 34667 Added: py/dist/py/documentation/apigen.txt Log: Document describing the current implementation of apigen (fijal, please review!), needs to be merged with the older documents describing ideas in apigen/ at some point, for now however it provides a basic explanation of wat apigen is and how to use it. Added: py/dist/py/documentation/apigen.txt ============================================================================== --- (empty file) +++ py/dist/py/documentation/apigen.txt Thu Nov 16 14:32:28 2006 @@ -0,0 +1,117 @@ +apigen - API documentation generation tool +=========================================== + +What is it? +------------ + +Apigen is a tool for automatically generating API reference documentation for +Python projects. It distinguishes itself from comparable tools by examining +code at runtime, rather than at compile time. This way it is capable of +displaying more information about registered and discovered classes and +functions (although it may find less of them). The tool can either be used +from code, or from py.test, in the latter case it will gather information +about modules registered using `initpkg`_. + +Using from code +---------------- + +The library provides a simple API to generate a py.rest.rst tree (which +can be converted to ReStructuredText), along with some helper classes to +control the output. The most important objects are the Tracer, which traces +code execution by registering itself with sys.settrace, the DocStorage class, +that stores Tracer information, and the RestGen class which creates a ReST +tree (see py.rest.rst). + +Gathering information +++++++++++++++++++++++ + +To gather information about documentation, you will first need to tell the tool +what objects it should investigate. Only information for registered objects +will be stored. An example:: + + >>> import py + >>> from py.__.apigen.tracer.docstorage import DocStorage + >>> from py.__.apigen.tracer.tracer import Tracer + >>> toregister = {'py.path.local': py.path.local, + ... 'py.path.svnwc': py.path.svnwc} + >>> ds = DocStorage().from_dict(toregister) + >>> t = Tracer(ds) + >>> t.start_tracing() + >>> p = py.path.local('.') + >>> p.check(dir=True) + True + >>> t.end_tracing() + +Now the 'ds' variable should contain all kinds of information about both the +py.path.local and the py.path.svnwc class (it will walk through 'toregister' to +find information about all it contains), and things like call stacks, and +possible argument types, etc. as additional information about +py.path.local.check() (since it was called from the traced code). + +Viewing information +++++++++++++++++++++ + +Viewing the information stored in the DocStorage instance isn't very hard +either. As explained there is a RestGen class that creates a py.rest.rst tree, +which can directly be serialized to ReStructuredText, which can in turn be +converted to other formats. Also the py.rest.rst tree can be manipulated +directly, or converted to formats other than ReST (currently only HTML) using +special transformers. + +There are several helper classes available that wrap the output format +generation. There are two types of helper classes, 'LinkWriters' and 'Writers'. +The first are responsible for generating external links (for viewing source), +the second for generating the actual output from the py.rest.rst tree, and +for generating internal links (which is directly related to generating output). +Instances of these classes are passed to the RestGen class as arguments on +instantiation. + +An example of creating a directory with seperate ReST files (using DirWriter) +from the 'ds' DocumentStorage instance we created below, without any external +links (using DirectPaste). +:: + + >>> from py.__.apigen.rest.genrest import RestGen, DirectPaste, DirWriter + >>> # create a temp dir in /tmp/pytest- + >>> tempdir = py.test.ensuretemp('apigen_example') + >>> rg = RestGen(ds, DirectPaste(), DirWriter(tempdir)) + >>> rg.write() + +An example of a somewhat more 'real-life' use case, writing to a directory of +HTML files (this uses py.rest.transform), generating links to ViewVC source +views:: + + >>> from py.__.apigen.rest.genrest import ViewVC, HTMLDirWriter + >>> from py.__.apigen.rest.htmlhandlers import HTMLHandler + >>> tempdir = py.test.ensuretemp('apigen_example_2') + >>> rg = RestGen(ds, ViewVC('http://some.host.com/viewvc/myproj/trunk/'), + ... HTMLDirWriter(HTMLHandler, HTMLHandler, tempdir)) + >>> rg.write() + +Using from py.test +------------------- + +XXX: fijal, please help me here a bit ;) + +Running unit tests forms an ideal opportunity for apigen to find out about what +happens when code is executed (assuming you have proper test coverage ;). There +are hooks built into py.test that allow you to do that: just write a simple +class called 'ApiGen' in your conftest (see the `py.test documentation`_ for +more details about conftest files) that implements a set of special methods +called 'get_doc_storage()' and 'write_docs()', that essentially implement the +functionality discussed above. Calling py.test with an --apigen (only works +when --session is used) argument will cause the methods to be called. + +For an example, see the 'conftest.py' file in the py lib itself. To see it in +action you run `py.test --session=L --apigen` in the root of the py lib; this +will result in documentation (in HTML) being written to /tmp/output. + +Questions, remarks, etc. +------------------------- + +For more information, questions, remarks, etc. see http://codespeak.net/py. +This website also contains links to mailing list and IRC channel. + +.. _`initpkg`: http://codespeak.net/svn/py/dist/py/initpkg.py +.. _`py.test documentation`: http://codespeak.net/svn/py/dist/py/documentation/test.txt + From guido at codespeak.net Thu Nov 16 14:57:02 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Thu, 16 Nov 2006 14:57:02 +0100 (CET) Subject: [py-svn] r34668 - in py/dist/py/rest: . testing Message-ID: <20061116135702.A725F100CF@code0.codespeak.net> Author: guido Date: Thu Nov 16 14:57:00 2006 New Revision: 34668 Removed: py/dist/py/rest/rst.py py/dist/py/rest/testing/test_rst.py Log: Removing unused py.rest.rst files, to be replaced with the stuff from py.rst (which is used already). Deleted: /py/dist/py/rest/rst.py ============================================================================== --- /py/dist/py/rest/rst.py Thu Nov 16 14:57:00 2006 +++ (empty file) @@ -1,285 +0,0 @@ -from __future__ import generators - -import py -from py.xml import Namespace, Tag -#from py.__.xmlobj.visit import SimpleUnicodeVisitor -#from py.__.xmlobj.html import HtmlVisitor - - -def itertext(text): - """ Generator: like string.split, but ''.join(itertext(t)) == t - - >>> list(itertext('word\n second word')) - ['word', '\n', ' ', 'second', ' ', 'word'] - """ - state_word = 'not isspace()' - state_whitespace = '\t\n\x0b\x0c\r' - state_space = ' ' - def compute_state(char): - if char in state_whitespace: - return state_whitespace - if char == ' ': - return state_space - return state_word - - word = '' - state = None - for char in text: - if state is None: - # init - state = compute_state(char) - word = char - continue - - next_state = compute_state(char) - if state != state_whitespace and state == next_state: - word += char - continue - yield word - word = char - - state = next_state - yield word - - -class Out: - """ wrap text like textwarp.wrap, but preserves \n - - all lines are left aligned - arguments to write must be strings or iterable and - return strings of length 1 (chars) - """ - - def __init__(self, width=70): - self.width = width - self.lines = [''] - - def copy(self): - ' like copy, except self.lines = ['']' - return self.__class__(width = self.width) - - def write(self, arg, literal=False): - line = self.lines.pop() - for w in itertext(arg): - if w == '\n': - self.lines.append(line) - line = '' - continue - if literal or self.width is None or \ - line and not line[-1].isspace(): - line += w - continue - if not line and w == ' '*len(w): - continue - if len(line) + len(w) > self.width: - if line != '': - self.lines.append(line) - line = w.lstrip() - continue - line += w - - self.lines.append(line) - return self - - def writeln(self, arg='', literal=False): - self.write(arg, literal) - self.write('\n') - - def write_literal(self, arg): - self.write(arg, literal=True) - - def writeln_literal(self, arg): - self.writeln(arg, literal=True) - - def append(self, out, indent = '', join=''): - keepends = True - self.write_literal(join.join( - [indent + line - for line in out.render().splitlines(keepends)])) - - def extend(self, out_list, indent = '', infix = ' ', - join = '', literal = False): - l = list(out_list) - if not l: return - for out in l[:-1]: - self.append(out, indent, join=join) - self.write(infix, literal=literal) - self.append(l[-1], indent, join=join) - - def max_length(self): - return max([len(l) for l in self.lines]) - - def render(self): - return '\n'.join(self.lines) - - -class RestTag(Tag): - start_string = '' - end_string = '' - sep = '' - write_literal = False - - def __init__(self, *args, **kwargs): - super(RestTag, self).__init__(*args, **kwargs) - self.parse_options(self.attr) - - def parse_options(self, attr): - pass - - def text(self, width = 70): - out = Out(width = width) - self.__rest__(out) - return out.render() - - def __rest__(self, out): - out.write(self.sep) - out.write(self.start_string) - self.write_children(out, self.render_children(out)) - out.write(self.end_string) - out.write(self.sep) - - def write_children(self, out, child_outs): - out.extend(child_outs) - - def render_children(self, out): - outs = [] - for child in self: - child_out = out.copy() - if isinstance(child, RestTag): - child.__rest__(child_out) - else: - child_out.write(child, literal = self.write_literal) - outs.append(child_out) - return outs - -class DirectiveMetaclass(type): - def __getattr__(self, name): - if name[:1] == '_': - raise AttributeError(name) - # convert '_' to '-' - name = name.replace('_','-') - classattr = {'name': name} - cls = type(name, (self.__tagclass__,), classattr) - setattr(self, name, cls) - return cls - -class UniversalDirective(RestTag): - sep = '\n\n' - start_string = '.. ' - - def write_children(self, out, child_outs): - out.write(self.name) - out.write(':: ') - out.writeln(child_outs[0].render()) - keys = [attr for attr in dir(self.attr) if not attr.startswith('__')] - keys.sort() - for key in keys: - value = str(getattr(self.attr, key)) - out.write_literal(' ' * len(self.start_string) +':%s: %s\n' \ - % (key,value)) - - if len(child_outs) > 1: - # directive block - out.writeln(' ' * len(self.start_string)) - out.extend(child_outs[1:], indent = ' ' * len(self.start_string)) - - - -class rest(UniversalDirective): - __tagclass__ = RestTag - __stickname__ = True - - class hyperref(RestTag): - start_string = '`' - end_string = '`_' - write_literal = True - - class emph(RestTag): - start_string = '*' - end_string = start_string - - class strongemph(RestTag): - start_string = '**' - end_string = start_string - - class inline_literal(RestTag): - start_string = "``" - end_string = "``" - - class interpreted_text(RestTag): - start_string ='`' - end_string = '`' - role = '' - - def parse_options(self, attr): - self.role = getattr(attr, 'role', self.role) - if self.role: - self.start_string = ':%s:%s' % (self.role, self.start_string) - - class paragraph(RestTag): - sep = '\n\n' - - class explicit_markup(RestTag): - sep = '\n\n' - start_string = '.. ' - - def write_children(self, out, child_outs): - out.extend(child_outs, join = ' ' * len(self.start_string)) - - class hypertarget(RestTag): - sep = '\n\n' - start_string = '.. _' - end_string = ':' - write_literal = True - - class title(RestTag): - sep = '\n' - start_string = '#' - end_string = '#' - quotes = """! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~""".split() - - def parse_options(self, attr): - self.start_string = getattr(attr, 'overline', '#') - self.end_string = getattr(attr, 'underline', '#') - - def __rest__(self, out): - child_outs = self.render_children(out) - max_length = max([o.max_length() for o in child_outs]) - out.write(self.sep) - out.writeln_literal(self.start_string * max_length) - out.extend(child_outs) - out.writeln() - out.writeln_literal(self.end_string * max_length) - out.write(self.sep) - - class list_item(RestTag): - sep = '\n\n' - start_string = '* ' - - def parse_options(self, attr): - self.start_string = getattr(attr, 'bullet', '*') + ' ' - if getattr(attr, 'auto_enumerate', False): - self.start_string = '#. ' - - def write_children(self, out, child_outs): - out.extend(child_outs, join = ' ' * len(self.start_string)) - - class literal_block(RestTag): - sep = '\n\n' - start_string = '::\n\n' - indent = ' ' - quote = ' ' - quotes = """! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~""".split() + [' '] - write_literal = True - - def parse_options(self, attr): - self.quote = getattr(attr, 'quote', ' ') - - def write_children(self, out, child_outs): - out.extend(child_outs, indent = self.quote, infix ='', literal = True) - - unidirective = UniversalDirective - - class directive: - __metaclass__ = DirectiveMetaclass - __tagclass__ = UniversalDirective Deleted: /py/dist/py/rest/testing/test_rst.py ============================================================================== --- /py/dist/py/rest/testing/test_rst.py Thu Nov 16 14:57:00 2006 +++ (empty file) @@ -1,366 +0,0 @@ -import py -from py.__.rest.rst import rest, Out, itertext, RestTag -from py.__.misc import rest as pyrest - -try: - import docutils -except ImportError: - py.test.skip("docutils not present") - -temp = py.test.ensuretemp('check_rest') -#temp = py.path.local.mkdtemp() - -def check_rest(content, include_dir = None): - " try to convert content to html " - if isinstance(content, RestTag): - content = content.text() - content = unicode(content) - print content - tempdir = py.path.local.make_numbered_dir(rootdir=temp) - if include_dir is None: - include_dir = tempdir - #logging - tempdir.ensure('input.txt').write(content) - try: - output = pyrest.convert_rest_html(content, include_dir) - except: - fail_msg = ''' - failed to convert %s to html, probably not valid reStructuredText - see recorded stderr for error message''' - - py.test.fail(fail_msg % tempdir.join('input.txt') + '\n\n' + str(py.code.ExceptionInfo())) - tempdir.ensure('output.html').write(output) - return True - -def render_xml(arg): - 'try to generate a xml representation of arg' - return arg.unicode() - -def strip_lines(line_list): - 'filter line_list and remove trailing whitespaces' - return [line.rstrip() for line in line_list if line.strip()] - -def print_lines(line_list): - 'pretty print line_list' - py.std.pprint.pprint(strip_lines(line_list)) - -class TestSplit: - - def test_empyt_string(self): - assert list(itertext('')) == [''] - - def test_whitespace(self): - assert list(itertext(' ')) == [' '] - - def test_single_word(self): - assert list(itertext('word')) == ['word'] - - def test_word_with_whitespace(self): - assert list(itertext('word ')) == ['word', ' '] - - def test_two_words(self): - assert list(itertext('first second')) == ['first', ' ', 'second'] - - def test_trailing_newline(self): - assert list(itertext('\nfirst word')) == ['\n', 'first', ' ', 'word'] - def test_newline_and_space_are_seperated(self): - assert list(itertext('\n third_item')) == ['\n', ' ', 'third_item'] - -class TestOut: - - def test_write_nothing(self): - assert Out().write('').render() == '' - - def test_write_returns_self(self): - out = Out() - assert out.write('') is out - assert out.write('') is not Out().write('') - - def test_write_newline(self): - out = Out() - out.write('\n') - assert len(out.lines) == 2 - assert out.render() == '\n' - - def test_write_one_line(self): - text = "'[B]ut assume that I have some other use case' isn't a valid use case. - Fredrik Lundh" - out = Out(width=None) - out.write(text) - assert out.lines == [text] - assert out.render() == text - - def test_write_and_width(self): - text = "This sentence is 36 characters wide." - out = Out(width = 36) - out.write(text) - assert len(out.lines) == 1 - out = Out(width = 35) - out.write(text) - assert len(out.lines) == 2 - - def test_write_and_newline(self): - text = "1234567890\n1234567890" - out = Out(width=30) - out.write(text) - assert len(out.lines) == 2 - assert len(out.lines[0]) == 10 - assert out.render() == text - out.write(text) - assert len(out.lines) == 3 - - def test_write_with_trailing_newline(self): - text = "0123456789\n" - out = Out() - out.write(text) - assert len(out.lines) == 2 - assert out.render() == text - - def test_write_long_word(self): - text = '12345678901234567890' - out = Out(width=19) - out.write(text) - assert len(out.lines) == 1 - assert text == out.render() - out.write('1') - assert len(out.lines) == 1 - out.write(' 2') - assert len(out.lines) == 2 - - def test_long_literal_and_newline(self): - text = '12345678901234567890' - out = Out(width=10) - out.write_literal(text) - assert len(out.lines) == 1 - text += '\n1234567890' - out.write_literal(text) - assert len(out.lines) == 2 - - def test_append(self): - out = Out() - out.write('First line\n') - out.write('Second line') - - root_out = Out() - root_out.write('Root') - root_out.append(out) - - assert len(root_out.lines) == 2 - - def test_extend_empty_list(self): - out = Out() - text = out.render() - out.extend([]) - assert text == out.render() - - def test_max_length(self): - out = Out() - out.write('1234567890') - out.writeln() - out.write('123456789') - assert out.max_length() == 10 - - -class TestRest: - disabled = False - def setup_method(self, method): - self.text = {} - - self.text['paragraph'] = "Paragraphs consist of blocks of " - "left-aligned text with no markup indicating any other body " - "element. Blank lines separate paragraphs from each other and " - "from other body elements. Paragraphs may contain inline markup." - - def test_paragraph(self): - para = rest.paragraph(self.text['paragraph']) - print render_xml(para) - text = para.text() - check_rest(text) - assert text[0] == '\n' - assert text[-1] == '\n' - - def test_paragraph_with_whitespaces(self): - phrase = "Perhaps if we wrote programs from " - "childhood on, as adults " - " we'd be able to read them." - para = rest.paragraph(phrase) - check_rest(para) - assert True not in [line.startswith(' ') for line in para.text().splitlines()] - - def test_emph(self): - emph = rest.emph('strong') - assert emph.text() == '*strong*' - assert check_rest(emph.text()) - - def test_paragraph_adds_whitespace(self): - para = rest.paragraph('Starttext', rest.emph('EMPHASIS'), 'endtext') - assert para.text() == para.sep +'Starttext *EMPHASIS* endtext'+ para.sep - - def test_nested_emph(self): - "Nested Inline Markup not allowed in ReST, don't try at home ;-)" - emph = rest.emph('start', rest.emph('middle'), 'end') - check_rest(emph.text()) - assert emph.text() == '*start *middle* end*' - - def test_strongemph(self): - phrase = 'failure is not an option' - emph = rest.strongemph(phrase) - assert emph.text() == '**' + phrase + '**' - - def test_title(self): - phrase = 'Everything should be built top-down, except the first time.' - title = rest.title(phrase) - expected = title.sep + title.start_string * len(phrase) \ - +'\n' + phrase + '\n' + title.end_string *len(phrase)\ - + '\n' + title.sep - assert title.text() == expected - check_rest(title.text()) - - - def test_list_item(self): - item_text = 'A short item.' - item = rest.list_item(item_text) - assert item.text() == item.sep + item.start_string + item_text \ - + item.end_string + item.sep - check_rest(item) - - def test_list_item_multiline(self): - item_text = '01234567890 1234567890' - item = rest.list_item(item_text) - text = item.text(width = 15) - assert len([l for l in text.splitlines() if l.strip()]) == 2 - check_rest(text) - out = Out(width = 15) - item.__rest__(out) - assert out.max_length() <= 15 - - def test_list_item_custom_bullet(self): - item_text = '12345678901234567890' - item = rest.list_item(item_text, bullet='+') - assert item.text().strip()[0] == '+' - check_rest(item) - - def test_auto_enumerated_list(self): - item_text = '12345678901234567890' - item = rest.list_item(item_text, auto_enumerate = True) - assert item.text().strip()[0:2] == '#.' - check_rest(item) - - def test_literal_block(self): - text = '''\ - This line is only 45 characters wide. - This one is even longer (two spaces).\ - ''' - block = rest.literal_block(text) - assert block.text()[:6] == block.sep + '::' + block.sep - out = Out() - block.__rest__(out) - assert out.max_length() == len(block.quote) + max([len(l) for l in text.splitlines()]) - check_rest(block) - - block = rest.literal_block(text, quote= rest.literal_block.quotes[3]) - assert block.text().strip()[4] == rest.literal_block.quotes[3] - check_rest(block) - - - - def test_interpreted_text(self): - - itext = rest.interpreted_text('just text') - assert itext.text() == '`just text`' - itext_role = rest.interpreted_text('just text with role', role = 'red') - assert itext_role.text().startswith(':red:`just') - - def test_directive(self): - dir = rest.directive.image('img.png') - assert dir.text() == dir.sep + '.. image:: img.png\n' + dir.sep - check_rest(dir.text()) - - def test_directive_with_options(self): - expected = """\ -.. figure:: picture.png - :alt: map to buried treasure - :scale: 50 - - This is the caption of the figure (a simple paragraph). - - The legend consists of all elements after the caption. In this - case, the legend consists of this paragraph.""" - - legend = """ The legend consists of all elements after the caption. In this\n case, the legend consists of this paragraph.""" - - figure = rest.directive.figure('picture.png', rest.paragraph('This is the caption of the figure (a simple paragraph).'), rest.paragraph(legend), scale=50, alt= 'map to buried treasure') - - check_rest(expected) - check_rest(figure.text()) - print_lines(expected.splitlines()) - print_lines(figure.text().splitlines()) - - assert strip_lines(expected.splitlines()) == strip_lines(figure.text().splitlines()) - - - def test_directive_replace_underscore_in_directive_name(self): - # should we replace underscore in keyword arguments? - expected = '''\ -.. csv-table:: Frozen Delights! - :header: "Treat", "Quantity", "Description" - :widths: 15, 10, 30 - - Albatross,2.99,On a stick! - Crunchy Frog,1.49,"If we took the bones out, it wouldn't be crunchy, - now would it?" - Gannet Ripple,1.99,On a stick! -''' #" - - data =[["Albatross", 2.99, "On a stick!"], - ["Crunchy Frog", 1.49, "If we took the bones out, it wouldn't be crunchy, now would it?"], - ["Gannet Ripple", 1.99, "On a stick!"] - ] - - out = Out(width = None) - py.std.csv.writer(out).writerows(data) - text = out.render() - table = rest.directive.csv_table('Frozen Delights!', text, - header = '"Treat", "Quantity", "Description"', - widths = "15, 10, 30") - - print_lines(expected.splitlines()) - print_lines(table.text().splitlines()) - check_rest(expected) - check_rest(table.text()) - assert strip_lines(expected.splitlines()) == strip_lines(table.text().splitlines()) - - -## def test_block_quote(self): -## block ="""\ -## This is an ordinary paragraph, introducing a block quote. - -## "It is my business to know things. That is my trade." - -## -- Sherlock Holmes -## """ -## assert check_rest(block) - - - -## def test_quoted_line_block(self): -## text = """\ -## Take it away, Eric the Orchestra Leader! - -## | A one, two, a one two three four -## | -## | Half a bee, philosophically, -## | must, *ipso facto*, half not be. -## | But half the bee has got to be, -## | *vis a vis* its entity. D'you see? -## | -## | But can a bee be said to be -## | or not to be an entire bee, -## | when half the bee is not a bee, -## | due to some ancient injury? -## | -## | Singing... -## """ -## assert check_rest(text) - -#def test_temdir_output(): -# py.test.skip('tempdir is %s' % temp) From guido at codespeak.net Thu Nov 16 15:04:12 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Thu, 16 Nov 2006 15:04:12 +0100 (CET) Subject: [py-svn] r34669 - in py/dist/py: apigen/rest apigen/rest/testing rest rest/testing rst Message-ID: <20061116140412.C752C100D0@code0.codespeak.net> Author: guido Date: Thu Nov 16 15:04:09 2006 New Revision: 34669 Added: py/dist/py/rest/rst.py - copied unchanged from r34657, py/dist/py/rst/rst.py py/dist/py/rest/testing/test_rst.py - copied, changed from r34657, py/dist/py/rst/testing/test_rst.py py/dist/py/rest/testing/test_transform.py - copied, changed from r34657, py/dist/py/rst/testing/test_transform.py py/dist/py/rest/transform.py - copied, changed from r34657, py/dist/py/rst/transform.py Removed: py/dist/py/rst/ Modified: py/dist/py/apigen/rest/genrest.py py/dist/py/apigen/rest/htmlhandlers.py py/dist/py/apigen/rest/testing/test_rest.py Log: Moved py.rst stuff to py.rest and removed py.rst dir. Modified: py/dist/py/apigen/rest/genrest.py ============================================================================== --- py/dist/py/apigen/rest/genrest.py (original) +++ py/dist/py/apigen/rest/genrest.py Thu Nov 16 15:04:09 2006 @@ -8,8 +8,9 @@ import re from py.__.apigen.tracer.docstorage import DocStorageAccessor -from py.__.rst.rst import * # XXX Maybe we should list it here +from py.__.rest.rst import * # XXX Maybe we should list it here from py.__.apigen.tracer import model +from py.__.rest.transform import RestTransformer class AbstractLinkWriter(object): """ Class implementing writing links to source code. @@ -123,7 +124,6 @@ self.directory = py.path.local(directory) def write_section(self, name, rest): - from py.__.rst.transform import RestTransformer if name == 'index': handler = self.indexhandler else: Modified: py/dist/py/apigen/rest/htmlhandlers.py ============================================================================== --- py/dist/py/apigen/rest/htmlhandlers.py (original) +++ py/dist/py/apigen/rest/htmlhandlers.py Thu Nov 16 15:04:09 2006 @@ -1,4 +1,4 @@ -from py.__.rst.transform import HTMLHandler, entitize +from py.__.rest.transform import HTMLHandler, entitize class PageHandler(HTMLHandler): def startDocument(self): 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 Thu Nov 16 15:04:09 2006 @@ -14,7 +14,8 @@ from py.__.apigen.tracer.testing.runtest import cut_pyc from py.__.documentation.conftest import genlinkchecks -from py.__.rst.rst import Rest, Paragraph +from py.__.rest.rst import Rest, Paragraph +from py.__.rest.transform import HTMLHandler # XXX: UUuuuuuuuuuuuuuuuuuuuuuuu, dangerous import def setup_module(mod): @@ -124,7 +125,6 @@ class TestHTMLDirWriter(WriterTest): def test_write_section(self): - from py.__.rst.transform import HTMLHandler tempdir = temppath.ensure('htmldirwriter', dir=1) hdw = self.get_filled_writer(HTMLDirWriter, HTMLHandler, HTMLHandler, tempdir) Copied: py/dist/py/rest/testing/test_rst.py (from r34657, py/dist/py/rst/testing/test_rst.py) ============================================================================== --- py/dist/py/rst/testing/test_rst.py (original) +++ py/dist/py/rest/testing/test_rst.py Thu Nov 16 15:04:09 2006 @@ -2,7 +2,7 @@ """ rst generation tests """ -from py.__.rst.rst import * +from py.__.rest.rst import * from py.__.documentation.conftest import restcheck import traceback Copied: py/dist/py/rest/testing/test_transform.py (from r34657, py/dist/py/rst/testing/test_transform.py) ============================================================================== --- py/dist/py/rst/testing/test_transform.py (original) +++ py/dist/py/rest/testing/test_transform.py Thu Nov 16 15:04:09 2006 @@ -1,6 +1,6 @@ import py -from py.__.rst.rst import * -from py.__.rst.transform import * +from py.__.rest.rst import * +from py.__.rest.transform import * def convert_to_html(tree): handler = HTMLHandler() @@ -8,7 +8,7 @@ t.parse(handler) return handler.html -class HTMLHandler(py.__.rst.transform.HTMLHandler): +class HTMLHandler(py.__.rest.transform.HTMLHandler): def startDocument(self): pass endDocument = startDocument Copied: py/dist/py/rest/transform.py (from r34657, py/dist/py/rst/transform.py) ============================================================================== --- py/dist/py/rst/transform.py (original) +++ py/dist/py/rest/transform.py Thu Nov 16 15:04:09 2006 @@ -1,5 +1,5 @@ import py -from py.__.rst import rst +from py.__.rest import rst class RestTransformer(object): def __init__(self, tree): From arigo at codespeak.net Thu Nov 16 18:10:43 2006 From: arigo at codespeak.net (arigo at codespeak.net) Date: Thu, 16 Nov 2006 18:10:43 +0100 (CET) Subject: [py-svn] r34683 - py/dist/py/process Message-ID: <20061116171043.94380100FB@code0.codespeak.net> Author: arigo Date: Thu Nov 16 18:10:42 2006 New Revision: 34683 Modified: py/dist/py/process/cmdexec.py Log: (cfbolz, arigo) Fix the user-visible name of the py.process.cmdexec.Error exception. Modified: py/dist/py/process/cmdexec.py ============================================================================== --- py/dist/py/process/cmdexec.py (original) +++ py/dist/py/process/cmdexec.py Thu Nov 16 18:10:42 2006 @@ -154,4 +154,10 @@ else: cmdexec = posix_exec_cmd +# export the exception under the name 'py.process.cmdexec.Error' cmdexec.Error = ExecutionFailed +try: + ExecutionFailed.__module__ = 'py.process.cmdexec' + ExecutionFailed.__name__ = 'Error' +except (AttributeError, TypeError): + pass From fijal at codespeak.net Fri Nov 17 11:10:40 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Fri, 17 Nov 2006 11:10:40 +0100 (CET) Subject: [py-svn] r34702 - in py/dist/py: path/local test Message-ID: <20061117101040.75803100AB@code0.codespeak.net> Author: fijal Date: Fri Nov 17 11:10:38 2006 New Revision: 34702 Modified: py/dist/py/path/local/local.py py/dist/py/test/config.py Log: Added mtime() checks for caching. Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Fri Nov 17 11:10:38 2006 @@ -376,11 +376,11 @@ #print "trying to import", self pkgpath = None if modname is None: - if modname is None: - try: + try: + if self.module_mtime == self.mtime(): return self.module - except AttributeError: - pass + except AttributeError: + pass pkgpath = self.pypkgpath() if pkgpath is not None: if ensuresyspath: @@ -402,6 +402,7 @@ self._prependsyspath(self.dirpath()) modname = self.purebasename mod = __import__(modname, None, None, ['__doc__']) + self.module_mtime = self.mtime() self.module = mod return mod else: Modified: py/dist/py/test/config.py ============================================================================== --- py/dist/py/test/config.py (original) +++ py/dist/py/test/config.py Fri Nov 17 11:10:38 2006 @@ -43,19 +43,17 @@ """ return 'name' value looked up from the first conftest file found up the path (including the path itself). """ - #try: - # return cls.values_cache[name] - #except KeyError: - # pass configpaths = guessconfigpaths(path) if trydefaultconfig: configpaths.append(defaultconfig) for p in configpaths: try: - mod = cls.configs_cache[p] - except KeyError: - mod = importconfig(p) - cls.configs_cache[p] = mod + mtime, mod = cls.configs_cache[p] + if mtime != p.mtime(): + raise IndexError() + except (KeyError, IndexError): + mod = importconfig(p) + cls.configs_cache[p] = p.mtime(), mod try: retval = getattr(mod, name) cls.values_cache[name] = retval From fijal at codespeak.net Fri Nov 17 13:50:18 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Fri, 17 Nov 2006 13:50:18 +0100 (CET) Subject: [py-svn] r34707 - in py/dist/py: path/local test Message-ID: <20061117125018.2B8AB100A5@code0.codespeak.net> Author: fijal Date: Fri Nov 17 13:50:05 2006 New Revision: 34707 Modified: py/dist/py/path/local/local.py py/dist/py/test/config.py Log: Reverting mtime addition. Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Fri Nov 17 13:50:05 2006 @@ -377,8 +377,7 @@ pkgpath = None if modname is None: try: - if self.module_mtime == self.mtime(): - return self.module + return self.module except AttributeError: pass pkgpath = self.pypkgpath() @@ -402,7 +401,6 @@ self._prependsyspath(self.dirpath()) modname = self.purebasename mod = __import__(modname, None, None, ['__doc__']) - self.module_mtime = self.mtime() self.module = mod return mod else: Modified: py/dist/py/test/config.py ============================================================================== --- py/dist/py/test/config.py (original) +++ py/dist/py/test/config.py Fri Nov 17 13:50:05 2006 @@ -48,16 +48,12 @@ configpaths.append(defaultconfig) for p in configpaths: try: - mtime, mod = cls.configs_cache[p] - if mtime != p.mtime(): - raise IndexError() + mod = cls.configs_cache[p] except (KeyError, IndexError): mod = importconfig(p) - cls.configs_cache[p] = p.mtime(), mod + cls.configs_cache[p] = mod try: - retval = getattr(mod, name) - cls.values_cache[name] = retval - return retval + return getattr(mod, name) except AttributeError: pass if default is not dummy: @@ -204,7 +200,7 @@ l.append(x) l.reverse() _config_paths_cache[key] = l - return l + return l def getanchorpaths(args): """ yield "anchors" from skimming the args for existing files/dirs. """ From fijal at codespeak.net Fri Nov 17 15:59:55 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Fri, 17 Nov 2006 15:59:55 +0100 (CET) Subject: [py-svn] r34709 - py/dist/py/test/testing Message-ID: <20061117145955.954B6100A7@code0.codespeak.net> Author: fijal Date: Fri Nov 17 15:59:52 2006 New Revision: 34709 Modified: py/dist/py/test/testing/test_collect.py Log: Added failing test. Modified: py/dist/py/test/testing/test_collect.py ============================================================================== --- py/dist/py/test/testing/test_collect.py (original) +++ py/dist/py/test/testing/test_collect.py Fri Nov 17 15:59:52 2006 @@ -389,3 +389,23 @@ assert fn != [1,2,3] assert [1,2,3] != fn assert col != fn + +def test_check_generator_collect_problems(): + py.test.skip("Fails") + tmp = py.test.ensuretemp("gener_coll") + tmp.ensure("test_one.py").write(py.code.Source(""" + def setup_module(mod): + mod.x = [1,2,3] + + def check(zzz): + assert zzz + + def test_one(): + for i in x: + yield check, i + """)) + tmp.ensure("__init__.py") + col = py.test.collect.Module(tmp.join("test_one.py")) + errors = [] + l = list(col.tryiter(reporterror=errors.append)) + assert len(errors) == 0 From fijal at codespeak.net Fri Nov 17 16:47:31 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Fri, 17 Nov 2006 16:47:31 +0100 (CET) Subject: [py-svn] r34711 - py/dist/py/bin Message-ID: <20061117154731.34358100A3@code0.codespeak.net> Author: fijal Date: Fri Nov 17 16:47:29 2006 New Revision: 34711 Modified: py/dist/py/bin/_findpy.py Log: Moved print to stderr. Modified: py/dist/py/bin/_findpy.py ============================================================================== --- py/dist/py/bin/_findpy.py (original) +++ py/dist/py/bin/_findpy.py Fri Nov 17 16:47:29 2006 @@ -19,7 +19,7 @@ # if p == current: # return True if current != sys.path[0]: # if we are already first, then ok - print "inserting into sys.path:", current + print >>sys.stderr, "inserting into sys.path:", current sys.path.insert(0, current) return True current = opd(current) From fijal at codespeak.net Fri Nov 17 16:49:05 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Fri, 17 Nov 2006 16:49:05 +0100 (CET) Subject: [py-svn] r34712 - py/dist/py/test Message-ID: <20061117154905.20506100A3@code0.codespeak.net> Author: fijal Date: Fri Nov 17 16:49:03 2006 New Revision: 34712 Modified: py/dist/py/test/cmdline.py py/dist/py/test/defaultconftest.py Log: Added rest backend options. Modified: py/dist/py/test/cmdline.py ============================================================================== --- py/dist/py/test/cmdline.py (original) +++ py/dist/py/test/cmdline.py Fri Nov 17 16:49:03 2006 @@ -24,6 +24,10 @@ if not issubclass(sessionclass, AbstractSession): sessionclass = LSession print "Cannot generate API without (R|L)Session, using lsession" + if config.option.restreport: + from py.__.test.rsession.rsession import AbstractSession, LSession + if not issubclass(sessionclass, AbstractSession): + sessionclass = LSession session = sessionclass(config) Modified: py/dist/py/test/defaultconftest.py ============================================================================== --- py/dist/py/test/defaultconftest.py (original) +++ py/dist/py/test/defaultconftest.py Fri Nov 17 16:49:03 2006 @@ -73,5 +73,8 @@ action="store_true", dest="runbrowser", default=False, help="Run browser to point to your freshly started web server." ), + Option('-r', '--rest', + action='store_true', dest="restreport", default=False, + help="Use a ReST as an output reporting"), ) From fijal at codespeak.net Fri Nov 17 16:49:22 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Fri, 17 Nov 2006 16:49:22 +0100 (CET) Subject: [py-svn] r34713 - in py/dist/py/test/rsession: . testing Message-ID: <20061117154922.E8B38100A5@code0.codespeak.net> Author: fijal Date: Fri Nov 17 16:49:19 2006 New Revision: 34713 Added: py/dist/py/test/rsession/rest.py (contents, props changed) py/dist/py/test/rsession/testing/test_rest.py (contents, props changed) Modified: py/dist/py/test/rsession/reporter.py py/dist/py/test/rsession/rsession.py py/dist/py/test/rsession/testing/test_reporter.py Log: Added basics od rest backend. Modified: py/dist/py/test/rsession/reporter.py ============================================================================== --- py/dist/py/test/rsession/reporter.py (original) +++ py/dist/py/test/rsession/reporter.py Fri Nov 17 16:49:19 2006 @@ -28,6 +28,12 @@ #self.count = 0 #self.lgt = 1000 + def get_host(self, item): + if item.channel: + return item.channel.gateway.sshaddress + # XXX: Testing purposes only + return 'localhost' + def report(self, what): repfun = getattr(self, "report_" + what.__class__.__name__, self.report_unknown) @@ -194,6 +200,9 @@ total = total_passed + total_failed + total_skipped skipped_str = create_str("skipped", total_skipped) failed_str = create_str("failed", total_failed) + self.print_summary(total, skipped_str, failed_str) + + def print_summary(self, total, skipped_str, failed_str): 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)) @@ -230,13 +239,7 @@ def report_Nodes(self, event): self.nodes = event.nodes -class RemoteReporter(AbstractReporter): - def get_host(self, item): - if item.channel: - return item.channel.gateway.sshaddress - # XXX: Testing purposes only - return 'localhost' - +class RemoteReporter(AbstractReporter): def get_item_name(self, event, colitem): return self.get_host(event) + ":" + \ "/".join(colitem.listnames()) @@ -250,9 +253,6 @@ 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()) Added: py/dist/py/test/rsession/rest.py ============================================================================== --- (empty file) +++ py/dist/py/test/rsession/rest.py Fri Nov 17 16:49:19 2006 @@ -0,0 +1,52 @@ + +""" Rest reporting stuff +""" + +import py +import sys +from py.__.test.rsession.reporter import AbstractReporter +from py.__.rest.rst import * + +class RestReporter(AbstractReporter): + def report_unknown(self, what): + print "Unknown report: %s" % what + + def report_TestStarted(self, event): + txt = "Test started, hosts: %s" % ", ".join(event.hosts) + print Title(txt, abovechar='=', belowchar='=').text() + "\n" + self.timestart = event.timestart + + def report_ItemStart(self, event): + item = event.item + print + if isinstance(item, py.test.collect.Module): + lgt = len(list(item.tryiter())) + name = "/".join(item.listnames()) + txt = 'Testing module %s (%d items)' % (name, lgt) + print Title(txt, belowchar='-').text() + + def print_summary(self, total, skipped_str, failed_str): + print "\n" + txt = "%d test run%s%s in %.2fs (rsync: %.2f)" % \ + (total, skipped_str, failed_str, self.timeend - self.timestart, + self.timersync - self.timestart) + print Title(txt, belowchar="=").text() + + 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 ListItem(Text("%10s: %s %s" %(sshhost[:10], status, itempath))).text() + Modified: py/dist/py/test/rsession/rsession.py ============================================================================== --- py/dist/py/test/rsession/rsession.py (original) +++ py/dist/py/test/rsession/rsession.py Fri Nov 17 16:49:19 2006 @@ -112,12 +112,8 @@ 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 startserverflag = self.config.option.startserver + restflag = self.config.option.restreport checkfun = lambda: None if startserverflag and reporter is None: @@ -129,6 +125,9 @@ import webbrowser webbrowser.open("localhost:8000") elif reporter is None: + if restflag: + from py.__.test.rsession.rest import RestReporter + reporter_class = RestReporter if arg: reporter_instance = reporter_class(self.config, sshhosts, self.getpkgdir(arg)) else: 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 Nov 17 16:49:19 2006 @@ -14,10 +14,10 @@ import sys from StringIO import StringIO -def setup_module(mod): - mod.pkgdir = py.path.local(py.__file__).dirpath() - class AbstractTestReporter(object): + def setup_class(cls): + cls.pkgdir = py.path.local(py.__file__).dirpath() + def prepare_outcomes(self): # possible outcomes try: @@ -39,7 +39,7 @@ def report_received_item_outcome(self): config, args = py.test.Config.parse(["some_sub"]) # we just go... - rootcol = py.test.collect.Directory(pkgdir.dirpath()) + rootcol = py.test.collect.Directory(self.pkgdir.dirpath()) item = rootcol.getitembynames(funcpass_spec) outcomes = self.prepare_outcomes() @@ -59,7 +59,7 @@ def _test_module(self): config, args = py.test.Config.parse(["some_sub"]) # we just go... - rootcol = py.test.collect.Directory(pkgdir.dirpath()) + rootcol = py.test.collect.Directory(self.pkgdir.dirpath()) funcitem = rootcol.getitembynames(funcpass_spec) moditem = rootcol.getitembynames(mod_spec) outcomes = self.prepare_outcomes() @@ -74,7 +74,7 @@ s = StringIO() stdoutcopy = sys.stdout sys.stdout = s - boxfun(pkgdir, config, moditem, funcitem, outcomes) + boxfun(self.pkgdir, config, moditem, funcitem, outcomes) sys.stdout = stdoutcopy return s.getvalue() Added: py/dist/py/test/rsession/testing/test_rest.py ============================================================================== --- (empty file) +++ py/dist/py/test/rsession/testing/test_rest.py Fri Nov 17 16:49:19 2006 @@ -0,0 +1,22 @@ + +""" tests of rest reporter backend +""" + +import py +from py.__.test.rsession.testing.test_reporter import AbstractTestReporter +from py.__.test.rsession.rest import RestReporter + +class TestRestReporter(AbstractTestReporter): + reporter = RestReporter + + def test_failed_to_load(self): + py.test.skip("Not implemented") + + def test_report_received_item_outcome(self): + val = self.report_received_item_outcome() + expected = """- localhost\\: FAILED py test rsession testing test\\_slave.py funcpass +- localhost\\: SKIPPED py test rsession testing test\\_slave.py funcpass +- localhost\\: FAILED py test rsession testing test\\_slave.py funcpass +- localhost\\: PASSED py test rsession testing test\\_slave.py funcpass +""" + assert val == expected From fijal at codespeak.net Sat Nov 18 15:19:06 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Sat, 18 Nov 2006 15:19:06 +0100 (CET) Subject: [py-svn] r34732 - py/dist/py/path/local Message-ID: <20061118141906.C66D1100BC@code0.codespeak.net> Author: fijal Date: Sat Nov 18 15:19:04 2006 New Revision: 34732 Modified: py/dist/py/path/local/local.py Log: changed caching name to _module. Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Sat Nov 18 15:19:04 2006 @@ -377,7 +377,7 @@ pkgpath = None if modname is None: try: - return self.module + return self._module except AttributeError: pass pkgpath = self.pypkgpath() @@ -401,7 +401,7 @@ self._prependsyspath(self.dirpath()) modname = self.purebasename mod = __import__(modname, None, None, ['__doc__']) - self.module = mod + self._module = mod return mod else: try: From fijal at codespeak.net Sat Nov 18 15:33:20 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Sat, 18 Nov 2006 15:33:20 +0100 (CET) Subject: [py-svn] r34733 - py/dist/py/test Message-ID: <20061118143320.ADD9F100B5@code0.codespeak.net> Author: fijal Date: Sat Nov 18 15:33:19 2006 New Revision: 34733 Modified: py/dist/py/test/config.py Log: Changed name, killed unused class slot. Modified: py/dist/py/test/config.py ============================================================================== --- py/dist/py/test/config.py (original) +++ py/dist/py/test/config.py Sat Nov 18 15:33:19 2006 @@ -26,8 +26,7 @@ class Config(object): """ central hub for dealing with configuration/initialization data. """ Option = optparse.Option - configs_cache = {} - values_cache = {} + _configs_cache = {} def __init__(self): self.option = optparse.Values() @@ -48,10 +47,10 @@ configpaths.append(defaultconfig) for p in configpaths: try: - mod = cls.configs_cache[p] + mod = cls._configs_cache[p] except (KeyError, IndexError): mod = importconfig(p) - cls.configs_cache[p] = mod + cls._configs_cache[p] = mod try: return getattr(mod, name) except AttributeError: From fijal at codespeak.net Sun Nov 19 16:31:15 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Sun, 19 Nov 2006 16:31:15 +0100 (CET) Subject: [py-svn] r34755 - py/dist/py/test/rsession Message-ID: <20061119153115.D9FC4100C0@code0.codespeak.net> Author: fijal Date: Sun Nov 19 16:31:13 2006 New Revision: 34755 Modified: py/dist/py/test/rsession/box.py Log: Fixed semantics, now test_slave should not fail. Modified: py/dist/py/test/rsession/box.py ============================================================================== --- py/dist/py/test/rsession/box.py (original) +++ py/dist/py/test/rsession/box.py Sun Nov 19 16:31:13 2006 @@ -59,7 +59,7 @@ nice_level = py.test.remote.nice_level pid = os.fork() if pid: - self.parent() + self.parent(pid) else: try: outcome = self.children(nice_level) @@ -101,8 +101,8 @@ retvalf.close() os._exit(0) - def parent(self): - pid, exitstat = os.wait() + def parent(self, pid): + pid, exitstat = os.waitpid(pid, 0) self.signal = exitstat & 0x7f self.exitstat = exitstat & 0xff00 From fijal at codespeak.net Sun Nov 19 16:35:08 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Sun, 19 Nov 2006 16:35:08 +0100 (CET) Subject: [py-svn] r34756 - py/dist/py/test/rsession Message-ID: <20061119153508.CC5C8100C0@code0.codespeak.net> Author: fijal Date: Sun Nov 19 16:35:06 2006 New Revision: 34756 Modified: py/dist/py/test/rsession/hostmanage.py Log: Make a proper handling of tests for hosts that we don't want to create connection to. Modified: py/dist/py/test/rsession/hostmanage.py ============================================================================== --- py/dist/py/test/rsession/hostmanage.py (original) +++ py/dist/py/test/rsession/hostmanage.py Sun Nov 19 16:35:06 2006 @@ -27,7 +27,8 @@ else: return base in self.rsync_roots -def prepare_gateway(sshosts, relpath, rsync_roots, optimise_localhost, remote_python, pkgdir): +def prepare_gateway(sshosts, relpath, rsync_roots, optimise_localhost, + remote_python, pkgdir, real_create=True): hosts = [] for num, host in enumerate(sshosts): if host != 'localhost' or not optimise_localhost: @@ -39,18 +40,21 @@ # XXX: because of NFS we do create different directories # otherwise, .pyc files overlap remoterootpath += "-" + host - # for tests we want to use somtehing different - if host == 'localhost' and optimise_localhost is False: - from py.__.execnet.register import PopenCmdGateway - gw = PopenCmdGateway("cd ~/%s; python -u -c 'exec input()'" % remoterootpath) - if not remoterootpath.startswith("/"): - remoteroopath = "~/" + remoterootpath - else: - if remote_python is None: - gw = py.execnet.SshGateway(host) + if real_create: + # for tests we want to use somtehing different + if host == 'localhost' and optimise_localhost is False: + from py.__.execnet.register import PopenCmdGateway + gw = PopenCmdGateway("cd ~/%s; python -u -c 'exec input()'" % remoterootpath) + if not remoterootpath.startswith("/"): + remoteroopath = "~/" + remoterootpath else: - gw = py.execnet.SshGateway(host, remotepython=remote_python) - + if remote_python is None: + gw = py.execnet.SshGateway(host) + else: + gw = py.execnet.SshGateway(host, remotepython=remote_python) + else: + gw = None + hosts.append((num, host, gw, remoterootpath)) else: if remote_python is None: @@ -71,7 +75,7 @@ exc_info = [None] hosts = prepare_gateway(sshhosts, relpath, rsync_roots, optimise_localhost, - remote_python, pkgdir) + remote_python, pkgdir, real_create=do_sync) # rsyncing rsynced = {} From fijal at codespeak.net Sun Nov 19 16:50:43 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Sun, 19 Nov 2006 16:50:43 +0100 (CET) Subject: [py-svn] r34757 - py/dist/py/test/rsession Message-ID: <20061119155043.125F3100B7@code0.codespeak.net> Author: fijal Date: Sun Nov 19 16:50:41 2006 New Revision: 34757 Modified: py/dist/py/test/rsession/rsync.py Log: s/os.mkdir/os.makedirs/ Modified: py/dist/py/test/rsession/rsync.py ============================================================================== --- py/dist/py/test/rsession/rsync.py (original) +++ py/dist/py/test/rsession/rsync.py Sun Nov 19 16:50:41 2006 @@ -129,7 +129,7 @@ os.unlink(path) st = None if not st: - os.mkdir(path) + os.makedirs(path) entrynames = {} for entryname in msg: receive_directory_structure(os.path.join(path, entryname), From fijal at codespeak.net Sun Nov 19 17:23:23 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Sun, 19 Nov 2006 17:23:23 +0100 (CET) Subject: [py-svn] r34758 - py/dist/py/test/rsession Message-ID: <20061119162323.87F09100B4@code0.codespeak.net> Author: fijal Date: Sun Nov 19 17:23:22 2006 New Revision: 34758 Modified: py/dist/py/test/rsession/hostmanage.py Log: Fixed tests. Modified: py/dist/py/test/rsession/hostmanage.py ============================================================================== --- py/dist/py/test/rsession/hostmanage.py (original) +++ py/dist/py/test/rsession/hostmanage.py Sun Nov 19 17:23:22 2006 @@ -44,9 +44,9 @@ # for tests we want to use somtehing different if host == 'localhost' and optimise_localhost is False: from py.__.execnet.register import PopenCmdGateway - gw = PopenCmdGateway("cd ~/%s; python -u -c 'exec input()'" % remoterootpath) + gw = PopenCmdGateway("cd ~; python -u -c 'exec input()'") if not remoterootpath.startswith("/"): - remoteroopath = "~/" + remoterootpath + remoteroopath = os.environ['HOME'] + '/' + remoterootpath else: if remote_python is None: gw = py.execnet.SshGateway(host) From fijal at codespeak.net Sun Nov 19 17:29:51 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Sun, 19 Nov 2006 17:29:51 +0100 (CET) Subject: [py-svn] r34759 - py/dist/py/path/local Message-ID: <20061119162951.19E31100BE@code0.codespeak.net> Author: fijal Date: Sun Nov 19 17:29:49 2006 New Revision: 34759 Modified: py/dist/py/path/local/local.py Log: Reverted (well, commented out) _module caching, unless we've got better reason to do that. Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Sun Nov 19 17:29:49 2006 @@ -376,10 +376,10 @@ #print "trying to import", self pkgpath = None if modname is None: - try: - return self._module - except AttributeError: - pass + #try: + # return self._module + #except AttributeError: + # pass pkgpath = self.pypkgpath() if pkgpath is not None: if ensuresyspath: @@ -401,7 +401,7 @@ self._prependsyspath(self.dirpath()) modname = self.purebasename mod = __import__(modname, None, None, ['__doc__']) - self._module = mod + #self._module = mod return mod else: try: From guido at codespeak.net Mon Nov 20 11:30:45 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Mon, 20 Nov 2006 11:30:45 +0100 (CET) Subject: [py-svn] r34769 - py/dist/py/documentation Message-ID: <20061120103045.937391008B@code0.codespeak.net> Author: guido Date: Mon Nov 20 11:30:44 2006 New Revision: 34769 Added: py/dist/py/documentation/apigen_comparison.txt Log: Document describing some other documentation generation tools. Added: py/dist/py/documentation/apigen_comparison.txt ============================================================================== --- (empty file) +++ py/dist/py/documentation/apigen_comparison.txt Mon Nov 20 11:30:44 2006 @@ -0,0 +1,103 @@ +Comparison of apigen with similar tools +======================================== + +Apigen is of course not the only documentation generation tool available for +Python. Although we knew in advance that our tool had certain features the +others do not offer, we decided to investigate a bit so that we could do a +proper comparison. + +Tools examined +--------------- + +After some 'googling around', it turned out that the amount of documentation +generation tools available was surprisingly low. There were only 5 packages +I could find, of which 1 (called 'HappyDoc') seems dead (last release 2001), +one (called 'Pudge') not yet born (perhaps DOA even? most of the links on the +website are dead), and one (called 'Endo') specific to the Enthought suite. +The remaining two were Epydoc, which is widely used [1]_, and PyDoctor, which is +used only by (and written for) the Twisted project, but can be used seperately. + +Epydoc +------- + +http://epydoc.sourceforge.net/ + +Epydoc is the best known, and most widely used, documentation generation tool +for Python. It builds a documentation tree by inspecting imported modules and +using Python's introspection features. This way it can display information like +containment, inheritance, and docstrings. + +The tool is relatively sophisticated, with support for generating HTML and PDF, +choosing different styles (CSS), generating graphs using Graphviz, etc. Also +it allows using markup (which can be ReST, JavaDoc, or their own 'epytext' +format) inside docstrings for displaying rich text in the result. + +Quick overview: + + * builds docs from object tree + * displays relatively little information, just inheritance trees, API and + docstrings + * supports some markup (ReST, 'epytext', JavaDoc) in docstrings + +PyDoctor +--------- + +http://codespeak.net/~mwh/pydoctor/ + +This tool is written by Michael Hudson for the Twisted project. The major +difference between this and Epydoc is that it browses the AST (Abstract Syntax +Tree) instead of using 'live' objects, which makes that code that uses special +import mechanisms, or depends on other code that is not available, can still +be inspected. + +The tool is relatively simple and doesn't support the more advanced features +that Epydoc offers, and since it's written basically only for Twisted, I don't +think it will see a lot of development in the future. + +Quick overview: + + * inspects AST rather than object tree + * again not a lot of information, the usual API docstrings, class inheritance + and module structure, but that's it + * rather heavy dependencies (depends on Twisted/Nevow (trunk version)) + * written for Twisted, but quite nice output with other applications (except + that generating docs of the 'py lib' resulted in a max recursion depth + error) + +Quick overview lists of the other tools +---------------------------------------- + +HappyDoc ++++++++++ + +http://happydoc.sourceforge.net/ + + * dead + * inspects AST + * quite flexible, different output formats (HTML, XML, SGML, PDF) + * pluggable docstring parsers + +Pudge +++++++ + +http://pudge.lesscode.org/ + + * immature, dead? + * builds docs from live object tree (I think?) + * supports ReST + * uses Kid templates + +Endo ++++++ + +https://svn.enthought.com/enthought/wiki/EndoHowTo + + * inspects object tree (I think?) + * 'traits' aware (see https://svn.enthought.com/enthought/wiki/Traits) + * customizable HTML output with custom templating engine + * little documentation, seems like it's written for Enthought's own use + mostly + * heavy dependencies + +.. [1] Epydoc doesn't seem to be developed anymore, either, but it's so + widely used it can not be ignored... From guido at codespeak.net Mon Nov 20 12:58:53 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Mon, 20 Nov 2006 12:58:53 +0100 (CET) Subject: [py-svn] r34783 - py/dist/py/test/rsession/testing Message-ID: <20061120115853.1208F10094@code0.codespeak.net> Author: guido Date: Mon Nov 20 12:58:51 2006 New Revision: 34783 Modified: py/dist/py/test/rsession/testing/test_webjs.py Log: Some more webjs tests. Modified: py/dist/py/test/rsession/testing/test_webjs.py ============================================================================== --- py/dist/py/test/rsession/testing/test_webjs.py (original) +++ py/dist/py/test/rsession/testing/test_webjs.py Mon Nov 20 12:58:51 2006 @@ -26,3 +26,41 @@ assert msgbox.childNodes[0].nodeName == 'PRE' assert msgbox.childNodes[0].childNodes[0].nodeValue == 'foo\nbar' +def test_show_info(): + info = dom.window.document.getElementById('info') + info.style.visibility = 'hidden' + info.innerHTML = '' + webjs.show_info('foobar') + content = info.innerHTML + assert content == 'foobar' + bgcolor = info.style.backgroundColor + assert bgcolor == 'beige' + +def test_hide_info(): + info = dom.window.document.getElementById('info') + info.style.visibility = 'visible' + webjs.hide_info() + assert info.style.visibility == 'hidden' + +def test_process(): + main_t = dom.window.document.getElementById('main_table') + assert len(main_t.getElementsByTagName('tr')) == 0 + assert not webjs.process({}) + + msg = {'type': 'ItemStart', + 'itemtype': 'Module', + 'itemname': 'foo.py', + 'fullitemname': 'modules/foo.py', + 'length': 10, + } + assert webjs.process(msg) + trs = main_t.getElementsByTagName('tr') + assert len(trs) == 1 + tr = trs[0] + assert len(tr.childNodes) == 2 + assert tr.childNodes[0].nodeName == 'TD' + assert tr.childNodes[0].innerHTML == 'foo.py[10]' + # XXX this is bad I think! table should be inside td + assert tr.childNodes[1].nodeName == 'TABLE' + assert len(tr.childNodes[1].getElementsByTagName('tr')) == 0 + From guido at codespeak.net Mon Nov 20 15:52:58 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Mon, 20 Nov 2006 15:52:58 +0100 (CET) Subject: [py-svn] r34794 - in py/dist/py/apigen: . rest Message-ID: <20061120145258.E34111006C@code0.codespeak.net> Author: guido Date: Mon Nov 20 15:52:56 2006 New Revision: 34794 Added: py/dist/py/apigen/apigen.js Modified: py/dist/py/apigen/rest/htmlhandlers.py py/dist/py/apigen/style.css Log: Small CSS changes, added breadcrumb trail for navigation, made module_py.html the default page in the iframe. Added: py/dist/py/apigen/apigen.js ============================================================================== --- (empty file) +++ py/dist/py/apigen/apigen.js Mon Nov 20 15:52:56 2006 @@ -0,0 +1,28 @@ +var anchors = []; +function set_breadcrumb(el) { + var breadcrumb = document.getElementById('breadcrumb'); + var href = el.href; + if (href.indexOf('module_') > -1) { + anchors = []; + }; + for (var i=0; i < anchors.length; i++) { + if (anchors[i].href == href) { + anchors = anchors.slice(0, i); + }; + }; + var clone = el.cloneNode(true); + if (anchors.length && clone.childNodes[0].nodeValue.indexOf('.') > -1) { + var chunks = clone.childNodes[0].nodeValue.split('.'); + clone.childNodes[0].nodeValue = chunks[chunks.length - 1]; + }; + anchors.push(clone); + while (breadcrumb.hasChildNodes()) { + breadcrumb.removeChild(breadcrumb.childNodes[0]); + }; + for (var i=0; i < anchors.length; i++) { + breadcrumb.appendChild(anchors[i]); + if (i < anchors.length - 1) { + breadcrumb.appendChild(document.createTextNode('.')); + }; + }; +}; Modified: py/dist/py/apigen/rest/htmlhandlers.py ============================================================================== --- py/dist/py/apigen/rest/htmlhandlers.py (original) +++ py/dist/py/apigen/rest/htmlhandlers.py Mon Nov 20 15:52:56 2006 @@ -4,24 +4,36 @@ def startDocument(self): self._data += ['\n', '\n', 'api reference\n', - '', (''), '\n', '\n'] + def handleLink(self, text, target): + self._data.append('%s' % ( + entitize(target), entitize(text))) + class IndexHandler(PageHandler): ignore_text = False def startDocument(self): - super(IndexHandler, self).startDocument() - self._data += [''] def startTitle(self, depth): self.ignore_text = True @@ -35,5 +47,7 @@ super(IndexHandler, self).handleText(text) def handleLink(self, text, target): - self._data.append('%s' % ( + self._data.append('%s' % ( entitize(target), entitize(text))) + Modified: py/dist/py/apigen/style.css ============================================================================== --- py/dist/py/apigen/style.css (original) +++ py/dist/py/apigen/style.css Mon Nov 20 15:52:56 2006 @@ -1,14 +1,21 @@ #sidebar { width: 9em; float: left; + vertical-align: top; + margin-top: 0px; } #main { - margin-left: 9em; + margin-left: 10em; } #content { border: 0px; + height: 95%; +} + +#breadcrumb { + height: 5%; } body, div, p, h1, h2, h3, h4 { @@ -24,8 +31,10 @@ ul { padding-left: 2em; + margin-top: 0px; } ul li { list-style-type: katakana; } + From guido at codespeak.net Mon Nov 20 21:02:00 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Mon, 20 Nov 2006 21:02:00 +0100 (CET) Subject: [py-svn] r34809 - in py/dist/py: apigen/tracer/testing/package misc/testing Message-ID: <20061120200200.A94CA1007C@code0.codespeak.net> Author: guido Date: Mon Nov 20 21:01:57 2006 New Revision: 34809 Added: py/dist/py/apigen/tracer/testing/package/__init__.py Modified: py/dist/py/misc/testing/test_initpkg.py Log: Fixed initpkg tests. Added: py/dist/py/apigen/tracer/testing/package/__init__.py ============================================================================== Modified: py/dist/py/misc/testing/test_initpkg.py ============================================================================== --- py/dist/py/misc/testing/test_initpkg.py (original) +++ py/dist/py/misc/testing/test_initpkg.py Mon Nov 20 21:01:57 2006 @@ -52,6 +52,8 @@ base.join('compat'), ) for p in base.visit('*.py', lambda x: x.check(dotfile=0)): + if p.basename == '__init__.py': + continue relpath = p.new(ext='').relto(base) if base.sep in relpath: # not py/*.py itself for x in nodirs: From guido at codespeak.net Mon Nov 20 22:29:14 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Mon, 20 Nov 2006 22:29:14 +0100 (CET) Subject: [py-svn] r34813 - py/dist/py Message-ID: <20061120212914.CC2D010077@code0.codespeak.net> Author: guido Date: Mon Nov 20 22:29:13 2006 New Revision: 34813 Modified: py/dist/py/conftest.py Log: Adding JS to the generated HTML when using --apigen. Modified: py/dist/py/conftest.py ============================================================================== --- py/dist/py/conftest.py (original) +++ py/dist/py/conftest.py Mon Nov 20 22:29:13 2006 @@ -41,4 +41,6 @@ HTMLDirWriter(IndexHandler, PageHandler, outdir)).write() if not outdir.join('style.css').check(): py.magic.autopath().dirpath().join('apigen/style.css').copy(outdir) + if not outdir.join('apigen.js').check(): + py.magic.autopath().dirpath().join('apigen/apigen.js').copy(outdir) write_docs = staticmethod(write_docs) From guido at codespeak.net Mon Nov 20 22:30:17 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Mon, 20 Nov 2006 22:30:17 +0100 (CET) Subject: [py-svn] r34814 - in py/dist/py: apigen apigen/rest rest rest/testing Message-ID: <20061120213017.9BED010078@code0.codespeak.net> Author: guido Date: Mon Nov 20 22:30:15 2006 New Revision: 34814 Modified: py/dist/py/apigen/rest/htmlhandlers.py py/dist/py/apigen/style.css py/dist/py/rest/testing/test_transform.py py/dist/py/rest/transform.py Log: Small style updates for apigen output, changed rest/transform.py so it uses py.xml.html instead of generating strings. Modified: py/dist/py/apigen/rest/htmlhandlers.py ============================================================================== --- py/dist/py/apigen/rest/htmlhandlers.py (original) +++ py/dist/py/apigen/rest/htmlhandlers.py Mon Nov 20 22:30:15 2006 @@ -1,39 +1,35 @@ from py.__.rest.transform import HTMLHandler, entitize +from py.xml import html class PageHandler(HTMLHandler): def startDocument(self): - self._data += ['\n', '\n', - 'api reference\n', - (''), - '\n', '\n'] + self.title = 'api reference' + super(PageHandler, self).startDocument() + self.head.append(html.link(type='text/css', rel='stylesheet', + href='style.css')) def handleLink(self, text, target): - self._data.append('%s' % ( - entitize(target), entitize(text))) + self.tagstack[-1].append(html.a(text, href=target, + onclick='parent.set_breadcrumb(this)', + target='content')) + class IndexHandler(PageHandler): ignore_text = False def startDocument(self): - self._data += ['\n', '\n', - 'api reference\n', - (''), - '', - '\n', - ('\n'), - '', '
    ', - '', - ('', '
    '] + maindiv = html.div(id="main") + maindiv.append(html.div(id="breadcrumb")) + maindiv.append(html.iframe(name='content', id='content', + src='module_py.html')) + self.body.append(maindiv) def startTitle(self, depth): self.ignore_text = True @@ -47,7 +43,7 @@ super(IndexHandler, self).handleText(text) def handleLink(self, text, target): - self._data.append('%s' % ( - entitize(target), entitize(text))) + self.tagstack[-1].append(html.a(text, href=target, + onclick='set_breadcrumb(this)', + target='content')) Modified: py/dist/py/apigen/style.css ============================================================================== --- py/dist/py/apigen/style.css (original) +++ py/dist/py/apigen/style.css Mon Nov 20 22:30:15 2006 @@ -12,6 +12,7 @@ #content { border: 0px; height: 95%; + width: 100%; } #breadcrumb { Modified: py/dist/py/rest/testing/test_transform.py ============================================================================== --- py/dist/py/rest/testing/test_transform.py (original) +++ py/dist/py/rest/testing/test_transform.py Mon Nov 20 22:30:15 2006 @@ -14,26 +14,27 @@ endDocument = startDocument def test_transform_basic_html(): - for rest, expected in ((Rest(Title('foo')), '

    foo

    \n'), - (Rest(Paragraph('foo')), '

    foo

    \n'), + for rest, expected in ((Rest(Title('foo')), '

    foo

    '), + (Rest(Paragraph('foo')), '

    foo

    '), (Rest(SubParagraph('foo')), - '

    foo

    \n'), - (Rest(LiteralBlock('foo\nbar')), - '
    foo\nbar
    \n'), + '

    foo

    '), + (Rest(LiteralBlock('foo\tbar')), + '
    foo\tbar
    '), (Rest(Paragraph(Link('foo', 'http://www.foo.com/'))), - '

    foo

    \n')): + ('

    \n foo' + '

    '))): html = convert_to_html(rest) assert html == expected def test_transform_list_simple(): rest = Rest(ListItem('foo'), ListItem('bar')) html = convert_to_html(rest) - assert html == '
    • foo
    • \n
    • bar
    • \n
    \n' + assert html == '
      \n
    • foo
    • \n
    • bar
    ' def test_transform_list_nested(): rest = Rest(ListItem('foo'), ListItem('bar', ListItem('baz'))) html = convert_to_html(rest) - assert html == ('
    • foo
    • \n
    • bar
      • baz
      • \n
      \n' - '
    • \n
    \n') + assert html == ('
      \n
    • foo
    • \n
    • bar\n
        ' + '\n
      • baz
    ') Modified: py/dist/py/rest/transform.py ============================================================================== --- py/dist/py/rest/transform.py (original) +++ py/dist/py/rest/transform.py Mon Nov 20 22:30:15 2006 @@ -1,5 +1,6 @@ import py from py.__.rest import rst +from py.xml import html class RestTransformer(object): def __init__(self, tree): @@ -18,6 +19,7 @@ if name == 'Rest': continue getattr(self, 'handle_%s' % (name,))(node, handler) + def handle_Title(self, node, handler): depthkey = (node.abovechar, node.belowchar) if depthkey not in self._titledepths: @@ -99,61 +101,77 @@ class HTMLHandler(object): def __init__(self, title='untitled rest document'): self.title = title - self._data = [] - self._listdepth = 0 + self.root = None + self.tagstack = [] + self._currlist = None def startDocument(self): - self._data += ['\n', '\n', - '%s\n' % (self.title,), - '\n', '\n'] + h = html.html() + self.head = head = html.head() + self.title = title = html.title(self.title) + self._push(h) + h.append(head) + h.append(title) + self.body = body = html.body() + self._push(body) def endDocument(self): - self._data += ['\n', '\n'] - - def startTitle(self, depth): - self._data.append('' % (depth,)) + self._pop() # body + self._pop() # html + def startTitle(self, depth): + h = getattr(html, 'h%s' % (depth,))() + self._push(h) + def endTitle(self, depth): - self._data.append('\n' % (depth,)) + self._pop() def startParagraph(self): - self._data.append('

    ') + self._push(html.p()) def endParagraph(self): - self._data.append('

    \n') + self._pop() def startSubParagraph(self): - self._data.append('

    ') + self._push(html.p(**{'class': 'sub'})) def endSubParagraph(self): - self._data.append('

    \n') + self._pop() def handleLiteralBlock(self, text): - self._data.append('
    %s
    \n' % (entitize(text),)) + self.tagstack[-1].append(html.pre(text)) def handleText(self, text): - self._data.append(entitize(text)) + self.tagstack[-1].append(text) def handleEm(self, text): - self._data.append('%s' % (entitize(text),)) + self.tagstack[-1].append(html.em(text)) def startListItem(self, type, startlist): if startlist: nodename = type == 'o' and 'ol' or 'ul' - self._data.append('<%s>' % (nodename,)) - self._data.append('
  • ') + self._push(getattr(html, nodename)()) + self._push(html.li()) def endListItem(self, type, closelist): - self._data.append('
  • \n') + self._pop() if closelist: - nodename = type == 'o' and 'ol' or 'ul' - self._data.append('\n' % (nodename,)) + self._pop() def handleLink(self, text, target): - self._data.append('%s' % (entitize(target), - entitize(text))) + self.tagstack[-1].append(html.a(text, href=target)) + + def _push(self, el): + if self.tagstack: + self.tagstack[-1].append(el) + else: + self.root = el + self.tagstack.append(el) + + def _pop(self): + self.tagstack.pop() def _html(self): - return ''.join(self._data) + return self.root.unicode() html = property(_html) From fijal at codespeak.net Tue Nov 21 13:25:22 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Tue, 21 Nov 2006 13:25:22 +0100 (CET) Subject: [py-svn] r34820 - py/dist/py/documentation Message-ID: <20061121122522.BB76D1006F@code0.codespeak.net> Author: fijal Date: Tue Nov 21 13:25:20 2006 New Revision: 34820 Added: py/dist/py/documentation/source-viewer.txt (contents, props changed) Log: Added random notes about source-viewer Added: py/dist/py/documentation/source-viewer.txt ============================================================================== --- (empty file) +++ py/dist/py/documentation/source-viewer.txt Tue Nov 21 13:25:20 2006 @@ -0,0 +1,37 @@ +======================= +Source viewer for pylib +======================= + +Random notes: +------------- + +* We want to construct this using an AST. Introspection is somehow better in + many many places, but an AST is more sticked to source (which is by + conincidence what we actually want to see). Additional stuff like things + attached to the module might be nice if we want to document them, but + they do not appear in source. + +* We want to have rather free (without all AST hassle) access to basic + info about classess, functions variables etc. So we can get all the + necessary informations even if the module has changed a bit. + +* We want this access for both SVN and file based sources. + +* We want to have html backend of those - which means at least syntax + highlightning with possibility of linking to those items. + +* Some crosslinking might be cool, variables are resolved syntactically, + so in 95% cases we can crossling to at least module.function, module.class + etc. + +* First: We don't want to have *ANY* type inference there. Two: If at + any point we want such thing, we make it through introspection, but better + not at all. + +Implementation notes: +--------------------- + +Let's use compiler package and if at any point there will be sth better, +like pypy's compiler package we'll move. We create out of AST stuff which +can be accessed by dot. Like module.function.firstlineno, with interface +to py.code.Source as well. From fijal at codespeak.net Tue Nov 21 21:25:42 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Tue, 21 Nov 2006 21:25:42 +0100 (CET) Subject: [py-svn] r34840 - in py/dist/py/test/rsession: . testing Message-ID: <20061121202542.C977B1007A@code0.codespeak.net> Author: fijal Date: Tue Nov 21 21:25:39 2006 New Revision: 34840 Modified: py/dist/py/test/rsession/conftest.py py/dist/py/test/rsession/hostmanage.py py/dist/py/test/rsession/rsession.py py/dist/py/test/rsession/testing/test_boxing.py py/dist/py/test/rsession/testing/test_executor.py Log: Moved default waittime to session options Modified: py/dist/py/test/rsession/conftest.py ============================================================================== --- py/dist/py/test/rsession/conftest.py (original) +++ py/dist/py/test/rsession/conftest.py Tue Nov 21 21:25:39 2006 @@ -1,14 +1,14 @@ -import py -Option = py.test.Config.Option +#import py +#Option = py.test.Config.Option -defaultwait = 100.0 +#defaultwait = 100.0 -option = py.test.Config.addoptions("distributed testing options", +#option = py.test.Config.addoptions("distributed testing options", # Option('-D', '--disthosts', # action="store", dest="disthosts", default=None, # help="comma separated list of testhosts"), - Option('', '--waittime', - action="store", dest="waittime", default=defaultwait, - help="How long (in seconds) to wait for hanging nodes" - " (default=%s sec)" % defaultwait), - ) +# Option('', '--waittime', +# action="store", dest="waittime", default=defaultwait, +# help="How long (in seconds) to wait for hanging nodes" +# " (default=%s sec)" % defaultwait), +# ) Modified: py/dist/py/test/rsession/hostmanage.py ============================================================================== --- py/dist/py/test/rsession/hostmanage.py (original) +++ py/dist/py/test/rsession/hostmanage.py Tue Nov 21 21:25:39 2006 @@ -7,9 +7,6 @@ from py.__.test.rsession import report from py.__.test.rsession.rsync import RSync -from py.__.test.rsession.conftest import option - - class HostRSync(RSync): def __init__(self, rsync_roots): @@ -112,6 +109,8 @@ exitfirst=False): for channel in channels: channel.send(None) + + from py.__.test.rsession.rsession import session_options clean = exitfirst while not clean: @@ -124,7 +123,7 @@ for channel in channels: try: - report.wrapcall(reporter, channel.waitclose, int(option.waittime)) + report.wrapcall(reporter, channel.waitclose, int(session_options.waittime)) except KeyboardInterrupt, SystemExit: raise except: Modified: py/dist/py/test/rsession/rsession.py ============================================================================== --- py/dist/py/test/rsession/rsession.py (original) +++ py/dist/py/test/rsession/rsession.py Tue Nov 21 21:25:39 2006 @@ -37,6 +37,7 @@ 'max_tasks_per_node' : 15, 'runner_policy' : 'plain_runner', 'nice_level' : 0, + 'waittime' : 100.0, } config = None 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 Nov 21 21:25:39 2006 @@ -9,7 +9,6 @@ from py.__.test.rsession.box import Box, RealBox, ScreenBox from py.__.test.rsession.testing import example2 -from py.__.test.rsession.conftest import option def setup_module(mod): from py.__.test.rsession.rsession import remote_options Modified: py/dist/py/test/rsession/testing/test_executor.py ============================================================================== --- py/dist/py/test/rsession/testing/test_executor.py (original) +++ py/dist/py/test/rsession/testing/test_executor.py Tue Nov 21 21:25:39 2006 @@ -6,7 +6,6 @@ from py.__.test.rsession.outcome import ReprOutcome from py.__.test.rsession.testing.test_slave import funcprint_spec, \ funcprintfail_spec -from py.__.test.rsession.conftest import option def setup_module(mod): mod.rootdir = py.path.local(py.__file__).dirpath().dirpath() From fijal at codespeak.net Tue Nov 21 21:27:38 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Tue, 21 Nov 2006 21:27:38 +0100 (CET) Subject: [py-svn] r34841 - py/dist/py/documentation Message-ID: <20061121202738.9AC221007A@code0.codespeak.net> Author: fijal Date: Tue Nov 21 21:27:37 2006 New Revision: 34841 Modified: py/dist/py/documentation/test.txt Log: Added new option. Modified: py/dist/py/documentation/test.txt ============================================================================== --- py/dist/py/documentation/test.txt (original) +++ py/dist/py/documentation/test.txt Tue Nov 21 21:27:37 2006 @@ -755,6 +755,8 @@ * `nice_level` - Level of nice under which tests are run * `runner_policy` - (for `LSession` only) - contains policy for the test boxig. `"plain_runner"` means no boxing, `"box_runner"` means boxing. +* `waittime` - Default waiting time for remote host to finish test (after + that period we consider node as dead, default to 100s). Development Notes ----------------- From guido at codespeak.net Wed Nov 22 12:03:35 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Wed, 22 Nov 2006 12:03:35 +0100 (CET) Subject: [py-svn] r34853 - in py/dist/py/rest: . testing Message-ID: <20061122110335.42E3810063@code0.codespeak.net> Author: guido Date: Wed Nov 22 12:03:33 2006 New Revision: 34853 Modified: py/dist/py/rest/rst.py py/dist/py/rest/testing/test_rst.py py/dist/py/rest/transform.py Log: Fixed bug in list item handling for items that don't have a parent (regardless of type), fixed bug in title handling: title should never be split over lines, fixed bug in LiteralBlock transformation to HTML when rendering only the pre element. Modified: py/dist/py/rest/rst.py ============================================================================== --- py/dist/py/rest/rst.py (original) +++ py/dist/py/rest/rst.py Wed Nov 22 12:03:33 2006 @@ -190,11 +190,11 @@ class Title(Paragraph): parentclass = Rest - belowchar = "" + belowchar = "=" abovechar = "" def text(self): - txt = Paragraph.text(self) + txt = self._get_text() lines = [] if self.abovechar: lines.append(self.abovechar * len(txt)) @@ -203,6 +203,12 @@ lines.append(self.belowchar * len(txt)) return "\n".join(lines) + def _get_text(self): + txt = [] + for node in self.children: + txt.append(node.text().strip()) + return ' '.join(txt) + class AbstractText(AbstractNode): parentclass = [Paragraph, Title] start = "" @@ -298,10 +304,11 @@ def get_indent_depth(self): depth = 0 current = self - while current.parent is not None: + while (current.parent is not None and + isinstance(current.parent, ListItem)): depth += 1 current = current.parent - return depth - 1 + return depth class OrderedListItem(ListItem): item_chars = ["#."] * 5 Modified: py/dist/py/rest/testing/test_rst.py ============================================================================== --- py/dist/py/rest/testing/test_rst.py (original) +++ py/dist/py/rest/testing/test_rst.py Wed Nov 22 12:03:33 2006 @@ -168,11 +168,29 @@ txt = Title(Text("Some title"), belowchar="=").text() assert txt == "Some title\n==========" checkrest(txt) - txt = Title(Text("Some title"), belowchar="#", abovechar="#").text() + txt = Title("Some title", belowchar="#", abovechar="#").text() assert txt == "##########\nSome title\n##########" html = checkrest(txt) assert '>Some title' in html +def test_title_long(): + txt = Title('Some very long title that doesn\'t fit on a single line ' + 'but still should not be split into multiple lines').text() + assert txt == ("Some very long title that doesn't fit on a single line " + "but still should not be split into multiple lines\n" + "=======================================================" + "=================================================") + checkrest(txt) + +def test_title_long_with_markup(): + txt = Title('Some very long title with', Em('some markup'), + 'to test whether that works as expected too...').text() + assert txt == ("Some very long title with *some markup* to test whether " + "that works as expected too...\n" + "========================================================" + "=============================") + checkrest(txt) + 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() @@ -271,6 +289,15 @@ assert txt == expected checkrest(txt) +def test_list_multiline_no_parent(): + expected = ("* test **strong**\n thisisaverylongwordthatdoesntfiton" + "thepreviouslineandthereforeshouldbeindented") + txt = ListItem(Text('test'), Strong('strong'), + Text('thisisaverylongwordthatdoesntfitontheprevious' + 'lineandthereforeshouldbeindented')).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'), Modified: py/dist/py/rest/transform.py ============================================================================== --- py/dist/py/rest/transform.py (original) +++ py/dist/py/rest/transform.py Wed Nov 22 12:03:33 2006 @@ -139,7 +139,11 @@ self._pop() def handleLiteralBlock(self, text): - self.tagstack[-1].append(html.pre(text)) + pre = html.pre(text) + if self.tagstack: + self.tagstack[-1].append(pre) + else: + self.root = pre def handleText(self, text): self.tagstack[-1].append(text) From fijal at codespeak.net Wed Nov 22 12:16:07 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Wed, 22 Nov 2006 12:16:07 +0100 (CET) Subject: [py-svn] r34854 - in py/dist/py/apigen: rest rest/testing source source/testing Message-ID: <20061122111607.C1CF410068@code0.codespeak.net> Author: fijal Date: Wed Nov 22 12:15:58 2006 New Revision: 34854 Added: py/dist/py/apigen/source/ (props changed) py/dist/py/apigen/source/__init__.py (contents, props changed) py/dist/py/apigen/source/browser.py (contents, props changed) py/dist/py/apigen/source/testing/ (props changed) py/dist/py/apigen/source/testing/__init__.py (contents, props changed) py/dist/py/apigen/source/testing/test_browser.py (contents, props changed) Modified: py/dist/py/apigen/rest/htmlhandlers.py (props changed) py/dist/py/apigen/rest/testing/somemodule.py (props changed) py/dist/py/apigen/rest/testing/someothermodule.py (props changed) Log: Added very silly source browser. Added: py/dist/py/apigen/source/__init__.py ============================================================================== Added: py/dist/py/apigen/source/browser.py ============================================================================== --- (empty file) +++ py/dist/py/apigen/source/browser.py Wed Nov 22 12:15:58 2006 @@ -0,0 +1,88 @@ + +""" source browser using compiler module + +WARNING!!! + +This is very simple and very silly attempt to make so. + +""" + +from compiler import parse, ast +import py + +from py.__.path.common import PathBase + +class Module(object): + def __init__(self, path, _dict): + self.path = path + self.dict = _dict + + def __getattr__(self, attr): + try: + return self.dict[attr] + except KeyError: + raise AttributeError(attr) + +def get_endline(start, lst): + l = reversed(lst) + for i in l: + if i.lineno: + return i.lineno + end_ch = get_endline(None, i.getChildNodes()) + if end_ch: + return end_ch + return start + +class Function(object): + def __init__(self, name, firstlineno, endlineno): + self.firstlineno = firstlineno + self.endlineno = endlineno + self.name = name + +class Method(object): + def __init__(self, name, firstlineno, endlineno): + self.name = name + self.firstlineno = firstlineno + self.endlineno = endlineno + +def function_from_ast(ast, cls=Function): + startline = ast.lineno + endline = get_endline(startline, ast.getChildNodes()) + assert endline + return cls(ast.name, startline, endline) + +def class_from_ast(cls_ast): + bases = [i.name for i in cls_ast.bases] + # XXX + methods = {} + startline = cls_ast.lineno + name = cls_ast.name + endline = get_endline(startline, cls_ast.getChildNodes()) + methods = dict([(i.name, function_from_ast(i, Method)) for i in \ + cls_ast.code.nodes if isinstance(i, ast.Function)]) + return Class(name, startline, endline, bases, methods) + +class Class(object): + def __init__(self, name, firstlineno, endlineno, bases, methods): + self.bases = bases + self.firstlineno = firstlineno + self.endlineno = endlineno + self.name = name + self.methods = methods + + def __getattr__(self, attr): + try: + return self.methods[attr] + except KeyError: + raise AttributeError(attr) + +def parse_path(path): + assert isinstance(path, PathBase), "Cannot work on files directly" + buf = path.open().read() + st = parse(buf) + # first go - we get all functions and classes defined on top-level + function_ast = [i for i in st.node.nodes if isinstance(i, ast.Function)] + classes_ast = [i for i in st.node.nodes if isinstance(i, ast.Class)] + mod_dict = dict([(i.name, function_from_ast(i)) for i in function_ast] + + [(i.name, class_from_ast(i)) for i in classes_ast]) + return Module(path, mod_dict) Added: py/dist/py/apigen/source/testing/__init__.py ============================================================================== Added: py/dist/py/apigen/source/testing/test_browser.py ============================================================================== --- (empty file) +++ py/dist/py/apigen/source/testing/test_browser.py Wed Nov 22 12:15:58 2006 @@ -0,0 +1,40 @@ + +""" test source browser abilities +""" + +from py.__.apigen.source.browser import parse_path, Class, Function, Method +import py + +def test_browser(): + tmp = py.test.ensuretemp("sourcebrowser") + tmp.ensure("a.py").write(py.code.Source(""" + def f(): + pass + + def g(): + pass + + class X: + pass + + class Z(object): + x = 1 + def zzz(self): + 1 + 2 + 3 + 4 + """)) + mod = parse_path(tmp.join("a.py")) + assert isinstance(mod.g, Function) + assert isinstance(mod.Z, Class) + py.test.raises(AttributeError, "mod.zzz") + assert mod.g.firstlineno == 5 + assert mod.g.name == "g" + assert mod.g.endlineno == 6 + assert mod.X.firstlineno == 8 + assert mod.X.endlineno == 9 + assert mod.Z.bases == ["object"] + assert isinstance(mod.Z.zzz, Method) + assert mod.Z.zzz.firstlineno == 13 + assert mod.Z.zzz.endlineno == 17 From guido at codespeak.net Wed Nov 22 13:56:36 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Wed, 22 Nov 2006 13:56:36 +0100 (CET) Subject: [py-svn] r34855 - in py/dist/py/rest: . testing Message-ID: <20061122125636.60D4B10063@code0.codespeak.net> Author: guido Date: Wed Nov 22 13:56:34 2006 New Revision: 34855 Modified: py/dist/py/rest/rst.py py/dist/py/rest/testing/test_rst.py Log: Fixed bug in title escaping. Modified: py/dist/py/rest/rst.py ============================================================================== --- py/dist/py/rest/rst.py (original) +++ py/dist/py/rest/rst.py Wed Nov 22 13:56:34 2006 @@ -206,7 +206,7 @@ def _get_text(self): txt = [] for node in self.children: - txt.append(node.text().strip()) + txt += node.wordlist() return ' '.join(txt) class AbstractText(AbstractNode): Modified: py/dist/py/rest/testing/test_rst.py ============================================================================== --- py/dist/py/rest/testing/test_rst.py (original) +++ py/dist/py/rest/testing/test_rst.py Wed Nov 22 13:56:34 2006 @@ -191,6 +191,11 @@ "=============================") checkrest(txt) +def test_title_escaping(): + txt = Title('foo *bar* baz').text() + assert txt == 'foo \\*bar\\* baz\n===============' + checkrest(txt) + 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() From guido at codespeak.net Wed Nov 22 14:19:42 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Wed, 22 Nov 2006 14:19:42 +0100 (CET) Subject: [py-svn] r34856 - py/dist/py/test Message-ID: <20061122131942.EBB7D10063@code0.codespeak.net> Author: guido Date: Wed Nov 22 14:19:41 2006 New Revision: 34856 Modified: py/dist/py/test/defaultconftest.py Log: Cleanups and consistency. Modified: py/dist/py/test/defaultconftest.py ============================================================================== --- py/dist/py/test/defaultconftest.py (original) +++ py/dist/py/test/defaultconftest.py Wed Nov 22 14:19:41 2006 @@ -1,16 +1,16 @@ import py -Module = py.test.collect.Module -DoctestFile = py.test.collect.DoctestFile +Module = py.test.collect.Module +DoctestFile = py.test.collect.DoctestFile Directory = py.test.collect.Directory Class = py.test.collect.Class -Generator = py.test.collect.Generator -Function = py.test.Function -Instance = py.test.collect.Instance +Generator = py.test.collect.Generator +Function = py.test.Function +Instance = py.test.collect.Instance additionalinfo = None -def adddefaultoptions(): +def adddefaultoptions(): Option = py.test.Config.Option py.test.Config.addoptions('general options', Option('-v', '--verbose', @@ -24,7 +24,8 @@ help="disable catching of sys.stdout/stderr output."), 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)."), @@ -49,32 +50,32 @@ 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.") + help="generate api documentation while testing (requires " + "initpkg/conftest config)."), ) - py.test.Config.addoptions('test-session related options', + py.test.Config.addoptions('test-session related options', Option('', '--tkinter', action="store_true", dest="tkinter", default=False, - help="use tkinter test session frontend."), + help="use tkinter test session frontend."), Option('', '--looponfailing', action="store_true", dest="looponfailing", default=False, - help="loop on failing test set."), - Option('', '--session', - action="store", dest="session", default=None, + help="loop on failing test set."), + Option('', '--session', + action="store", dest="session", default=None, help="use given sessionclass, default is terminal."), 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:8000 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." + help="run browser to point to your freshly started web server." ), Option('-r', '--rest', action='store_true', dest="restreport", default=False, - help="Use a ReST as an output reporting"), + help="restructured text output reporting."), ) From guido at codespeak.net Wed Nov 22 14:23:41 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Wed, 22 Nov 2006 14:23:41 +0100 (CET) Subject: [py-svn] r34857 - py/dist/py/compat/testing Message-ID: <20061122132341.5F02710068@code0.codespeak.net> Author: guido Date: Wed Nov 22 14:23:40 2006 New Revision: 34857 Modified: py/dist/py/compat/testing/_findpy.py Log: Directing 'inserting into sys.path' message to stderr. Modified: py/dist/py/compat/testing/_findpy.py ============================================================================== --- py/dist/py/compat/testing/_findpy.py (original) +++ py/dist/py/compat/testing/_findpy.py Wed Nov 22 14:23:40 2006 @@ -19,7 +19,7 @@ # if p == current: # return True if current != sys.path[0]: # if we are already first, then ok - print "inserting into sys.path:", current + print >>sys.stderr, "inserting into sys.path:", current sys.path.insert(0, current) return True current = opd(current) From guido at codespeak.net Wed Nov 22 14:26:04 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Wed, 22 Nov 2006 14:26:04 +0100 (CET) Subject: [py-svn] r34858 - py/dist/py/apigen/tracer/testing/package Message-ID: <20061122132604.92D111006C@code0.codespeak.net> Author: guido Date: Wed Nov 22 14:26:03 2006 New Revision: 34858 Modified: py/dist/py/apigen/tracer/testing/package/ (props changed) py/dist/py/apigen/tracer/testing/package/__init__.py (props changed) Log: fixeol From guido at codespeak.net Wed Nov 22 14:30:54 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Wed, 22 Nov 2006 14:30:54 +0100 (CET) Subject: [py-svn] r34859 - in py/dist/py/test/rsession: . testing Message-ID: <20061122133054.80C0D1006C@code0.codespeak.net> Author: guido Date: Wed Nov 22 14:30:53 2006 New Revision: 34859 Modified: py/dist/py/test/rsession/reporter.py py/dist/py/test/rsession/rest.py py/dist/py/test/rsession/testing/test_rest.py Log: Cleanup and improvements of the ReST reporter. More will follow, however currently executing 'py.test --rest > output.rst' should at least result in a parsable document with test results and exceptions. Modified: py/dist/py/test/rsession/reporter.py ============================================================================== --- py/dist/py/test/rsession/reporter.py (original) +++ py/dist/py/test/rsession/reporter.py Wed Nov 22 14:30:53 2006 @@ -34,6 +34,9 @@ # XXX: Testing purposes only return 'localhost' + def get_item_name(self, event, colitem): + return "/".join(colitem.listnames()) + def report(self, what): repfun = getattr(self, "report_" + what.__class__.__name__, self.report_unknown) Modified: py/dist/py/test/rsession/rest.py ============================================================================== --- py/dist/py/test/rsession/rest.py (original) +++ py/dist/py/test/rsession/rest.py Wed Nov 22 14:30:53 2006 @@ -4,34 +4,54 @@ import py import sys +from StringIO import StringIO from py.__.test.rsession.reporter import AbstractReporter from py.__.rest.rst import * class RestReporter(AbstractReporter): def report_unknown(self, what): - print "Unknown report: %s" % what + self.add_rest(Paragraph("Unknown report: %s" % what)) + + def report_SendItem(self, item): + address = self.get_host(item) + if self.config.option.verbose: + self.add_rest(Paragraph('sending item %s to %s' % (item.item, + address))) + + def report_HostRSyncing(self, item): + self.add_rest(LiteralBlock('%10s: RSYNC ==> %s' % (item.hostname[:10], + item.remoterootpath))) + + def report_HostReady(self, item): + self.add_rest(LiteralBlock('%10s: READY' % (item.hostname[:10],))) def report_TestStarted(self, event): - txt = "Test started, hosts: %s" % ", ".join(event.hosts) - print Title(txt, abovechar='=', belowchar='=').text() + "\n" + txt = "Running tests on hosts: %s" % ", ".join(event.hosts) + self.add_rest(Title(txt, abovechar='=', belowchar='=')) self.timestart = event.timestart + def report_TestFinished(self, item): + self.timeend = item.timeend + self.summary() + + def report_ImmediateFailure(self, item): + pass + def report_ItemStart(self, event): item = event.item - print if isinstance(item, py.test.collect.Module): lgt = len(list(item.tryiter())) - name = "/".join(item.listnames()) + lns = item.listnames() + name = "/".join(lns) txt = 'Testing module %s (%d items)' % (name, lgt) - print Title(txt, belowchar='-').text() + self.add_rest(Title(txt, belowchar='-')) def print_summary(self, total, skipped_str, failed_str): - print "\n" - txt = "%d test run%s%s in %.2fs (rsync: %.2f)" % \ + txt = "%d tests run%s%s in %.2fs (rsync: %.2f)" % \ (total, skipped_str, failed_str, self.timeend - self.timestart, self.timersync - self.timestart) - print Title(txt, belowchar="=").text() - + self.add_rest(Title(txt, belowchar='-')) + def report_ReceivedItemOutcome(self, event): host = self.get_host(event) if event.outcome.passed: @@ -46,7 +66,20 @@ 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 ListItem(Text("%10s: %s %s" %(sshhost[:10], status, itempath))).text() - + lns = event.item.listnames()[1:] + for i, ln in enumerate(lns): + if i > 0 and ln != '()': + lns[i] = '/%s' % (ln,) + itempath = ''.join(lns) + self.add_rest(ListItem(Text("%10s:" % (host[:10],)), Strong(status), + Text(itempath))) + if event.outcome.excinfo: + excinfo = event.outcome.excinfo + self.add_rest(Paragraph('exception:', Strong(excinfo.typename))) + self.add_rest(LiteralBlock(excinfo.value)) + self.add_rest(LiteralBlock('\n'.join([str(x) for x in + excinfo.traceback]))) + + def add_rest(self, item): + self.out.write('%s\n\n' % (item.text(),)) + Modified: py/dist/py/test/rsession/testing/test_rest.py ============================================================================== --- py/dist/py/test/rsession/testing/test_rest.py (original) +++ py/dist/py/test/rsession/testing/test_rest.py Wed Nov 22 14:30:53 2006 @@ -5,6 +5,133 @@ import py from py.__.test.rsession.testing.test_reporter import AbstractTestReporter from py.__.test.rsession.rest import RestReporter +from py.__.rest.rst import * + +class RestTestReporter(RestReporter): + def __init__(self, *args, **kwargs): + if args: + super(RestReporter, self).__init__(*args, **kwargs) + +class Container(object): + def __init__(self, **kwargs): + self.__dict__.update(kwargs) + +class TestRestUnits(object): + def setup_method(self, method): + config, args = py.test.Config.parse(["some_sub"]) + config.option.verbose = False + method.im_func.func_globals['reporter'] = r = RestReporter(config, + ['localhost']) + method.im_func.func_globals['stdout'] = s = py.std.StringIO.StringIO() + r.out = s # XXX will need to become a real reporter some time perhaps? + + def test_report_unknown(self): + reporter.report_unknown('foo') + assert stdout.getvalue() == 'Unknown report\\: foo\n\n' + + def test_report_SendItem(self): + item = Container(item='foo/bar.py', channel=False) + reporter.report_SendItem(item) + assert stdout.getvalue() == '' + stdout.seek(0) + stdout.truncate() + reporter.config.option.verbose = True + reporter.report_SendItem(item) + assert stdout.getvalue() == ('sending item foo/bar.py to ' + 'localhost\n\n') + + def test_report_HostRSyncing(self): + item = Container(hostname='localhost', remoterootpath='/foo/bar') + reporter.report_HostRSyncing(item) + assert stdout.getvalue() == ('::\n\n localhost: RSYNC ==> ' + '/foo/bar\n\n') + + def test_report_HostReady(self): + item = Container(hostname='localhost') + reporter.report_HostReady(item) + assert stdout.getvalue() == '::\n\n localhost: READY\n\n' + + def test_report_TestStarted(self): + event = Container(hosts=['localhost', 'foo.com'], timestart=0) + reporter.report_TestStarted(event) + assert stdout.getvalue() == """\ +=========================================== +Running tests on hosts\: localhost, foo.com +=========================================== + +""" + + def test_report_ItemStart(self): + class FakeModule(py.test.collect.Module): + def __init__(self): + pass + def tryiter(self): + return ['test_foo', 'test_bar'] + def listnames(self): + return ['foo', 'bar.py'] + event = Container(item=FakeModule()) + reporter.report_ItemStart(event) + assert stdout.getvalue() == """\ +Testing module foo/bar.py (2 items) +----------------------------------- + +""" + + def test_print_summary(self): + reporter.timestart = 10 + reporter.timeend = 20 + reporter.timersync = 15 + reporter.print_summary(10, '', '') + assert stdout.getvalue() == """\ +10 tests run in 10.00s (rsync\: 5.00) +------------------------------------- + +""" + + def test_ReceivedItemOutcome_PASSED(self): + outcome = Container(passed=True, excinfo=False) + item = Container(listnames=lambda: ['', 'foo.py', 'bar', '()', 'baz']) + event = Container(channel=False, outcome=outcome, item=item) + reporter.report_ReceivedItemOutcome(event) + assert stdout.getvalue() == ('* localhost\\: **PASSED** ' + 'foo.py/bar()/baz\n\n') + + def test_ReceivedItemOutcome_SKIPPED(self): + outcome = Container(passed=False, skipped=True, excinfo=False) + item = Container(listnames=lambda: ['', 'foo.py', 'bar', '()', 'baz']) + event = Container(channel=False, outcome=outcome, item=item) + reporter.report_ReceivedItemOutcome(event) + assert stdout.getvalue() == ('* localhost\\: **SKIPPED** ' + 'foo.py/bar()/baz\n\n') + + def test_ReceivedItemOutcome_FAILED(self): + excinfo = Container(typename='FooError', value='a foo has occurred', + traceback=[' in foo in line 1, in foo:', + ' bar()', + ' in bar in line 4, in bar:', + (' raise FooError("a foo has ' + 'occurred")')]) + outcome = Container(passed=False, skipped=False, excinfo=excinfo) + item = Container(listnames=lambda: ['', 'foo.py', 'bar', '()', 'baz']) + event = Container(channel=False, outcome=outcome, item=item) + reporter.report_ReceivedItemOutcome(event) + assert stdout.getvalue() == """\ +* localhost\: **FAILED** foo.py/bar()/baz + +exception\: **FooError** + +:: + + a foo has occurred + +:: + + in foo in line 1, in foo: + bar() + in bar in line 4, in bar: + raise FooError("a foo has occurred") + +""" class TestRestReporter(AbstractTestReporter): reporter = RestReporter @@ -19,4 +146,29 @@ - localhost\\: FAILED py test rsession testing test\\_slave.py funcpass - localhost\\: PASSED py test rsession testing test\\_slave.py funcpass """ - assert val == expected + print repr(val) + expected_start = """\ +* localhost\: **FAILED** py/test/rsession/testing/test\_slave.py/funcpass + +* localhost\: **SKIPPED** py/test/rsession/testing/test\_slave.py/funcpass + +* localhost\: **FAILED** py/test/rsession/testing/test\_slave.py/funcpass + +exception\\: **ZeroDivisionError** + +:: + + integer division or modulo by zero + +:: + +""" + + expected_end = """ + +* localhost\: **PASSED** py/test/rsession/testing/test\_slave.py/funcpass + +""" + assert val.startswith(expected_start) + assert val.endswith(expected_end) + From fijal at codespeak.net Thu Nov 23 10:48:11 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Thu, 23 Nov 2006 10:48:11 +0100 (CET) Subject: [py-svn] r34884 - py/dist/py/documentation Message-ID: <20061123094811.1BDC410061@code0.codespeak.net> Author: fijal Date: Thu Nov 23 10:48:06 2006 New Revision: 34884 Modified: py/dist/py/documentation/source-viewer.txt Log: Added note about introspection. Modified: py/dist/py/documentation/source-viewer.txt ============================================================================== --- py/dist/py/documentation/source-viewer.txt (original) +++ py/dist/py/documentation/source-viewer.txt Thu Nov 23 10:48:06 2006 @@ -35,3 +35,11 @@ like pypy's compiler package we'll move. We create out of AST stuff which can be accessed by dot. Like module.function.firstlineno, with interface to py.code.Source as well. + +We can use introspection for some aspects. Right now only top-level functions +and classes (not laying inside if and such) are exposed. We can use +introspection to check which functions are exposed at a module level +(all the ifs and such are executed ususally during initialisation of +module, so we'll have all of necessary informations). Also we can check +if elements was not overwritten, or has changed name by compiling code +and checking. From guido at codespeak.net Thu Nov 23 11:43:28 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Thu, 23 Nov 2006 11:43:28 +0100 (CET) Subject: [py-svn] r34885 - in py/dist/py/test: . rsession rsession/testing Message-ID: <20061123104328.2AED210061@code0.codespeak.net> Author: guido Date: Thu Nov 23 11:43:24 2006 New Revision: 34885 Modified: py/dist/py/test/defaultconftest.py py/dist/py/test/rsession/rest.py py/dist/py/test/rsession/testing/test_rest.py Log: Added link writer mechanism to create links for paths in the ReST, added code to display exceptions and signals. Modified: py/dist/py/test/defaultconftest.py ============================================================================== --- py/dist/py/test/defaultconftest.py (original) +++ py/dist/py/test/defaultconftest.py Thu Nov 23 11:43:24 2006 @@ -10,6 +10,8 @@ additionalinfo = None +linkwriter = py.test.rest.RelLinkWriter() + def adddefaultoptions(): Option = py.test.Config.Option py.test.Config.addoptions('general options', Modified: py/dist/py/test/rsession/rest.py ============================================================================== --- py/dist/py/test/rsession/rest.py (original) +++ py/dist/py/test/rsession/rest.py Thu Nov 23 11:43:24 2006 @@ -6,9 +6,21 @@ import sys from StringIO import StringIO from py.__.test.rsession.reporter import AbstractReporter +from py.__.test.rsession import report from py.__.rest.rst import * class RestReporter(AbstractReporter): + linkwriter = None + + def __init__(self, *args, **kwargs): + super(RestReporter, self).__init__(*args, **kwargs) + self.rest = Rest() + + def get_linkwriter(self): + if self.linkwriter is None: + self.linkwriter = self.config.getinitialvalue('linkwriter') + return self.linkwriter + def report_unknown(self, what): self.add_rest(Paragraph("Unknown report: %s" % what)) @@ -41,10 +53,21 @@ item = event.item if isinstance(item, py.test.collect.Module): lgt = len(list(item.tryiter())) - lns = item.listnames() + lns = item.listnames()[1:] name = "/".join(lns) + link = self.get_linkwriter().get_link(self.get_rootpath(item), + item.fspath) + if link: + name = Link(name, link) txt = 'Testing module %s (%d items)' % (name, lgt) - self.add_rest(Title(txt, belowchar='-')) + self.add_rest(Title('Testing module', name, '(%d items)' % (lgt,), + belowchar='-')) + + def get_rootpath(self, item): + root = item.parent + while root.parent is not None: + root = root.parent + return root.fspath def print_summary(self, total, skipped_str, failed_str): txt = "%d tests run%s%s in %.2fs (rsync: %.2f)" % \ @@ -52,6 +75,13 @@ self.timersync - self.timestart) self.add_rest(Title(txt, belowchar='-')) + self.skips() + self.failures() + + # since we're rendering each item, the links haven't been rendered + # yet + self.out.write(self.rest.render_links()) + def report_ReceivedItemOutcome(self, event): host = self.get_host(event) if event.outcome.passed: @@ -66,20 +96,159 @@ self.failed[host] += 1 self.failed_tests_outcome.append(event) # we'll take care of them later - lns = event.item.listnames()[1:] - for i, ln in enumerate(lns): - if i > 0 and ln != '()': - lns[i] = '/%s' % (ln,) - itempath = ''.join(lns) + itempath = self.get_path_from_item(event.item) self.add_rest(ListItem(Text("%10s:" % (host[:10],)), Strong(status), Text(itempath))) - if event.outcome.excinfo: - excinfo = event.outcome.excinfo - self.add_rest(Paragraph('exception:', Strong(excinfo.typename))) - self.add_rest(LiteralBlock(excinfo.value)) - self.add_rest(LiteralBlock('\n'.join([str(x) for x in - excinfo.traceback]))) + + def skips(self): + # XXX hrmph, copied code + 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.add_rest(Title('Reasons for skipped tests:', belowchar='+')) + for text, items in texts.items(): + for item in items: + self.add_rest(ListItem('%s: %s' % (item, text))) + + def failures(self): + tbstyle = self.config.option.tbstyle + if self.failed_tests_outcome: + self.add_rest(Title('Exceptions:', belowchar='+')) + for i, event in enumerate(self.failed_tests_outcome): + if i > 0: + self.add_rest(Transition()) + if isinstance(event, report.ReceivedItemOutcome): + host = self.get_host(event) + itempath = self.get_path_from_item(event.item) + root = self.get_rootpath(event.item) + link = self.get_linkwriter().get_link(root, event.item.fspath) + t = Title(belowchar='+') + if link: + t.add(Link(itempath, link)) + else: + t.add(Text(itempath)) + t.add(Text('on %s' % (host,))) + self.add_rest(t) + if event.outcome.signal: + self.repr_signal(event.item, event.outcome) + else: + self.repr_failure(event.item, event.outcome, tbstyle) + else: + itempath = self.get_path_from_item(event.item) + root = self.get_rootpath(event.item) + link = self.get_linkwriter().get_link(root, event.item.fspath) + t = Title(abovechar='+', belowchar='+') + if link: + t.add(Link(itempath, link)) + else: + t.add(Text(itempath)) + out = outcome.Outcome(excinfo=event.excinfo) + self.repr_failure(event.item, + outcome.ReprOutcome(out.make_repr()), + tbstyle) + + def repr_signal(self, item, outcome): + signal = outcome.signal + self.add_rest(Title('Received signal: %d' % (outcome.signal,), + abovechar='+', belowchar='+')) + if outcome.stdout.strip(): + self.add_rest(Paragraph('Captured process stdout:')) + self.add_rest(LiteralBlock(outcome.stdout)) + if outcome.stderr.strip(): + self.add_rest(Paragraph('Captured process stderr:')) + self.add_rest(LiteralBlock(outcome.stderr)) + + def repr_failure(self, item, outcome, style): + excinfo = outcome.excinfo + traceback = excinfo.traceback + if not traceback: + self.add_rest(Paragraph('empty traceback from item %r' % (item,))) + return + self.repr_traceback(item, excinfo, traceback, style) + if outcome.stdout: + self.add_rest(Title('Captured process stdout:', abovechar='+', + belowchar='+')) + self.add_rest(LiteralBlock(outcome.stdout)) + if outcome.stderr: + self.add_rest(Title('Captured process stderr:', abovechar='+', + belowchar='+')) + self.add_rest(LiteralBlock(outcome.stderr)) + + def repr_traceback(self, item, excinfo, traceback, style): + root = self.get_rootpath(item) + if style == 'long': + for entry in traceback: + link = self.get_linkwriter().get_link(root, + py.path.local(entry.path)) + if link: + self.add_rest(Title(Link(entry.path, link), + 'line %d' % (entry.lineno,), + belowchar='+', abovechar='+')) + else: + self.add_rest(Title('%s line %d' % (entry.path, + entry.lineno,), + belowchar='+', abovechar='+')) + self.add_rest(LiteralBlock(self.prepare_source(entry.relline, + entry.source))) + elif style == 'short': + text = [] + for entry in traceback: + text.append('%s line %d' % (entry.path, entry.lineno)) + text.append(' %s' % (entry.source.strip(),)) + self.add_rest(LiteralBlock('\n'.join(text))) + self.add_rest(Title(excinfo.typename, belowchar='+')) + self.add_rest(LiteralBlock(excinfo.value)) + + def prepare_source(self, relline, source): + text = [] + for num, line in enumerate(source.split('\n')): + if num == relline: + text.append('>>> %s' % (line,)) + else: + text.append(' %s' % (line,)) + return '\n'.join(text) def add_rest(self, item): + self.rest.add(item) self.out.write('%s\n\n' % (item.text(),)) + def get_path_from_item(self, item): + lns = item.listnames()[1:] + for i, ln in enumerate(lns): + if i > 0 and ln != '()': + lns[i] = '/%s' % (ln,) + itempath = ''.join(lns) + return itempath + +class AbstractLinkWriter(object): + def get_link(self, base, path): + pass + +class NoLinkWriter(AbstractLinkWriter): + def get_link(self, base, path): + return '' + +class LinkWriter(AbstractLinkWriter): + def __init__(self, baseurl): + self.baseurl = baseurl + + def get_link(self, base, path): + relpath = path.relto(base) + return self.baseurl + relpath + +class RelLinkWriter(AbstractLinkWriter): + def get_link(self, base, path): + return path.relto(base) + Modified: py/dist/py/test/rsession/testing/test_rest.py ============================================================================== --- py/dist/py/test/rsession/testing/test_rest.py (original) +++ py/dist/py/test/rsession/testing/test_rest.py Thu Nov 23 11:43:24 2006 @@ -4,7 +4,8 @@ import py from py.__.test.rsession.testing.test_reporter import AbstractTestReporter -from py.__.test.rsession.rest import RestReporter +from py.__.test.rsession import report +from py.__.test.rsession.rest import RestReporter, NoLinkWriter from py.__.rest.rst import * class RestTestReporter(RestReporter): @@ -16,6 +17,12 @@ def __init__(self, **kwargs): self.__dict__.update(kwargs) +class FakeOutcome(Container, report.ReceivedItemOutcome): + pass + +class FakeTryiter(Container, report.SkippedTryiter): + pass + class TestRestUnits(object): def setup_method(self, method): config, args = py.test.Config.parse(["some_sub"]) @@ -24,6 +31,7 @@ ['localhost']) method.im_func.func_globals['stdout'] = s = py.std.StringIO.StringIO() r.out = s # XXX will need to become a real reporter some time perhaps? + r.linkwriter = NoLinkWriter() def test_report_unknown(self): reporter.report_unknown('foo') @@ -63,13 +71,15 @@ def test_report_ItemStart(self): class FakeModule(py.test.collect.Module): - def __init__(self): - pass + def __init__(self, parent): + self.parent = parent + self.fspath = py.path.local('.') def tryiter(self): return ['test_foo', 'test_bar'] def listnames(self): - return ['foo', 'bar.py'] - event = Container(item=FakeModule()) + return ['package', 'foo', 'bar.py'] + parent = Container(parent=None, fspath=py.path.local('.')) + event = Container(item=FakeModule(parent)) reporter.report_ItemStart(event) assert stdout.getvalue() == """\ Testing module foo/bar.py (2 items) @@ -105,31 +115,148 @@ 'foo.py/bar()/baz\n\n') def test_ReceivedItemOutcome_FAILED(self): - excinfo = Container(typename='FooError', value='a foo has occurred', - traceback=[' in foo in line 1, in foo:', - ' bar()', - ' in bar in line 4, in bar:', - (' raise FooError("a foo has ' - 'occurred")')]) - outcome = Container(passed=False, skipped=False, excinfo=excinfo) + outcome = Container(passed=False, skipped=False) item = Container(listnames=lambda: ['', 'foo.py', 'bar', '()', 'baz']) event = Container(channel=False, outcome=outcome, item=item) reporter.report_ReceivedItemOutcome(event) assert stdout.getvalue() == """\ * localhost\: **FAILED** foo.py/bar()/baz -exception\: **FooError** +""" + + def test_skips(self): + reporter.skips() + assert stdout.getvalue() == '' + reporter.skipped_tests_outcome = [ + FakeOutcome(outcome=Container(skipped='problem X'), + item=Container(listnames=lambda: ['foo', 'bar.py'])), + FakeTryiter(excinfo=Container(value='problem Y'), + item=Container(listnames=lambda: ['foo', 'baz.py']))] + reporter.skips() + assert stdout.getvalue() == """\ +Reasons for skipped tests\: ++++++++++++++++++++++++++++ + +* foo/bar.py\: problem X + +* foo/baz.py\: problem Y + +""" + + def test_failures(self): + parent = Container(parent=None, fspath=py.path.local('.')) + reporter.failed_tests_outcome = [ + FakeOutcome( + outcome=Container( + signal=False, + excinfo=Container( + typename='FooError', + value='A foo has occurred', + traceback=[ + Container( + path='foo/bar.py', + lineno=1, + relline=1, + source='foo()', + ), + Container( + path='foo/baz.py', + lineno=4, + relline=1, + source='raise FooError("A foo has occurred")', + ), + ] + ), + stdout='', + stderr='', + ), + item=Container( + listnames=lambda: ['package', 'foo', 'bar.py', + 'baz', '()'], + parent=parent, + fspath=py.path.local('.'), + ), + channel=None, + ), + ] + reporter.config.option.tbstyle = 'no' + reporter.failures() + assert stdout.getvalue() == """\ +Exceptions\: +++++++++++++ + +foo/bar.py/baz() on localhost ++++++++++++++++++++++++++++++ + +FooError +++++++++ :: - a foo has occurred + A foo has occurred + +""" + + reporter.config.option.tbstyle = 'short' + stdout.seek(0) + stdout.truncate() + reporter.failures() + assert stdout.getvalue() == """\ +Exceptions\: +++++++++++++ + +foo/bar.py/baz() on localhost ++++++++++++++++++++++++++++++ + +:: + + foo/bar.py line 1 + foo() + foo/baz.py line 4 + raise FooError("A foo has occurred") + +FooError +++++++++ :: - in foo in line 1, in foo: - bar() - in bar in line 4, in bar: - raise FooError("a foo has occurred") + A foo has occurred + +""" + + reporter.config.option.tbstyle = 'long' + stdout.seek(0) + stdout.truncate() + reporter.failures() + stdout.getvalue() == """\ +Exceptions\: +++++++++++++ + +foo/bar.py/baz() on localhost ++++++++++++++++++++++++++++++ + ++++++++++++++++++ +foo/bar.py line 1 ++++++++++++++++++ + +:: + + foo() + ++++++++++++++++++ +foo/baz.py line 4 ++++++++++++++++++ + +:: + + raise FooError("A foo has occurred") + +FooError +++++++++ + +:: + + A foo has occurred """ @@ -141,34 +268,16 @@ def test_report_received_item_outcome(self): val = self.report_received_item_outcome() - expected = """- localhost\\: FAILED py test rsession testing test\\_slave.py funcpass -- localhost\\: SKIPPED py test rsession testing test\\_slave.py funcpass -- localhost\\: FAILED py test rsession testing test\\_slave.py funcpass -- localhost\\: PASSED py test rsession testing test\\_slave.py funcpass -""" - print repr(val) - expected_start = """\ + expected = """\ * localhost\: **FAILED** py/test/rsession/testing/test\_slave.py/funcpass * localhost\: **SKIPPED** py/test/rsession/testing/test\_slave.py/funcpass * localhost\: **FAILED** py/test/rsession/testing/test\_slave.py/funcpass -exception\\: **ZeroDivisionError** - -:: - - integer division or modulo by zero - -:: - -""" - - expected_end = """ - * localhost\: **PASSED** py/test/rsession/testing/test\_slave.py/funcpass """ - assert val.startswith(expected_start) - assert val.endswith(expected_end) + print val + assert val == expected From guido at codespeak.net Thu Nov 23 14:37:38 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Thu, 23 Nov 2006 14:37:38 +0100 (CET) Subject: [py-svn] r34893 - py/dist/py Message-ID: <20061123133738.D708C10061@code0.codespeak.net> Author: guido Date: Thu Nov 23 14:37:36 2006 New Revision: 34893 Modified: py/dist/py/__init__.py Log: Added initpkg entries for the py.test.rest stuff. Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Thu Nov 23 14:37:36 2006 @@ -50,6 +50,10 @@ 'test.collect.Generator' : ('./test/collect.py', 'Generator'), 'test.Item' : ('./test/item.py', 'Item'), 'test.Function' : ('./test/item.py', 'Function'), + 'test.rest.RestReporter' : ('./test/rsession/rest.py', 'RestReporter'), + 'test.rest.NoLinkWriter' : ('./test/rsession/rest.py', 'NoLinkWriter'), + 'test.rest.LinkWriter' : ('./test/rsession/rest.py', 'LinkWriter'), + 'test.rest.RelLinkWriter': ('./test/rsession/rest.py', 'RelLinkWriter'), # thread related API (still in early design phase) '_thread.WorkerPool' : ('./thread/pool.py', 'WorkerPool'), From guido at codespeak.net Thu Nov 23 16:01:19 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Thu, 23 Nov 2006 16:01:19 +0100 (CET) Subject: [py-svn] r34904 - py/dist/py/misc Message-ID: <20061123150119.E83B210061@code0.codespeak.net> Author: guido Date: Thu Nov 23 16:01:11 2006 New Revision: 34904 Modified: py/dist/py/misc/error.py Log: Added another possible error code for ENOTDIR on win32 Modified: py/dist/py/misc/error.py ============================================================================== --- py/dist/py/misc/error.py (original) +++ py/dist/py/misc/error.py Thu Nov 23 16:01:11 2006 @@ -21,6 +21,7 @@ _winerrnomap = { 2: errno.ENOENT, 3: errno.ENOENT, + 22: errno.ENOTDIR, 267: errno.ENOTDIR, 5: errno.EACCES, # anything better? } From guido at codespeak.net Thu Nov 23 16:48:17 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Thu, 23 Nov 2006 16:48:17 +0100 (CET) Subject: [py-svn] r34908 - in py/dist/py: misc path/svn/testing Message-ID: <20061123154817.279A810063@code0.codespeak.net> Author: guido Date: Thu Nov 23 16:48:14 2006 New Revision: 34908 Modified: py/dist/py/misc/error.py py/dist/py/path/svn/testing/test_urlcommand.py py/dist/py/path/svn/testing/test_wccommand.py Log: Added more error codes, fixed stupid win32 escaping issues in path.svn Modified: py/dist/py/misc/error.py ============================================================================== --- py/dist/py/misc/error.py (original) +++ py/dist/py/misc/error.py Thu Nov 23 16:48:14 2006 @@ -21,6 +21,7 @@ _winerrnomap = { 2: errno.ENOENT, 3: errno.ENOENT, + 17: errno.EEXIST, 22: errno.ENOTDIR, 267: errno.ENOTDIR, 5: errno.EACCES, # anything better? 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 Thu Nov 23 16:48:14 2006 @@ -31,6 +31,11 @@ py.path.svnurl("http://host.com:8080/some/dir") def test_svnurl_characters_colon_path(self): + if py.std.sys.platform == 'win32': + # colons are allowed on win32, because they're part of the drive + # part of an absolute path... however, they shouldn't be allowed in + # other parts, I think + py.test.skip('XXX fixme win32') py.test.raises(ValueError, 'py.path.svnurl("http://host.com/foo:bar")') def test_svnurl_characters_tilde_end(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 Thu Nov 23 16:48:14 2006 @@ -293,7 +293,13 @@ py.process.cmdexec('svnadmin create "%s"' % svncommon._escape_helper(repo)) wc = py.path.svnwc(wcdir) - wc.checkout(url='file://%s' % repo) + repopath = repo.strpath + if py.std.sys.platform.startswith('win32'): + # strange win problem, paths become something like file:///c:\\foo + repourl = 'file:///%s' % (repopath.replace('\\', '/'),) + else: + repourl = 'file://%s' % (repopath,) + wc.checkout(url=repourl) cls.wc = wc def test_info(self): From fijal at codespeak.net Thu Nov 23 21:51:29 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Thu, 23 Nov 2006 21:51:29 +0100 (CET) Subject: [py-svn] r34918 - in py/dist/py/apigen/source: . testing Message-ID: <20061123205129.5A12E10068@code0.codespeak.net> Author: fijal Date: Thu Nov 23 21:51:21 2006 New Revision: 34918 Added: py/dist/py/apigen/source/html.py (contents, props changed) py/dist/py/apigen/source/testing/test_html.py (contents, props changed) Modified: py/dist/py/apigen/source/browser.py Log: (fijal, guido) - HTML generation out of source browser Modified: py/dist/py/apigen/source/browser.py ============================================================================== --- py/dist/py/apigen/source/browser.py (original) +++ py/dist/py/apigen/source/browser.py Thu Nov 23 21:51:21 2006 @@ -12,7 +12,10 @@ from py.__.path.common import PathBase -class Module(object): +class BaseElem(object): + pass # purely for testing isinstance + +class Module(BaseElem): def __init__(self, path, _dict): self.path = path self.dict = _dict @@ -22,6 +25,9 @@ return self.dict[attr] except KeyError: raise AttributeError(attr) + + def get_children(self): + return self.dict.values() def get_endline(start, lst): l = reversed(lst) @@ -33,13 +39,13 @@ return end_ch return start -class Function(object): +class Function(BaseElem): def __init__(self, name, firstlineno, endlineno): self.firstlineno = firstlineno self.endlineno = endlineno self.name = name -class Method(object): +class Method(BaseElem): def __init__(self, name, firstlineno, endlineno): self.name = name self.firstlineno = firstlineno @@ -62,7 +68,7 @@ cls_ast.code.nodes if isinstance(i, ast.Function)]) return Class(name, startline, endline, bases, methods) -class Class(object): +class Class(BaseElem): def __init__(self, name, firstlineno, endlineno, bases, methods): self.bases = bases self.firstlineno = firstlineno @@ -75,9 +81,13 @@ return self.methods[attr] except KeyError: raise AttributeError(attr) + + def get_children(self): + return self.methods.values() def parse_path(path): - assert isinstance(path, PathBase), "Cannot work on files directly" + if not isinstance(path, PathBase): + path = py.path.local(path) buf = path.open().read() st = parse(buf) # first go - we get all functions and classes defined on top-level Added: py/dist/py/apigen/source/html.py ============================================================================== --- (empty file) +++ py/dist/py/apigen/source/html.py Thu Nov 23 21:51:21 2006 @@ -0,0 +1,59 @@ + +""" html - generating ad-hoc html out of source browser +""" + +from py.xml import html +from compiler import ast + +class HtmlEnchanter(object): + def __init__(self, mod): + self.mod = mod + self.create_caches() + + def create_caches(self): + mod = self.mod + linecache = {} + for item in mod.get_children(): + linecache[item.firstlineno] = item + self.linecache = linecache + + def enchant_row(self, num, row): + # add some informations to row, like functions defined in that line, etc. + try: + item = self.linecache[num] + # XXX: this should not be assertion, rather check, but we want to + # know if stuff is working + pos = row.find(item.name) + assert pos != -1 + end = len(item.name) + pos + return [row[:pos], html.a(row[pos:end], href="#" + item.name, + name=item.name), row[end:]] + except KeyError: + return [row] # no more info + +def make_code(lst): + # I HATE HTML, I HATE HTML + output = [] + for elem in lst: + if isinstance(elem, str): + output.append(html.code(elem)) + else: + output.append(elem) + return output + +def create_html(mod): + # out is some kind of stream + #*[html.tr(html.td(i.name)) for i in mod.get_children()] + lines = mod.path.open().readlines() + + enchanter = HtmlEnchanter(mod) + rows = [enchanter.enchant_row(num + 1, row[:-1]) for num, row in enumerate(lines)] + html_rows = [html.tr(html.td(html.pre(num + 1), html.td(html.pre(*i)))) \ + for num, i in enumerate(rows)] + + output = html.table( + html.tbody( + *html_rows + ) + ) + return output Added: py/dist/py/apigen/source/testing/test_html.py ============================================================================== --- (empty file) +++ py/dist/py/apigen/source/testing/test_html.py Thu Nov 23 21:51:21 2006 @@ -0,0 +1,47 @@ + +""" test of html generation +""" + +from py.__.apigen.source.html import create_html +from py.__.apigen.source.browser import parse_path + +import py +import os + +def create_html_and_show(path): + mod = parse_path(path) + html = create_html(mod) + testfile = py.test.ensuretemp("htmloutput").ensure("test.html") + testfile.write(unicode(html)) + return testfile + +def test_basic(): + tmp = py.test.ensuretemp("sourcehtml") + inp = tmp.ensure("one.py") + inp.write(py.code.Source(""" + def func_one(): + pass + + def func_two(x, y): + x = 1 + y = 2 + return x + y + + class B: + pass + + class A(B): + def meth1(self): + pass + + def meth2(self): + pass + """)) + + testfile = create_html_and_show(inp) + data = testfile.open().read() + assert data.find(' Author: fijal Date: Thu Nov 23 21:55:28 2006 New Revision: 34920 Modified: py/dist/py/apigen/source/html.py Log: Make not appear inside other Modified: py/dist/py/apigen/source/html.py ============================================================================== --- py/dist/py/apigen/source/html.py (original) +++ py/dist/py/apigen/source/html.py Thu Nov 23 21:55:28 2006 @@ -48,7 +48,7 @@ enchanter = HtmlEnchanter(mod) rows = [enchanter.enchant_row(num + 1, row[:-1]) for num, row in enumerate(lines)] - html_rows = [html.tr(html.td(html.pre(num + 1), html.td(html.pre(*i)))) \ + html_rows = [html.tr(html.td(html.pre(num + 1)), html.td(html.pre(*i))) \ for num, i in enumerate(rows)] output = html.table( From guido at codespeak.net Thu Nov 23 22:13:50 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Thu, 23 Nov 2006 22:13:50 +0100 (CET) Subject: [py-svn] r34922 - py/dist/py/path/local Message-ID: <20061123211350.513FA10061@code0.codespeak.net> Author: guido Date: Thu Nov 23 22:13:48 2006 New Revision: 34922 Modified: py/dist/py/path/local/local.py Log: Fixed bare excepts. Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Thu Nov 23 22:13:48 2006 @@ -630,21 +630,21 @@ # make link... try: username = os.environ['USER'] #linux, et al - except: + except KeyError: try: username = os.environ['USERNAME'] #windows - except: + except KeyError: username = 'current' src = str(udir) dest = src[:src.rfind('-')] + '-' + username try: os.unlink(dest) - except: + except OSError: pass try: os.symlink(src, dest) - except: + except OSError: pass return udir From guido at codespeak.net Thu Nov 23 22:17:44 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Thu, 23 Nov 2006 22:17:44 +0100 (CET) Subject: [py-svn] r34923 - py/dist/py/path/local Message-ID: <20061123211744.DCE3A10061@code0.codespeak.net> Author: guido Date: Thu Nov 23 22:17:43 2006 New Revision: 34923 Modified: py/dist/py/path/local/local.py Log: On Windows an AttributeError is raised when trying to symlink (duh) Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Thu Nov 23 22:17:43 2006 @@ -644,7 +644,7 @@ pass try: os.symlink(src, dest) - except OSError: + except (OSError, AttributeError): # AttributeError on win32 pass return udir From guido at codespeak.net Thu Nov 23 23:12:01 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Thu, 23 Nov 2006 23:12:01 +0100 (CET) Subject: [py-svn] r34924 - in py/dist/py/apigen/rest: . testing Message-ID: <20061123221201.E4CC410061@code0.codespeak.net> Author: guido Date: Thu Nov 23 23:11:59 2006 New Revision: 34924 Modified: py/dist/py/apigen/rest/genrest.py py/dist/py/apigen/rest/testing/test_rest.py Log: Fixed tests for win32, fixed problem with path to URL conversion in win32 Modified: py/dist/py/apigen/rest/genrest.py ============================================================================== --- py/dist/py/apigen/rest/genrest.py (original) +++ py/dist/py/apigen/rest/genrest.py Thu Nov 23 23:11:59 2006 @@ -44,6 +44,9 @@ relname = filename[len(path):] if relname.endswith('.pyc'): relname = relname[:-1] + sep = py.std.os.sep + if sep != '/': + relname = relname.replace(sep, '/') return ('%s:%s' % (filename, lineno), self.basepath + relname[1:] + '?view=markup') 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 Thu Nov 23 23:11:59 2006 @@ -18,6 +18,12 @@ from py.__.rest.transform import HTMLHandler # XXX: UUuuuuuuuuuuuuuuuuuuuuuuu, dangerous import +def _nl(s): + """normalize newlines (converting to \n)""" + s = s.replace('\r\n', '\n') + s = s.replace('\r', '\n') + return s + def setup_module(mod): mod.temppath = py.test.ensuretemp('restgen') @@ -86,8 +92,8 @@ 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\n' - assert tempdir.join('bar.txt').read() == 'bar data\n' + assert _nl(tempdir.join('foo.txt').read()) == 'foo data\n' + assert _nl(tempdir.join('bar.txt').read()) == 'bar data\n' def test_getlink(self): dw = DirWriter(temppath.join('dirwriter_getlink')) @@ -239,7 +245,7 @@ r.write() index = tempdir.join('module_Unknown module.txt') assert index.check(file=True) - data = index.read() + data = _nl(index.read()) assert data.find('.. _`fun`: function_fun.html\n') > -1 assert data.find('.. _`fun`: #function-fun\n') == -1 @@ -247,7 +253,7 @@ file=True) r = RestGen(ds, lg, FileWriter(tempfile)) r.write() - data = tempfile.read() + data = _nl(tempfile.read()) assert data.find('.. _`fun`: #function-fun\n') > -1 assert data.find('.. _`fun`: function_fun.html') == -1 @@ -256,7 +262,7 @@ tempfile = temppath.join('internal_links.txt') if not tempfile.check(): py.test.skip('depends on previous test, which failed') - data = tempfile.read() + data = _nl(tempfile.read()) # index should be above the rest print data assert data.find('classes\\:') > -1 From guido at codespeak.net Thu Nov 23 23:35:32 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Thu, 23 Nov 2006 23:35:32 +0100 (CET) Subject: [py-svn] r34925 - py/dist/py/test/rsession/testing Message-ID: <20061123223532.4350B10061@code0.codespeak.net> Author: guido Date: Thu Nov 23 23:35:30 2006 New Revision: 34925 Modified: py/dist/py/test/rsession/testing/test_lsession.py Log: Skipping tests that test forking code on platforms that don't have os.fork. 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 Thu Nov 23 23:35:30 2006 @@ -68,6 +68,8 @@ assert str(tb[0].source).find("execute") != -1 def test_normal(self): + if not hasattr(py.std.os, 'fork'): + py.test.skip('operating system not supported') self.example_distribution(box_runner) def test_plain(self): @@ -103,6 +105,8 @@ assert len(l) == 1 def test_minus_x(self): + if not hasattr(py.std.os, 'fork'): + py.test.skip('operating system not supported') tmpdir = tmp tmpdir.ensure("sub2", "__init__.py") tmpdir.ensure("sub2", "test_one.py").write(py.code.Source(""" @@ -138,6 +142,8 @@ assert len(failevents) == 1 def test_minus_k(self): + if not hasattr(py.std.os, 'fork'): + py.test.skip('operating system not supported') tmpdir = tmp tmpdir.ensure("sub3", "__init__.py") tmpdir.ensure("sub3", "test_some.py").write(py.code.Source(""" @@ -225,6 +231,8 @@ def test_assert_reinterpret(self): + if not hasattr(py.std.os, 'fork'): + py.test.skip('operating system not supported') tmpdir = tmp tmpdir.ensure("sub6", "__init__.py") tmpdir.ensure("sub6", "test_some.py").write(py.code.Source(""" From guido at codespeak.net Thu Nov 23 23:44:59 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Thu, 23 Nov 2006 23:44:59 +0100 (CET) Subject: [py-svn] r34926 - py/dist/py/documentation Message-ID: <20061123224459.8236910061@code0.codespeak.net> Author: guido Date: Thu Nov 23 23:44:58 2006 New Revision: 34926 Removed: py/dist/py/documentation/apigen_comparison.txt Modified: py/dist/py/documentation/apigen.txt Log: Merging apigen documents. Modified: py/dist/py/documentation/apigen.txt ============================================================================== --- py/dist/py/documentation/apigen.txt (original) +++ py/dist/py/documentation/apigen.txt Thu Nov 23 23:44:58 2006 @@ -106,6 +106,110 @@ action you run `py.test --session=L --apigen` in the root of the py lib; this will result in documentation (in HTML) being written to /tmp/output. +Comparison with other documentation generation tools +---------------------------------------------------- + +Apigen is of course not the only documentation generation tool available for +Python. Although we knew in advance that our tool had certain features the +others do not offer, we decided to investigate a bit so that we could do a +proper comparison. + +Tools examined +++++++++++++++ + +After some 'googling around', it turned out that the amount of documentation +generation tools available was surprisingly low. There were only 5 packages +I could find, of which 1 (called 'HappyDoc') seems dead (last release 2001), +one (called 'Pudge') not yet born (perhaps DOA even? most of the links on the +website are dead), and one (called 'Endo') specific to the Enthought suite. +The remaining two were Epydoc, which is widely used [1]_, and PyDoctor, which is +used only by (and written for) the Twisted project, but can be used seperately. + +Epydoc +~~~~~~ + +http://epydoc.sourceforge.net/ + +Epydoc is the best known, and most widely used, documentation generation tool +for Python. It builds a documentation tree by inspecting imported modules and +using Python's introspection features. This way it can display information like +containment, inheritance, and docstrings. + +The tool is relatively sophisticated, with support for generating HTML and PDF, +choosing different styles (CSS), generating graphs using Graphviz, etc. Also +it allows using markup (which can be ReST, JavaDoc, or their own 'epytext' +format) inside docstrings for displaying rich text in the result. + +Quick overview: + + * builds docs from object tree + * displays relatively little information, just inheritance trees, API and + docstrings + * supports some markup (ReST, 'epytext', JavaDoc) in docstrings + +PyDoctor +~~~~~~~~ + +http://codespeak.net/~mwh/pydoctor/ + +This tool is written by Michael Hudson for the Twisted project. The major +difference between this and Epydoc is that it browses the AST (Abstract Syntax +Tree) instead of using 'live' objects, which makes that code that uses special +import mechanisms, or depends on other code that is not available, can still +be inspected. + +The tool is relatively simple and doesn't support the more advanced features +that Epydoc offers, and since it's written basically only for Twisted, I don't +think it will see a lot of development in the future. + +Quick overview: + + * inspects AST rather than object tree + * again not a lot of information, the usual API docstrings, class inheritance + and module structure, but that's it + * rather heavy dependencies (depends on Twisted/Nevow (trunk version)) + * written for Twisted, but quite nice output with other applications (except + that generating docs of the 'py lib' resulted in a max recursion depth + error) + +Quick overview lists of the other tools ++++++++++++++++++++++++++++++++++++++++ + +HappyDoc +~~~~~~~~ + +http://happydoc.sourceforge.net/ + + * dead + * inspects AST + * quite flexible, different output formats (HTML, XML, SGML, PDF) + * pluggable docstring parsers + +Pudge +~~~~~ + +http://pudge.lesscode.org/ + + * immature, dead? + * builds docs from live object tree (I think?) + * supports ReST + * uses Kid templates + +Endo +~~~~ + +https://svn.enthought.com/enthought/wiki/EndoHowTo + + * inspects object tree (I think?) + * 'traits' aware (see https://svn.enthought.com/enthought/wiki/Traits) + * customizable HTML output with custom templating engine + * little documentation, seems like it's written for Enthought's own use + mostly + * heavy dependencies + +.. [1] Epydoc doesn't seem to be developed anymore, either, but it's so + widely used it can not be ignored... + Questions, remarks, etc. ------------------------- Deleted: /py/dist/py/documentation/apigen_comparison.txt ============================================================================== --- /py/dist/py/documentation/apigen_comparison.txt Thu Nov 23 23:44:58 2006 +++ (empty file) @@ -1,103 +0,0 @@ -Comparison of apigen with similar tools -======================================== - -Apigen is of course not the only documentation generation tool available for -Python. Although we knew in advance that our tool had certain features the -others do not offer, we decided to investigate a bit so that we could do a -proper comparison. - -Tools examined ---------------- - -After some 'googling around', it turned out that the amount of documentation -generation tools available was surprisingly low. There were only 5 packages -I could find, of which 1 (called 'HappyDoc') seems dead (last release 2001), -one (called 'Pudge') not yet born (perhaps DOA even? most of the links on the -website are dead), and one (called 'Endo') specific to the Enthought suite. -The remaining two were Epydoc, which is widely used [1]_, and PyDoctor, which is -used only by (and written for) the Twisted project, but can be used seperately. - -Epydoc -------- - -http://epydoc.sourceforge.net/ - -Epydoc is the best known, and most widely used, documentation generation tool -for Python. It builds a documentation tree by inspecting imported modules and -using Python's introspection features. This way it can display information like -containment, inheritance, and docstrings. - -The tool is relatively sophisticated, with support for generating HTML and PDF, -choosing different styles (CSS), generating graphs using Graphviz, etc. Also -it allows using markup (which can be ReST, JavaDoc, or their own 'epytext' -format) inside docstrings for displaying rich text in the result. - -Quick overview: - - * builds docs from object tree - * displays relatively little information, just inheritance trees, API and - docstrings - * supports some markup (ReST, 'epytext', JavaDoc) in docstrings - -PyDoctor ---------- - -http://codespeak.net/~mwh/pydoctor/ - -This tool is written by Michael Hudson for the Twisted project. The major -difference between this and Epydoc is that it browses the AST (Abstract Syntax -Tree) instead of using 'live' objects, which makes that code that uses special -import mechanisms, or depends on other code that is not available, can still -be inspected. - -The tool is relatively simple and doesn't support the more advanced features -that Epydoc offers, and since it's written basically only for Twisted, I don't -think it will see a lot of development in the future. - -Quick overview: - - * inspects AST rather than object tree - * again not a lot of information, the usual API docstrings, class inheritance - and module structure, but that's it - * rather heavy dependencies (depends on Twisted/Nevow (trunk version)) - * written for Twisted, but quite nice output with other applications (except - that generating docs of the 'py lib' resulted in a max recursion depth - error) - -Quick overview lists of the other tools ----------------------------------------- - -HappyDoc -+++++++++ - -http://happydoc.sourceforge.net/ - - * dead - * inspects AST - * quite flexible, different output formats (HTML, XML, SGML, PDF) - * pluggable docstring parsers - -Pudge -++++++ - -http://pudge.lesscode.org/ - - * immature, dead? - * builds docs from live object tree (I think?) - * supports ReST - * uses Kid templates - -Endo -+++++ - -https://svn.enthought.com/enthought/wiki/EndoHowTo - - * inspects object tree (I think?) - * 'traits' aware (see https://svn.enthought.com/enthought/wiki/Traits) - * customizable HTML output with custom templating engine - * little documentation, seems like it's written for Enthought's own use - mostly - * heavy dependencies - -.. [1] Epydoc doesn't seem to be developed anymore, either, but it's so - widely used it can not be ignored... From guido at codespeak.net Thu Nov 23 23:59:13 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Thu, 23 Nov 2006 23:59:13 +0100 (CET) Subject: [py-svn] r34927 - py/dist/py/thread/testing Message-ID: <20061123225913.D3E5810061@code0.codespeak.net> Author: guido Date: Thu Nov 23 23:59:01 2006 New Revision: 34927 Modified: py/dist/py/thread/testing/test_pool.py Log: Not sure why, but this fixes the test on Windows (and doesn't break it on Linux). Modified: py/dist/py/thread/testing/test_pool.py ============================================================================== --- py/dist/py/thread/testing/test_pool.py (original) +++ py/dist/py/thread/testing/test_pool.py Thu Nov 23 23:59:01 2006 @@ -20,7 +20,13 @@ q.get() assert len(pool._alive) == 4 pool.shutdown() - pool.join(timeout=1.0) + # XXX I replaced the following join() with a time.sleep(1), which seems + # to fix the test on Windows, and doesn't break it on Linux... Completely + # unsure what the idea is, though, so it would be nice if someone with some + # more understanding of what happens here would either fix this better, or + # remove this comment... + # pool.join(timeout=1.0) + py.std.time.sleep(1) assert len(pool._alive) == 0 assert len(pool._ready) == 0 From guido at codespeak.net Fri Nov 24 12:49:51 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Fri, 24 Nov 2006 12:49:51 +0100 (CET) Subject: [py-svn] r34931 - py/dist/py/path/svn Message-ID: <20061124114951.9813910075@code0.codespeak.net> Author: guido Date: Fri Nov 24 12:49:49 2006 New Revision: 34931 Added: py/dist/py/path/svn/quoting.txt Log: Document describing problems with paths and URLs in Subversion, to serve as a reference when implementing proper path/URL handling in path/svn/*. Added: py/dist/py/path/svn/quoting.txt ============================================================================== --- (empty file) +++ py/dist/py/path/svn/quoting.txt Fri Nov 24 12:49:49 2006 @@ -0,0 +1,53 @@ +URL escaping in Subversion +========================== + +A quick document describing the rules (as far as we've found out, that is) that +apply to quoting of URLs and file paths in Subversion. Handling quoting +properly is a bit of a challenge, since different rules apply for file paths +and URLs, and those rules aren't entirely clear in either case. + +What follows is a list of semi-random notes that need to be taken into +consideration when implementing proper quoting in the 'py lib'. + +**DISCLAIMER**: currently the idea is just to have this document around as a +TODO list for implementation, not sure what will happen to it in the future... +Don't consider it part of the py lib documentation, and do understand it may be +incomplete or even incorrect... + +* SVN deals with remote objects using URLs and local ones using paths + +* URLs follow (almost) normal `URL encoding rules`_ + + characters that aren't allowed in URL paths (such as :, @, %, etc.) should + be replaced with a % sign following the ASCII value of the character (two + digit HEX) + + an exception (the only one I could find so far) is the drive letter in a file + URL in windows, the following path was required to get a file 'bar' from a + repo in 'c:\\foo':: + + file:///c:/foo/bar + +* URLs always have / as seperator + + on Windows, the \\ characters in paths will have to be replaced with a / + + also (see above) if the path contains a drive letter, a / should be prepended + +* paths don't require encoding + + normally paths don't have to be encoded, however @ can confuse SVN in certain + cases; a workaround is to add @HEAD after the path (also works for relative + paths, I encountered this doing an SVN info on a file called 'bar at baz', in + the end the command 'svn info bar at baz@HEAD' worked) + +* all characters that are supported in paths by all operating systems seem to + be supported by SVN + + basically SVN doesn't deal with platforms that aren't capable of using + certain characters: it will happily allow you to check a file with a name + containing a backslash (\\) in, resulting in a repo that isn't usable in + Windows anymore (you'll get a nasty message explaining how your local + checkout is broken on checking it out)... + +.. _`URL encoding rules`: http://en.wikipedia.org/wiki/Percent-encoding From fijal at codespeak.net Fri Nov 24 16:18:46 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Fri, 24 Nov 2006 16:18:46 +0100 (CET) Subject: [py-svn] r34936 - py/dist/py/documentation Message-ID: <20061124151846.6C6E51007B@code0.codespeak.net> Author: fijal Date: Fri Nov 24 16:18:44 2006 New Revision: 34936 Modified: py/dist/py/documentation/test-distributed.txt py/dist/py/documentation/test.txt Log: Updated a bit distributed testing docs. Modified: py/dist/py/documentation/test-distributed.txt ============================================================================== --- py/dist/py/documentation/test-distributed.txt (original) +++ py/dist/py/documentation/test-distributed.txt Fri Nov 24 16:18:44 2006 @@ -29,6 +29,12 @@ Writing down new reporter is relatively easy, way easier than writing session from a scratch, so one can write down GTK reporter and whatnot. +Only thing to do is to write down new reporter classs which subclasses +`AbstractReporter` in `reporter.py`_ and overrides all the report_Xxx methods +(each of these method is called when one of the message, defined in +`report.py`_ arrives to reporter). Special consideration is needed when dealing +with ReceivedItemOutcome, which is in details described in `outcome.py`_. + 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 @@ -42,16 +48,17 @@ .. _`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 +.. _`reporter.py`: http://codespeak.net/svn/py/dist/py/test/rsession/reporter.py +.. _`outcome.py`: http://codespeak.net/svn/py/dist/py/test/rsession/outcome.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. +--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: ================ Modified: py/dist/py/documentation/test.txt ============================================================================== --- py/dist/py/documentation/test.txt (original) +++ py/dist/py/documentation/test.txt Fri Nov 24 16:18:44 2006 @@ -755,8 +755,9 @@ * `nice_level` - Level of nice under which tests are run * `runner_policy` - (for `LSession` only) - contains policy for the test boxig. `"plain_runner"` means no boxing, `"box_runner"` means boxing. -* `waittime` - Default waiting time for remote host to finish test (after +* `waittime` - Default waiting time for remote host to finish tests (after that period we consider node as dead, default to 100s). +* `max_tasks_per_node` - Maximum number of tasks which can be send to one node. Development Notes ----------------- @@ -764,11 +765,11 @@ Changing the behavior of the web based reporter requires `pypy`_ since the javascript is actually generated fom rpython source. -At some point it would be good to split the default collection process into a -separate collector and reporter as well. This would all the web based reporter -to be used locally. +There exists as well `L/Rsession document`_ which discusses in more details +unique features and developement notes. .. _`pypy`: http://codespeak.net/pypy +.. _`L/Rsession document`: test-distributed.html Future/Planned Features of py.test From arigo at codespeak.net Sat Nov 25 15:02:56 2006 From: arigo at codespeak.net (arigo at codespeak.net) Date: Sat, 25 Nov 2006 15:02:56 +0100 (CET) Subject: [py-svn] r34959 - py/dist/py/rest Message-ID: <20061125140256.CC0A510077@code0.codespeak.net> Author: arigo Date: Sat Nov 25 15:02:55 2006 New Revision: 34959 Modified: py/dist/py/rest/latex.py Log: Don't put a py.path object in sys.path. Confusion. Modified: py/dist/py/rest/latex.py ============================================================================== --- py/dist/py/rest/latex.py (original) +++ py/dist/py/rest/latex.py Sat Nov 25 15:02:55 2006 @@ -82,7 +82,7 @@ configfile = py.path.local(configfile) path = configfile.dirpath() configfile_dic = {} - py.std.sys.path.insert(0, path) + py.std.sys.path.insert(0, str(path)) execfile(str(configfile), configfile_dic) pagebreak = configfile_dic.get("pagebreak", False) rest_sources = [py.path.local(p) From fijal at codespeak.net Sat Nov 25 15:50:35 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Sat, 25 Nov 2006 15:50:35 +0100 (CET) Subject: [py-svn] r34962 - in py/dist/py/apigen/source: . testing Message-ID: <20061125145035.B64A010084@code0.codespeak.net> Author: fijal Date: Sat Nov 25 15:50:32 2006 New Revision: 34962 Modified: py/dist/py/apigen/source/browser.py py/dist/py/apigen/source/testing/test_browser.py Log: Added possibility of ifing functions and classes. Modified: py/dist/py/apigen/source/browser.py ============================================================================== --- py/dist/py/apigen/source/browser.py (original) +++ py/dist/py/apigen/source/browser.py Sat Nov 25 15:50:32 2006 @@ -12,6 +12,8 @@ from py.__.path.common import PathBase +blockers = [ast.Function, ast.Class] + class BaseElem(object): pass # purely for testing isinstance @@ -85,14 +87,35 @@ def get_children(self): return self.methods.values() +def dir_nodes(st): + """ List all the subnodes, which are not blockers + """ + res = [] + for i in st.getChildNodes(): + res.append(i) + if not i.__class__ in blockers: + res += dir_nodes(i) + return res + +def update_mod_dict(imp_mod, mod_dict): + # make sure that things that are in mod_dict, and not in imp_mod, + # are not shown + for key, value in mod_dict.items(): + if not hasattr(imp_mod, key): + del mod_dict[key] + def parse_path(path): if not isinstance(path, PathBase): path = py.path.local(path) buf = path.open().read() st = parse(buf) # first go - we get all functions and classes defined on top-level - function_ast = [i for i in st.node.nodes if isinstance(i, ast.Function)] - classes_ast = [i for i in st.node.nodes if isinstance(i, ast.Class)] + nodes = dir_nodes(st) + function_ast = [i for i in nodes if isinstance(i, ast.Function)] + classes_ast = [i for i in nodes if isinstance(i, ast.Class)] mod_dict = dict([(i.name, function_from_ast(i)) for i in function_ast] + [(i.name, class_from_ast(i)) for i in classes_ast]) + # we check all the elements, if they're really there + mod = path.pyimport() + update_mod_dict(mod, mod_dict) return Module(path, mod_dict) Modified: py/dist/py/apigen/source/testing/test_browser.py ============================================================================== --- py/dist/py/apigen/source/testing/test_browser.py (original) +++ py/dist/py/apigen/source/testing/test_browser.py Sat Nov 25 15:50:32 2006 @@ -38,3 +38,17 @@ assert isinstance(mod.Z.zzz, Method) assert mod.Z.zzz.firstlineno == 13 assert mod.Z.zzz.endlineno == 17 + +def test_if_browser(): + tmp = py.test.ensuretemp("sourcebrowser") + tmp.ensure("b.py").write(py.code.Source(""" + if 1: + def f(): + pass + if 0: + def g(): + pass + """)) + mod = parse_path(tmp.join("b.py")) + assert isinstance(mod.f, Function) + py.test.raises(AttributeError, 'mod.g') From fijal at codespeak.net Sat Nov 25 16:04:38 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Sat, 25 Nov 2006 16:04:38 +0100 (CET) Subject: [py-svn] r34963 - in py/dist/py/test/rsession: . webdata Message-ID: <20061125150438.62E2A10086@code0.codespeak.net> Author: fijal Date: Sat Nov 25 16:04:35 2006 New Revision: 34963 Modified: py/dist/py/test/rsession/webdata/source.js py/dist/py/test/rsession/webjs.py Log: Added counters. 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 Sat Nov 25 16:04:35 2006 @@ -23,8 +23,10 @@ tracebacks = {} skips = {} counters = {} +max_items = {} +short_item_names = {} -MAX_COUNTER = 50 +MAX_COUNTER = 50 # Maximal size of one-line table class Pending(object): def __init__(self): @@ -70,7 +72,10 @@ tr = create_elem("tr") td = create_elem("td") tr.appendChild(td) - td.appendChild(create_text_elem("%s[%s]" % (msg['itemname'], msg['length']))) + td.appendChild(create_text_elem("%s[0/%s]" % (msg['itemname'], msg['length']))) + max_items[msg['fullitemname']] = int(msg['length']) + short_item_names[msg['fullitemname']] = msg['itemname'] + td.id = '_txt_' + msg['fullitemname'] #tr.setAttribute("id", msg['fullitemname']) td.setAttribute("onmouseover", "show_info('%s')" % msg['fullitemname']) td.setAttribute("onmouseout", "hide_info()") @@ -119,8 +124,12 @@ if counters[msg['fullmodulename']] % MAX_COUNTER == 0: tr = create_elem("tr") module_part.appendChild(tr) - - counters[msg['fullmodulename']] += 1 + + name = msg['fullmodulename'] + counters[name] += 1 + counter_part = get_elem('_txt_' + name) + counter_part.childNodes[0].nodeValue = "%s[%d/%d]" % \ + (short_item_names[name], counters[name], max_items[name]) module_part.childNodes[-1].appendChild(td) except: dom.get_document().getElementById("testmain").innerHTML += "some error" From fijal at codespeak.net Sat Nov 25 16:05:24 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Sat, 25 Nov 2006 16:05:24 +0100 (CET) Subject: [py-svn] r34964 - py/dist/py/test/rsession/testing Message-ID: <20061125150524.E99EC10086@code0.codespeak.net> Author: fijal Date: Sat Nov 25 16:05:23 2006 New Revision: 34964 Modified: py/dist/py/test/rsession/testing/test_webjs.py Log: Fixed test. Modified: py/dist/py/test/rsession/testing/test_webjs.py ============================================================================== --- py/dist/py/test/rsession/testing/test_webjs.py (original) +++ py/dist/py/test/rsession/testing/test_webjs.py Sat Nov 25 16:05:23 2006 @@ -59,7 +59,7 @@ tr = trs[0] assert len(tr.childNodes) == 2 assert tr.childNodes[0].nodeName == 'TD' - assert tr.childNodes[0].innerHTML == 'foo.py[10]' + assert tr.childNodes[0].innerHTML == 'foo.py[0/10]' # XXX this is bad I think! table should be inside td assert tr.childNodes[1].nodeName == 'TABLE' assert len(tr.childNodes[1].getElementsByTagName('tr')) == 0 From fijal at codespeak.net Sat Nov 25 16:23:04 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Sat, 25 Nov 2006 16:23:04 +0100 (CET) Subject: [py-svn] r34965 - py/dist/py/test/rsession/testing Message-ID: <20061125152304.1B30B1008E@code0.codespeak.net> Author: fijal Date: Sat Nov 25 16:23:02 2006 New Revision: 34965 Modified: py/dist/py/test/rsession/testing/test_webjs.py Log: Added (partly failing) test. Guido please investigate failure. Modified: py/dist/py/test/rsession/testing/test_webjs.py ============================================================================== --- py/dist/py/test/rsession/testing/test_webjs.py (original) +++ py/dist/py/test/rsession/testing/test_webjs.py Sat Nov 25 16:23:02 2006 @@ -64,3 +64,25 @@ assert tr.childNodes[1].nodeName == 'TABLE' assert len(tr.childNodes[1].getElementsByTagName('tr')) == 0 +def test_process_two(): + main_t = dom.window.document.getElementById('main_table') + msg = {'type': 'ItemStart', + 'itemtype': 'Module', + 'itemname': 'foo.py', + 'fullitemname': 'modules/foo.py', + 'length': 10, + } + webjs.process(msg) + msg = {'type': 'ReceivedItemOutcome', + 'fullmodulename': 'modules/foo.py', + 'passed' : 'True', + 'fullitemname' : 'modules/foo.py/test_item', + } + webjs.process(msg) + trs = main_t.getElementsByTagName('tr') + tds = trs[0].getElementsByTagName('td') + assert len(tds) == 2 + # XXX: This assert obviously is true in output code, why it does not work? + #assert tds[0].innerHTML == 'foo.py[1/10]' + assert tds[1].innerHTML == '.' + From arigo at codespeak.net Sun Nov 26 12:35:23 2006 From: arigo at codespeak.net (arigo at codespeak.net) Date: Sun, 26 Nov 2006 12:35:23 +0100 (CET) Subject: [py-svn] r35000 - py/dist/py/c-extension/greenlet Message-ID: <20061126113523.952861007B@code0.codespeak.net> Author: arigo Date: Sun Nov 26 12:35:20 2006 New Revision: 35000 Modified: py/dist/py/c-extension/greenlet/test_greenlet.py Log: Adapt the test so that it runs on top of pypy-c too. It doesn't *pass* yet, mind. Modified: py/dist/py/c-extension/greenlet/test_greenlet.py ============================================================================== --- py/dist/py/c-extension/greenlet/test_greenlet.py (original) +++ py/dist/py/c-extension/greenlet/test_greenlet.py Sun Nov 26 12:35:20 2006 @@ -4,8 +4,12 @@ except (ImportError, RuntimeError), e: py.test.skip(str(e)) -import sys, thread, threading +import sys, gc from py.test import raises +try: + import thread, threading +except ImportError: + thread = None def test_simple(): lst = [] @@ -22,6 +26,8 @@ assert lst == range(5) def test_threads(): + if not thread: + py.test.skip("this is a test about thread") success = [] def f(): test_simple() @@ -81,11 +87,15 @@ g2.switch(seen) assert seen == [] del g1 + gc.collect() assert seen == [greenlet.GreenletExit] del g2 + gc.collect() assert seen == [greenlet.GreenletExit, greenlet.GreenletExit] def test_dealloc_other_thread(): + if not thread: + py.test.skip("this is a test about thread") seen = [] someref = [] lock = thread.allocate_lock() @@ -97,6 +107,7 @@ g1.switch(seen) someref.append(g1) del g1 + gc.collect() lock.release() lock2.acquire() greenlet() # trigger release @@ -108,6 +119,7 @@ assert seen == [] assert len(someref) == 1 del someref[:] + gc.collect() # g1 is not released immediately because it's from another thread assert seen == [] lock2.release() From guido at codespeak.net Mon Nov 27 14:09:47 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Mon, 27 Nov 2006 14:09:47 +0100 (CET) Subject: [py-svn] r35028 - py/dist/py/test/rsession/testing Message-ID: <20061127130947.9E74110076@code0.codespeak.net> Author: guido Date: Mon Nov 27 14:09:45 2006 New Revision: 35028 Modified: py/dist/py/test/rsession/testing/test_webjs.py Log: Test works after fixing a bug in pypy/translator/js/modules/dom.py. Modified: py/dist/py/test/rsession/testing/test_webjs.py ============================================================================== --- py/dist/py/test/rsession/testing/test_webjs.py (original) +++ py/dist/py/test/rsession/testing/test_webjs.py Mon Nov 27 14:09:45 2006 @@ -83,6 +83,6 @@ tds = trs[0].getElementsByTagName('td') assert len(tds) == 2 # XXX: This assert obviously is true in output code, why it does not work? - #assert tds[0].innerHTML == 'foo.py[1/10]' + assert tds[0].innerHTML == 'foo.py[1/10]' assert tds[1].innerHTML == '.' From mwh at codespeak.net Mon Nov 27 14:29:20 2006 From: mwh at codespeak.net (mwh at codespeak.net) Date: Mon, 27 Nov 2006 14:29:20 +0100 (CET) Subject: [py-svn] r35032 - py/dist/py/documentation Message-ID: <20061127132920.2D25D1007E@code0.codespeak.net> Author: mwh Date: Mon Nov 27 14:29:18 2006 New Revision: 35032 Modified: py/dist/py/documentation/apigen.txt Log: clarify pydoctor's intent. also, it works on the py lib now. Modified: py/dist/py/documentation/apigen.txt ============================================================================== --- py/dist/py/documentation/apigen.txt (original) +++ py/dist/py/documentation/apigen.txt Mon Nov 27 14:29:18 2006 @@ -154,13 +154,14 @@ This tool is written by Michael Hudson for the Twisted project. The major difference between this and Epydoc is that it browses the AST (Abstract Syntax -Tree) instead of using 'live' objects, which makes that code that uses special -import mechanisms, or depends on other code that is not available, can still -be inspected. +Tree) instead of using 'live' objects, which means that code that uses special +import mechanisms, or depends on other code that is not available can still be +inspected. On the other hand, code that, for example, puts bound methods into a +module namespace is not documented. The tool is relatively simple and doesn't support the more advanced features -that Epydoc offers, and since it's written basically only for Twisted, I don't -think it will see a lot of development in the future. +that Epydoc offers. It was written for Twisted and there are no current plans to +promote its use for unrelated projects. Quick overview: @@ -168,9 +169,7 @@ * again not a lot of information, the usual API docstrings, class inheritance and module structure, but that's it * rather heavy dependencies (depends on Twisted/Nevow (trunk version)) - * written for Twisted, but quite nice output with other applications (except - that generating docs of the 'py lib' resulted in a max recursion depth - error) + * written for Twisted, but quite nice output with other applications Quick overview lists of the other tools +++++++++++++++++++++++++++++++++++++++ From guido at codespeak.net Mon Nov 27 21:14:01 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Mon, 27 Nov 2006 21:14:01 +0100 (CET) Subject: [py-svn] r35056 - in py/dist/py/apigen/source: . testing Message-ID: <20061127201401.BB71F1008D@code0.codespeak.net> Author: guido Date: Mon Nov 27 21:13:59 2006 New Revision: 35056 Modified: py/dist/py/apigen/source/html.py py/dist/py/apigen/source/testing/test_html.py Log: Improved HTML output; there's now an HTMLDocument class that takes care of building the HTML, which has a method 'add_row()' to add a line to the document. Also some whitespace fixes and cleanups. Modified: py/dist/py/apigen/source/html.py ============================================================================== --- py/dist/py/apigen/source/html.py (original) +++ py/dist/py/apigen/source/html.py Mon Nov 27 21:13:59 2006 @@ -2,7 +2,7 @@ """ html - generating ad-hoc html out of source browser """ -from py.xml import html +from py.xml import html, raw from compiler import ast class HtmlEnchanter(object): @@ -18,7 +18,8 @@ self.linecache = linecache def enchant_row(self, num, row): - # add some informations to row, like functions defined in that line, etc. + # add some informations to row, like functions defined in that + # line, etc. try: item = self.linecache[num] # XXX: this should not be assertion, rather check, but we want to @@ -26,8 +27,8 @@ pos = row.find(item.name) assert pos != -1 end = len(item.name) + pos - return [row[:pos], html.a(row[pos:end], href="#" + item.name, - name=item.name), row[end:]] + return [row[:pos], html.a(row[pos:end], href="#" + item.name, + name=item.name), row[end:]] except KeyError: return [row] # no more info @@ -41,19 +42,75 @@ output.append(elem) return output +class HTMLDocument(object): + def __init__(self): + self.html = root = html.html() + self.head = head = self.create_head() + root.append(head) + self.body = body = self.create_body() + root.append(body) + self.table, self.tbody = table, tbody = self.create_table() + body.append(table) + + def create_head(self): + return html.head( + html.title('source view'), + html.style(""" + body, td { + background-color: #FFC; + color: black; + font-family: monospace; + } + + table, tr { + margin: 0px; + padding: 0px; + border-width: 0px; + } + + .lineno { + text-align: right; + color: blue; + width: 3em; + padding-right: 1em; + border: 0px solid black; + border-right-width: 1px; + } + + .code { + padding-left: 1em; + white-space: pre; + } + """, type='text/css'), + ) + + def create_body(self): + return html.body() + + def create_table(self): + table = html.table(cellpadding='0', cellspacing='0') + tbody = html.tbody() + table.append(tbody) + return table, tbody + + def add_row(self, lineno, text): + if text == ['']: + text = [raw(' ')] + self.tbody.append(html.tr(html.td(str(lineno), class_='lineno'), + html.td(class_='code', *text))) + + def unicode(self): + return unicode(self.html) + def create_html(mod): # out is some kind of stream #*[html.tr(html.td(i.name)) for i in mod.get_children()] lines = mod.path.open().readlines() enchanter = HtmlEnchanter(mod) - rows = [enchanter.enchant_row(num + 1, row[:-1]) for num, row in enumerate(lines)] - html_rows = [html.tr(html.td(html.pre(num + 1)), html.td(html.pre(*i))) \ - for num, i in enumerate(rows)] - - output = html.table( - html.tbody( - *html_rows - ) - ) - return output + doc = HTMLDocument() + for i, row in enumerate(lines): + row = enchanter.enchant_row(i + 1, row) + doc.add_row(i + 1, row) + return doc.unicode() + Modified: py/dist/py/apigen/source/testing/test_html.py ============================================================================== --- py/dist/py/apigen/source/testing/test_html.py (original) +++ py/dist/py/apigen/source/testing/test_html.py Mon Nov 27 21:13:59 2006 @@ -2,8 +2,9 @@ """ test of html generation """ -from py.__.apigen.source.html import create_html +from py.__.apigen.source.html import create_html, HTMLDocument from py.__.apigen.source.browser import parse_path +from py.xml import html import py import os @@ -44,4 +45,46 @@ assert data.find('source view') > -1 + assert py.std.re.search('', + rendered) + + def test_body(self): + doc = _HTMLDocument() + body = doc.create_body() + assert unicode(body) == '' + + def test_table(self): + doc = _HTMLDocument() + table, tbody = doc.create_table() + assert isinstance(table, html.table) + assert isinstance(tbody, html.tbody) + assert tbody == table[0] + + def test_add_row(self): + doc = HTMLDocument() + doc.add_row(1, ['""" this is a foo implementation """']) + doc.add_row(2, ['']) + doc.add_row(3, ['class ', html.a('Foo', name='Foo'), ':']) + doc.add_row(4, [' pass']) + tbody = doc.tbody + assert len(tbody) == 4 + assert unicode(tbody[0][0]) == '1' + assert unicode(tbody[0][1]) == ('""" ' + 'this is a foo implementation ' + '"""') + assert unicode(tbody[1][1]) == ' ' + assert unicode(tbody[2][1]) == ('class ' + 'Foo:') + From guido at codespeak.net Mon Nov 27 21:41:26 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Mon, 27 Nov 2006 21:41:26 +0100 (CET) Subject: [py-svn] r35059 - py/dist/py/path/svn/testing Message-ID: <20061127204126.452CE10092@code0.codespeak.net> Author: guido Date: Mon Nov 27 21:41:24 2006 New Revision: 35059 Modified: py/dist/py/path/svn/testing/test_urlcommand.py Log: Fixed tests that only worked between 2005-11-24 and 2006-11-24. 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 Mon Nov 27 21:41:24 2006 @@ -1,6 +1,9 @@ import py from py.__.path.svn.urlcommand import InfoSvnCommand -from py.__.path.svn.testing.svntestbase import CommonCommandAndBindingTests, getrepowc +from py.__.path.svn.testing.svntestbase import CommonCommandAndBindingTests, \ + getrepowc +import datetime +import time try: svnversion = py.path.local.sysfind('svn') @@ -61,12 +64,13 @@ def test_svn_1_2(self): line = " 2256 hpk 165 Nov 24 17:55 __init__.py" info = InfoSvnCommand(line) + now = datetime.datetime.now() assert info.last_author == 'hpk' assert info.created_rev == 2256 assert info.kind == 'file' - assert info.mtime == 1132854900.0 + assert time.gmtime(info.mtime)[:6] == (now.year, 11, 24, 17, 55, 0) assert info.size == 165 - assert info.time == 1132854900000000.0 + assert info.time == info.mtime * 1000000 def test_svn_1_3(self): line =" 4784 hpk 2 Jun 01 2004 __init__.py" From guido at codespeak.net Tue Nov 28 13:22:07 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Tue, 28 Nov 2006 13:22:07 +0100 (CET) Subject: [py-svn] r35072 - in py/dist/py/apigen/source: . testing Message-ID: <20061128122207.E0BE210088@code0.codespeak.net> Author: guido Date: Tue Nov 28 13:22:06 2006 New Revision: 35072 Modified: py/dist/py/apigen/source/html.py py/dist/py/apigen/source/testing/test_html.py Log: Made that there's some indentation in the resulting HTML document. Modified: py/dist/py/apigen/source/html.py ============================================================================== --- py/dist/py/apigen/source/html.py (original) +++ py/dist/py/apigen/source/html.py Tue Nov 28 13:22:06 2006 @@ -99,8 +99,8 @@ self.tbody.append(html.tr(html.td(str(lineno), class_='lineno'), html.td(class_='code', *text))) - def unicode(self): - return unicode(self.html) + def __unicode__(self): + return self.html.unicode() def create_html(mod): # out is some kind of stream @@ -112,5 +112,5 @@ for i, row in enumerate(lines): row = enchanter.enchant_row(i + 1, row) doc.add_row(i + 1, row) - return doc.unicode() + return unicode(doc) Modified: py/dist/py/apigen/source/testing/test_html.py ============================================================================== --- py/dist/py/apigen/source/testing/test_html.py (original) +++ py/dist/py/apigen/source/testing/test_html.py Tue Nov 28 13:22:06 2006 @@ -88,3 +88,10 @@ assert unicode(tbody[2][1]) == ('class ' 'Foo:') + def test_unicode(self): + doc = HTMLDocument() + h = unicode(doc) + print h + assert py.std.re.match(r'\s+\s+[^<]+' + '.*\w*$', h, py.std.re.S) + From fijal at codespeak.net Tue Nov 28 13:30:50 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Tue, 28 Nov 2006 13:30:50 +0100 (CET) Subject: [py-svn] r35073 - py/dist/py/documentation Message-ID: <20061128123050.894A010088@code0.codespeak.net> Author: fijal Date: Tue Nov 28 13:30:49 2006 New Revision: 35073 Modified: py/dist/py/documentation/conftest.py Log: Ignore javascript: links when checking. Modified: py/dist/py/documentation/conftest.py ============================================================================== --- py/dist/py/documentation/conftest.py (original) +++ py/dist/py/documentation/conftest.py Tue Nov 28 13:30:49 2006 @@ -101,6 +101,8 @@ class ReSTChecker(py.test.collect.Module): DoctestText = DoctestText + ReSTSyntaxTest = ReSTSyntaxTest + def __repr__(self): return py.test.collect.Collector.__repr__(self) @@ -112,7 +114,7 @@ return [self.fspath.basename, 'checklinks', 'doctest'] def join(self, name): if name == self.fspath.basename: - return ReSTSyntaxTest(name, parent=self) + return self.ReSTSyntaxTest(name, parent=self) elif name == 'checklinks': return LinkCheckerMaker(name, self) elif name == 'doctest': @@ -159,6 +161,8 @@ def localrefcheck(tryfn, path, lineno): # assume it should be a file i = tryfn.find('#') + if tryfn.startswith('javascript:'): + return # don't check JS refs if i != -1: anchor = tryfn[i+1:] tryfn = tryfn[:i] From fijal at codespeak.net Tue Nov 28 13:45:28 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Tue, 28 Nov 2006 13:45:28 +0100 (CET) Subject: [py-svn] r35078 - py/dist/py/test/rsession Message-ID: <20061128124528.F234410097@code0.codespeak.net> Author: fijal Date: Tue Nov 28 13:45:25 2006 New Revision: 35078 Modified: py/dist/py/test/rsession/web.py Log: Updates for use outside. Modified: py/dist/py/test/rsession/web.py ============================================================================== --- py/dist/py/test/rsession/web.py (original) +++ py/dist/py/test/rsession/web.py Tue Nov 28 13:45:25 2006 @@ -219,6 +219,8 @@ exported_methods = ExportedMethods() class TestHandler(BaseHTTPRequestHandler): + exported_methods = exported_methods + def do_GET(self): path = self.path if path.endswith("/"): @@ -231,9 +233,10 @@ getargs = m.group(2) else: getargs = "" - method_to_call = getattr(self, "run_" + path, None) + name_path = path.replace(".", "_") + method_to_call = getattr(self, "run_" + name_path, None) if method_to_call is None: - exec_meth = getattr(exported_methods, path, None) + exec_meth = getattr(self.exported_methods, name_path, None) if exec_meth is None: self.send_error(404, "File %s not found" % path) else: @@ -288,10 +291,15 @@ self.end_headers() self.wfile.write(data) -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 start_server(server_address = ('', 8000), handler=TestHandler, start_new=True): + httpd = HTTPServer(server_address, handler) + + if start_new: + thread.start_new_thread(httpd.serve_forever, ()) + print "Server started, listening on %s" % (server_address,) + else: + print "Server started, listening on %s" % (server_address,) + httpd.serve_forever() def kill_server(): exported_methods.pending_events.put(None) From guido at codespeak.net Wed Nov 29 11:31:15 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Wed, 29 Nov 2006 11:31:15 +0100 (CET) Subject: [py-svn] r35111 - in py/dist/py/test/rsession: . testing webdata Message-ID: <20061129103115.CF4EA10092@code0.codespeak.net> Author: guido Date: Wed Nov 29 11:31:12 2006 New Revision: 35111 Modified: py/dist/py/test/rsession/testing/test_web.py py/dist/py/test/rsession/web.py py/dist/py/test/rsession/webdata/source.js py/dist/py/test/rsession/webjs.py Log: Whitespace and indentation cleanups, fixed bug in parse_args that didn't take care of unquoting (which I assume will not occur in this webserver, but still... the webdeveloper in me couldn't ignore this serious problem ;). 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 Wed Nov 29 11:31:12 2006 @@ -20,3 +20,16 @@ source = rpython2javascript(webjs, FUNCTION_LIST, Options, use_pdb=False) assert source + +def test_parse_args(): + from py.__.test.rsession.web import TestHandler + class TestTestHandler(TestHandler): + def __init__(self): + pass + h = TestTestHandler() + assert h.parse_args('foo=bar') == {'foo': 'bar'} + assert h.parse_args('foo=bar%20baz') == {'foo': 'bar baz'} + assert h.parse_args('foo%20bar=baz') == {'foo bar': 'baz'} + assert h.parse_args('foo=bar%baz') == {'foo': 'bar\xbaz'} + py.test.raises(ValueError, 'h.parse_args("foo")') + Modified: py/dist/py/test/rsession/web.py ============================================================================== --- py/dist/py/test/rsession/web.py (original) +++ py/dist/py/test/rsession/web.py Wed Nov 29 11:31:12 2006 @@ -29,7 +29,7 @@ try: from pypy.rpython.ootypesystem.bltregistry import MethodDesc, BasicExternal,\ - described + described from pypy.translator.js.main import rpython2javascript, Options from pypy.translator.js import commproxy @@ -54,8 +54,8 @@ itemtype = item.__class__.__name__ itemname = item.name fullitemname = "/".join(item.listnames()) - d = {'fullitemname': fullitemname, 'itemtype':itemtype, - 'itemname':itemname} + d = {'fullitemname': fullitemname, 'itemtype': itemtype, + 'itemname': itemname} if itemtype == 'Module': try: d['length'] = str(len(list(event.item.tryiter()))) @@ -90,16 +90,19 @@ def show_hosts(self): self.start_event.wait() return json.write(self.hosts) - show_hosts = described(retval={"aa":"aa"})(show_hosts) + show_hosts = described(retval={"aa": "aa"})(show_hosts) def show_skip(self, item_name="aa"): - return json.write({'item_name':item_name, 'reason':escape(self.skip_reasons[item_name])}) - show_skip = described(retval={"aa":"aa"})(show_skip) + return json.write({'item_name': item_name, + 'reason': escape(self.skip_reasons[item_name])}) + show_skip = described(retval={"aa": "aa"})(show_skip) def show_fail(self, item_name="aa"): - return json.write({'item_name':item_name, 'traceback':escape(str(self.fail_reasons[item_name])),\ - 'stdout':self.stdout[item_name], 'stderr':self.stderr[item_name]}) - show_fail = described(retval={"aa":"aa"})(show_fail) + return json.write({'item_name':item_name, + 'traceback':escape(str(self.fail_reasons[item_name])), + 'stdout':self.stdout[item_name], + 'stderr':self.stderr[item_name]}) + show_fail = described(retval={"aa": "aa"})(show_fail) def show_all_statuses(self): retlist = [self.show_status_change()] @@ -107,7 +110,7 @@ retlist.append(self.show_status_change()) retval = json.write(retlist) return retval - show_all_statuses = described(retval=[{"aa":"aa"}])(show_all_statuses) + show_all_statuses = described(retval=[{"aa": "aa"}])(show_all_statuses) def show_status_change(self): event = self.pending_events.get() @@ -128,7 +131,7 @@ if outcome.skipped: self.skip_reasons[fullitemname] = outcome.skipped elif outcome.excinfo: - self.fail_reasons[fullitemname] = self.repr_failure_tblong(\ + self.fail_reasons[fullitemname] = self.repr_failure_tblong( event.item, outcome.excinfo, outcome.excinfo.traceback) self.stdout[fullitemname] = outcome.stdout self.stderr[fullitemname] = outcome.stderr @@ -155,7 +158,7 @@ def repr_failure_tblong(self, item, excinfo, traceback): lines = [] - for index, entry in py.builtin.enumerate(traceback): + for index, entry in py.builtin.enumerate(traceback): lines.append('----------') lines.append("%s: %s" % (entry.path, entry.lineno)) lines += self.repr_source(entry.relline, entry.source) @@ -196,7 +199,7 @@ self.pending_events.put(event) def report(self, what): - repfun = getattr(self, "report_" + what.__class__.__name__, + repfun = getattr(self, "report_" + what.__class__.__name__, self.report_unknown) try: repfun(what) @@ -240,7 +243,8 @@ if exec_meth is None: self.send_error(404, "File %s not found" % path) else: - self.serve_data('text/json', exec_meth(**self.parse_args(getargs))) + self.serve_data('text/json', + exec_meth(**self.parse_args(getargs))) else: method_to_call() @@ -249,11 +253,12 @@ if getargs == "": return {} + unquote = py.std.urllib.unquote args = {} arg_pairs = getargs.split("&") for arg in arg_pairs: key, value = arg.split("=") - args[key] = value + args[unquote(key)] = unquote(value) return args def log_message(self, format, *args): @@ -306,3 +311,4 @@ 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 Wed Nov 29 11:31:12 2006 @@ -34,7 +34,7 @@ glob = Pending() -def comeback(msglist=[{"aa":"aa"}]): +def comeback(msglist=[{"aa": "aa"}]): if len(msglist) == 0: return for item in glob.pending[:]: @@ -72,12 +72,14 @@ tr = create_elem("tr") td = create_elem("td") tr.appendChild(td) - td.appendChild(create_text_elem("%s[0/%s]" % (msg['itemname'], msg['length']))) + td.appendChild(create_text_elem("%s[0/%s]" % (msg['itemname'], + msg['length']))) max_items[msg['fullitemname']] = int(msg['length']) short_item_names[msg['fullitemname']] = msg['itemname'] td.id = '_txt_' + msg['fullitemname'] #tr.setAttribute("id", msg['fullitemname']) - td.setAttribute("onmouseover", "show_info('%s')" % msg['fullitemname']) + td.setAttribute("onmouseover", "show_info('%s')" % ( + msg['fullitemname'],)) td.setAttribute("onmouseout", "hide_info()") table = create_elem("table") @@ -97,7 +99,8 @@ return True td = create_elem("td") - td.setAttribute("onmouseover", "show_info('%s')" % msg['fullitemname']) + td.setAttribute("onmouseover", "show_info('%s')" % ( + msg['fullitemname'],)) td.setAttribute("onmouseout", "hide_info()") item_name = msg['fullitemname'] # TODO: dispatch output @@ -107,15 +110,15 @@ elif msg["skipped"] != 'None': exported_methods.show_skip(item_name, skip_come_back) link = create_elem("a") - link.setAttribute("href", "javascript:show_skip('%s')" % \ - msg['fullitemname']) + link.setAttribute("href", "javascript:show_skip('%s')" % ( + msg['fullitemname'],)) txt = create_text_elem('s') link.appendChild(txt) td.appendChild(link) else: link = create_elem("a") - link.setAttribute("href", "javascript:show_traceback('%s')" % \ - msg['fullitemname']) + link.setAttribute("href", "javascript:show_traceback('%s')" % ( + msg['fullitemname'],)) txt = create_text_elem('F') link.appendChild(txt) td.appendChild(link) @@ -128,14 +131,16 @@ name = msg['fullmodulename'] counters[name] += 1 counter_part = get_elem('_txt_' + name) - counter_part.childNodes[0].nodeValue = "%s[%d/%d]" % \ - (short_item_names[name], counters[name], max_items[name]) + counter_part.childNodes[0].nodeValue = "%s[%d/%d]" % ( + short_item_names[name], counters[name], max_items[name]) module_part.childNodes[-1].appendChild(td) except: - dom.get_document().getElementById("testmain").innerHTML += "some error" + 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]" + dom.get_document().getElementById("Tests").childNodes[0].nodeValue = \ + "Tests [FINISHED]" elif msg['type'] == 'FailedTryiter': module_part = get_elem(msg['fullitemname']) if not module_part: @@ -154,7 +159,7 @@ return True tr = create_elem("tr") td = create_elem("td") - txt = create_text_elem("- skipped (%s)" % msg['reason']) + txt = create_text_elem("- skipped (%s)" % (msg['reason'],)) td.appendChild(txt) tr.appendChild(td) module_part.appendChild(tr) @@ -174,8 +179,8 @@ dom.get_document().location = "#message" def show_traceback(item_name="aa"): - data = "====== Traceback: =========\n%s\n======== Stdout: ========\n%s\n"\ - "========== Stderr: ==========\n%s\n" % tracebacks[item_name] + data = ("====== Traceback: =========\n%s\n======== Stdout: ========\n%s\n" + "========== Stderr: ==========\n%s\n" % tracebacks[item_name]) set_msgbox(item_name, data) def fail_come_back(msg): From guido at codespeak.net Wed Nov 29 15:59:29 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Wed, 29 Nov 2006 15:59:29 +0100 (CET) Subject: [py-svn] r35121 - py/branch/test-web-multiclient Message-ID: <20061129145929.85EB210061@code0.codespeak.net> Author: guido Date: Wed Nov 29 15:59:28 2006 New Revision: 35121 Added: py/branch/test-web-multiclient/ - copied from r35120, py/dist/ Log: Branching to work on multi-client access for the py.test web server. From guido at codespeak.net Wed Nov 29 16:10:25 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Wed, 29 Nov 2006 16:10:25 +0100 (CET) Subject: [py-svn] r35122 - in py/branch/test-web-multiclient/py/test/rsession: . testing webdata Message-ID: <20061129151025.857BA10082@code0.codespeak.net> Author: guido Date: Wed Nov 29 16:10:23 2006 New Revision: 35122 Modified: py/branch/test-web-multiclient/py/test/rsession/testing/test_web.py py/branch/test-web-multiclient/py/test/rsession/web.py py/branch/test-web-multiclient/py/test/rsession/webdata/source.js py/branch/test-web-multiclient/py/test/rsession/webjs.py Log: First steps to multi-client access to the web server. There is now a MultiQueue object that is used instead of a normal queue, to queue events. This holds a queue per connected client, determining the id of the client using a session id. The session id is already passed along, and the MultiQueue in place, but somehow accessing the server from multiple clients at the same time freezes the app... For Fijal to test. Modified: py/branch/test-web-multiclient/py/test/rsession/testing/test_web.py ============================================================================== --- py/branch/test-web-multiclient/py/test/rsession/testing/test_web.py (original) +++ py/branch/test-web-multiclient/py/test/rsession/testing/test_web.py Wed Nov 29 16:10:23 2006 @@ -14,6 +14,9 @@ except ImportError: py.test.skip("No PyPy detected") +from py.__.test.rsession.web import TestHandler as _TestHandler +from py.__.test.rsession.web import MultiQueue + def test_js_generate(): from py.__.test.rsession import webjs from py.__.test.rsession.web import FUNCTION_LIST @@ -22,8 +25,7 @@ assert source def test_parse_args(): - from py.__.test.rsession.web import TestHandler - class TestTestHandler(TestHandler): + class TestTestHandler(_TestHandler): def __init__(self): pass h = TestTestHandler() @@ -33,3 +35,53 @@ assert h.parse_args('foo=bar%baz') == {'foo': 'bar\xbaz'} py.test.raises(ValueError, 'h.parse_args("foo")') +class TestMultiQueue(object): + def test_get_one_sessid(self): + mq = MultiQueue() + mq.put(1) + result = mq.get(1234) + assert result == 1 + + def test_get_two_sessid(self): + mq = MultiQueue() + mq.put(1) + result = mq.get(1234) + assert result == 1 + mq.put(2) + result = mq.get(1234) + assert result == 2 + result = mq.get(5678) + assert result == 1 + result = mq.get(5678) + assert result == 2 + + def test_get_blocking(self): + import thread + result = [] + def getitem(mq, sessid): + result.append(mq.get(sessid)) + mq = MultiQueue() + thread.start_new_thread(getitem, (mq, 1234)) + assert not result + mq.put(1) + py.std.time.sleep(0.1) + assert result == [1] + + def test_empty(self): + mq = MultiQueue() + assert mq.empty() + mq.put(1) + assert not mq.empty() + result = mq.get(1234) + result == 1 + assert mq.empty() + mq.put(2) + result = mq.get(4567) + assert result == 1 + result = mq.get(1234) + assert result == 2 + assert not mq.empty() + result = mq.get(4567) + assert result == 2 + assert mq.empty() + Modified: py/branch/test-web-multiclient/py/test/rsession/web.py ============================================================================== --- py/branch/test-web-multiclient/py/test/rsession/web.py (original) +++ py/branch/test-web-multiclient/py/test/rsession/web.py Wed Nov 29 16:10:23 2006 @@ -63,10 +63,59 @@ d['length'] = "?" return d +class MultiQueue(object): + """ a tailor-made queue (internally using Queue) for py.test.rsession.web + + API-wise the main difference is that the get() method gets a sessid + argument, which is used to determine what data to feed to the client + + when a data queue for a sessid doesn't yet exist, it is created, and + filled with data that has already been fed to the other clients + """ + def __init__(self): + self._cache = [] + self._session_queues = {} + self._lock = py.std.thread.allocate_lock() + + def put(self, item): + self._lock.acquire() + try: + self._cache.append(item) + for q in self._session_queues.values(): + q.put(item) + finally: + self._lock.release() + + def get(self, sessid=-1): + self._lock.acquire() + try: + if not sessid in self._session_queues: + self._create_session_queue(sessid) + finally: + self._lock.release() + return self._session_queues[sessid].get(sessid) + + def empty(self): + self._lock.acquire() + try: + if not self._session_queues: + return not len(self._cache) + for q in self._session_queues.values(): + if not q.empty(): + return False + finally: + self._lock.release() + return True + + def _create_session_queue(self, sessid): + self._session_queues[sessid] = q = Queue.Queue() + for item in self._cache: + q.put(item) + class ExportedMethods(BasicExternal): _render_xmlhttp = True def __init__(self): - self.pending_events = Queue.Queue() + self.pending_events = MultiQueue() self.start_event = threading.Event() self.end_event = threading.Event() self.skip_reasons = {} @@ -104,16 +153,35 @@ 'stderr':self.stderr[item_name]}) show_fail = described(retval={"aa": "aa"})(show_fail) - def show_all_statuses(self): - retlist = [self.show_status_change()] + _sessids = None + _sesslock = py.std.thread.allocate_lock() + def show_sessid(self): + if not self._sessids: + self._sessids = [] + self._sesslock.acquire() + try: + while 1: + chars = list(py.std.string.lowercase) + py.std.random.shuffle(chars) + sessid = ''.join(chars[:8]) + if sessid not in self._sessids: + self._sessids.append(sessid) + break + finally: + self._sesslock.release() + return json.write(sessid) + show_sessid = described(retval="aa")(show_sessid) + + def show_all_statuses(self, sessid=-1): + retlist = [self.show_status_change(sessid)] while not self.pending_events.empty(): - retlist.append(self.show_status_change()) + retlist.append(self.show_status_change(sessid)) retval = json.write(retlist) return retval show_all_statuses = described(retval=[{"aa": "aa"}])(show_all_statuses) - def show_status_change(self): - event = self.pending_events.get() + def show_status_change(self, sessid): + event = self.pending_events.get(sessid) if event is None: self.end_event.set() return {} Modified: py/branch/test-web-multiclient/py/test/rsession/webdata/source.js ============================================================================== Binary files. No diff available. Modified: py/branch/test-web-multiclient/py/test/rsession/webjs.py ============================================================================== --- py/branch/test-web-multiclient/py/test/rsession/webjs.py (original) +++ py/branch/test-web-multiclient/py/test/rsession/webjs.py Wed Nov 29 16:10:23 2006 @@ -28,11 +28,11 @@ MAX_COUNTER = 50 # Maximal size of one-line table -class Pending(object): +class Globals(object): def __init__(self): self.pending = [] -glob = Pending() +glob = Globals() def comeback(msglist=[{"aa": "aa"}]): if len(msglist) == 0: @@ -44,7 +44,7 @@ for msg in msglist: if not process(msg): return - exported_methods.show_all_statuses(comeback) + exported_methods.show_all_statuses(glob.sessid, comeback) def show_info(data="aa"): info = dom.get_document().getElementById("info") @@ -199,7 +199,11 @@ td.id = host elem.appendChild(td) +def sessid_comeback(id): + glob.sessid = id + exported_methods.show_all_statuses(id, comeback) + def main(): exported_methods.show_hosts(host_init) - exported_methods.show_all_statuses(comeback) + exported_methods.show_sessid(sessid_comeback) From fijal at codespeak.net Wed Nov 29 16:56:00 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Wed, 29 Nov 2006 16:56:00 +0100 (CET) Subject: [py-svn] r35127 - in py/branch/test-web-multiclient/py/test/rsession: . webdata Message-ID: <20061129155600.A2E6D10088@code0.codespeak.net> Author: fijal Date: Wed Nov 29 16:55:57 2006 New Revision: 35127 Modified: py/branch/test-web-multiclient/py/test/rsession/web.py py/branch/test-web-multiclient/py/test/rsession/webdata/source.js Log: Fixed multiplayer semantics. Modified: py/branch/test-web-multiclient/py/test/rsession/web.py ============================================================================== --- py/branch/test-web-multiclient/py/test/rsession/web.py (original) +++ py/branch/test-web-multiclient/py/test/rsession/web.py Wed Nov 29 16:55:57 2006 @@ -81,12 +81,12 @@ self._lock.acquire() try: self._cache.append(item) - for q in self._session_queues.values(): + for key, q in self._session_queues.items(): q.put(item) finally: self._lock.release() - def get(self, sessid=-1): + def get(self, sessid): self._lock.acquire() try: if not sessid in self._session_queues: @@ -107,6 +107,9 @@ self._lock.release() return True + def empty_queue(self, sessid): + return self._session_queues[sessid].empty() + def _create_session_queue(self, sessid): self._session_queues[sessid] = q = Queue.Queue() for item in self._cache: @@ -174,7 +177,7 @@ def show_all_statuses(self, sessid=-1): retlist = [self.show_status_change(sessid)] - while not self.pending_events.empty(): + while not self.pending_events.empty_queue(sessid): retlist.append(self.show_status_change(sessid)) retval = json.write(retlist) return retval Modified: py/branch/test-web-multiclient/py/test/rsession/webdata/source.js ============================================================================== Binary files. No diff available. From guido at codespeak.net Wed Nov 29 19:23:24 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Wed, 29 Nov 2006 19:23:24 +0100 (CET) Subject: [py-svn] r35134 - in py/dist/py/test/rsession: . testing webdata Message-ID: <20061129182324.D3DB8100A3@code0.codespeak.net> Author: guido Date: Wed Nov 29 19:23:19 2006 New Revision: 35134 Modified: py/dist/py/test/rsession/testing/test_web.py py/dist/py/test/rsession/web.py py/dist/py/test/rsession/webdata/source.js py/dist/py/test/rsession/webjs.py Log: Merging with test-web-multiclient branch. 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 Wed Nov 29 19:23:19 2006 @@ -14,6 +14,9 @@ except ImportError: py.test.skip("No PyPy detected") +from py.__.test.rsession.web import TestHandler as _TestHandler +from py.__.test.rsession.web import MultiQueue + def test_js_generate(): from py.__.test.rsession import webjs from py.__.test.rsession.web import FUNCTION_LIST @@ -22,8 +25,7 @@ assert source def test_parse_args(): - from py.__.test.rsession.web import TestHandler - class TestTestHandler(TestHandler): + class TestTestHandler(_TestHandler): def __init__(self): pass h = TestTestHandler() @@ -33,3 +35,53 @@ assert h.parse_args('foo=bar%baz') == {'foo': 'bar\xbaz'} py.test.raises(ValueError, 'h.parse_args("foo")') +class TestMultiQueue(object): + def test_get_one_sessid(self): + mq = MultiQueue() + mq.put(1) + result = mq.get(1234) + assert result == 1 + + def test_get_two_sessid(self): + mq = MultiQueue() + mq.put(1) + result = mq.get(1234) + assert result == 1 + mq.put(2) + result = mq.get(1234) + assert result == 2 + result = mq.get(5678) + assert result == 1 + result = mq.get(5678) + assert result == 2 + + def test_get_blocking(self): + import thread + result = [] + def getitem(mq, sessid): + result.append(mq.get(sessid)) + mq = MultiQueue() + thread.start_new_thread(getitem, (mq, 1234)) + assert not result + mq.put(1) + py.std.time.sleep(0.1) + assert result == [1] + + def test_empty(self): + mq = MultiQueue() + assert mq.empty() + mq.put(1) + assert not mq.empty() + result = mq.get(1234) + result == 1 + assert mq.empty() + mq.put(2) + result = mq.get(4567) + assert result == 1 + result = mq.get(1234) + assert result == 2 + assert not mq.empty() + result = mq.get(4567) + assert result == 2 + assert mq.empty() + Modified: py/dist/py/test/rsession/web.py ============================================================================== --- py/dist/py/test/rsession/web.py (original) +++ py/dist/py/test/rsession/web.py Wed Nov 29 19:23:19 2006 @@ -63,10 +63,62 @@ d['length'] = "?" return d +class MultiQueue(object): + """ a tailor-made queue (internally using Queue) for py.test.rsession.web + + API-wise the main difference is that the get() method gets a sessid + argument, which is used to determine what data to feed to the client + + when a data queue for a sessid doesn't yet exist, it is created, and + filled with data that has already been fed to the other clients + """ + def __init__(self): + self._cache = [] + self._session_queues = {} + self._lock = py.std.thread.allocate_lock() + + def put(self, item): + self._lock.acquire() + try: + self._cache.append(item) + for key, q in self._session_queues.items(): + q.put(item) + finally: + self._lock.release() + + def get(self, sessid): + self._lock.acquire() + try: + if not sessid in self._session_queues: + self._create_session_queue(sessid) + finally: + self._lock.release() + return self._session_queues[sessid].get(sessid) + + def empty(self): + self._lock.acquire() + try: + if not self._session_queues: + return not len(self._cache) + for q in self._session_queues.values(): + if not q.empty(): + return False + finally: + self._lock.release() + return True + + def empty_queue(self, sessid): + return self._session_queues[sessid].empty() + + def _create_session_queue(self, sessid): + self._session_queues[sessid] = q = Queue.Queue() + for item in self._cache: + q.put(item) + class ExportedMethods(BasicExternal): _render_xmlhttp = True def __init__(self): - self.pending_events = Queue.Queue() + self.pending_events = MultiQueue() self.start_event = threading.Event() self.end_event = threading.Event() self.skip_reasons = {} @@ -104,16 +156,35 @@ 'stderr':self.stderr[item_name]}) show_fail = described(retval={"aa": "aa"})(show_fail) - def show_all_statuses(self): - retlist = [self.show_status_change()] - while not self.pending_events.empty(): - retlist.append(self.show_status_change()) + _sessids = None + _sesslock = py.std.thread.allocate_lock() + def show_sessid(self): + if not self._sessids: + self._sessids = [] + self._sesslock.acquire() + try: + while 1: + chars = list(py.std.string.lowercase) + py.std.random.shuffle(chars) + sessid = ''.join(chars[:8]) + if sessid not in self._sessids: + self._sessids.append(sessid) + break + finally: + self._sesslock.release() + return json.write(sessid) + show_sessid = described(retval="aa")(show_sessid) + + def show_all_statuses(self, sessid=-1): + retlist = [self.show_status_change(sessid)] + while not self.pending_events.empty_queue(sessid): + retlist.append(self.show_status_change(sessid)) retval = json.write(retlist) return retval show_all_statuses = described(retval=[{"aa": "aa"}])(show_all_statuses) - def show_status_change(self): - event = self.pending_events.get() + def show_status_change(self, sessid): + event = self.pending_events.get(sessid) if event is None: self.end_event.set() return {} 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 Wed Nov 29 19:23:19 2006 @@ -28,11 +28,11 @@ MAX_COUNTER = 50 # Maximal size of one-line table -class Pending(object): +class Globals(object): def __init__(self): self.pending = [] -glob = Pending() +glob = Globals() def comeback(msglist=[{"aa": "aa"}]): if len(msglist) == 0: @@ -44,7 +44,7 @@ for msg in msglist: if not process(msg): return - exported_methods.show_all_statuses(comeback) + exported_methods.show_all_statuses(glob.sessid, comeback) def show_info(data="aa"): info = dom.get_document().getElementById("info") @@ -199,7 +199,11 @@ td.id = host elem.appendChild(td) +def sessid_comeback(id): + glob.sessid = id + exported_methods.show_all_statuses(id, comeback) + def main(): exported_methods.show_hosts(host_init) - exported_methods.show_all_statuses(comeback) + exported_methods.show_sessid(sessid_comeback) From guido at codespeak.net Wed Nov 29 19:24:05 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Wed, 29 Nov 2006 19:24:05 +0100 (CET) Subject: [py-svn] r35135 - py/branch/test-web-multiclient Message-ID: <20061129182405.B321B100A0@code0.codespeak.net> Author: guido Date: Wed Nov 29 19:24:04 2006 New Revision: 35135 Removed: py/branch/test-web-multiclient/ Log: Removing branch, no more use for it. From guido at codespeak.net Wed Nov 29 20:50:45 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Wed, 29 Nov 2006 20:50:45 +0100 (CET) Subject: [py-svn] r35139 - py/dist/py/path/svn Message-ID: <20061129195045.B50F510082@code0.codespeak.net> Author: guido Date: Wed Nov 29 20:50:44 2006 New Revision: 35139 Modified: py/dist/py/path/svn/quoting.txt py/dist/py/path/svn/svncommon.py Log: Small adjustments of the doc on quoting, and first bits of code that should improve path to url conversions and path and url quoting (not used yet). Modified: py/dist/py/path/svn/quoting.txt ============================================================================== --- py/dist/py/path/svn/quoting.txt (original) +++ py/dist/py/path/svn/quoting.txt Wed Nov 29 20:50:44 2006 @@ -16,6 +16,9 @@ * SVN deals with remote objects using URLs and local ones using paths +URL related notes +----------------- + * URLs follow (almost) normal `URL encoding rules`_ characters that aren't allowed in URL paths (such as :, @, %, etc.) should @@ -33,21 +36,46 @@ on Windows, the \\ characters in paths will have to be replaced with a / also (see above) if the path contains a drive letter, a / should be prepended - -* paths don't require encoding - normally paths don't have to be encoded, however @ can confuse SVN in certain - cases; a workaround is to add @HEAD after the path (also works for relative - paths, I encountered this doing an SVN info on a file called 'bar at baz', in - the end the command 'svn info bar at baz@HEAD' worked) +* ignore casing on Windows? + + since Windows is case-insensitive, it may make sense to consider ignoring + case on that platform(?) + +* long file names + + don't even want to go there... `filed an issue on this on in the tracker`_... -* all characters that are supported in paths by all operating systems seem to +Path related notes +------------------ + +* all characters that are supported in paths by any operating system seem to be supported by SVN - basically SVN doesn't deal with platforms that aren't capable of using + basically SVN doesn't think about platforms that aren't capable of using certain characters: it will happily allow you to check a file with a name containing a backslash (\\) in, resulting in a repo that isn't usable in Windows anymore (you'll get a nasty message explaining how your local checkout is broken on checking it out)... + I think py.path.svn* should take the approach of not allowing the characters + that will result in failing checkouts on Windows. These characters are (I + think, list taken from `some website`_):: + + * | \ / : < > ? + + This would mean that both svnwc and svnurl should fail on initializing when + the path (or the path part of the URL) contains one of these characters. Also + join() and other functions that take (parts of) paths as arguments should + check for, and fail on, these characters. + +* paths don't require encoding + + normally paths don't have to be encoded, however @ can confuse SVN in certain + cases; a workaround is to add @HEAD after the path (also works for relative + paths, I encountered this doing an SVN info on a file called 'bar at baz', in + the end the command 'svn info bar at baz@HEAD' worked) + +.. _`filed an issue on this on in the tracker`: https://codespeak.net/issue/py-dev/issue38 .. _`URL encoding rules`: http://en.wikipedia.org/wiki/Percent-encoding +.. _`some website`: http://linuxboxadmin.com/articles/filefriction.php Modified: py/dist/py/path/svn/svncommon.py ============================================================================== --- py/dist/py/path/svn/svncommon.py (original) +++ py/dist/py/path/svn/svncommon.py Wed Nov 29 20:50:44 2006 @@ -1,7 +1,7 @@ """ module with a base subversion path object. """ -import os, sys, time, re +import os, sys, time, re, string import py from py.__.path import common @@ -287,3 +287,49 @@ if sys.platform != 'win32': return 'LC_ALL=C ' return '' + +# some nasty chunk of code to solve path and url conversion and quoting issues +ILLEGAL_CHARS = '* | \ / : < > ? \t \n \x0b \x0c \r'.split(' ') +if os.sep in ILLEGAL_CHARS: + ILLEGAL_CHARS.remove(os.sep) +ISWINDOWS = sys.platform == 'win32' +_reg_allow_disk = re.compile(r'^([a-z]\:\\)?[^:]+$', re.I) +def _check_path(path): + illegal = ILLEGAL_CHARS[:] + sp = path.strpath + if ISWINDOWS: + illegal.remove(':') + if not _reg_allow_disk.match(sp): + raise ValueError('path may not contain a colon (:)') + for char in sp: + if char not in string.printable or char in illegal: + raise ValueError('illegal character %r in path' % (char,)) + +def path_to_fspath(path, addat=True): + _check_path(path) + sp = path.strpath + if addat and path.rev != -1: + sp = '%s@%s' % (sp, path.rev) + elif addat: + sp = '%s at HEAD' % (sp,) + return sp + +def url_from_path(path): + fspath = path_to_fspath(path, False) + quote = py.std.urllib.quote + if ISWINDOWS: + match = _reg_allow_disk.match(fspath) + fspath = fspath.replace('\\', '/') + if match.group(1): + fspath = '/%s%s' % (match.group(1).replace('\\', '/'), + quote(fspath[len(match.group(1)):])) + else: + fspath = quote(fspath) + else: + fspath = quote(fspath) + if path.rev != -1: + fspath = '%s@%s' % (fspath, path.rev) + else: + fspath = '%s at HEAD' % (fspath,) + return 'file://%s' % (fspath,) + From fijal at codespeak.net Thu Nov 30 00:18:56 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Thu, 30 Nov 2006 00:18:56 +0100 (CET) Subject: [py-svn] r35140 - py/dist/py/test Message-ID: <20061129231856.3681B1007F@code0.codespeak.net> Author: fijal Date: Thu Nov 30 00:18:54 2006 New Revision: 35140 Modified: py/dist/py/test/item.py Log: deholgerize Modified: py/dist/py/test/item.py ============================================================================== --- py/dist/py/test/item.py (original) +++ py/dist/py/test/item.py Thu Nov 30 00:18:54 2006 @@ -59,7 +59,7 @@ def __repr__(self): return "<%s %r>" %(self.__class__.__name__, self.name) - def getpathlineno(self): + def getpathlineno(self): code = py.code.Code(self.obj) return code.path, code.firstlineno From fijal at codespeak.net Thu Nov 30 00:20:12 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Thu, 30 Nov 2006 00:20:12 +0100 (CET) Subject: [py-svn] r35143 - in py/dist/py/test/rsession: . testing webdata Message-ID: <20061129232012.2695A10082@code0.codespeak.net> Author: fijal Date: Thu Nov 30 00:20:04 2006 New Revision: 35143 Modified: py/dist/py/test/rsession/hostmanage.py py/dist/py/test/rsession/report.py py/dist/py/test/rsession/testing/test_webjs.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: * Some improvements in tests host keys * Now we can see what tests are running on each node. Modified: py/dist/py/test/rsession/hostmanage.py ============================================================================== --- py/dist/py/test/rsession/hostmanage.py (original) +++ py/dist/py/test/rsession/hostmanage.py Thu Nov 30 00:20:04 2006 @@ -49,16 +49,16 @@ gw = py.execnet.SshGateway(host) else: gw = py.execnet.SshGateway(host, remotepython=remote_python) + gw.hostid = host + str(num) else: gw = None - hosts.append((num, host, gw, remoterootpath)) else: if remote_python is None: gw = py.execnet.PopenGateway() else: gw = py.execnet.PopenGateway(remotepython=remote_python) - gw.sshaddress = 'localhost' + gw.hostid = 'localhost' + str(num) hosts.append((num, host, gw, str(pkgdir.dirpath()))) return hosts @@ -85,7 +85,8 @@ continue rsynced[(host, remoterootpath)] = True def done(host=host): - reporter(report.HostReady(host)) + key = host + str(num) + reporter(report.HostReady(host, key)) reporter(report.HostRSyncing(host, remoterootpath)) if do_sync: rsync.add_target(gw, remoterootpath, done) Modified: py/dist/py/test/rsession/report.py ============================================================================== --- py/dist/py/test/rsession/report.py (original) +++ py/dist/py/test/rsession/report.py Thu Nov 30 00:20:04 2006 @@ -75,8 +75,9 @@ self.remoterootpath = remoterootpath class HostReady(ReportEvent): - def __init__(self, hostname): + def __init__(self, hostname, hostid): self.hostname = hostname + self.hostid = hostid class TestStarted(ReportEvent): def __init__(self, hosts): Modified: py/dist/py/test/rsession/testing/test_webjs.py ============================================================================== --- py/dist/py/test/rsession/testing/test_webjs.py (original) +++ py/dist/py/test/rsession/testing/test_webjs.py Thu Nov 30 00:20:04 2006 @@ -85,4 +85,3 @@ # XXX: This assert obviously is true in output code, why it does not work? assert tds[0].innerHTML == 'foo.py[1/10]' assert tds[1].innerHTML == '.' - Modified: py/dist/py/test/rsession/web.py ============================================================================== --- py/dist/py/test/rsession/web.py (original) +++ py/dist/py/test/rsession/web.py Thu Nov 30 00:20:04 2006 @@ -20,7 +20,8 @@ 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"] +FUNCTION_LIST = ["main", "show_skip", "show_traceback", "show_info", "hide_info", + "show_host", "hide_host"] def escape(s): return s @@ -206,16 +207,18 @@ event.item, outcome.excinfo, outcome.excinfo.traceback) self.stdout[fullitemname] = outcome.stdout self.stderr[fullitemname] = outcome.stderr + if event.channel: + args['hostkey'] = event.channel.gateway.hostid + else: + args['hostkey'] = '' elif isinstance(event, report.ItemStart): args = add_item(event) + elif isinstance(event, report.SendItem): + args = add_item(event) + args['hostkey'] = event.channel.gateway.hostid elif isinstance(event, report.HostReady): - host = event.hostname - num = 0 - while self.ready_hosts[host]: - host = event.hostname + "_" + str(num) - num += 1 - self.ready_hosts[host] = True - args = {'hostname' : event.hostname, 'hostkey' : host} + self.ready_hosts[event.hostid] = True + args = {'hostname' : event.hostname, 'hostkey' : event.hostid} elif isinstance(event, report.FailedTryiter): args = add_item(event) elif isinstance(event, report.SkippedTryiter): @@ -258,12 +261,8 @@ def report_TestStarted(self, event): self.hosts = {} self.ready_hosts = {} - for host in event.hosts: - host_str = host - num = 0 - while host_str in self.hosts: - host_str = host + "_" + str(num) - num += 1 + for num, host in enumerate(event.hosts): + host_str = host + str(num) self.hosts[host_str] = host self.ready_hosts[host_str] = False self.start_event.set() 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 Nov 30 00:20:04 2006 @@ -27,6 +27,10 @@
    + + + +
    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 Nov 30 00:20:04 2006 @@ -31,6 +31,7 @@ class Globals(object): def __init__(self): self.pending = [] + self.host = "" glob = Globals() @@ -88,9 +89,18 @@ counters[msg['fullitemname']] = 0 main_t.appendChild(tr) + elif msg['type'] == 'SendItem': + host_elem = dom.get_document().getElementById(msg['hostkey']) + glob.host_pending[msg['hostkey']].insert(0, msg['fullitemname']) + count = len(glob.host_pending[msg['hostkey']]) + host_elem.childNodes[0].nodeValue = glob.host_dict[msg['hostkey']] + '[' +\ + str(count) + ']' + elif msg['type'] == 'HostReady': - dom.get_document().getElementById(msg['hostkey']).style.background = \ + host_elem = dom.get_document().getElementById(msg['hostkey']) + host_elem.style.background = \ "#00ff00" + host_elem.childNodes[0].nodeValue = glob.host_dict[msg['hostkey']] + '[0]' elif msg['type'] == 'ReceivedItemOutcome': try: module_part = get_elem(msg['fullmodulename']) @@ -134,6 +144,14 @@ counter_part.childNodes[0].nodeValue = "%s[%d/%d]" % ( short_item_names[name], counters[name], max_items[name]) module_part.childNodes[-1].appendChild(td) + + if msg['hostkey']: + host_elem = dom.get_document().getElementById(msg['hostkey']) + glob.host_pending[msg['hostkey']].pop() + count = len(glob.host_pending[msg['hostkey']]) + host_elem.childNodes[0].nodeValue = glob.host_dict[msg['hostkey']] + '[' +\ + str(count) + ']' + except: dom.get_document().getElementById("testmain").innerHTML += \ "some error" @@ -189,6 +207,32 @@ def skip_come_back(msg): skips[msg['item_name']] = msg['reason'] +def reshow_host(): + if glob.host == "": + return + show_host(glob.host) + +def show_host(host_name="aa"): + elem = dom.get_document().getElementById("jobs") + while len(elem.childNodes): + elem.removeChild(elem.childNodes[0]) + for item in glob.host_pending[host_name]: + tr = create_elem("tr") + td = create_elem("td") + td.appendChild(create_text_elem(item)) + tr.appendChild(td) + elem.appendChild(tr) + elem.style.visibility = "visible" + glob.host = host_name + dom.setTimeout(reshow_host, 100) + +def hide_host(): + elem = dom.get_document().getElementById("jobs") + while len(elem.childNodes): + elem.removeChild(elem.childNodes[0]) + elem.style.visibility = "hidden" + glob.host = "" + def host_init(host_dict): elem = dom.get_document().getElementById("hostrow") for host in host_dict.keys(): @@ -198,6 +242,12 @@ td.appendChild(txt) td.id = host elem.appendChild(td) + td.setAttribute("onmouseover", "show_host('%s')" % host) + td.setAttribute("onmouseout", "hide_host()") + glob.host_dict = host_dict + glob.host_pending = {} + for key in host_dict.keys(): + glob.host_pending[key] = [] def sessid_comeback(id): glob.sessid = id From fijal at codespeak.net Thu Nov 30 11:22:40 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Thu, 30 Nov 2006 11:22:40 +0100 (CET) Subject: [py-svn] r35151 - in py/dist/py/test/rsession: . webdata Message-ID: <20061130102240.6CA091007B@code0.codespeak.net> Author: fijal Date: Thu Nov 30 11:22:34 2006 New Revision: 35151 Modified: py/dist/py/test/rsession/hostmanage.py py/dist/py/test/rsession/webdata/source.js py/dist/py/test/rsession/webjs.py Log: Strange python semantics... Modified: py/dist/py/test/rsession/hostmanage.py ============================================================================== --- py/dist/py/test/rsession/hostmanage.py (original) +++ py/dist/py/test/rsession/hostmanage.py Thu Nov 30 11:22:34 2006 @@ -84,7 +84,7 @@ and optimise_localhost): continue rsynced[(host, remoterootpath)] = True - def done(host=host): + def done(host=host, num=num): key = host + str(num) reporter(report.HostReady(host, key)) reporter(report.HostRSyncing(host, remoterootpath)) 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 Nov 30 11:22:34 2006 @@ -107,7 +107,14 @@ if not module_part: glob.pending.append(msg) return True - + + if msg['hostkey']: + host_elem = dom.get_document().getElementById(msg['hostkey']) + glob.host_pending[msg['hostkey']].pop() + count = len(glob.host_pending[msg['hostkey']]) + host_elem.childNodes[0].nodeValue = glob.host_dict[msg['hostkey']] + '[' +\ + str(count) + ']' + td = create_elem("td") td.setAttribute("onmouseover", "show_info('%s')" % ( msg['fullitemname'],)) @@ -144,14 +151,7 @@ counter_part.childNodes[0].nodeValue = "%s[%d/%d]" % ( short_item_names[name], counters[name], max_items[name]) module_part.childNodes[-1].appendChild(td) - - if msg['hostkey']: - host_elem = dom.get_document().getElementById(msg['hostkey']) - glob.host_pending[msg['hostkey']].pop() - count = len(glob.host_pending[msg['hostkey']]) - host_elem.childNodes[0].nodeValue = glob.host_dict[msg['hostkey']] + '[' +\ - str(count) + ']' - + except: dom.get_document().getElementById("testmain").innerHTML += \ "some error" From fijal at codespeak.net Thu Nov 30 12:29:34 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Thu, 30 Nov 2006 12:29:34 +0100 (CET) Subject: [py-svn] r35153 - in py/dist/py/apigen/tracer: . testing Message-ID: <20061130112934.6C93D10071@code0.codespeak.net> Author: fijal Date: Thu Nov 30 12:29:31 2006 New Revision: 35153 Modified: py/dist/py/apigen/tracer/description.py py/dist/py/apigen/tracer/docstorage.py py/dist/py/apigen/tracer/testing/test_docgen.py Log: Cleanup of methods. Modified: py/dist/py/apigen/tracer/description.py ============================================================================== --- py/dist/py/apigen/tracer/description.py (original) +++ py/dist/py/apigen/tracer/description.py Thu Nov 30 12:29:31 2006 @@ -195,7 +195,7 @@ result = self.pyobj except: result = self - return (result, self.pyobj) + return result code = property(getcode) def consider_call(self, inputcells): @@ -271,6 +271,16 @@ ## def has_code(self, code): ## return self.pyobj.im_func.func_code is code + def __hash__(self): + return hash((self.code, self.pyobj.im_class)) + + def __eq__(self, other): + if isinstance(other, tuple): + return self.code is other[0] and self.pyobj.im_class is other[1] + if isinstance(other, MethodDesc): + return self.pyobj is other.pyobj + return False + def consider_start_locals(self, frame): # XXX recursion issues? obj = frame.f_locals[self.pyobj.im_func.func_code.co_varnames[0]] Modified: py/dist/py/apigen/tracer/docstorage.py ============================================================================== --- py/dist/py/apigen/tracer/docstorage.py (original) +++ py/dist/py/apigen/tracer/docstorage.py Thu Nov 30 12:29:31 2006 @@ -45,12 +45,11 @@ desc.consider_exception(exc_class, value) def find_desc(self, code, locals): - if code.name == '__init__': + try: # argh, very fragile specialcasing - key = (code.raw, locals[code.raw.co_varnames[0]].__class__) - else: - key = code.raw - return self.desc_cache.get(key, None) + return self.desc_cache[(code.raw, locals[code.raw.co_varnames[0]].__class__)] + except (KeyError, IndexError): + return self.desc_cache.get(code.raw, None) #for desc in self.descs.values(): # if desc.has_code(frame.code.raw): # return desc 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 Thu Nov 30 12:29:31 2006 @@ -286,7 +286,6 @@ assert dsa.get_method_origin('C.foo') is None def test_multiple_methods(): - py.test.skip("Failing") class A(object): def meth(self): pass From guido at codespeak.net Thu Nov 30 15:00:34 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Thu, 30 Nov 2006 15:00:34 +0100 (CET) Subject: [py-svn] r35158 - in py/dist/py/test/rsession: . testing Message-ID: <20061130140034.D1C6110072@code0.codespeak.net> Author: guido Date: Thu Nov 30 15:00:30 2006 New Revision: 35158 Modified: py/dist/py/test/rsession/testing/test_webjs.py py/dist/py/test/rsession/webjs.py Log: Fixed unit test, removed bare except that hid the bug, fixed HTML bug where a table was a direct child of a table row (not allowed), whitespace fixes. Modified: py/dist/py/test/rsession/testing/test_webjs.py ============================================================================== --- py/dist/py/test/rsession/testing/test_webjs.py (original) +++ py/dist/py/test/rsession/testing/test_webjs.py Thu Nov 30 15:00:30 2006 @@ -60,8 +60,8 @@ assert len(tr.childNodes) == 2 assert tr.childNodes[0].nodeName == 'TD' assert tr.childNodes[0].innerHTML == 'foo.py[0/10]' - # XXX this is bad I think! table should be inside td - assert tr.childNodes[1].nodeName == 'TABLE' + assert tr.childNodes[1].nodeName == 'TD' + assert tr.childNodes[1].childNodes[0].nodeName == 'TABLE' assert len(tr.childNodes[1].getElementsByTagName('tr')) == 0 def test_process_two(): @@ -77,11 +77,15 @@ 'fullmodulename': 'modules/foo.py', 'passed' : 'True', 'fullitemname' : 'modules/foo.py/test_item', + 'hostkey': None, } webjs.process(msg) + print '%s' % (dom.get_document().documentElement.innerHTML,) trs = main_t.getElementsByTagName('tr') tds = trs[0].getElementsByTagName('td') - assert len(tds) == 2 - # XXX: This assert obviously is true in output code, why it does not work? - assert tds[0].innerHTML == 'foo.py[1/10]' - assert tds[1].innerHTML == '.' + # two cells in the row, one in the table inside one of the cells + assert len(tds) == 3 + html = tds[0].innerHTML + assert html == 'foo.py[1/10]' + assert tds[2].innerHTML == '.' + Modified: py/dist/py/test/rsession/webjs.py ============================================================================== --- py/dist/py/test/rsession/webjs.py (original) +++ py/dist/py/test/rsession/webjs.py Thu Nov 30 15:00:30 2006 @@ -79,13 +79,17 @@ short_item_names[msg['fullitemname']] = msg['itemname'] td.id = '_txt_' + msg['fullitemname'] #tr.setAttribute("id", msg['fullitemname']) - td.setAttribute("onmouseover", "show_info('%s')" % ( - msg['fullitemname'],)) + td.setAttribute("onmouseover", + "show_info('%s')" % (msg['fullitemname'],)) td.setAttribute("onmouseout", "hide_info()") + td2 = create_elem('td') + tr.appendChild(td2) table = create_elem("table") - tr.appendChild(table) - table.id = msg['fullitemname'] + td2.appendChild(table) + tbody = create_elem('tbody') + tbody.id = msg['fullitemname'] + table.appendChild(tbody) counters[msg['fullitemname']] = 0 main_t.appendChild(tr) @@ -93,68 +97,65 @@ host_elem = dom.get_document().getElementById(msg['hostkey']) glob.host_pending[msg['hostkey']].insert(0, msg['fullitemname']) count = len(glob.host_pending[msg['hostkey']]) - host_elem.childNodes[0].nodeValue = glob.host_dict[msg['hostkey']] + '[' +\ - str(count) + ']' + host_elem.childNodes[0].nodeValue = '%s[%s]' % ( + glob.host_dict[msg['hostkey']], count) elif msg['type'] == 'HostReady': host_elem = dom.get_document().getElementById(msg['hostkey']) host_elem.style.background = \ "#00ff00" - host_elem.childNodes[0].nodeValue = glob.host_dict[msg['hostkey']] + '[0]' + host_elem.childNodes[0].nodeValue = '%s[0]' % ( + glob.host_dict[msg['hostkey']],) elif msg['type'] == 'ReceivedItemOutcome': - try: - module_part = get_elem(msg['fullmodulename']) - if not module_part: - glob.pending.append(msg) - return True - - if msg['hostkey']: - host_elem = dom.get_document().getElementById(msg['hostkey']) - glob.host_pending[msg['hostkey']].pop() - count = len(glob.host_pending[msg['hostkey']]) - host_elem.childNodes[0].nodeValue = glob.host_dict[msg['hostkey']] + '[' +\ - str(count) + ']' - - td = create_elem("td") - td.setAttribute("onmouseover", "show_info('%s')" % ( - msg['fullitemname'],)) - td.setAttribute("onmouseout", "hide_info()") - item_name = msg['fullitemname'] - # TODO: dispatch output - if msg["passed"] == 'True': - txt = create_text_elem(".") - td.appendChild(txt) - elif msg["skipped"] != 'None': - exported_methods.show_skip(item_name, skip_come_back) - link = create_elem("a") - link.setAttribute("href", "javascript:show_skip('%s')" % ( - msg['fullitemname'],)) - txt = create_text_elem('s') - link.appendChild(txt) - td.appendChild(link) - else: - link = create_elem("a") - link.setAttribute("href", "javascript:show_traceback('%s')" % ( - msg['fullitemname'],)) - txt = create_text_elem('F') - link.appendChild(txt) - td.appendChild(link) - exported_methods.show_fail(item_name, fail_come_back) - - if counters[msg['fullmodulename']] % MAX_COUNTER == 0: - tr = create_elem("tr") - module_part.appendChild(tr) - - name = msg['fullmodulename'] - counters[name] += 1 - counter_part = get_elem('_txt_' + name) - counter_part.childNodes[0].nodeValue = "%s[%d/%d]" % ( - short_item_names[name], counters[name], max_items[name]) - module_part.childNodes[-1].appendChild(td) - - except: - dom.get_document().getElementById("testmain").innerHTML += \ - "some error" + module_part = get_elem(msg['fullmodulename']) + if not module_part: + glob.pending.append(msg) + return True + + if msg['hostkey']: + host_elem = dom.get_document().getElementById(msg['hostkey']) + glob.host_pending[msg['hostkey']].pop() + count = len(glob.host_pending[msg['hostkey']]) + host_elem.childNodes[0].nodeValue = '%s[%s]' % ( + glob.host_dict[msg['hostkey']], count) + + td = create_elem("td") + td.setAttribute("onmouseover", "show_info('%s')" % ( + msg['fullitemname'],)) + td.setAttribute("onmouseout", "hide_info()") + item_name = msg['fullitemname'] + # TODO: dispatch output + if msg["passed"] == 'True': + txt = create_text_elem(".") + td.appendChild(txt) + elif msg["skipped"] != 'None': + exported_methods.show_skip(item_name, skip_come_back) + link = create_elem("a") + link.setAttribute("href", "javascript:show_skip('%s')" % ( + msg['fullitemname'],)) + txt = create_text_elem('s') + link.appendChild(txt) + td.appendChild(link) + else: + link = create_elem("a") + link.setAttribute("href", "javascript:show_traceback('%s')" % ( + msg['fullitemname'],)) + txt = create_text_elem('F') + link.appendChild(txt) + td.appendChild(link) + exported_methods.show_fail(item_name, fail_come_back) + + if counters[msg['fullmodulename']] % MAX_COUNTER == 0: + tr = create_elem("tr") + module_part.appendChild(tr) + + name = msg['fullmodulename'] + counters[name] += 1 + counter_part = get_elem('_txt_' + name) + newcontent = "%s[%d/%d]" % (short_item_names[name], counters[name], + max_items[name]) + counter_part.childNodes[0].nodeValue = newcontent + module_part.childNodes[-1].appendChild(td) elif msg['type'] == 'TestFinished': dom.get_document().title = "Py.test [FINISHED]" dom.get_document().getElementById("Tests").childNodes[0].nodeValue = \ @@ -202,7 +203,8 @@ set_msgbox(item_name, data) def fail_come_back(msg): - tracebacks[msg['item_name']] = (msg['traceback'], msg['stdout'], msg['stderr']) + tracebacks[msg['item_name']] = (msg['traceback'], msg['stdout'], + msg['stderr']) def skip_come_back(msg): skips[msg['item_name']] = msg['reason'] From guido at codespeak.net Thu Nov 30 15:53:00 2006 From: guido at codespeak.net (guido at codespeak.net) Date: Thu, 30 Nov 2006 15:53:00 +0100 (CET) Subject: [py-svn] r35165 - in py/dist/py/test/rsession: . webdata Message-ID: <20061130145300.D1FC010079@code0.codespeak.net> Author: guido Date: Thu Nov 30 15:52:57 2006 New Revision: 35165 Modified: py/dist/py/test/rsession/webdata/index.html py/dist/py/test/rsession/webdata/source.js py/dist/py/test/rsession/webjs.py Log: Shuffled HTML around a bit and applied some CSS. Also made that the page keeps scrolling along with the tests until some message is displayed in the message box. 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 Nov 30 15:52:57 2006 @@ -1,46 +1,116 @@ - - Py.test - - - - -

    Tests

    -
    - - - - - - - - - - - - - - - - - -
    - - - -
    - -
    -
    -Data: - -
    -
    -
    - -
    -
    - + + Py.test + + + + + +

    Tests

    +
    +
    +   +
    + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + Data: + +
    +
    +
    + +
    +
    + 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 Nov 30 15:52:57 2006 @@ -32,6 +32,7 @@ def __init__(self): self.pending = [] self.host = "" + self.data_empty = True glob = Globals() @@ -182,6 +183,9 @@ td.appendChild(txt) tr.appendChild(td) module_part.appendChild(tr) + if glob.data_empty: + mbox = dom.get_document().getElementById('messagebox') + mbox.parentNode.scrollIntoView() return True def show_skip(item_name="aa"): @@ -196,6 +200,7 @@ pre.appendChild(txt) msgbox.appendChild(pre) dom.get_document().location = "#message" + glob.data_empty = False def show_traceback(item_name="aa"): data = ("====== Traceback: =========\n%s\n======== Stdout: ========\n%s\n" @@ -236,14 +241,16 @@ glob.host = "" def host_init(host_dict): - elem = dom.get_document().getElementById("hostrow") + tbody = dom.get_document().getElementById("hostsbody") for host in host_dict.keys(): + tr = create_elem('tr') + tbody.appendChild(tr) td = create_elem("td") td.style.background = "#ff0000" txt = create_text_elem(host_dict[host]) td.appendChild(txt) td.id = host - elem.appendChild(td) + tr.appendChild(td) td.setAttribute("onmouseover", "show_host('%s')" % host) td.setAttribute("onmouseout", "hide_host()") glob.host_dict = host_dict From fijal at codespeak.net Thu Nov 30 23:23:43 2006 From: fijal at codespeak.net (fijal at codespeak.net) Date: Thu, 30 Nov 2006 23:23:43 +0100 (CET) Subject: [py-svn] r35183 - py/dist/py/bin Message-ID: <20061130222343.14BD51007E@code0.codespeak.net> Author: fijal Date: Thu Nov 30 23:23:42 2006 New Revision: 35183 Modified: py/dist/py/bin/py.lookup Log: stupid bug. Modified: py/dist/py/bin/py.lookup ============================================================================== --- py/dist/py/bin/py.lookup (original) +++ py/dist/py/bin/py.lookup Thu Nov 30 23:23:42 2006 @@ -39,6 +39,6 @@ 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): + for count in range(max(0, i-context), min(len(lines) - 1, i+context+1)): print "%s:%d: %s" %(x.relto(curdir), count+1, lines[count].rstrip()) print "-"*50