[py-svn] r52664 - in py/branch/bugfix-0.9.0/py: . apigen apigen/testing apigen/tracer/testing/package/submodule/pak bin builtin builtin/testing code code/testing doc doc/future execnet execnet/script execnet/testing io magic misc misc/testing path/local path/svn path/svn/testing test test/rsession test/rsession/testing test/rsession/webdata test/testing tool

guido at codespeak.net guido at codespeak.net
Mon Mar 17 20:18:50 CET 2008


Author: guido
Date: Mon Mar 17 20:18:48 2008
New Revision: 52664

Added:
   py/branch/bugfix-0.9.0/py/misc/killproc.py   (contents, props changed)
   py/branch/bugfix-0.9.0/py/misc/testing/test_oskill.py   (contents, props changed)
   py/branch/bugfix-0.9.0/py/path/svn/testing/test_auth.py   (contents, props changed)
Modified:
   py/branch/bugfix-0.9.0/py/__init__.py
   py/branch/bugfix-0.9.0/py/apigen/apigen.py   (props changed)
   py/branch/bugfix-0.9.0/py/apigen/html.py   (props changed)
   py/branch/bugfix-0.9.0/py/apigen/testing/test_apigen_functional.py   (contents, props changed)
   py/branch/bugfix-0.9.0/py/apigen/testing/test_htmlgen.py   (props changed)
   py/branch/bugfix-0.9.0/py/apigen/todo-apigen.txt   (props changed)
   py/branch/bugfix-0.9.0/py/apigen/tracer/testing/package/submodule/pak/somenamespace.py   (props changed)
   py/branch/bugfix-0.9.0/py/bin/_docgen.py   (contents, props changed)
   py/branch/bugfix-0.9.0/py/bin/_update_website.py   (props changed)
   py/branch/bugfix-0.9.0/py/bin/py.lookup
   py/branch/bugfix-0.9.0/py/builtin/set.py   (props changed)
   py/branch/bugfix-0.9.0/py/builtin/testing/test_set.py   (props changed)
   py/branch/bugfix-0.9.0/py/code/excinfo.py
   py/branch/bugfix-0.9.0/py/code/source.py
   py/branch/bugfix-0.9.0/py/code/testing/test_source.py
   py/branch/bugfix-0.9.0/py/code/traceback2.py
   py/branch/bugfix-0.9.0/py/conftest.py
   py/branch/bugfix-0.9.0/py/doc/code.txt   (props changed)
   py/branch/bugfix-0.9.0/py/doc/conftest.py
   py/branch/bugfix-0.9.0/py/doc/future/pylib_pypy.txt   (props changed)
   py/branch/bugfix-0.9.0/py/doc/impl-test.txt   (contents, props changed)
   py/branch/bugfix-0.9.0/py/doc/path.txt   (contents, props changed)
   py/branch/bugfix-0.9.0/py/execnet/channel.py
   py/branch/bugfix-0.9.0/py/execnet/gateway.py
   py/branch/bugfix-0.9.0/py/execnet/inputoutput.py
   py/branch/bugfix-0.9.0/py/execnet/register.py
   py/branch/bugfix-0.9.0/py/execnet/script/loop_socketserver.py   (props changed)
   py/branch/bugfix-0.9.0/py/execnet/testing/test_gateway.py
   py/branch/bugfix-0.9.0/py/io/fdcapture.py
   py/branch/bugfix-0.9.0/py/magic/greenlet.py
   py/branch/bugfix-0.9.0/py/misc/conftest-socketgatewayrun.py   (contents, props changed)
   py/branch/bugfix-0.9.0/py/misc/testing/test_terminal.py   (props changed)
   py/branch/bugfix-0.9.0/py/misc/testing/test_update_website.py   (props changed)
   py/branch/bugfix-0.9.0/py/path/local/local.py
   py/branch/bugfix-0.9.0/py/path/svn/svncommon.py
   py/branch/bugfix-0.9.0/py/path/svn/testing/test_urlcommand.py
   py/branch/bugfix-0.9.0/py/path/svn/testing/test_wccommand.py
   py/branch/bugfix-0.9.0/py/path/svn/urlcommand.py
   py/branch/bugfix-0.9.0/py/path/svn/wccommand.py
   py/branch/bugfix-0.9.0/py/test/collect.py
   py/branch/bugfix-0.9.0/py/test/conftesthandle.py   (props changed)
   py/branch/bugfix-0.9.0/py/test/item.py
   py/branch/bugfix-0.9.0/py/test/outcome.py   (contents, props changed)
   py/branch/bugfix-0.9.0/py/test/representation.py   (contents, props changed)
   py/branch/bugfix-0.9.0/py/test/rsession/executor.py
   py/branch/bugfix-0.9.0/py/test/rsession/reporter.py
   py/branch/bugfix-0.9.0/py/test/rsession/testing/basetest.py   (props changed)
   py/branch/bugfix-0.9.0/py/test/rsession/testing/test_executor.py
   py/branch/bugfix-0.9.0/py/test/rsession/testing/test_hostmanage.py   (props changed)
   py/branch/bugfix-0.9.0/py/test/rsession/testing/test_reporter.py
   py/branch/bugfix-0.9.0/py/test/rsession/testing/test_rsession.py
   py/branch/bugfix-0.9.0/py/test/rsession/testing/test_web.py
   py/branch/bugfix-0.9.0/py/test/rsession/web.py
   py/branch/bugfix-0.9.0/py/test/rsession/webdata/index.html
   py/branch/bugfix-0.9.0/py/test/rsession/webdata/source.js
   py/branch/bugfix-0.9.0/py/test/rsession/webjs.py
   py/branch/bugfix-0.9.0/py/test/session.py
   py/branch/bugfix-0.9.0/py/test/testing/setupdata.py   (props changed)
   py/branch/bugfix-0.9.0/py/test/testing/test_conftesthandle.py   (props changed)
   py/branch/bugfix-0.9.0/py/test/testing/test_repr.py   (props changed)
   py/branch/bugfix-0.9.0/py/test/testing/test_session.py
   py/branch/bugfix-0.9.0/py/test/testing/test_setup_nested.py
   py/branch/bugfix-0.9.0/py/tool/utestconvert.py
Log:
Merging all bugfixes and some small features from the trunk to the 0.9 branch,
in preparation of the upcoming 0.9.1 release.

Revisions that were merged:

38967, 38969, 39106, 39340, 39655, 39982, 39994, 40001, 40002, 40702, 40737,
40738, 40739, 40831, 40832, 40834, 40934, 41224, 41480, 43299, 43575, 44248,
44648, 44655, 45294, 45295, 45483, 45484, 45518, 45519, 45535, 45538, 45539,
45541, 45545, 45547, 45548, 45549, 45646, 45647, 45648, 45649, 45655, 45671,
45901, 45906, 45994, 46010, 46692, 47277, 49423, 49974, 50606, 50645, 50755,
51285, 51292, 52000, 52001, 52481


Modified: py/branch/bugfix-0.9.0/py/__init__.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/__init__.py	(original)
+++ py/branch/bugfix-0.9.0/py/__init__.py	Mon Mar 17 20:18:48 2008
@@ -1,3 +1,5 @@
+
+# -*- coding: utf-8 -*-
 """
     the py lib is a development support library featuring
     py.test, ad-hoc distributed execution, micro-threads
@@ -5,7 +7,7 @@
 """
 from initpkg import initpkg
 
-version = "0.9.0"
+version = "0.9.1-alpha"
 
 initpkg(__name__,
     description = "py lib: agile development and test support library",
@@ -13,9 +15,9 @@
     lastchangedate = '$LastChangedDate$',
     version = version, 
     url = "http://codespeak.net/py",
-    download_url = "http://codespeak.net/download/py/py-%s.tar.gz" %(version,), 
+    download_url = "XXX", # "http://codespeak.net/download/py/py-%s.tar.gz" %(version,), 
     license = "MIT license",
-    platforms = ['unix', 'linux', 'cygwin'],
+    platforms = ['unix', 'linux', 'cygwin', 'win32'],
     author = "holger krekel, Carl Friedrich Bolz, Guido Wesdorp, Maciej Fijalkowski, Armin Rigo & others",
     author_email = "py-dev at codespeak.net",
     long_description = globals()['__doc__'],
@@ -29,6 +31,8 @@
     'test.skip'              : ('./test/item.py', 'skip'),
     'test.fail'              : ('./test/item.py', 'fail'),
     'test.exit'              : ('./test/session.py', 'exit'),
+    'test.broken'            : ('./test/item.py', 'Broken'),
+    'test.notimplemented'    : ('./test/item.py', '_NotImplemented'),
 
     # configuration/initialization related test api
     'test.config'            : ('./test/config.py', 'config_per_process'),
@@ -62,6 +66,7 @@
     'path.svnwc'             : ('./path/svn/wccommand.py', 'SvnWCCommandPath'),
     'path.svnurl'            : ('./path/svn/urlcommand.py', 'SvnCommandPath'),
     'path.local'             : ('./path/local/local.py', 'LocalPath'),
+    'path.SvnAuth'           : ('./path/svn/svncommon.py', 'SvnAuth'),
 
     # some nice slightly magic APIs
     'magic.__doc__'          : ('./magic/__init__.py', '__doc__'),

Modified: py/branch/bugfix-0.9.0/py/apigen/testing/test_apigen_functional.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/apigen/testing/test_apigen_functional.py	(original)
+++ py/branch/bugfix-0.9.0/py/apigen/testing/test_apigen_functional.py	Mon Mar 17 20:18:48 2008
@@ -116,7 +116,9 @@
     pkgname, documentable = apigen.get_documentable_items_pkgdir(
                                                fs_root.join(package_name))
     assert pkgname == 'pak'
-    assert sorted(documentable.keys()) ==  [
+    keys = documentable.keys()
+    keys.sort()
+    assert keys ==  [
         'main.SomeTestClass', 'main.SomeTestSubClass', 'main.func',
         'main.sub.func', 'somenamespace.baz', 'somenamespace.foo']
 

Modified: py/branch/bugfix-0.9.0/py/bin/_docgen.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/bin/_docgen.py	(original)
+++ py/branch/bugfix-0.9.0/py/bin/_docgen.py	Mon Mar 17 20:18:48 2008
@@ -28,7 +28,7 @@
 def build_docs(targetpath, testargs):
     docpath = pypath.join('doc')
     run_tests(docpath, '',
-              testargs + ' --forcegen --apigenrelpath="apigen/"')
+              testargs + ' --forcegen --apigen="%s/apigen/apigen.py"' % (pypath,))
     docpath.copy(targetpath)
 
 def build_nav(targetpath, docs=True, api=True):

Modified: py/branch/bugfix-0.9.0/py/bin/py.lookup
==============================================================================
--- py/branch/bugfix-0.9.0/py/bin/py.lookup	(original)
+++ py/branch/bugfix-0.9.0/py/bin/py.lookup	Mon Mar 17 20:18:48 2008
@@ -42,8 +42,11 @@
     string = args[0]
     if options.ignorecase:
         string = string.lower()
-    for x in curdir.visit('*.py', rec): 
-        s = x.read()
+    for x in curdir.visit('*.py', rec):
+        try:
+            s = x.read()
+        except py.error.ENOENT:
+            pass # whatever, probably broken link (ie emacs lock)
         searchs = s
         if options.ignorecase:
             searchs = s.lower()

Modified: py/branch/bugfix-0.9.0/py/code/excinfo.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/code/excinfo.py	(original)
+++ py/branch/bugfix-0.9.0/py/code/excinfo.py	Mon Mar 17 20:18:48 2008
@@ -18,7 +18,7 @@
                     self._striptext = 'AssertionError: '
         self._excinfo = tup
         self.type, self.value, tb = self._excinfo
-        self.typename = str(self.type)
+        self.typename = self.type.__module__ + '.' + self.type.__name__
         self.traceback = py.code.Traceback(tb) 
 
     def exconly(self, tryshort=False): 

Modified: py/branch/bugfix-0.9.0/py/code/source.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/code/source.py	(original)
+++ py/branch/bugfix-0.9.0/py/code/source.py	Mon Mar 17 20:18:48 2008
@@ -109,7 +109,7 @@
         for start in range(lineno, -1, -1):
             trylines = self.lines[start:lineno+1]
             # quick hack to indent the source and get it as a string in one go
-            trylines.insert(0, 'if 0:')
+            trylines.insert(0, 'def xxx():')
             trysource = '\n '.join(trylines)
             #              ^ space here
             try:

Modified: py/branch/bugfix-0.9.0/py/code/testing/test_source.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/code/testing/test_source.py	(original)
+++ py/branch/bugfix-0.9.0/py/code/testing/test_source.py	Mon Mar 17 20:18:48 2008
@@ -281,3 +281,15 @@
     """
     lines = deindent(source.splitlines())
     assert lines == ['', 'def f():', '    def g():', '        pass', '    ']
+
+def test_write_read():
+    py.test.skip("Failing")
+    tmpdir = py.test.ensuretemp("source_write_read")
+    source = py.code.Source('''
+    class A(object):
+        def method(self):
+            x = 1
+    ''')
+    tmpdir.ensure("a.py").write(source)
+    s2 = py.code.Source(tmpdir.join("a.py").pyimport().A)
+    assert source == s2

Modified: py/branch/bugfix-0.9.0/py/code/traceback2.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/code/traceback2.py	(original)
+++ py/branch/bugfix-0.9.0/py/code/traceback2.py	Mon Mar 17 20:18:48 2008
@@ -1,5 +1,6 @@
 from __future__ import generators 
-import py 
+import py
+import sys
 
 class TracebackEntry(object):
     """ a single entry in a traceback """
@@ -9,6 +10,9 @@
     def __init__(self, rawentry):
         self._rawentry = rawentry
         self.frame = py.code.Frame(rawentry.tb_frame)
+        # Ugh. 2.4 and 2.5 differs here when encountering
+        # multi-line statements. Not sure about the solution, but
+        # should be portable
         self.lineno = rawentry.tb_lineno - 1
         self.relline = self.lineno - self.frame.code.firstlineno
 

Modified: py/branch/bugfix-0.9.0/py/conftest.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/conftest.py	(original)
+++ py/branch/bugfix-0.9.0/py/conftest.py	Mon Mar 17 20:18:48 2008
@@ -33,6 +33,9 @@
                action='store', dest='docpath',
                default="doc", type='string',
                help="relative path to doc output location (relative from py/)"), 
+        Option('', '--runslowtests',
+               action="store_true", dest="runslowtests", default=False,
+               help="run slow tests)"),
     )
 
 dist_rsync_roots = ['.']

Modified: py/branch/bugfix-0.9.0/py/doc/conftest.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/doc/conftest.py	(original)
+++ py/branch/bugfix-0.9.0/py/doc/conftest.py	Mon Mar 17 20:18:48 2008
@@ -240,8 +240,9 @@
     fn = ishtml and fn.new(ext='.txt') or fn
     print "filename is", fn 
     if not fn.check(): # not ishtml or not fn.check(): 
-        py.test.fail("reference error %r in %s:%d" %(
-                      tryfn, path.basename, lineno+1))
+        if not py.path.local(tryfn).check(): # the html could be there 
+            py.test.fail("reference error %r in %s:%d" %(
+                          tryfn, path.basename, lineno+1))
     if anchor: 
         source = unicode(fn.read(), 'latin1')
         source = source.lower().replace('-', ' ') # aehem

Modified: py/branch/bugfix-0.9.0/py/doc/impl-test.txt
==============================================================================
--- py/branch/bugfix-0.9.0/py/doc/impl-test.txt	(original)
+++ py/branch/bugfix-0.9.0/py/doc/impl-test.txt	Mon Mar 17 20:18:48 2008
@@ -180,8 +180,8 @@
 custom Collectors and Items if they are found 
 in a local ``conftest.py`` file.  
 
-example: perform additional ReST checs 
-++++++++++++++++++++++++++++++++++++++
+example: perform additional ReST checks 
++++++++++++++++++++++++++++++++++++++++
 
 With your custom collectors or items you can completely 
 derive from the standard way of collecting and running

Modified: py/branch/bugfix-0.9.0/py/doc/path.txt
==============================================================================
--- py/branch/bugfix-0.9.0/py/doc/path.txt	(original)
+++ py/branch/bugfix-0.9.0/py/doc/path.txt	Mon Mar 17 20:18:48 2008
@@ -187,6 +187,23 @@
   >>> len(wc.status().prop_modified)
   0
 
+SVN authentication
+++++++++++++++++++++++
+
+Some uncommon functionality can also be provided as extensions, such as SVN
+authentication::
+
+  >>> auth = py.path.SvnAuth('anonymous', 'user', cache_auth=False,
+  ...             interactive=False)
+  >>> wc.auth = auth
+  >>> wc.update() # this should work
+  >>> path = wc.ensure('thisshouldnotexist.txt')
+  >>> try:
+  ...     path.commit('testing')
+  ... except py.process.cmdexec.Error, e:
+  ...     pass
+  >>> 'authorization failed' in str(e)
+  True
 
 Known problems / limitations
 ===================================

Modified: py/branch/bugfix-0.9.0/py/execnet/channel.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/execnet/channel.py	(original)
+++ py/branch/bugfix-0.9.0/py/execnet/channel.py	Mon Mar 17 20:18:48 2008
@@ -85,7 +85,7 @@
                 Msg = Message.CHANNEL_LAST_MESSAGE
             else:
                 Msg = Message.CHANNEL_CLOSE
-            self.gateway._outgoing.put(Msg(self.id))
+            self.gateway._send(Msg(self.id))
 
     def _getremoteerror(self):
         try:
@@ -117,7 +117,7 @@
             # state transition "opened/sendonly" --> "closed"
             # threads warning: the channel might be closed under our feet,
             # but it's never damaging to send too many CHANNEL_CLOSE messages
-            put = self.gateway._outgoing.put
+            put = self.gateway._send 
             if error is not None:
                 put(Message.CHANNEL_CLOSE_ERROR(self.id, str(error)))
             else:
@@ -157,7 +157,7 @@
             data = Message.CHANNEL_NEW(self.id, item.id)
         else:
             data = Message.CHANNEL_DATA(self.id, item)
-        self.gateway._outgoing.put(data)
+        self.gateway._send(data)
 
     def receive(self):
         """receives an item that was sent from the other side,

Modified: py/branch/bugfix-0.9.0/py/execnet/gateway.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/execnet/gateway.py	(original)
+++ py/branch/bugfix-0.9.0/py/execnet/gateway.py	Mon Mar 17 20:18:48 2008
@@ -22,33 +22,62 @@
     from py.__.execnet.channel import ChannelFactory, Channel
     from py.__.execnet.message import Message
     ThreadOut = py._thread.ThreadOut 
-    WorkerPool = py._thread.WorkerPool 
-    NamedThreadPool = py._thread.NamedThreadPool 
 
 import os
-debug = open('/tmp/execnet-debug-%d' % os.getpid()  , 'wa')
+debug = 0 # open('/tmp/execnet-debug-%d' % os.getpid()  , 'wa')
 
 sysex = (KeyboardInterrupt, SystemExit)
 
+# ----------------------------------------------------------
+# cleanup machinery (for exiting processes) 
+# ----------------------------------------------------------
+
+class GatewayCleanup:
+    def __init__(self): 
+        self._activegateways = weakref.WeakKeyDictionary()
+        atexit.register(self.cleanup_atexit)
+
+    def register(self, gateway):
+        assert gateway not in self._activegateways
+        self._activegateways[gateway] = True
+
+    def unregister(self, gateway):
+        del self._activegateways[gateway]
+
+    def cleanup_atexit(self):
+        if debug:
+            print >>debug, "="*20 + "cleaning up" + "=" * 20
+            debug.flush()
+        for gw in self._activegateways.keys():
+            gw.exit()
+            #gw.join() # should work as well
+
+# ----------------------------------------------------------
+# Base Gateway (used for both remote and local side) 
+# ----------------------------------------------------------
+
 class Gateway(object):
+    class _StopExecLoop(Exception): pass
     _ThreadOut = ThreadOut 
     remoteaddress = ""
-    def __init__(self, io, execthreads=None, _startcount=2): 
+    _requestqueue = None
+    _cleanup = GatewayCleanup()
+
+    def __init__(self, io, _startcount=2): 
         """ initialize core gateway, using the given 
-            inputoutput object and 'execthreads' execution
-            threads. 
+            inputoutput object. 
         """
-        global registered_cleanup
-        self._execpool = WorkerPool(maxthreads=execthreads)
         self._io = io
-        self._outgoing = Queue.Queue()
         self._channelfactory = ChannelFactory(self, _startcount)
-        if not registered_cleanup:
-            atexit.register(cleanup_atexit)
-            registered_cleanup = True
-        _active_sendqueues[self._outgoing] = True
-        self._pool = NamedThreadPool(receiver = self._thread_receiver, 
-                                     sender = self._thread_sender)
+        self._cleanup.register(self) 
+
+    def _initreceive(self, requestqueue=False):
+        if requestqueue: 
+            self._requestqueue = Queue.Queue()
+        self._receiverthread = threading.Thread(name="receiver", 
+                                 target=self._thread_receiver)
+        self._receiverthread.setDaemon(1)
+        self._receiverthread.start() 
 
     def __repr__(self):
         """ return string representing gateway type and status. """
@@ -58,10 +87,9 @@
         else:
             addr = ''
         try:
-            r = (len(self._pool.getstarted('receiver'))
-                 and "receiving" or "not receiving")
-            s = (len(self._pool.getstarted('sender')) 
-                 and "sending" or "not sending")
+            r = (self._receiverthread.isAlive() and "receiving" or 
+                 "not receiving")
+            s = "sending" # XXX
             i = len(self._channelfactory.channels())
         except AttributeError:
             r = s = "uninitialized"
@@ -69,9 +97,6 @@
         return "<%s%s %s/%s (%s active channels)>" %(
                 self.__class__.__name__, addr, r, s, i)
 
-##    def _local_trystopexec(self):
-##        self._execpool.shutdown() 
-
     def _trace(self, *args):
         if debug:
             try:
@@ -111,32 +136,25 @@
                     self._traceex(exc_info())
                     break 
         finally:
-            self._outgoing.put(None)
+            self._stopexec()
+            self._stopsend()
             self._channelfactory._finished_receiving()
             self._trace('leaving %r' % threading.currentThread())
 
-    def _thread_sender(self):
-        """ thread to send Messages over the wire. """
-        try:
-            from sys import exc_info
-            while 1:
-                msg = self._outgoing.get()
-                try:
-                    if msg is None:
-                        self._io.close_write()
-                        break
-                    msg.writeto(self._io)
-                except:
-                    excinfo = exc_info()
-                    self._traceex(excinfo)
-                    if msg is not None:
-                        msg.post_sent(self, excinfo)
-                    break
-                else:
-                    self._trace('sent -> %r' % msg)
-                    msg.post_sent(self)
-        finally:
-            self._trace('leaving %r' % threading.currentThread())
+    from sys import exc_info
+    def _send(self, msg):
+        if msg is None:
+            self._io.close_write()
+        else:
+            try:
+                msg.writeto(self._io) 
+            except: 
+                excinfo = self.exc_info()
+                self._traceex(excinfo)
+                msg.post_sent(self, excinfo)
+            else:
+                msg.post_sent(self)
+                self._trace('sent -> %r' % msg)
 
     def _local_redirect_thread_output(self, outid, errid): 
         l = []
@@ -152,9 +170,58 @@
                 channel.close() 
         return close 
 
-    def _thread_executor(self, channel, (source, outid, errid)):
-        """ worker thread to execute source objects from the execution queue. """
+    def _local_schedulexec(self, channel, sourcetask):
+        if self._requestqueue is not None:
+            self._requestqueue.put((channel, sourcetask)) 
+        else:
+            # we will not execute, let's send back an error
+            # to inform the other side
+            channel.close("execution disallowed")
+
+    def _servemain(self, joining=True):
         from sys import exc_info
+        self._initreceive(requestqueue=True)
+        try:
+            while 1:
+                item = self._requestqueue.get()
+                if item is None:
+                    self._stopsend()
+                    break
+                try:
+                    self._executetask(item)
+                except self._StopExecLoop:
+                    break
+        finally:
+            self._trace("_servemain finished") 
+        if joining:
+            self.join()
+
+    def remote_init_threads(self, num=None):
+        """ start up to 'num' threads for subsequent 
+            remote_exec() invocations to allow concurrent
+            execution. 
+        """
+        if hasattr(self, '_remotechannelthread'):
+            raise IOError("remote threads already running")
+        from py.__.thread import pool
+        source = py.code.Source(pool, """
+            execpool = WorkerPool(maxthreads=%r)
+            gw = channel.gateway
+            while 1:
+                task = gw._requestqueue.get()
+                if task is None:
+                    gw._stopsend()
+                    execpool.shutdown()
+                    execpool.join()
+                    raise gw._StopExecLoop
+                execpool.dispatch(gw._executetask, task)
+        """ % num)
+        self._remotechannelthread = self.remote_exec(source)
+
+    def _executetask(self, item):
+        """ execute channel/source items. """
+        from sys import exc_info
+        channel, (source, outid, errid) = item 
         try:
             loc = { 'channel' : channel }
             self._trace("execution starts:", repr(source)[:50])
@@ -168,6 +235,9 @@
                 self._trace("execution finished:", repr(source)[:50])
         except (KeyboardInterrupt, SystemExit):
             pass 
+        except self._StopExecLoop:
+            channel.close()
+            raise
         except:
             excinfo = exc_info()
             l = traceback.format_exception(*excinfo)
@@ -177,10 +247,6 @@
         else:
             channel.close()
 
-    def _local_schedulexec(self, channel, sourcetask): 
-        self._trace("dispatching exec")
-        self._execpool.dispatch(self._thread_executor, channel, sourcetask) 
-
     def _newredirectchannelid(self, callback): 
         if callback is None: 
             return  
@@ -219,8 +285,8 @@
         channel = self.newchannel() 
         outid = self._newredirectchannelid(stdout) 
         errid = self._newredirectchannelid(stderr) 
-        self._outgoing.put(Message.CHANNEL_OPEN(channel.id, 
-                               (source, outid, errid)))
+        self._send(Message.CHANNEL_OPEN(
+                    channel.id, (source, outid, errid)))
         return channel 
 
     def _remote_redirect(self, stdout=None, stderr=None): 
@@ -254,27 +320,26 @@
         return Handle()
 
     def exit(self):
-        """ Try to stop all IO activity. """
-        try:
-            del _active_sendqueues[self._outgoing]
-        except KeyError:
-            pass
-        else:
-            self._outgoing.put(None)
+        """ Try to stop all exec and IO activity. """
+        self._cleanup.unregister(self)
+        self._stopexec()
+        self._stopsend()
+
+    def _stopsend(self):
+        self._send(None)
+
+    def _stopexec(self):
+        if self._requestqueue is not None:
+            self._requestqueue.put(None)
 
     def join(self, joinexec=True):
         """ Wait for all IO (and by default all execution activity) 
-            to stop. 
+            to stop. the joinexec parameter is obsolete. 
         """
         current = threading.currentThread()
-        for x in self._pool.getstarted(): 
-            if x != current: 
-                self._trace("joining %s" % x)
-                x.join()
-        self._trace("joining sender/reciver threads finished, current %r" % current) 
-        if joinexec: 
-            self._execpool.join()
-            self._trace("joining execution threads finished, current %r" % current) 
+        if self._receiverthread.isAlive():
+            self._trace("joining receiver thread")
+            self._receiverthread.join()
 
 def getid(gw, cache={}):
     name = gw.__class__.__name__
@@ -284,15 +349,3 @@
         cache[name][id(gw)] = x = "%s:%s.%d" %(os.getpid(), gw.__class__.__name__, len(cache[name]))
         return x
 
-registered_cleanup = False
-_active_sendqueues = weakref.WeakKeyDictionary()
-def cleanup_atexit():
-    if debug:
-        print >>debug, "="*20 + "cleaning up" + "=" * 20
-        debug.flush()
-    while True:
-        try:
-            queue, ignored = _active_sendqueues.popitem()
-        except KeyError:
-            break
-        queue.put(None)

Modified: py/branch/bugfix-0.9.0/py/execnet/inputoutput.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/execnet/inputoutput.py	(original)
+++ py/branch/bugfix-0.9.0/py/execnet/inputoutput.py	Mon Mar 17 20:18:48 2008
@@ -3,7 +3,7 @@
   across process or computer barriers.
 """
 
-import socket, os, sys
+import socket, os, sys, thread
 
 class SocketIO:
     server_stmt = """
@@ -43,11 +43,17 @@
 
     def close_read(self):
         if self.readable:
-            self.sock.shutdown(0)
+            try:
+                self.sock.shutdown(0)
+            except socket.error:
+                pass
             self.readable = None
     def close_write(self):
         if self.writeable:
-            self.sock.shutdown(1)
+            try:
+                self.sock.shutdown(1)
+            except socket.error:
+                pass
             self.writeable = None
 
 class Popen2IO:
@@ -70,6 +76,7 @@
             msvcrt.setmode(outfile.fileno(), os.O_BINARY)
         self.outfile, self.infile = infile, outfile
         self.readable = self.writeable = True
+        self.lock = thread.allocate_lock()
 
     def read(self, numbytes):
         """Read exactly 'bytes' bytes from the pipe. """
@@ -93,6 +100,10 @@
             self.infile.close()
             self.readable = None
     def close_write(self):
-        if self.writeable:
-            self.outfile.close()
-            self.writeable = None
+        self.lock.acquire()
+        try:
+            if self.writeable:
+                self.outfile.close()
+                self.writeable = None
+        finally:
+            self.lock.release()

Modified: py/branch/bugfix-0.9.0/py/execnet/register.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/execnet/register.py	(original)
+++ py/branch/bugfix-0.9.0/py/execnet/register.py	Mon Mar 17 20:18:48 2008
@@ -11,7 +11,6 @@
 
 startup_modules = [
     'py.__.thread.io', 
-    'py.__.thread.pool', 
     'py.__.execnet.inputoutput', 
     'py.__.execnet.gateway', 
     'py.__.execnet.message', 
@@ -29,6 +28,8 @@
     def __init__(self, io):
         self._remote_bootstrap_gateway(io)
         super(InstallableGateway, self).__init__(io=io, _startcount=1) 
+        # XXX we dissallow execution form the other side
+        self._initreceive(requestqueue=False) 
 
     def _remote_bootstrap_gateway(self, io, extra=''):
         """ return Gateway with a asynchronously remotely
@@ -41,7 +42,7 @@
         bootstrap = [extra]
         bootstrap += [getsource(x) for x in startup_modules]
         bootstrap += [io.server_stmt, 
-                      "Gateway(io=io, _startcount=2).join(joinexec=False)",
+                      "Gateway(io=io, _startcount=2)._servemain()", 
                      ]
         source = "\n".join(bootstrap)
         self._trace("sending gateway bootstrap code")

Modified: py/branch/bugfix-0.9.0/py/execnet/testing/test_gateway.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/execnet/testing/test_gateway.py	(original)
+++ py/branch/bugfix-0.9.0/py/execnet/testing/test_gateway.py	Mon Mar 17 20:18:48 2008
@@ -83,8 +83,7 @@
 
 class BasicRemoteExecution:
     def test_correct_setup(self):
-        for x in 'sender', 'receiver':  
-            assert self.gw._pool.getstarted(x) 
+        assert self.gw._receiverthread.isAlive()
 
     def test_repr_doesnt_crash(self):
         assert isinstance(repr(self), str)
@@ -373,6 +372,18 @@
         res = channel.receive()
         assert res == 42
 
+    def test_non_reverse_execution(self):
+        gw = self.gw
+        c1 = gw.remote_exec("""
+            c = channel.gateway.remote_exec("pass")
+            try:
+                c.waitclose()
+            except c.RemoteError, e: 
+                channel.send(str(e))
+        """)
+        text = c1.receive()
+        assert text.find("execution disallowed") != -1 
+    
 #class TestBlockingIssues: 
 #    def test_join_blocked_execution_gateway(self): 
 #        gateway = py.execnet.PopenGateway() 
@@ -486,3 +497,23 @@
             # now it did
             py.test.raises(IOError, gw.remote_exec, "...")
 
+def test_threads():
+    gw = py.execnet.PopenGateway()
+    gw.remote_init_threads(3)
+    c1 = gw.remote_exec("channel.send(channel.receive())")
+    c2 = gw.remote_exec("channel.send(channel.receive())")
+    c2.send(1)
+    res = c2.receive()
+    assert res == 1
+    c1.send(42)
+    res = c1.receive()
+    assert res == 42
+    gw.exit()
+
+def test_threads_twice():
+    gw = py.execnet.PopenGateway()
+    gw.remote_init_threads(3)
+    py.test.raises(IOError, gw.remote_init_threads, 3)
+    gw.exit() 
+    
+    

Modified: py/branch/bugfix-0.9.0/py/io/fdcapture.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/io/fdcapture.py	(original)
+++ py/branch/bugfix-0.9.0/py/io/fdcapture.py	Mon Mar 17 20:18:48 2008
@@ -2,6 +2,7 @@
 import os
 import sys
 import py
+import tempfile
 
 class FDCapture: 
     """ Capture IO to/from a given os-level filedescriptor. """
@@ -41,7 +42,7 @@
     def maketmpfile(self): 
         """ create a temporary file
         """
-        f = os.tmpfile()
+        f = tempfile.TemporaryFile()
         newf = py.io.dupfile(f) 
         f.close()
         return newf 
@@ -49,7 +50,7 @@
     def writeorg(self, str):
         """ write a string to the original file descriptor
         """
-        tempfp = os.tmpfile()
+        tempfp = tempfile.TemporaryFile()
         try:
             os.dup2(self._savefd, tempfp.fileno())
             tempfp.write(str)

Modified: py/branch/bugfix-0.9.0/py/magic/greenlet.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/magic/greenlet.py	(original)
+++ py/branch/bugfix-0.9.0/py/magic/greenlet.py	Mon Mar 17 20:18:48 2008
@@ -2,8 +2,10 @@
 if '_stackless' in sys.builtin_module_names:
     # when running on top of a pypy with stackless support
     from _stackless import greenlet
+elif hasattr(sys, 'pypy_objspaceclass'):
+    raise ImportError("Detected pypy without stackless support")
 else:
-    # regular CPython (or pypy without stackless support, and then crash :-)
+    # regular CPython
     import py
     gdir = py.path.local(py.__file__).dirpath() 
     path = gdir.join('c-extension', 'greenlet', 'greenlet.c')

Modified: py/branch/bugfix-0.9.0/py/misc/conftest-socketgatewayrun.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/misc/conftest-socketgatewayrun.py	(original)
+++ py/branch/bugfix-0.9.0/py/misc/conftest-socketgatewayrun.py	Mon Mar 17 20:18:48 2008
@@ -28,8 +28,8 @@
         return True
     
 class MySession(RemoteTerminalSession):
-    socketserveradr = ('10.9.4.148', 8888)
     socketserveradr = ('10.9.2.62', 8888)
+    socketserveradr = ('10.9.4.148', 8888)
 
     def _initslavegateway(self):
         print "MASTER: initializing remote socket gateway"
@@ -59,3 +59,5 @@
         assert remotepypath.startswith(topdir), (remotepypath, topdir)
         #print "remote side has rsynced pythonpath ready: %r" %(topdir,)
         return gw, topdir
+
+dist_hosts = ['localhost', 'cobra', 'cobra']

Added: py/branch/bugfix-0.9.0/py/misc/killproc.py
==============================================================================
--- (empty file)
+++ py/branch/bugfix-0.9.0/py/misc/killproc.py	Mon Mar 17 20:18:48 2008
@@ -0,0 +1,10 @@
+
+import py
+import os, sys
+
+def killproc(pid):
+    if sys.platform == "win32":
+        py.process.cmdexec("taskkill /F /PID %d" %(pid,))
+    else:
+        os.kill(pid, 15)
+        

Added: py/branch/bugfix-0.9.0/py/misc/testing/test_oskill.py
==============================================================================
--- (empty file)
+++ py/branch/bugfix-0.9.0/py/misc/testing/test_oskill.py	Mon Mar 17 20:18:48 2008
@@ -0,0 +1,19 @@
+
+import py, sys
+
+from py.__.misc.killproc import killproc
+
+def test_win_killsubprocess():
+    if sys.platform == 'win32' and not py.path.local.sysfind('taskkill'):
+        py.test.skip("you\'re using an older version of windows, which "
+                     "doesn\'t support 'taskkill' - py.misc.killproc is not "
+                     "available")
+    tmp = py.test.ensuretemp("test_win_killsubprocess")
+    t = tmp.join("t.py")
+    t.write("import time ; time.sleep(100)")
+    proc = py.std.subprocess.Popen([sys.executable, str(t)])
+    assert proc.poll() is None # no return value yet
+    killproc(proc.pid)
+    ret = proc.wait()
+    assert ret != 0
+        

Modified: py/branch/bugfix-0.9.0/py/path/local/local.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/path/local/local.py	(original)
+++ py/branch/bugfix-0.9.0/py/path/local/local.py	Mon Mar 17 20:18:48 2008
@@ -68,9 +68,8 @@
         elif isinstance(path, str):
             self.strpath = os.path.abspath(os.path.normpath(str(path)))
         else:
-            raise ValueError(
-                "can only pass None, Path instances "
-                "or non-empty strings to LocalPath")
+            raise ValueError("can only pass None, Path instances "
+                             "or non-empty strings to LocalPath")
         assert isinstance(self.strpath, str)
         return self
 

Modified: py/branch/bugfix-0.9.0/py/path/svn/svncommon.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/path/svn/svncommon.py	(original)
+++ py/branch/bugfix-0.9.0/py/path/svn/svncommon.py	Mon Mar 17 20:18:48 2008
@@ -5,7 +5,7 @@
 import py
 from py.__.path import common
 
-ALLOWED_CHARS = "_ -/\\=$.~" #add characters as necessary when tested
+ALLOWED_CHARS = "_ -/\\=$.~+" #add characters as necessary when tested
 if sys.platform == "win32":
     ALLOWED_CHARS += ":"
 ALLOWED_CHARS_HOST = ALLOWED_CHARS + '@:'
@@ -65,6 +65,7 @@
         """
         obj = object.__new__(self.__class__)
         obj.rev = kw.get('rev', self.rev)
+        obj.auth = kw.get('auth', self.auth)
         dirname, basename, purebasename, ext = self._getbyspec(
              "dirname,basename,purebasename,ext")
         if 'basename' in kw:
@@ -138,7 +139,7 @@
 
         args = tuple([arg.strip(self.sep) for arg in args])
         parts = (self.strpath, ) + args
-        newpath = self.__class__(self.sep.join(parts), self.rev)
+        newpath = self.__class__(self.sep.join(parts), self.rev, self.auth)
         return newpath
 
     def propget(self, name):
@@ -330,3 +331,27 @@
         fspath = '%s at HEAD' % (fspath,)
     return 'file://%s' % (fspath,)
 
+class SvnAuth(object):
+    """ container for auth information for Subversion """
+    def __init__(self, username, password, cache_auth=True, interactive=True):
+        self.username = username
+        self.password = password
+        self.cache_auth = cache_auth
+        self.interactive = interactive
+
+    def makecmdoptions(self):
+        uname = self.username.replace('"', '\\"')
+        passwd = self.password.replace('"', '\\"')
+        ret = []
+        if uname:
+            ret.append('--username="%s"' % (uname,))
+        if passwd:
+            ret.append('--password="%s"' % (passwd,))
+        if not self.cache_auth:
+            ret.append('--no-auth-cache')
+        if not self.interactive:
+            ret.append('--non-interactive')
+        return ' '.join(ret)
+
+    def __str__(self):
+        return "<SvnAuth username=%s ...>" %(self.username,)

Added: py/branch/bugfix-0.9.0/py/path/svn/testing/test_auth.py
==============================================================================
--- (empty file)
+++ py/branch/bugfix-0.9.0/py/path/svn/testing/test_auth.py	Mon Mar 17 20:18:48 2008
@@ -0,0 +1,479 @@
+import py
+from py.path import SvnAuth
+import svntestbase
+from threading import Thread
+import time
+from py.__.misc.killproc import killproc
+from py.__.conftest import option
+
+def make_repo_auth(repo, userdata):
+    """ write config to repo
+    
+        user information in userdata is used for auth
+        userdata has user names as keys, and a tuple (password, readwrite) as
+        values, where 'readwrite' is either 'r' or 'rw'
+    """
+    confdir = py.path.local(repo).join('conf')
+    confdir.join('svnserve.conf').write('''\
+[general]
+anon-access = none
+password-db = passwd
+authz-db = authz
+realm = TestRepo
+''')
+    authzdata = '[/]\n'
+    passwddata = '[users]\n'
+    for user in userdata:
+        authzdata += '%s = %s\n' % (user, userdata[user][1])
+        passwddata += '%s = %s\n' % (user, userdata[user][0])
+    confdir.join('authz').write(authzdata)
+    confdir.join('passwd').write(passwddata)
+
+def serve_bg(repopath):
+    pidfile = py.path.local(repopath).join('pid')
+    port = 10000
+    e = None
+    while port < 10010:
+        cmd = 'svnserve -d -T --listen-port=%d --pid-file=%s -r %s' % (
+               port, pidfile, repopath)
+        try:
+            py.process.cmdexec(cmd)
+        except py.process.cmdexec.Error, e:
+            pass
+        else:
+            # XXX we assume here that the pid file gets written somewhere, I
+            # guess this should be relatively safe... (I hope, at least?)
+            while True:
+                pid = pidfile.read()
+                if pid:
+                    break
+                # needs a bit more time to boot
+                time.sleep(0.1)
+            return port, int(pid)
+        port += 1
+    raise IOError('could not start svnserve: %s' % (e,))
+
+class TestSvnAuth(object):
+    def test_basic(self):
+        auth = py.path.SvnAuth('foo', 'bar')
+        assert auth.username == 'foo'
+        assert auth.password == 'bar'
+        assert str(auth)
+
+    def test_makecmdoptions_uname_pw_makestr(self):
+        auth = py.path.SvnAuth('foo', 'bar')
+        assert auth.makecmdoptions() == '--uername="foo" --password="bar"'
+
+    def test_makecmdoptions_quote_escape(self):
+        auth = py.path.SvnAuth('fo"o', '"ba\'r"')
+        assert auth.makecmdoptions() == '--username="fo\\"o" --password="\\"ba\'r\\""'
+
+    def test_makecmdoptions_no_cache_auth(self):
+        auth = py.path.SvnAuth('foo', 'bar', cache_auth=False)
+        assert auth.makecmdoptions() == ('--username="foo" --password="bar" '
+                                         '--no-auth-cache')
+
+    def test_makecmdoptions_no_interactive(self):
+        auth = py.path.SvnAuth('foo', 'bar', interactive=False)
+        assert auth.makecmdoptions() == ('--username="foo" --password="bar" '
+                                         '--non-interactive')
+
+    def test_makecmdoptions_no_interactive_no_cache_auth(self):
+        auth = py.path.SvnAuth('foo', 'bar', cache_auth=False,
+                               interactive=False)
+        assert auth.makecmdoptions() == ('--username="foo" --password="bar" '
+                                         '--no-auth-cache --non-interactive')
+
+class svnwc_no_svn(py.path.svnwc):
+    def __init__(self, *args, **kwargs):
+        self.commands = []
+        super(svnwc_no_svn, self).__init__(*args, **kwargs)
+
+    def _svn(self, *args):
+        self.commands.append(args)
+
+class TestSvnWCAuth(object):
+    def setup_method(self, meth):
+        self.auth = SvnAuth('user', 'pass', cache_auth=False)
+
+    def test_checkout(self):
+        wc = svnwc_no_svn('foo', auth=self.auth)
+        wc.checkout('url')
+        assert wc.commands[0][-1] == ('--username="user" --password="pass" '
+                                      '--no-auth-cache')
+
+    def test_commit(self):
+        wc = svnwc_no_svn('foo', auth=self.auth)
+        wc.commit('msg')
+        assert wc.commands[0][-1] == ('--username="user" --password="pass" '
+                                      '--no-auth-cache')
+
+    def test_checkout_no_cache_auth(self):
+        wc = svnwc_no_svn('foo', auth=self.auth)
+        wc.checkout('url')
+        assert wc.commands[0][-1] == ('--username="user" --password="pass" '
+                                      '--no-auth-cache')
+
+    def test_checkout_auth_from_constructor(self):
+        wc = svnwc_no_svn('foo', auth=self.auth)
+        wc.checkout('url')
+        assert wc.commands[0][-1] == ('--username="user" --password="pass" '
+                                      '--no-auth-cache')
+
+class svnurl_no_svn(py.path.svnurl):
+    cmdexec_output = 'test'
+    popen_output = 'test'
+
+    def _cmdexec(self, cmd):
+        self.commands.append(cmd)
+        return self.cmdexec_output
+
+    def _popen(self, cmd):
+        self.commands.append(cmd)
+        return self.popen_output
+
+class TestSvnURLAuth(object):
+    def setup_method(self, meth):
+        svnurl_no_svn.commands = []
+        self.auth = SvnAuth('foo', 'bar')
+
+    def test_init(self):
+        u = svnurl_no_svn('http://foo.bar/svn')
+        assert u.auth is None
+
+        u = svnurl_no_svn('http://foo.bar/svn', auth=self.auth)
+        assert u.auth is self.auth
+
+    def test_new(self):
+        u = svnurl_no_svn('http://foo.bar/svn/foo', auth=self.auth)
+        new = u.new(basename='bar')
+        assert new.auth is self.auth
+        assert new.url == 'http://foo.bar/svn/bar'
+
+    def test_join(self):
+        u = svnurl_no_svn('http://foo.bar/svn', auth=self.auth)
+        new = u.join('foo')
+        assert new.auth is self.auth
+        assert new.url == 'http://foo.bar/svn/foo'
+
+    def test_listdir(self):
+        u = svnurl_no_svn('http://foo.bar/svn', auth=self.auth)
+        u.cmdexec_output = '''\
+   1717 johnny           1529 Nov 04 14:32 LICENSE.txt
+   1716 johnny           5352 Nov 04 14:28 README.txt
+'''
+        paths = u.listdir()
+        assert paths[0].auth is self.auth
+        assert paths[1].auth is self.auth
+        assert paths[0].basename == 'LICENSE.txt'
+
+    def test_info(self):
+        u = svnurl_no_svn('http://foo.bar/svn/LICENSE.txt', auth=self.auth)
+        def dirpath(self):
+            return self
+        u.cmdexec_output = '''\
+   1717 johnny           1529 Nov 04 14:32 LICENSE.txt
+   1716 johnny           5352 Nov 04 14:28 README.txt
+'''
+        org_dp = u.__class__.dirpath
+        u.__class__.dirpath = dirpath
+        try:
+            info = u.info()
+        finally:
+            u.dirpath = org_dp
+        assert info.size == 1529
+
+    def test_open(self):
+        u = svnurl_no_svn('http://foo.bar/svn', auth=self.auth)
+        foo = u.join('foo')
+        foo.check = lambda *args, **kwargs: True
+        ret = foo.open()
+        assert ret == 'test'
+        assert '--username="foo" --password="bar"' in foo.commands[0]
+
+    def test_dirpath(self):
+        u = svnurl_no_svn('http://foo.bar/svn/foo', auth=self.auth)
+        parent = u.dirpath()
+        assert parent.auth is self.auth
+
+    def test_mkdir(self):
+        u = svnurl_no_svn('http://foo.bar/svn', auth=self.auth)
+        u.mkdir('foo', msg='created dir foo')
+        assert '--username="foo" --password="bar"' in u.commands[0]
+
+    def test_copy(self):
+        u = svnurl_no_svn('http://foo.bar/svn', auth=self.auth)
+        u2 = svnurl_no_svn('http://foo.bar/svn2')
+        u.copy(u2, 'copied dir')
+        assert '--username="foo" --password="bar"' in u.commands[0]
+
+    def test_rename(self):
+        u = svnurl_no_svn('http://foo.bar/svn/foo', auth=self.auth)
+        u.rename('http://foo.bar/svn/bar', 'moved foo to bar')
+        assert '--username="foo" --password="bar"' in u.commands[0]
+
+    def test_remove(self):
+        u = svnurl_no_svn('http://foo.bar/svn/foo', auth=self.auth)
+        u.remove(msg='removing foo')
+        assert '--username="foo" --password="bar"' in u.commands[0]
+
+    def test_export(self):
+        u = svnurl_no_svn('http://foo.bar/svn', auth=self.auth)
+        target = py.path.local('/foo')
+        u.export(target)
+        assert '--username="foo" --password="bar"' in u.commands[0]
+
+    def test_log(self):
+        u = svnurl_no_svn('http://foo.bar/svn/foo', auth=self.auth)
+        u.popen_output = py.std.StringIO.StringIO('''\
+<?xml version="1.0"?>
+<log>
+<logentry revision="51381">
+<author>guido</author>
+<date>2008-02-11T12:12:18.476481Z</date>
+<msg>Creating branch to work on auth support for py.path.svn*.
+</msg>
+</logentry>
+</log>
+''')
+        u.check = lambda *args, **kwargs: True
+        ret = u.log(10, 20, verbose=True)
+        assert '--username="foo" --password="bar"' in u.commands[0]
+        assert len(ret) == 1
+        assert int(ret[0].rev) == 51381
+        assert ret[0].author == 'guido'
+
+    def test_propget(self):
+        u = svnurl_no_svn('http://foo.bar/svn', auth=self.auth)
+        u.propget('foo')
+        assert '--username="foo" --password="bar"' in u.commands[0]
+
+class SvnAuthFunctionalTestBase(object):
+    def setup_class(cls):
+        if not option.runslowtests:
+            py.test.skip('skipping slow functional tests - use --runslowtests '
+                         'to override')
+
+    def setup_method(self, meth):
+        func_name = meth.im_func.func_name
+        self.repo = svntestbase.make_test_repo('TestSvnAuthFunctional.%s' % (
+                                               func_name,))
+        repodir = str(self.repo)[7:]
+        if py.std.sys.platform == 'win32':
+            # remove trailing slash...
+            repodir = repodir[1:]
+        self.repopath = py.path.local(repodir)
+        self.temppath = py.test.ensuretemp('TestSvnAuthFunctional.%s' % (
+                                           func_name))
+        self.auth = py.path.SvnAuth('johnny', 'foo', cache_auth=False,
+                                    interactive=False)
+
+    def _start_svnserve(self):
+        make_repo_auth(self.repopath, {'johnny': ('foo', 'rw')})
+        try:
+            return serve_bg(self.repopath.dirpath())
+        except IOError, e:
+            py.test.skip(str(e))
+
+class TestSvnWCAuthFunctional(SvnAuthFunctionalTestBase):
+    def test_checkout_constructor_arg(self):
+        port, pid = self._start_svnserve()
+        try:
+            wc = py.path.svnwc(self.temppath, auth=self.auth)
+            wc.checkout(
+                'svn://localhost:%s/%s' % (port, self.repopath.basename))
+            assert wc.join('.svn').check()
+        finally:
+            # XXX can we do this in a teardown_method too? not sure if that's
+            # guaranteed to get called...
+            killproc(pid)
+
+    def test_checkout_function_arg(self):
+        port, pid = self._start_svnserve()
+        try:
+            wc = py.path.svnwc(self.temppath, auth=self.auth)
+            wc.checkout(
+                'svn://localhost:%s/%s' % (port, self.repopath.basename))
+            assert wc.join('.svn').check()
+        finally:
+            killproc(pid)
+
+    def test_checkout_failing_non_interactive(self):
+        port, pid = self._start_svnserve()
+        try:
+            auth = py.path.SvnAuth('johnny', 'bar', cache_auth=False,
+                                   interactive=False)
+            wc = py.path.svnwc(self.temppath, auth)
+            py.test.raises(Exception,
+                ("wc.checkout('svn://localhost:%s/%s' % "
+                 "(port, self.repopath.basename))"))
+        finally:
+            killproc(pid)
+
+    def test_log(self):
+        port, pid = self._start_svnserve()
+        try:
+            wc = py.path.svnwc(self.temppath, self.auth)
+            wc.checkout(
+                'svn://localhost:%s/%s' % (port, self.repopath.basename))
+            foo = wc.ensure('foo.txt')
+            wc.commit('added foo.txt')
+            log = foo.log()
+            assert len(log) == 1
+            assert log[0].msg == 'added foo.txt'
+        finally:
+            killproc(pid)
+
+    def test_switch(self):
+        port, pid = self._start_svnserve()
+        try:
+            wc = py.path.svnwc(self.temppath, auth=self.auth)
+            svnurl = 'svn://localhost:%s/%s' % (port, self.repopath.basename)
+            wc.checkout(svnurl)
+            wc.ensure('foo', dir=True).ensure('foo.txt').write('foo')
+            wc.commit('added foo dir with foo.txt file')
+            wc.ensure('bar', dir=True)
+            wc.commit('added bar dir')
+            bar = wc.join('bar')
+            bar.switch(svnurl + '/foo')
+            assert bar.join('foo.txt')
+        finally:
+            killproc(pid)
+
+    def test_update(self):
+        port, pid = self._start_svnserve()
+        try:
+            wc1 = py.path.svnwc(self.temppath.ensure('wc1', dir=True),
+                                auth=self.auth)
+            wc2 = py.path.svnwc(self.temppath.ensure('wc2', dir=True),
+                                auth=self.auth)
+            wc1.checkout(
+                'svn://localhost:%s/%s' % (port, self.repopath.basename))
+            wc2.checkout(
+                'svn://localhost:%s/%s' % (port, self.repopath.basename))
+            wc1.ensure('foo', dir=True)
+            wc1.commit('added foo dir')
+            wc2.update()
+            assert wc2.join('foo').check()
+
+            auth = py.path.SvnAuth('unknown', 'unknown', interactive=False)
+            wc2.auth = auth
+            py.test.raises(Exception, 'wc2.update()')
+        finally:
+            killproc(pid)
+
+    def test_lock_unlock_status(self):
+        port, pid = self._start_svnserve()
+        try:
+            wc = py.path.svnwc(self.temppath, auth=self.auth)
+            wc.checkout(
+                'svn://localhost:%s/%s' % (port, self.repopath.basename,))
+            wc.ensure('foo', file=True)
+            wc.commit('added foo file')
+            foo = wc.join('foo')
+            foo.lock()
+            status = foo.status()
+            assert status.locked
+            foo.unlock()
+            status = foo.status()
+            assert not status.locked
+
+            auth = py.path.SvnAuth('unknown', 'unknown', interactive=False)
+            foo.auth = auth
+            py.test.raises(Exception, 'foo.lock()')
+            py.test.raises(Exception, 'foo.unlock()')
+        finally:
+            killproc(pid)
+
+    def test_diff(self):
+        port, pid = self._start_svnserve()
+        try:
+            wc = py.path.svnwc(self.temppath, auth=self.auth)
+            wc.checkout(
+                'svn://localhost:%s/%s' % (port, self.repopath.basename,))
+            wc.ensure('foo', file=True)
+            wc.commit('added foo file')
+            wc.update()
+            rev = int(wc.status().rev)
+            foo = wc.join('foo')
+            foo.write('bar')
+            diff = foo.diff()
+            assert '\n+bar\n' in diff
+            foo.commit('added some content')
+            diff = foo.diff()
+            assert not diff
+            diff = foo.diff(rev=rev)
+            assert '\n+bar\n' in diff
+
+            auth = py.path.SvnAuth('unknown', 'unknown', interactive=False)
+            foo.auth = auth
+            py.test.raises(Exception, 'foo.diff(rev=rev)')
+        finally:
+            killproc(pid)
+
+class TestSvnURLAuthFunctional(SvnAuthFunctionalTestBase):
+    def test_listdir(self):
+        port, pid = self._start_svnserve()
+        try:
+            u = py.path.svnurl(
+                'svn://localhost:%s/%s' % (port, self.repopath.basename),
+                auth=self.auth)
+            u.ensure('foo')
+            paths = u.listdir()
+            assert len(paths) == 1
+            assert paths[0].auth is self.auth
+
+            auth = SvnAuth('foo', 'bar', interactive=False)
+            u = py.path.svnurl(
+                'svn://localhost:%s/%s' % (port, self.repopath.basename),
+                auth=auth)
+            py.test.raises(Exception, 'u.listdir()')
+        finally:
+            killproc(pid)
+
+    def test_copy(self):
+        port, pid = self._start_svnserve()
+        try:
+            u = py.path.svnurl(
+                'svn://localhost:%s/%s' % (port, self.repopath.basename),
+                auth=self.auth)
+            foo = u.ensure('foo')
+            bar = u.join('bar')
+            foo.copy(bar)
+            assert bar.check()
+            assert bar.auth is self.auth
+
+            auth = SvnAuth('foo', 'bar', interactive=False)
+            u = py.path.svnurl(
+                'svn://localhost:%s/%s' % (port, self.repopath.basename),
+                auth=auth)
+            foo = u.join('foo')
+            bar = u.join('bar')
+            py.test.raises(Exception, 'foo.copy(bar)')
+        finally:
+            killproc(pid)
+
+    def test_write_read(self):
+        port, pid = self._start_svnserve()
+        try:
+            u = py.path.svnurl(
+                'svn://localhost:%s/%s' % (port, self.repopath.basename),
+                auth=self.auth)
+            foo = u.ensure('foo')
+            fp = foo.open()
+            try:
+                data = fp.read()
+            finally:
+                fp.close()
+            assert data == ''
+
+            auth = SvnAuth('foo', 'bar', interactive=False)
+            u = py.path.svnurl(
+                'svn://localhost:%s/%s' % (port, self.repopath.basename),
+                auth=auth)
+            foo = u.join('foo')
+            py.test.raises(Exception, 'foo.open()')
+        finally:
+            killproc(pid)
+
+    # XXX rinse, repeat... :|

Modified: py/branch/bugfix-0.9.0/py/path/svn/testing/test_urlcommand.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/path/svn/testing/test_urlcommand.py	(original)
+++ py/branch/bugfix-0.9.0/py/path/svn/testing/test_urlcommand.py	Mon Mar 17 20:18:48 2008
@@ -59,6 +59,44 @@
             py.test.skip('XXX fixme win32')
         py.test.raises(ValueError, 'py.path.svnurl("http://host.com/foo:bar")')
 
+    def test_export(self):
+        repo, wc = getrepowc('test_export_repo', 'test_export_wc')
+        foo = wc.join('foo').ensure(dir=True)
+        bar = foo.join('bar').ensure(file=True)
+        bar.write('bar\n')
+        foo.commit('testing something')
+        exportpath = py.test.ensuretemp('test_export_exportdir')
+        url = py.path.svnurl(repo + '/foo')
+        foo = url.export(exportpath.join('foo'))
+        assert foo == exportpath.join('foo')
+        assert isinstance(foo, py.path.local)
+        assert foo.join('bar').check()
+        assert not foo.join('.svn').check()
+
+    def test_export_rev(self):
+        repo, wc = getrepowc('test_export_rev_repo', 'test_export_rev_wc')
+        foo = wc.join('foo').ensure(dir=True)
+        bar = foo.join('bar').ensure(file=True)
+        bar.write('bar\n')
+        rev1 = foo.commit('testing something')
+        print 'rev1:', rev1
+        baz = foo.join('baz').ensure(file=True)
+        baz.write('baz\n')
+        rev2 = foo.commit('testing more')
+        
+        exportpath = py.test.ensuretemp('test_export_rev_exportdir')
+        url = py.path.svnurl(repo + '/foo', rev=rev1)
+        foo1 = url.export(exportpath.join('foo1'))
+        assert foo1.check()
+        assert foo1.join('bar').check()
+        assert not foo1.join('baz').check()
+
+        url = py.path.svnurl(repo + '/foo', rev=rev2)
+        foo2 = url.export(exportpath.join('foo2'))
+        assert foo2.check()
+        assert foo2.join('bar').check()
+        assert foo2.join('baz').check()
+
 class TestSvnInfoCommand:
 
     def test_svn_1_2(self):

Modified: py/branch/bugfix-0.9.0/py/path/svn/testing/test_wccommand.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/path/svn/testing/test_wccommand.py	(original)
+++ py/branch/bugfix-0.9.0/py/path/svn/testing/test_wccommand.py	Mon Mar 17 20:18:48 2008
@@ -1,13 +1,27 @@
 import py
+import sys
 from py.__.path.svn.testing.svntestbase import CommonSvnTests, getrepowc
 from py.__.path.svn.wccommand import InfoSvnWCCommand
 from py.__.path.svn.wccommand import parse_wcinfotime
 from py.__.path.svn import svncommon
 
-
 if py.path.local.sysfind('svn') is None:
     py.test.skip("cannot test py.path.svn, 'svn' binary not found")
 
+if sys.platform != 'win32':
+    def normpath(p):
+        return p
+else:
+    try:
+        import win32api
+    except ImportError:
+        def normpath(p):
+            py.test.skip('this test requires win32api to run on windows')
+    else:
+        import os
+        def normpath(p):
+            p = win32api.GetShortPathName(p)
+            return os.path.normpath(os.path.normcase(p))
 
 class TestWCSvnCommandPath(CommonSvnTests):
 
@@ -156,6 +170,15 @@
         finally:
             notexisting.remove()
 
+    def test_nonversioned_remove(self):
+        assert self.root.check(versioned=1)
+        somefile = self.root.join('nonversioned/somefile')
+        nonwc = py.path.local(somefile)
+        nonwc.ensure()
+        assert somefile.check()
+        assert not somefile.check(versioned=True)
+        somefile.remove() # this used to fail because it tried to 'svn rm'
+
     def test_properties(self):
         try:
             self.root.propset('gaga', 'this')
@@ -217,6 +240,63 @@
             p.remove(rec=1)
             f.remove()
 
+    def test_lock_unlock(self):
+        root = self.root
+        somefile = root.join('somefile')
+        somefile.ensure(file=True)
+        # not yet added to repo
+        py.test.raises(py.process.cmdexec.Error, 'somefile.lock()')
+        somefile.write('foo')
+        somefile.commit('test')
+        assert somefile.check(versioned=True)
+        somefile.lock()
+        try:
+            locked = root.status().locked
+            assert len(locked) == 1
+            assert normpath(str(locked[0])) == normpath(str(somefile))
+            #assert somefile.locked()
+            py.test.raises(Exception, 'somefile.lock()')
+        finally:
+            somefile.unlock()
+        #assert not somefile.locked()
+        locked = root.status().locked
+        assert locked == []
+        py.test.raises(Exception, 'somefile,unlock()')
+        somefile.remove()
+
+    def test_commit_nonrecursive(self):
+        root = self.root
+        somedir = root.join('sampledir')
+        somefile = somedir.join('otherfile')
+        somefile.write('foo')
+        somedir.propset('foo', 'bar')
+        status = somedir.status()
+        assert len(status.prop_modified) == 1
+        assert len(status.modified) == 1
+
+        somedir.commit('non-recursive commit', rec=0)
+        status = somedir.status()
+        assert len(status.prop_modified) == 0
+        assert len(status.modified) == 1
+
+        somedir.commit('recursive commit')
+        status = somedir.status()
+        assert len(status.prop_modified) == 0
+        assert len(status.modified) == 0
+
+    def test_commit_return_value(self):
+        root = self.root
+        testfile = root.join('test.txt').ensure(file=True)
+        testfile.write('test')
+        rev = root.commit('testing')
+        assert type(rev) == int
+
+        anotherfile = root.join('another.txt').ensure(file=True)
+        anotherfile.write('test')
+        rev2 = root.commit('testing more')
+        assert type(rev2) == int
+        assert rev2 == rev + 1
+
     #def test_log(self):
     #   l = self.root.log()
     #   assert len(l) == 3  # might need to be upped if more tests are added

Modified: py/branch/bugfix-0.9.0/py/path/svn/urlcommand.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/path/svn/urlcommand.py	(original)
+++ py/branch/bugfix-0.9.0/py/path/svn/urlcommand.py	Mon Mar 17 20:18:48 2008
@@ -21,10 +21,11 @@
     _lsrevcache = BuildcostAccessCache(maxentries=128)
     _lsnorevcache = AgingCache(maxentries=1000, maxseconds=60.0)
 
-    def __new__(cls, path, rev=None):
+    def __new__(cls, path, rev=None, auth=None):
         self = object.__new__(cls)
         if isinstance(path, cls): 
             rev = path.rev 
+            auth = path.auth
             path = path.strpath 
         proto, uri = path.split("://", 1)
         host, uripath = uri.split('/', 1)
@@ -36,6 +37,7 @@
         path = path.rstrip('/')
         self.strpath = path
         self.rev = rev
+        self.auth = auth
         return self
 
     def __repr__(self):
@@ -44,7 +46,8 @@
         else:
             return 'svnurl(%r, %r)' % (self.strpath, self.rev)
 
-    def _svn(self, cmd, *args):
+    def _svnwithrev(self, cmd, *args):
+        """ execute an svn command, append our own url and revision """
         if self.rev is None:
             return self._svnwrite(cmd, *args)
         else:
@@ -52,16 +55,28 @@
             return self._svnwrite(cmd, *args)
 
     def _svnwrite(self, cmd, *args):
+        """ execute an svn command, append our own url """
         l = ['svn %s' % cmd]
         args = ['"%s"' % self._escape(item) for item in args]
         l.extend(args)
         l.append('"%s"' % self._encodedurl())
         # fixing the locale because we can't otherwise parse
-        string = svncommon.fixlocale() + " ".join(l)
+        string = " ".join(l)
         if DEBUG:
             print "execing", string
+        out = self._svncmdexecauth(string)
+        return out
+
+    def _svncmdexecauth(self, cmd):
+        """ execute an svn command 'as is' """
+        cmd = svncommon.fixlocale() + cmd
+        if self.auth is not None:
+            cmd += ' ' + self.auth.makecmdoptions()
+        return self._cmdexec(cmd)
+
+    def _cmdexec(self, cmd):
         try:
-            out = process.cmdexec(string)
+            out = process.cmdexec(cmd)
         except py.process.cmdexec.Error, e:
             if (e.err.find('File Exists') != -1 or
                             e.err.find('File already exists') != -1):
@@ -69,21 +84,33 @@
             raise
         return out
 
+    def _svnpopenauth(self, cmd):
+        """ execute an svn command, return a pipe for reading stdin """
+        cmd = svncommon.fixlocale() + cmd
+        if self.auth is not None:
+            cmd += ' ' + self.auth.makecmdoptions()
+        return self._popen(cmd)
+
+    def _popen(self, cmd):
+        return os.popen(cmd)
+
     def _encodedurl(self):
         return self._escape(self.strpath)
 
+    def _norev_delentry(self, path):
+        auth = self.auth and self.auth.makecmdoptions() or None
+        self._lsnorevcache.delentry((str(path), auth))
+
     def open(self, mode='r'):
         """ return an opened file with the given mode. """
         assert 'w' not in mode and 'a' not in mode, "XXX not implemented for svn cmdline"
         assert self.check(file=1) # svn cat returns an empty file otherwise
-        def popen(cmd):
-            return os.popen(cmd)
         if self.rev is None:
-            return popen(svncommon.fixlocale() +
-                            'svn cat "%s"' % (self._escape(self.strpath), ))
+            return self._svnpopenauth('svn cat "%s"' % (
+                                      self._escape(self.strpath), ))
         else:
-            return popen(svncommon.fixlocale() +
-                            'svn cat -r %s "%s"' % (self.rev, self._escape(self.strpath)))
+            return self._svnpopenauth('svn cat -r %s "%s"' % (
+                                      self.rev, self._escape(self.strpath)))
 
     def dirpath(self, *args, **kwargs):
         """ return the directory path of the current path joined
@@ -104,33 +131,47 @@
         commit_msg=kwargs.get('msg', "mkdir by py lib invocation")
         createpath = self.join(*args)
         createpath._svnwrite('mkdir', '-m', commit_msg)
-        self._lsnorevcache.delentry(createpath.dirpath().strpath)
+        self._norev_delentry(createpath.dirpath())
         return createpath
 
     def copy(self, target, msg='copied by py lib invocation'):
         """ copy path to target with checkin message msg."""
         if getattr(target, 'rev', None) is not None:
             raise py.error.EINVAL(target, "revisions are immutable")
-        process.cmdexec('svn copy -m "%s" "%s" "%s"' %(msg, 
-                                                self._escape(self), self._escape(target)))
-        self._lsnorevcache.delentry(target.dirpath().strpath)
+        self._svncmdexecauth('svn copy -m "%s" "%s" "%s"' %(msg,
+                             self._escape(self), self._escape(target)))
+        self._norev_delentry(target.dirpath())
 
     def rename(self, target, msg="renamed by py lib invocation"):
         """ rename this path to target with checkin message msg. """
         if getattr(self, 'rev', None) is not None:
             raise py.error.EINVAL(self, "revisions are immutable")
-        py.process.cmdexec('svn move -m "%s" --force "%s" "%s"' %(
-                           msg, self._escape(self), self._escape(target)))
-        self._lsnorevcache.delentry(self.dirpath().strpath)
-        self._lsnorevcache.delentry(self.strpath)
+        self._svncmdexecauth('svn move -m "%s" --force "%s" "%s"' %(
+                             msg, self._escape(self), self._escape(target)))
+        self._norev_delentry(self.dirpath())
+        self._norev_delentry(self)
 
     def remove(self, rec=1, msg='removed by py lib invocation'):
         """ remove a file or directory (or a directory tree if rec=1) with
 checkin message msg."""
         if self.rev is not None:
             raise py.error.EINVAL(self, "revisions are immutable")
-        process.cmdexec('svn rm -m "%s" "%s"' %(msg, self._escape(self)))
-        self._lsnorevcache.delentry(self.dirpath().strpath)
+        self._svncmdexecauth('svn rm -m "%s" "%s"' %(msg, self._escape(self)))
+        self._norev_delentry(self.dirpath())
+
+    def export(self, topath):
+        """ export to a local path
+
+            topath should not exist prior to calling this, returns a
+            py.path.local instance
+        """
+        topath = py.path.local(topath)
+        args = ['"%s"' % (self._escape(self),),
+                '"%s"' % (self._escape(topath),)]
+        if self.rev is not None:
+            args = ['-r', str(self.rev)] + args
+        self._svncmdexecauth('svn export %s' % (' '.join(args),))
+        return topath
 
     def ensure(self, *args, **kwargs):
         """ ensure that an args-joined path exists (by default as
@@ -159,19 +200,19 @@
                     "ensure %s" % self._escape(tocreate), 
                     self._escape(tempdir.join(basename)), 
                     x.join(basename)._encodedurl())
-            process.cmdexec(cmd) 
-            self._lsnorevcache.delentry(x.strpath)  # !!! 
+            self._svncmdexecauth(cmd) 
+            self._norev_delentry(x)
         finally:    
             tempdir.remove() 
         return target
 
     # end of modifying methods
     def _propget(self, name):
-        res = self._svn('propget', name)
+        res = self._svnwithrev('propget', name)
         return res[:-1] # strip trailing newline
 
     def _proplist(self):
-        res = self._svn('proplist')
+        res = self._svnwithrev('proplist')
         lines = res.split('\n')
         lines = map(str.strip, lines[1:])
         return svncommon.PropListDict(self, lines)
@@ -180,7 +221,7 @@
         """ return sequence of name-info directory entries of self """
         def builder():
             try:
-                res = self._svn('ls', '-v')
+                res = self._svnwithrev('ls', '-v')
             except process.cmdexec.Error, e:
                 if e.err.find('non-existent in that revision') != -1:
                     raise py.error.ENOENT(self, e.err)
@@ -200,10 +241,13 @@
                     info = InfoSvnCommand(lsline)
                     nameinfo_seq.append((info._name, info))
             return nameinfo_seq
+        auth = self.auth and self.auth.makecmdoptions() or None
         if self.rev is not None:
-            return self._lsrevcache.getorbuild((self.strpath, self.rev), builder)
+            return self._lsrevcache.getorbuild((self.strpath, self.rev, auth),
+                                               builder)
         else:
-            return self._lsnorevcache.getorbuild(self.strpath, builder)
+            return self._lsnorevcache.getorbuild((self.strpath, auth),
+                                                 builder)
 
     def log(self, rev_start=None, rev_end=1, verbose=False):
         """ return a list of LogEntry instances for this path.
@@ -220,9 +264,8 @@
         else:
             rev_opt = "-r %s:%s" % (rev_start, rev_end)
         verbose_opt = verbose and "-v" or ""
-        xmlpipe =  os.popen(svncommon.fixlocale() +
-                      'svn log --xml %s %s "%s"' %
-                      (rev_opt, verbose_opt, self.strpath))
+        xmlpipe =  self._svnpopenauth('svn log --xml %s %s "%s"' %
+                                      (rev_opt, verbose_opt, self.strpath))
         from xml.dom import minidom
         tree = minidom.parse(xmlpipe)
         result = []
@@ -240,7 +283,7 @@
     # the '0?' part in the middle is an indication of whether the resource is
     # locked, see 'svn help ls'
     lspattern = re.compile(
-        r'^ *(?P<rev>\d+) +(?P<author>\S+) +(0? *(?P<size>\d+))? '
+        r'^ *(?P<rev>\d+) +(?P<author>.+?) +(0? *(?P<size>\d+))? '
             '*(?P<date>\w+ +\d{2} +[\d:]+) +(?P<file>.*)$')
     def __init__(self, line):
         # this is a typical line from 'svn ls http://...'

Modified: py/branch/bugfix-0.9.0/py/path/svn/wccommand.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/path/svn/wccommand.py	(original)
+++ py/branch/bugfix-0.9.0/py/path/svn/wccommand.py	Mon Mar 17 20:18:48 2008
@@ -25,7 +25,7 @@
     """
     sep = os.sep
 
-    def __new__(cls, wcpath=None):
+    def __new__(cls, wcpath=None, auth=None):
         self = object.__new__(cls)
         if isinstance(wcpath, cls):
             if wcpath.__class__ == cls:
@@ -35,6 +35,7 @@
                                           svncommon.ALLOWED_CHARS):
             raise ValueError("bad char in wcpath %s" % (wcpath, ))
         self.localpath = py.path.local(wcpath)
+        self.auth = auth
         return self
 
     strpath = property(lambda x: str(x.localpath), None, None, "string path")
@@ -63,13 +64,22 @@
         info = self.info()
         return py.path.svnurl(info.url)
 
-
     def __repr__(self):
         return "svnwc(%r)" % (self.strpath) # , self._url)
 
     def __str__(self):
         return str(self.localpath)
 
+    def _makeauthoptions(self):
+        if self.auth is None:
+            return ''
+        return self.auth.makecmdoptions()
+
+    def _authsvn(self, cmd, args=None):
+        args = args and list(args) or []
+        args.append(self._makeauthoptions())
+        return self._svn(cmd, *args)
+        
     def _svn(self, cmd, *args):
         l = ['svn %s' % cmd]
         args = [self._escape(item) for item in args]
@@ -101,9 +111,9 @@
             raise
         return out
 
-    def switch(self, url): 
+    def switch(self, url):
         """ switch to given URL. """
-        self._svn('switch', url)
+        self._authsvn('switch', [url])
 
     def checkout(self, url=None, rev=None):
         """ checkout from url to local wcpath. """
@@ -118,12 +128,13 @@
             if svncommon._getsvnversion() == '1.3':
                 url += "@%d" % rev
             else:
-                args.append('-r', str(rev))
-        self._svn('co', url, *args)
+                args.append('-r' + str(rev))
+        args.append(url)
+        self._authsvn('co', args)
 
     def update(self, rev = 'HEAD'):
         """ update working copy item to given revision. (None -> HEAD). """
-        self._svn('up -r %s' % rev)
+        self._authsvn('up', ['-r', rev])
 
     def write(self, content, mode='wb'):
         """ write content into local filesystem wc. """
@@ -131,7 +142,7 @@
 
     def dirpath(self, *args):
         """ return the directory Path of the current Path. """
-        return self.__class__(self.localpath.dirpath(*args))
+        return self.__class__(self.localpath.dirpath(*args), auth=self.auth)
 
     def _ensuredirs(self):
         parent = self.dirpath()
@@ -180,6 +191,10 @@
             underlying svn semantics.
         """
         assert rec, "svn cannot remove non-recursively"
+        if not self.check(versioned=True):
+            # not added to svn (anymore?), just remove
+            py.path.local(self).remove()
+            return
         flags = []
         if force:
             flags.append('--force')
@@ -193,7 +208,32 @@
         """ rename this path to target. """
         py.process.cmdexec("svn move --force %s %s" %(str(self), str(target)))
 
-    _rex_status = re.compile(r'\s+(\d+|-)\s+(\S+)\s+(\S+)\s+(.*)')
+    # XXX a bit scary to assume there's always 2 spaces between username and
+    # path, however with win32 allowing spaces in user names there doesn't
+    # seem to be a more solid approach :(
+    _rex_status = re.compile(r'\s+(\d+|-)\s+(\S+)\s+(.+?)\s{2,}(.*)')
+
+    def lock(self):
+        """ set a lock (exclusive) on the resource """
+        out = self._authsvn('lock').strip()
+        if not out:
+            # warning or error, raise exception
+            raise Exception(out[4:])
+    
+    def unlock(self):
+        """ unset a previously set lock """
+        out = self._authsvn('unlock').strip()
+        if out.startswith('svn:'):
+            # warning or error, raise exception
+            raise Exception(out[4:])
+
+    def cleanup(self):
+        """ remove any locks from the resource """
+        # XXX should be fixed properly!!!
+        try:
+            self.unlock()
+        except:
+            pass
 
     def status(self, updates=0, rec=0, externals=0):
         """ return (collective) Status object for this file. """
@@ -222,7 +262,8 @@
 
         update_rev = None
 
-        out = self._svn('status -v %s %s %s' % (updates, rec, externals))
+        cmd = 'status -v %s %s %s' % (updates, rec, externals)
+        out = self._authsvn(cmd)
         rootstatus = WCStatus(self)
         for line in out.split('\n'):
             if not line.strip():
@@ -230,7 +271,7 @@
             #print "processing %r" % line
             flags, rest = line[:8], line[8:]
             # first column
-            c0,c1,c2,c3,c4,x5,x6,c7 = flags
+            c0,c1,c2,c3,c4,c5,x6,c7 = flags
             #if '*' in line:
             #    print "flags", repr(flags), "rest", repr(rest)
 
@@ -240,7 +281,8 @@
                     wcpath = self.join(fn, abs=1)
                     rootstatus.unknown.append(wcpath)
                 elif c0 == 'X':
-                    wcpath = self.__class__(self.localpath.join(fn, abs=1))
+                    wcpath = self.__class__(self.localpath.join(fn, abs=1),
+                                            auth=self.auth)
                     rootstatus.external.append(wcpath)
                 elif c0 == 'I':
                     wcpath = self.join(fn, abs=1)
@@ -287,7 +329,8 @@
 
             if c1 == 'M':
                 rootstatus.prop_modified.append(wcpath)
-            if c2 == 'L':
+            # XXX do we cover all client versions here?
+            if c2 == 'L' or c5 == 'K':
                 rootstatus.locked.append(wcpath)
             if c7 == '*':
                 rootstatus.update_available.append(wcpath)
@@ -305,10 +348,10 @@
         """ return a diff of the current path against revision rev (defaulting
             to the last one).
         """
-        if rev is None:
-            out = self._svn('diff')
-        else:
-            out = self._svn('diff -r %d' % rev)
+        args = []
+        if rev is not None:
+            args.append("-r %d" % rev)
+        out = self._authsvn('diff', args)
         return out
 
     def blame(self):
@@ -329,11 +372,14 @@
         return result
 
     _rex_commit = re.compile(r'.*Committed revision (\d+)\.$', re.DOTALL)
-    def commit(self, message=""): 
-        """commit() returns None if there was nothing to commit
-           and the revision number of the commit otherwise.
-        """
-        out = self._svn('commit -m "%s"' % message)
+    def commit(self, msg='', rec=1):
+        """ commit with support for non-recursive commits """
+        from py.__.path.svn import cache
+        # XXX i guess escaping should be done better here?!?
+        cmd = 'commit -m "%s" --force-log' % (msg.replace('"', '\\"'),)
+        if not rec:
+            cmd += ' -N'
+        out = self._authsvn(cmd)
         try:
             del cache.info[self]
         except KeyError:
@@ -399,7 +445,7 @@
             localpath = self.localpath.new(**kw)
         else:
             localpath = self.localpath
-        return self.__class__(localpath)
+        return self.__class__(localpath, auth=self.auth)
 
     def join(self, *args, **kwargs):
         """ return a new Path (with the same revision) which is composed
@@ -408,7 +454,7 @@
         if not args:
             return self
         localpath = self.localpath.join(*args, **kwargs)
-        return self.__class__(localpath)
+        return self.__class__(localpath, auth=self.auth)
 
     def info(self, usecache=1):
         """ return an Info structure with svn-provided information. """
@@ -451,7 +497,7 @@
 
         paths = []
         for localpath in self.localpath.listdir(notsvn):
-            p = self.__class__(localpath)
+            p = self.__class__(localpath, auth=self.auth)
             paths.append(p)
 
         if fil or sort:
@@ -502,11 +548,13 @@
         else:
             rev_opt = "-r %s:%s" % (rev_start, rev_end)
         verbose_opt = verbose and "-v" or ""
-        s = svncommon.fixlocale()
+        locale_env = svncommon.fixlocale()
         # some blather on stderr
-        stdin, stdout, stderr  = os.popen3(s + 'svn log --xml %s %s "%s"' % (
-                                           rev_opt, verbose_opt,
-                                           self.strpath))
+        auth_opt = self._makeauthoptions()
+        stdin, stdout, stderr  = os.popen3(locale_env +
+                                           'svn log --xml %s %s %s "%s"' % (
+                                            rev_opt, verbose_opt, auth_opt,
+                                            self.strpath))
         from xml.dom import minidom
         from xml.parsers.expat import ExpatError
         try:
@@ -530,13 +578,13 @@
         return self.info().mtime
 
     def __hash__(self):
-        return hash((self.strpath, self.__class__))
+        return hash((self.strpath, self.__class__, self.auth))
 
 
 class WCStatus:
     attrnames = ('modified','added', 'conflict', 'unchanged', 'external',
                 'deleted', 'prop_modified', 'unknown', 'update_available',
-                'incomplete', 'kindmismatch', 'ignored'
+                'incomplete', 'kindmismatch', 'ignored', 'locked'
                 )
 
     def __init__(self, wcpath, rev=None, modrev=None, author=None):

Modified: py/branch/bugfix-0.9.0/py/test/collect.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/test/collect.py	(original)
+++ py/branch/bugfix-0.9.0/py/test/collect.py	Mon Mar 17 20:18:48 2008
@@ -46,9 +46,6 @@
         self.name = name 
         self.parent = parent
         self._config = getattr(parent, '_config', py.test.config)
-        if parent is not None:
-            if hasattr(parent, 'config'):
-                py.test.pdb()
         self.fspath = getattr(parent, 'fspath', None) 
 
     Module = configproperty('Module')
@@ -289,7 +286,7 @@
             name2items[name] = res 
         return res
 
-class PyCollectorMixin(object): 
+class PyCollectorMixin(Collector): 
     def funcnamefilter(self, name): 
         return name.startswith('test') 
     def classnamefilter(self, name): 
@@ -333,7 +330,7 @@
                 raise 
             except:
                 self._name2items_exception = py.std.sys.exc_info()
-                raise 
+                raise
 
     def run(self): 
         self._prepare()
@@ -350,7 +347,7 @@
     def run(self):
         if getattr(self.obj, 'disabled', 0):
             return []
-        return PyCollectorMixin.run(self) 
+        return super(Module, self).run()
 
     def join(self, name):
         res = super(Module, self).join(name)
@@ -442,7 +439,40 @@
                        Collector.Function.__get__(self)) # XXX for python 2.2 
     Function = property(Function)
 
-class Generator(PyCollectorMixin, Collector): 
+
+class FunctionMixin(object):
+    """ mixin for the code common to Function and Generator.
+    """
+    def _getpathlineno(self):
+        code = py.code.Code(self.obj) 
+        return code.path, code.firstlineno 
+
+    def _getsortvalue(self):  
+        return self._getpathlineno() 
+
+    def setup(self): 
+        """ perform setup for this test function. """
+        if getattr(self.obj, 'im_self', None): 
+            name = 'setup_method' 
+        else: 
+            name = 'setup_function' 
+        obj = self.parent.obj 
+        meth = getattr(obj, name, None)
+        if meth is not None: 
+            return meth(self.obj) 
+
+    def teardown(self): 
+        """ perform teardown for this test function. """
+        if getattr(self.obj, 'im_self', None): 
+            name = 'teardown_method' 
+        else: 
+            name = 'teardown_function' 
+        obj = self.parent.obj 
+        meth = getattr(obj, name, None)
+        if meth is not None: 
+            return meth(self.obj) 
+
+class Generator(FunctionMixin, PyCollectorMixin, Collector): 
     def run(self): 
         self._prepare()
         itemlist = self._name2items
@@ -468,13 +498,6 @@
             call, args = obj, ()
         return call, args 
 
-    def _getpathlineno(self): 
-        code = py.code.Code(self.obj) 
-        return code.path, code.firstlineno 
-
-    def _getsortvalue(self):  
-        return self._getpathlineno() 
-
 class DoctestFile(PyCollectorMixin, FSCollector): 
     def run(self):
         return [self.fspath.basename]

Modified: py/branch/bugfix-0.9.0/py/test/item.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/test/item.py	(original)
+++ py/branch/bugfix-0.9.0/py/test/item.py	Mon Mar 17 20:18:48 2008
@@ -2,6 +2,7 @@
 
 from inspect import isclass, ismodule
 from py.__.test.outcome import Skipped, Failed, Passed
+from py.__.test.collect import FunctionMixin
 
 _dummy = object()
 
@@ -37,7 +38,7 @@
     def finishcapture(self): 
         self._config._finishcapture(self)
 
-class Function(Item): 
+class Function(FunctionMixin, Item): 
     """ a Function Item is responsible for setting up  
         and executing a Python callable test object.
     """
@@ -52,10 +53,6 @@
     def __repr__(self): 
         return "<%s %r>" %(self.__class__.__name__, self.name)
 
-    def _getpathlineno(self):
-        code = py.code.Code(self.obj) 
-        return code.path, code.firstlineno 
-
     def _getsortvalue(self):  
         if self._sort_value is None:
             return self._getpathlineno()
@@ -70,32 +67,28 @@
         """ execute the given test function. """
         target(*args)
 
-    def setup(self): 
-        """ perform setup for this test function. """
-        if getattr(self.obj, 'im_self', None): 
-            name = 'setup_method' 
-        else: 
-            name = 'setup_function' 
-        obj = self.parent.obj 
-        meth = getattr(obj, name, None)
-        if meth is not None: 
-            return meth(self.obj) 
-
-    def teardown(self): 
-        """ perform teardown for this test function. """
-        if getattr(self.obj, 'im_self', None): 
-            name = 'teardown_method' 
-        else: 
-            name = 'teardown_function' 
-        obj = self.parent.obj 
-        meth = getattr(obj, name, None)
-        if meth is not None: 
-            return meth(self.obj) 
-
 #
 # triggering specific outcomes while executing Items
 #
-def skip(msg="unknown reason"):
+class BaseReason(object):
+    def __init__(self, msg="unknown reason", **kwds):
+        self.msg = msg
+        self.__dict__.update(kwds)
+
+    def __repr__(self):
+        return self.msg
+
+class Broken(BaseReason):
+    def __repr__(self):
+        return "Broken: %s" % (self.msg,)
+
+class _NotImplemented(BaseReason):
+    def __repr__(self):
+        return "Not implemented: %s" % (self.msg,)
+
+# whatever comes here....
+
+def skip(msg=BaseReason()):
     """ skip with the given Message. """
     __tracebackhide__ = True
     raise Skipped(msg=msg) 

Modified: py/branch/bugfix-0.9.0/py/test/outcome.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/test/outcome.py	(original)
+++ py/branch/bugfix-0.9.0/py/test/outcome.py	Mon Mar 17 20:18:48 2008
@@ -9,7 +9,7 @@
 
     def __repr__(self):
         if self.msg: 
-            return self.msg 
+            return repr(self.msg) 
         return "<%s instance>" %(self.__class__.__name__,)
     __str__ = __repr__
 

Modified: py/branch/bugfix-0.9.0/py/test/representation.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/test/representation.py	(original)
+++ py/branch/bugfix-0.9.0/py/test/representation.py	Mon Mar 17 20:18:48 2008
@@ -60,8 +60,11 @@
             s = str(source.getstatement(len(source)-1))
         except KeyboardInterrupt: 
             raise 
-        except: 
-            s = str(source[-1])
+        except:
+            try:
+                s = str(source[-1])
+            except IndexError:
+                s = "<Cannot get source>"
         indent = " " * (4 + (len(s) - len(s.lstrip())))
         # get the real exception information out 
         lines = excinfo.exconly(tryshort=True).split('\n')

Modified: py/branch/bugfix-0.9.0/py/test/rsession/executor.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/test/rsession/executor.py	(original)
+++ py/branch/bugfix-0.9.0/py/test/rsession/executor.py	Mon Mar 17 20:18:48 2008
@@ -40,7 +40,7 @@
             raise
         except:
             e = sys.exc_info()[1]
-            if isinstance(e, Failed):
+            if isinstance(e, Failed) and e.excinfo:
                 excinfo = e.excinfo
             else:
                 excinfo = py.code.ExceptionInfo()

Modified: py/branch/bugfix-0.9.0/py/test/rsession/reporter.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/test/rsession/reporter.py	(original)
+++ py/branch/bugfix-0.9.0/py/test/rsession/reporter.py	Mon Mar 17 20:18:48 2008
@@ -14,6 +14,7 @@
 from py.__.test.representation import Presenter
 
 import sys
+import thread
 
 class AbstractReporter(object):
     def __init__(self, config, hosts):
@@ -280,6 +281,8 @@
     def report_FailedTryiter(self, event):
         self.out.line("FAILED TO LOAD MODULE: %s\n" % "/".join(event.item.listnames()))
         self.failed_tests_outcome.append(event)
+        # argh! bad hack, need to fix it
+        self.failed[self.hosts[0]] += 1
     
     def report_SkippedTryiter(self, event):
         self.out.line("Skipped (%s) %s\n" % (str(event.excinfo.value), "/".
@@ -301,6 +304,7 @@
         #self.show_item(event.item, False)
         self.out.write("- FAILED TO LOAD MODULE")
         self.failed_tests_outcome.append(event)
+        self.failed[self.hosts[0]] += 1
     
     def report_ReceivedItemOutcome(self, event):
         host = self.hosts[0]

Modified: py/branch/bugfix-0.9.0/py/test/rsession/testing/test_executor.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/test/rsession/testing/test_executor.py	(original)
+++ py/branch/bugfix-0.9.0/py/test/rsession/testing/test_executor.py	Mon Mar 17 20:18:48 2008
@@ -37,6 +37,10 @@
     def run(self):
         raise Failed(excinfo="3")
 
+class ItemTestFailingExplicitEmpty(Item):
+    def run(self):
+        py.test.raises(ValueError, lambda : 123)
+
 class TestExecutor(BasicRsessionTest):
     def test_run_executor(self):
         ex = RunExecutor(ItemTestPassing("pass", self.config), config=self.config)
@@ -158,3 +162,10 @@
         outcome = ex.execute()
         assert not outcome.passed
         assert outcome.excinfo == "3"
+
+    def test_executor_explicit_Faile_no_excinfo(self):
+        ex = RunExecutor(ItemTestFailingExplicitEmpty("failexx", self.config),
+                         config=self.config)
+        outcome = ex.execute()
+        assert not outcome.passed
+

Modified: py/branch/bugfix-0.9.0/py/test/rsession/testing/test_reporter.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/test/rsession/testing/test_reporter.py	(original)
+++ py/branch/bugfix-0.9.0/py/test/rsession/testing/test_reporter.py	Mon Mar 17 20:18:48 2008
@@ -18,7 +18,6 @@
 
 
 import py, os
-#py.test.skip("in progress")
 from py.__.test.rsession.rsession import LocalReporter, AbstractSession,\
     RemoteReporter
 from py.__.test.rsession import repevent
@@ -135,11 +134,13 @@
             r.report(repevent.RsyncFinished())
             list(rootcol._tryiter(reporterror=lambda x : AbstractSession.reporterror(r.report, x)))
             r.report(repevent.TestFinished())
+            return r
         
         cap = py.io.StdCaptureFD()
-        boxfun()
+        r = boxfun()
         out, err = cap.reset()
         assert not err
+        assert out.find("1 failed in") != -1
         assert out.find("NameError: name 'sadsadsa' is not defined") != -1
 
     def _test_still_to_go(self):
@@ -171,23 +172,19 @@
     reporter = LocalReporter
     
     def test_report_received_item_outcome(self):
-        #py.test.skip("XXX rewrite test to not rely on exact formatting")
         assert self.report_received_item_outcome() == 'FsF.'
 
     def test_module(self):
-        #py.test.skip("XXX rewrite test to not rely on exact formatting")
         output = self._test_module()
         assert output.find("test_one") != -1
         assert output.endswith("FsF."), output
     
     def test_full_module(self):
-        #py.test.skip("XXX rewrite test to not rely on exact formatting")
         received = self._test_full_module()
-        expected = """
-repmod/test_one.py[1] 
-repmod/test_three.py[0] - FAILED TO LOAD MODULE
-repmod/test_two.py[0] - skipped (reason)"""
-        assert received.find(expected) != -1 
+        expected_lst = ["repmod/test_one.py", "FAILED TO LOAD MODULE",
+                        "skipped", "reason"]
+        for i in expected_lst:
+            assert received.find(i) != -1
 
 class TestRemoteReporter(AbstractTestReporter):
     reporter = RemoteReporter
@@ -196,28 +193,24 @@
         self._test_still_to_go()
 
     def test_report_received_item_outcome(self):
-        py.test.skip("XXX rewrite test to not rely on exact formatting")
         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.find(expected) != -1
+        expected_lst = ["localhost", "FAILED",
+                        "funcpass", "test_one",
+                        "SKIPPED",
+                        "PASSED"]
+        for expected in expected_lst:
+            assert val.find(expected) != -1
     
     def test_module(self):
-        py.test.skip("XXX rewrite test to not rely on exact formatting")
         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.find(expected) != -1
+        expected_lst = ["localhost", "FAILED",
+                        "funcpass", "test_one",
+                        "SKIPPED",
+                        "PASSED"]
+        for expected in expected_lst:
+            assert val.find(expected) != -1
     
     def test_full_module(self):
-        #py.test.skip("XXX rewrite test to not rely on exact formatting")
         val = self._test_full_module()
-        assert val.find('FAILED TO LOAD MODULE: repmod/test_three.py\n'\
-        '\nSkipped (reason) repmod/test_two.py') != -1
+        assert val.find("FAILED TO LOAD MODULE: repmod/test_three.py\n"\
+        "\nSkipped ('reason') repmod/test_two.py") != -1

Modified: py/branch/bugfix-0.9.0/py/test/rsession/testing/test_rsession.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/test/rsession/testing/test_rsession.py	(original)
+++ py/branch/bugfix-0.9.0/py/test/rsession/testing/test_rsession.py	Mon Mar 17 20:18:48 2008
@@ -29,7 +29,7 @@
     rootcol = py.test.collect.Directory(tmpdir)
     data = list(rootcol._tryiter(reporterror=events.append))
     assert len(events) == 2
-    assert str(events[1][0].value) == "Reason"
+    assert str(events[1][0].value).find("Reason") != -1
 
 class TestRSessionRemote(DirSetup, BasicRsessionTest): 
     def test_example_distribution_minus_x(self):

Modified: py/branch/bugfix-0.9.0/py/test/rsession/testing/test_web.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/test/rsession/testing/test_web.py	(original)
+++ py/branch/bugfix-0.9.0/py/test/rsession/testing/test_web.py	Mon Mar 17 20:18:48 2008
@@ -24,7 +24,7 @@
     from py.__.test.rsession import webjs
     from py.__.test.rsession.web import FUNCTION_LIST, IMPORTED_PYPY
     
-    source = rpython2javascript(webjs, FUNCTION_LIST)
+    source = rpython2javascript(webjs, FUNCTION_LIST, use_pdb=False)
     assert source
 
 def test_parse_args():

Modified: py/branch/bugfix-0.9.0/py/test/rsession/web.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/test/rsession/web.py	(original)
+++ py/branch/bugfix-0.9.0/py/test/rsession/web.py	Mon Mar 17 20:18:48 2008
@@ -24,11 +24,10 @@
     "show_host", "hide_host", "hide_messagebox", "opt_scroll"]
 
 try:
-    from pypy.rpython.ootypesystem.bltregistry import MethodDesc, BasicExternal,\
-                                                      described
+    from pypy.rpython.ootypesystem.bltregistry import MethodDesc, BasicExternal
     from pypy.translator.js.main import rpython2javascript
     from pypy.translator.js import commproxy
-    from pypy.rpython.extfunc import _callable
+    from pypy.translator.js.lib.support import callback
 
     commproxy.USE_MOCHIKIT = False
     IMPORTED_PYPY = True
@@ -36,14 +35,11 @@
     class BasicExternal(object):
         pass
 
-    def described(*args, **kwargs):
+    def callback(*args, **kwargs):
         def decorator(func):
             return func
         return decorator
 
-    def _callable(*args, **kwargs):
-        pass
-
     IMPORTED_PYPY = False
 
 def add_item(event):
@@ -153,22 +149,19 @@
         for host in self.hosts:
             to_send[host.hostid] = host.hostname
         return to_send
-    show_hosts = described(retval={str:str}, args=[('callback',
-            _callable([{str:str}]))])(show_hosts)
+    show_hosts = callback(retval={str:str})(show_hosts)
     
     def show_skip(self, item_name="aa"):
         return {'item_name': item_name,
                            'reason': self.skip_reasons[item_name]}
-    show_skip = described(retval={str:str}, args=[('item_name',str),('callback',
-            _callable([{str:str}]))])(show_skip)
+    show_skip = callback(retval={str:str})(show_skip)
     
     def show_fail(self, item_name="aa"):
         return {'item_name':item_name,
                            'traceback':str(self.fail_reasons[item_name]),
                            'stdout':self.stdout[item_name],
                            'stderr':self.stderr[item_name]}
-    show_fail = described(retval={str:str}, args=[('item_name',str),('callback',
-            _callable([{str:str}]))])(show_fail)
+    show_fail = callback(retval={str:str})(show_fail)
     
     _sessids = None
     _sesslock = py.std.thread.allocate_lock()
@@ -186,8 +179,7 @@
         finally:
             self._sesslock.release()
         return sessid
-    show_sessid = described(retval=str, args=[('callback',
-            _callable([str]))])(show_sessid)
+    show_sessid = callback(retval=str)(show_sessid)
     
     def failed(self, **kwargs):
         if not 'sessid' in kwargs:
@@ -201,14 +193,13 @@
             del self._sessids[to_del]
         self.pending_events._del(kwargs['sessid'])
     
-    def show_all_statuses(self, sessid=-1):
+    def show_all_statuses(self, sessid='xx'):
         retlist = [self.show_status_change(sessid)]
         while not self.pending_events.empty_queue(sessid):
             retlist.append(self.show_status_change(sessid))
         retval = retlist
         return retval
-    show_all_statuses = described(retval=[{str:str}],args=
-         [('sessid',str), ('callback',_callable([[{str:str}]]))])(show_all_statuses)
+    show_all_statuses = callback(retval=[{str:str}])(show_all_statuses)
         
     def show_status_change(self, sessid):
         event = self.pending_events.get(sessid)
@@ -276,7 +267,7 @@
     
     def repr_source(self, relline, source):
         lines = []
-        for num, line in enumerate(source.split("\n")):
+        for num, line in enumerate(str(source).split("\n")):
             if num == relline:
                 lines.append(">>>>" + line)
             else:
@@ -286,6 +277,14 @@
     def report_ReceivedItemOutcome(self, event):
         self.all += 1
         self.pending_events.put(event)
+
+    def report_FailedTryiter(self, event):
+        fullitemname = "/".join(event.item.listnames())
+        self.fail_reasons[fullitemname] = self.repr_failure_tblong(
+            event.item, event.excinfo, event.excinfo.traceback)
+        self.stdout[fullitemname] = ''
+        self.stderr[fullitemname] = ''
+        self.pending_events.put(event)
     
     def report_ItemStart(self, event):
         if isinstance(event.item, py.test.collect.Module):
@@ -400,7 +399,8 @@
     def run_jssource(self):
         js_name = py.path.local(__file__).dirpath("webdata").join("source.js")
         web_name = py.path.local(__file__).dirpath().join("webjs.py")
-        if IMPORTED_PYPY and web_name.mtime() > js_name.mtime():
+        if IMPORTED_PYPY and web_name.mtime() > js_name.mtime() or \
+            (not js_name.check()) or 1:
             from py.__.test.rsession import webjs
 
             javascript_source = rpython2javascript(webjs,

Modified: py/branch/bugfix-0.9.0/py/test/rsession/webdata/index.html
==============================================================================
--- py/branch/bugfix-0.9.0/py/test/rsession/webdata/index.html	(original)
+++ py/branch/bugfix-0.9.0/py/test/rsession/webdata/index.html	Mon Mar 17 20:18:48 2008
@@ -20,6 +20,10 @@
         z-index: 2;
       }
 
+      div.main {
+        margin-right: 170px;
+      }
+
       .error {
         color: #F00;
         font-weight: bold;
@@ -32,6 +36,7 @@
         background-color: #ffd;
         border: 1px solid #003;
         z-index: 1;
+        width: 160px;
       }
 
       #navbar tr, #navbar td {

Modified: py/branch/bugfix-0.9.0/py/test/rsession/webdata/source.js
==============================================================================
Binary files. No diff available.

Modified: py/branch/bugfix-0.9.0/py/test/rsession/webjs.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/test/rsession/webjs.py	(original)
+++ py/branch/bugfix-0.9.0/py/test/rsession/webjs.py	Mon Mar 17 20:18:48 2008
@@ -156,7 +156,7 @@
         td.appendChild(link)
         exported_methods.show_fail(item_name, fail_come_back)
         
-    if counters[msg['fullmodulename']] % MAX_COUNTER == 0:
+    if counters[msg['fullmodulename']] == 0:
         tr = create_elem("tr")
         module_part.appendChild(tr)
 
@@ -212,10 +212,16 @@
             return True
         tr = create_elem("tr")
         td = create_elem("td")
+        a = create_elem("a")
+        a.setAttribute("href", "javascript:show_traceback('%s')" % (
+                        msg['fullitemname'],))
         txt = create_text_elem("- FAILED TO LOAD MODULE")
-        td.appendChild(txt)
+        a.appendChild(txt)
+        td.appendChild(a)
         tr.appendChild(td)
         module_part.appendChild(tr)
+        item_name = msg['fullitemname']
+        exported_methods.show_fail(item_name, fail_come_back)
     elif msg['type'] == 'SkippedTryiter':
         module_part = get_elem(msg['fullitemname'])
         if not module_part:

Modified: py/branch/bugfix-0.9.0/py/test/session.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/test/session.py	(original)
+++ py/branch/bugfix-0.9.0/py/test/session.py	Mon Mar 17 20:18:48 2008
@@ -67,6 +67,7 @@
                 self.footer(colitems) 
         except Exit, ex:
             pass
+        return self.getitemoutcomepairs(Failed)
 
     def runtraced(self, colitem):
         if self.shouldclose(): 

Modified: py/branch/bugfix-0.9.0/py/test/testing/test_session.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/test/testing/test_session.py	(original)
+++ py/branch/bugfix-0.9.0/py/test/testing/test_session.py	Mon Mar 17 20:18:48 2008
@@ -314,3 +314,24 @@
         expected_output = '\nE   ' + line_to_report + '\n'
         print 'Looking for:', expected_output
         assert expected_output in out
+
+        
+def test_skip_reasons():
+    tmp = py.test.ensuretemp("check_skip_reasons")
+    tmp.ensure("test_one.py").write(py.code.Source("""
+        import py
+        def test_1():
+            py.test.skip(py.test.broken('stuff'))
+        
+        def test_2():
+            py.test.skip(py.test.notimplemented('stuff'))
+    """))
+    tmp.ensure("__init__.py")
+    config = py.test.config._reparse([tmp])
+    session = config.initsession()
+    session.main()
+    skips = session.getitemoutcomepairs(Skipped)
+    assert len(skips) == 2
+    assert repr(skips[0][1]) == 'Broken: stuff'
+    assert repr(skips[1][1]) == 'Not implemented: stuff'
+    

Modified: py/branch/bugfix-0.9.0/py/test/testing/test_setup_nested.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/test/testing/test_setup_nested.py	(original)
+++ py/branch/bugfix-0.9.0/py/test/testing/test_setup_nested.py	Mon Mar 17 20:18:48 2008
@@ -42,14 +42,23 @@
 
 class TestSetupTeardownOnInstance(TestSimpleClassSetup):
     def setup_method(self, method):
-        self.clslevel.append(17)
+        self.clslevel.append(method.__name__)
 
     def teardown_method(self, method):
         x = self.clslevel.pop()
-        assert x == 17
+        assert x == method.__name__
 
     def test_setup(self):
-        assert self.clslevel[-1] == 17
+        assert self.clslevel[-1] == 'test_setup'
+
+    def test_generate(self):
+        assert self.clslevel[-1] == 'test_generate'
+        yield self.generated, 5
+        assert self.clslevel[-1] == 'test_generate'
+
+    def generated(self, value):
+        assert value == 5
+        assert self.clslevel[-1] == 'test_generate'
 
 def test_teardown_method_worked(): 
     assert not TestSetupTeardownOnInstance.clslevel 

Modified: py/branch/bugfix-0.9.0/py/tool/utestconvert.py
==============================================================================
--- py/branch/bugfix-0.9.0/py/tool/utestconvert.py	(original)
+++ py/branch/bugfix-0.9.0/py/tool/utestconvert.py	Mon Mar 17 20:18:48 2008
@@ -225,7 +225,7 @@
 
     if not args:
         s = ''
-        for block in blocksplitter(sys.stdin.read()):
+        for block in blocksplitter(sys.stdin):
             s += rewrite_utest(block)
         sys.stdout.write(s)
 


More information about the py-svn mailing list