[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