[py-svn] r51535 - in py/branch/guido-svn-auth/py: . path/svn path/svn/testing

guido at codespeak.net guido at codespeak.net
Fri Feb 15 15:09:38 CET 2008


Author: guido
Date: Fri Feb 15 15:09:38 2008
New Revision: 51535

Modified:
   py/branch/guido-svn-auth/py/conftest.py
   py/branch/guido-svn-auth/py/path/svn/auth.txt
   py/branch/guido-svn-auth/py/path/svn/svncommon.py
   py/branch/guido-svn-auth/py/path/svn/testing/test_auth.py
   py/branch/guido-svn-auth/py/path/svn/wccommand.py
Log:
Added 'functional' argument to SvnAuth, renamed 'auth_cache' to 'cache_auth' to
make it clearer it's a Boolean, not some cache object, fixed some small bugs in
the path methods, added functional test.


Modified: py/branch/guido-svn-auth/py/conftest.py
==============================================================================
--- py/branch/guido-svn-auth/py/conftest.py	(original)
+++ py/branch/guido-svn-auth/py/conftest.py	Fri Feb 15 15:09:38 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/guido-svn-auth/py/path/svn/auth.txt
==============================================================================
--- py/branch/guido-svn-auth/py/path/svn/auth.txt	(original)
+++ py/branch/guido-svn-auth/py/path/svn/auth.txt	Fri Feb 15 15:09:38 2008
@@ -1,9 +1,9 @@
 SVN authentication support
 ==========================
 
-This document describes authentication support to both py.path.svnwc and
+This document describes authentication support for both py.path.svnwc and
 py.path.svnurl (yet in its implemention phase). This allows using the library
-in a completely automated situation, without having to provide svn credentials
+in a completely automated fashion, without having to provide svn credentials
 interactively.
 
 Current implementation
@@ -15,8 +15,8 @@
 join(), ensure(), etc.
 
 To pass credentials to path objects, an SvnAuth class needs to be created to
-hold them. This is then passed to the constructor or methods, usually as
-the 'auth' keyword argument.
+hold them. This is then passed to the constructor or methods as the 'auth'
+keyword argument.
 
 It is configurable whether the credentials are stored on disk. Storing them is
 useful in certain situations (executive access to the repository will not
@@ -26,12 +26,20 @@
 be controlled by passing a False value for the 'cache_auth' argument to
 SvnAuth.
 
+Also it is configurable what behaviour is displayed when the credentials do not
+validate: if a keyword argument to the SvnAuth constructor called 'interactive'
+has a True value (which is currently the default (XXX I think this should be
+changed!)), an interactive prompt is displayed - this is useful for terminal
+applications where you want to have an interactive fallback. When this has a
+False value, an exception is raised (XXX define the exception properly).
+
 Code examples
 -------------
 
 So, tying this together, code using this feature would look something like::
 
-  >>> auth = py.path.SvnAuth('user', 'pass', cache_auth=False)
+  >>> auth = py.path.SvnAuth('user', 'pass', cache_auth=False,
+  ...                        interactive=False)
   >>> wc = py.path.svnwc(url, auth=auth)
 
 Open issues
@@ -49,21 +57,6 @@
   Current idea: ignore this and let the client handle (so no passing auth
   around to the children).
 
-* Functional testing
-
-  Functional testing is relatively hard, and will not work on all systems. It
-  looks like a setup using 'svnserve' is possible, but it will be slow and
-  provide relatively little advantages, apart from testing the integration.
-  Holger suggested that perhaps functional tests could be skipped in favour
-  of only unit tests.
-
-* Non-interactive sessions
-
-  I still think having a way to tell the system we don't want the session to
-  be interactive would be very useful... It is unacceptable for certain types
-  of applications to block on user input. Should we have 'interactive' as an
-  argument to the methods/constructor, with a default value of True?
-
 * Affected methods
 
   - switch
@@ -74,3 +67,4 @@
   - diff (when using older revisions?)
   - commit
   - log
+  - status (for locking, etc.?)

Modified: py/branch/guido-svn-auth/py/path/svn/svncommon.py
==============================================================================
--- py/branch/guido-svn-auth/py/path/svn/svncommon.py	(original)
+++ py/branch/guido-svn-auth/py/path/svn/svncommon.py	Fri Feb 15 15:09:38 2008
@@ -330,21 +330,27 @@
         fspath = '%s at HEAD' % (fspath,)
     return 'file://%s' % (fspath,)
 
-
 class SvnAuth(object):
     """ container for auth information for Subversion """
-    def __init__(self, username, password, auth_cache=True):
+    def __init__(self, username, password, cache_auth=True, interactive=True):
         self.username = username
         self.password = password
-        self.auth_cache = auth_cache
+        self.cache_auth = cache_auth
+        self.interactive = interactive
 
-    def makecmdoptions(self): 
+    def makecmdoptions(self):
         uname = self.username.replace('"', '\\"')
         passwd = self.password.replace('"', '\\"')
-        ret = '--username="%s" --password="%s"' % (uname, passwd)
-        if not self.auth_cache:
-            ret += ' --no-auth-cache'
-        return ret
+        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,)

Modified: py/branch/guido-svn-auth/py/path/svn/testing/test_auth.py
==============================================================================
--- py/branch/guido-svn-auth/py/path/svn/testing/test_auth.py	(original)
+++ py/branch/guido-svn-auth/py/path/svn/testing/test_auth.py	Fri Feb 15 15:09:38 2008
@@ -1,5 +1,57 @@
 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 = 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):
@@ -16,9 +68,21 @@
         auth = py.path.SvnAuth('fo"o', '"ba\'r"')
         assert auth.makecmdoptions() == '--username="fo\\"o" --password="\\"ba\'r\\""'
 
-    def test_makecmdoptions_no_auth_cache(self):
-        auth = py.path.SvnAuth('foo', 'bar', auth_cache=False)
-        assert auth.makecmdoptions() == '--username="foo" --password="bar" --no-auth-cache'
+    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):
@@ -45,7 +109,7 @@
 
     def test_checkout_no_cache_auth(self):
         wc = svnwc_no_svn('foo')
-        auth = SvnAuth('user', 'pass', auth_cache=False)
+        auth = SvnAuth('user', 'pass', cache_auth=False)
         wc.checkout('url', auth=auth)
         assert wc.commands == [('co', 'url',
                                 ('--username="user" --password="pass" '
@@ -57,3 +121,169 @@
         wc.checkout('url')
         assert wc.commands == [('co', 'url',
                                 '--username="user" --password="pass"')]
+
+class TestSvnAuthFunctional(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,))
+        self.repopath = py.path.local(str(self.repo)[7:])
+        self.temppath = py.test.ensuretemp('TestSvnAuthFunctional.%s' % (
+                                           func_name))
+
+    def test_checkout_constructor_arg(self):
+        port, pid = self._start_svnserve()
+        try:
+            auth = py.path.SvnAuth('johnny', 'foo', cache_auth=False)
+            wc = py.path.svnwc(self.temppath, auth=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:
+            auth = py.path.SvnAuth('johnny', 'foo', cache_auth=False)
+            wc = py.path.svnwc(self.temppath)
+            wc.checkout(
+                'svn://localhost:%s/%s' % (port, self.repopath.basename),
+                auth=auth)
+            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:
+            auth = py.path.SvnAuth('johnny', 'foo', cache_auth=False,
+                                   interactive=False)
+            wc = py.path.svnwc(self.temppath, auth)
+            wc.checkout(
+                'svn://localhost:%s/%s' % (port, self.repopath.basename))
+            foo = wc.ensure('foo.txt')
+            wc.commit('added foo.txt')
+            log = foo.log(auth=auth)
+            assert len(log) == 1
+            assert log[0].msg == 'added foo.txt'
+        finally:
+            killproc(pid)
+
+    def test_switch(self):
+        port, pid = self._start_svnserve()
+        try:
+            auth = py.path.SvnAuth('johnny', 'foo', cache_auth=False,
+                                   interactive=False)
+            wc = py.path.svnwc(self.temppath, auth=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', auth=auth)
+            assert bar.join('foo.txt')
+        finally:
+            killproc(pid)
+
+    def test_update(self):
+        port, pid = self._start_svnserve()
+        try:
+            auth = py.path.SvnAuth('johnny', 'foo', cache_auth=False,
+                                   interactive=False)
+            wc1 = py.path.svnwc(self.temppath.ensure('wc1', dir=True),
+                                auth=auth)
+            wc2 = py.path.svnwc(self.temppath.ensure('wc2', dir=True),
+                                auth=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)
+            py.test.raises(Exception, 'wc2.update(auth=auth)')
+        finally:
+            killproc(pid)
+
+    def test_lock_unlock_status(self):
+        port, pid = self._start_svnserve()
+        try:
+            auth = py.path.SvnAuth('johnny', 'foo', cache_auth=False,
+                                   interactive=False)
+            wc = py.path.svnwc(self.temppath, auth=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(auth=auth)
+            status = foo.status(auth=auth)
+            assert status.locked
+            foo.unlock(auth=auth)
+            status = foo.status(auth=auth)
+            assert not status.locked
+
+            auth = py.path.SvnAuth('unknown', 'unknown', interactive=False)
+            py.test.raises(Exception, 'foo.lock(auth=auth)')
+            py.test.raises(Exception, 'foo.unlock(auth=auth)')
+        finally:
+            killproc(pid)
+
+    def test_diff(self):
+        port, pid = self._start_svnserve()
+        try:
+            auth = py.path.SvnAuth('johnny', 'foo', cache_auth=False,
+                                   interactive=False)
+            wc = py.path.svnwc(self.temppath, auth=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', auth=auth)
+            diff = foo.diff()
+            assert not diff
+            diff = foo.diff(rev=rev, auth=auth)
+            assert '\n+bar\n' in diff
+
+            auth = py.path.SvnAuth('unknown', 'unknown', interactive=False)
+            py.test.raises(Exception, 'foo.diff(rev=rev, auth=auth)')
+        finally:
+            killproc(pid)
+
+    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))

Modified: py/branch/guido-svn-auth/py/path/svn/wccommand.py
==============================================================================
--- py/branch/guido-svn-auth/py/path/svn/wccommand.py	(original)
+++ py/branch/guido-svn-auth/py/path/svn/wccommand.py	Fri Feb 15 15:09:38 2008
@@ -116,7 +116,7 @@
 
     def switch(self, url, auth=None):
         """ switch to given URL. """
-        self._authsvn('switch', url, [], auth=auth)
+        self._authsvn('switch', [url], auth=auth)
 
     def checkout(self, url=None, rev=None, auth=None):
         """ checkout from url to local wcpath. """
@@ -235,7 +235,7 @@
         except:
             pass
 
-    def status(self, updates=0, rec=0, externals=0):
+    def status(self, updates=0, rec=0, externals=0, auth=None):
         """ return (collective) Status object for this file. """
         # http://svnbook.red-bean.com/book.html#svn-ch-3-sect-4.3.1
         #             2201     2192        jum   test
@@ -262,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, auth=auth)
         rootstatus = WCStatus(self)
         for line in out.split('\n'):
             if not line.strip():
@@ -351,7 +352,7 @@
         args = []
         if rev is not None:
             args.append("-r %d" % rev)
-        out = self._authsvn('diff', args, auth=auth) 
+        out = self._authsvn('diff', args, auth=auth)
         return out
 
     def blame(self):
@@ -550,7 +551,7 @@
         verbose_opt = verbose and "-v" or ""
         locale_env = svncommon.fixlocale()
         # some blather on stderr
-        auth_opt = self._makeauthargs(auth) 
+        auth_opt = self._makeauthoptions(auth)
         stdin, stdout, stderr  = os.popen3(locale_env +
                                            'svn log --xml %s %s %s "%s"' % (
                                             rev_opt, verbose_opt, auth_opt,



More information about the pytest-commit mailing list