From guido at codespeak.net Sat Mar 1 11:00:07 2008 From: guido at codespeak.net (guido at codespeak.net) Date: Sat, 1 Mar 2008 11:00:07 +0100 (CET) Subject: [py-svn] r51978 - in py/branch/guido-svn-auth/py/path/svn: . testing Message-ID: <20080301100007.32BA8168405@codespeak.net> Author: guido Date: Sat Mar 1 11:00:07 2008 New Revision: 51978 Modified: py/branch/guido-svn-auth/py/path/svn/testing/test_auth.py py/branch/guido-svn-auth/py/path/svn/wccommand.py Log: Refactored the tests a bit to avoid redundancy, made that the svnwc api is similar to the svnurl one (so both now have a property 'auth' that allows overriding auth on a per-object basis, rather than having to pass the SvnAuth instance as an argument to all methods that hit the server). 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 Sat Mar 1 11:00:07 2008 @@ -93,34 +93,32 @@ 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 = SvnAuth('user', 'pass') - wc.checkout('url', auth=auth) - assert wc.commands == [('co', 'url', - '--username="user" --password="pass"')] + 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 = SvnAuth('user', 'pass') - wc.commit('msg', auth=auth) - assert wc.commands == [('commit -m "msg" --force-log', - '--username="user" --password="pass"')] + 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 = SvnAuth('user', 'pass', cache_auth=False) - wc.checkout('url', auth=auth) - assert wc.commands == [('co', 'url', - ('--username="user" --password="pass" ' - '--no-auth-cache'))] + 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): - auth = SvnAuth('user', 'pass') - wc = svnwc_no_svn('foo', auth=auth) + wc = svnwc_no_svn('foo', auth=self.auth) wc.checkout('url') - assert wc.commands == [('co', 'url', - '--username="user" --password="pass"')] + assert wc.commands[0][-1] == ('--username="user" --password="pass" ' + '--no-auth-cache') class svnurl_no_svn(py.path.svnurl): cmdexec_output = 'test' @@ -137,44 +135,40 @@ 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 - auth = SvnAuth('foo', 'bar') - u = svnurl_no_svn('http://foo.bar/svn', auth=auth) - assert u.auth is auth + u = svnurl_no_svn('http://foo.bar/svn', auth=self.auth) + assert u.auth is self.auth def test_new(self): - auth = SvnAuth('foo', 'bar') - u = svnurl_no_svn('http://foo.bar/svn/foo', auth=auth) + u = svnurl_no_svn('http://foo.bar/svn/foo', auth=self.auth) new = u.new(basename='bar') - assert new.auth is auth + assert new.auth is self.auth assert new.url == 'http://foo.bar/svn/bar' def test_join(self): - auth = SvnAuth('foo', 'bar') - u = svnurl_no_svn('http://foo.bar/svn', auth=auth) + u = svnurl_no_svn('http://foo.bar/svn', auth=self.auth) new = u.join('foo') - assert new.auth is auth + assert new.auth is self.auth assert new.url == 'http://foo.bar/svn/foo' def test_listdir(self): - auth = SvnAuth('foo', 'bar') - u = svnurl_no_svn('http://foo.bar/svn', auth=auth) + 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 auth - assert paths[1].auth is auth + assert paths[0].auth is self.auth + assert paths[1].auth is self.auth assert paths[0].basename == 'LICENSE.txt' def test_info(self): - auth = SvnAuth('foo', 'bar') - u = svnurl_no_svn('http://foo.bar/svn/LICENSE.txt', auth=auth) + u = svnurl_no_svn('http://foo.bar/svn/LICENSE.txt', auth=self.auth) def dirpath(self): return self u.cmdexec_output = '''\ @@ -190,67 +184,47 @@ assert info.size == 1529 def test_open(self): - auth = SvnAuth('foo', 'bar') - u = svnurl_no_svn('http://foo.bar/svn', auth=auth) + 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 foo.commands[0].endswith('svn cat "http://foo.bar/svn/foo" ' - '--username="foo" --password="bar"') + assert '--username="foo" --password="bar"' in foo.commands[0] def test_dirpath(self): - auth = SvnAuth('foo', 'bar') - u = svnurl_no_svn('http://foo.bar/svn/foo', auth=auth) + u = svnurl_no_svn('http://foo.bar/svn/foo', auth=self.auth) parent = u.dirpath() - assert parent.auth is auth + assert parent.auth is self.auth def test_mkdir(self): - auth = SvnAuth('foo', 'bar') - u = svnurl_no_svn('http://foo.bar/svn', auth=auth) + u = svnurl_no_svn('http://foo.bar/svn', auth=self.auth) u.mkdir('foo', msg='created dir foo') - assert u.commands[0].endswith('svn mkdir "-m" "created dir foo" ' - '"http://foo.bar/svn/foo" ' - '--username="foo" --password="bar"') + assert '--username="foo" --password="bar"' in u.commands[0] def test_copy(self): - auth = SvnAuth('foo', 'bar') - u = svnurl_no_svn('http://foo.bar/svn', auth=auth) + 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 u.commands[0].endswith('svn copy -m "copied dir" ' - '"http://foo.bar/svn" ' - '"http://foo.bar/svn2" ' - '--username="foo" --password="bar"') + assert '--username="foo" --password="bar"' in u.commands[0] def test_rename(self): - auth = SvnAuth('foo', 'bar') - u = svnurl_no_svn('http://foo.bar/svn/foo', auth=auth) + u = svnurl_no_svn('http://foo.bar/svn/foo', auth=self.auth) u.rename('http://foo.bar/svn/bar', 'moved foo to bar') - assert u.commands[0].endswith('svn move -m "moved foo to bar" --force ' - '"http://foo.bar/svn/foo" ' - '"http://foo.bar/svn/bar" ' - '--username="foo" --password="bar"') + assert '--username="foo" --password="bar"' in u.commands[0] def test_remove(self): - auth = SvnAuth('foo', 'bar') - u = svnurl_no_svn('http://foo.bar/svn/foo', auth=auth) + u = svnurl_no_svn('http://foo.bar/svn/foo', auth=self.auth) u.remove(msg='removing foo') - assert u.commands[0].endswith('svn rm -m "removing foo" ' - '"http://foo.bar/svn/foo" ' - '--username="foo" --password="bar"') + assert '--username="foo" --password="bar"' in u.commands[0] def test_export(self): - auth = SvnAuth('foo', 'bar') - u = svnurl_no_svn('http://foo.bar/svn', auth=auth) + u = svnurl_no_svn('http://foo.bar/svn', auth=self.auth) target = py.path.local('/foo') u.export(target) - assert u.commands[0].endswith('svn export "http://foo.bar/svn" "/foo" ' - '--username="foo" --password="bar"') + assert '--username="foo" --password="bar"' in u.commands[0] def test_log(self): - auth = SvnAuth('foo', 'bar') - u = svnurl_no_svn('http://foo.bar/svn/foo', auth=auth) + u = svnurl_no_svn('http://foo.bar/svn/foo', auth=self.auth) u.popen_output = py.std.StringIO.StringIO('''\ @@ -264,20 +238,15 @@ ''') u.check = lambda *args, **kwargs: True ret = u.log(10, 20, verbose=True) - assert u.commands[0].endswith('svn log --xml -r 10:20 -v ' - '"http://foo.bar/svn/foo" ' - '--username="foo" --password="bar"') + 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): - auth = SvnAuth('foo', 'bar') - u = svnurl_no_svn('http://foo.bar/svn', auth=auth) + u = svnurl_no_svn('http://foo.bar/svn', auth=self.auth) u.propget('foo') - assert u.commands[0].endswith('svn propget "foo" ' - '"http://foo.bar/svn" ' - '--username="foo" --password="bar"') + assert '--username="foo" --password="bar"' in u.commands[0] class SvnAuthFunctionalTestBase(object): def setup_class(cls): @@ -292,6 +261,8 @@ self.repopath = py.path.local(str(self.repo)[7:]) 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')}) @@ -304,8 +275,7 @@ 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 = py.path.svnwc(self.temppath, auth=self.auth) wc.checkout( 'svn://localhost:%s/%s' % (port, self.repopath.basename)) assert wc.join('.svn').check() @@ -317,11 +287,9 @@ 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 = py.path.svnwc(self.temppath, auth=self.auth) wc.checkout( - 'svn://localhost:%s/%s' % (port, self.repopath.basename), - auth=auth) + 'svn://localhost:%s/%s' % (port, self.repopath.basename)) assert wc.join('.svn').check() finally: killproc(pid) @@ -341,14 +309,12 @@ 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 = 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(auth=auth) + log = foo.log() assert len(log) == 1 assert log[0].msg == 'added foo.txt' finally: @@ -357,9 +323,7 @@ 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) + 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') @@ -367,7 +331,7 @@ wc.ensure('bar', dir=True) wc.commit('added bar dir') bar = wc.join('bar') - bar.switch(svnurl + '/foo', auth=auth) + bar.switch(svnurl + '/foo') assert bar.join('foo.txt') finally: killproc(pid) @@ -375,12 +339,10 @@ 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) + auth=self.auth) wc2 = py.path.svnwc(self.temppath.ensure('wc2', dir=True), - auth=auth) + auth=self.auth) wc1.checkout( 'svn://localhost:%s/%s' % (port, self.repopath.basename)) wc2.checkout( @@ -391,40 +353,38 @@ assert wc2.join('foo').check() auth = py.path.SvnAuth('unknown', 'unknown', interactive=False) - py.test.raises(Exception, 'wc2.update(auth=auth)') + wc2.auth = auth + py.test.raises(Exception, 'wc2.update()') 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 = 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(auth=auth) - status = foo.status(auth=auth) + foo.lock() + status = foo.status() assert status.locked - foo.unlock(auth=auth) - status = foo.status(auth=auth) + foo.unlock() + status = foo.status() 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)') + 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: - auth = py.path.SvnAuth('johnny', 'foo', cache_auth=False, - interactive=False) - wc = py.path.svnwc(self.temppath, auth=auth) + wc = py.path.svnwc(self.temppath, auth=self.auth) wc.checkout( 'svn://localhost:%s/%s' % (port, self.repopath.basename,)) wc.ensure('foo', file=True) @@ -435,14 +395,15 @@ foo.write('bar') diff = foo.diff() assert '\n+bar\n' in diff - foo.commit('added some content', auth=auth) + foo.commit('added some content') diff = foo.diff() assert not diff - diff = foo.diff(rev=rev, auth=auth) + diff = foo.diff(rev=rev) assert '\n+bar\n' in diff auth = py.path.SvnAuth('unknown', 'unknown', interactive=False) - py.test.raises(Exception, 'foo.diff(rev=rev, auth=auth)') + foo.auth = auth + py.test.raises(Exception, 'foo.diff(rev=rev)') finally: killproc(pid) @@ -450,15 +411,13 @@ def test_listdir(self): port, pid = self._start_svnserve() try: - auth = SvnAuth('johnny', 'foo', cache_auth=False, - interactive=False) u = py.path.svnurl( 'svn://localhost:%s/%s' % (port, self.repopath.basename), - auth=auth) + auth=self.auth) u.ensure('foo') paths = u.listdir() assert len(paths) == 1 - assert paths[0].auth is auth + assert paths[0].auth is self.auth auth = SvnAuth('foo', 'bar', interactive=False) u = py.path.svnurl( @@ -471,16 +430,14 @@ def test_copy(self): port, pid = self._start_svnserve() try: - auth = SvnAuth('johnny', 'foo', cache_auth=False, - interactive=False) u = py.path.svnurl( 'svn://localhost:%s/%s' % (port, self.repopath.basename), - auth=auth) + auth=self.auth) foo = u.ensure('foo') bar = u.join('bar') foo.copy(bar) assert bar.check() - assert bar.auth is auth + assert bar.auth is self.auth auth = SvnAuth('foo', 'bar', interactive=False) u = py.path.svnurl( @@ -495,11 +452,9 @@ def test_write_read(self): port, pid = self._start_svnserve() try: - auth = SvnAuth('johnny', 'foo', cache_auth=False, - interactive=False) u = py.path.svnurl( 'svn://localhost:%s/%s' % (port, self.repopath.basename), - auth=auth) + auth=self.auth) foo = u.ensure('foo') fp = foo.open() try: 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 Sat Mar 1 11:00:07 2008 @@ -35,7 +35,7 @@ svncommon.ALLOWED_CHARS): raise ValueError("bad char in wcpath %s" % (wcpath, )) self.localpath = py.path.local(wcpath) - self._auth = auth + self.auth = auth return self strpath = property(lambda x: str(x.localpath), None, None, "string path") @@ -64,24 +64,21 @@ 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, auth): - if auth is None: - auth = self._auth - if auth is None: + def _makeauthoptions(self): + if self.auth is None: return '' - return auth.makecmdoptions() + return self.auth.makecmdoptions() - def _authsvn(self, cmd, args=None, auth=None): - args = args and list(args) or [] - args.append(self._makeauthoptions(auth)) - return self._svn(cmd, *args) + 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] @@ -114,11 +111,11 @@ raise return out - def switch(self, url, auth=None): + def switch(self, url): """ switch to given URL. """ - self._authsvn('switch', [url], auth=auth) + self._authsvn('switch', [url]) - def checkout(self, url=None, rev=None, auth=None): + def checkout(self, url=None, rev=None): """ checkout from url to local wcpath. """ args = [] if url is None: @@ -133,11 +130,11 @@ else: args.append('-r' + str(rev)) args.append(url) - self._authsvn('co', args, auth=auth) + self._authsvn('co', args) - def update(self, rev = 'HEAD', auth=None): + def update(self, rev = 'HEAD'): """ update working copy item to given revision. (None -> HEAD). """ - self._authsvn('up', ['-r', rev], auth=auth) + self._authsvn('up', ['-r', rev]) def write(self, content, mode='wb'): """ write content into local filesystem wc. """ @@ -145,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() @@ -213,16 +210,16 @@ _rex_status = re.compile(r'\s+(\d+|-)\s+(\S+)\s+(\S+)\s+(.*)') - def lock(self, auth=None): + def lock(self): """ set a lock (exclusive) on the resource """ - out = self._authsvn('lock', auth=auth).strip() + out = self._authsvn('lock').strip() if not out: # warning or error, raise exception raise Exception(out[4:]) - def unlock(self, auth=None): + def unlock(self): """ unset a previously set lock """ - out = self._authsvn('unlock', auth=auth).strip() + out = self._authsvn('unlock').strip() if out.startswith('svn:'): # warning or error, raise exception raise Exception(out[4:]) @@ -235,7 +232,7 @@ except: pass - def status(self, updates=0, rec=0, externals=0, auth=None): + def status(self, updates=0, rec=0, externals=0): """ 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 @@ -263,7 +260,7 @@ update_rev = None cmd = 'status -v %s %s %s' % (updates, rec, externals) - out = self._authsvn(cmd, auth=auth) + out = self._authsvn(cmd) rootstatus = WCStatus(self) for line in out.split('\n'): if not line.strip(): @@ -281,7 +278,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) @@ -345,14 +343,14 @@ continue return rootstatus - def diff(self, rev=None, auth=None): + def diff(self, rev=None): """ return a diff of the current path against revision rev (defaulting to the last one). """ args = [] if rev is not None: args.append("-r %d" % rev) - out = self._authsvn('diff', args, auth=auth) + out = self._authsvn('diff', args) return out def blame(self): @@ -373,14 +371,14 @@ return result _rex_commit = re.compile(r'.*Committed revision (\d+)\.$', re.DOTALL) - def commit(self, msg='', rec=1, auth=None): + 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, auth=auth) + out = self._authsvn(cmd) try: del cache.info[self] except KeyError: @@ -446,7 +444,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 @@ -455,7 +453,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. """ @@ -498,7 +496,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: @@ -533,7 +531,7 @@ else: return True - def log(self, rev_start=None, rev_end=1, verbose=False, auth=None): + def log(self, rev_start=None, rev_end=1, verbose=False): """ return a list of LogEntry instances for this path. rev_start is the starting revision (defaulting to the first one). rev_end is the last revision (defaulting to HEAD). @@ -551,7 +549,7 @@ verbose_opt = verbose and "-v" or "" locale_env = svncommon.fixlocale() # some blather on stderr - auth_opt = self._makeauthoptions(auth) + auth_opt = self._makeauthoptions() stdin, stdout, stderr = os.popen3(locale_env + 'svn log --xml %s %s %s "%s"' % ( rev_opt, verbose_opt, auth_opt, @@ -579,7 +577,7 @@ return self.info().mtime def __hash__(self): - return hash((self.strpath, self.__class__)) + return hash((self.strpath, self.__class__, self.auth)) class WCStatus: From guido at codespeak.net Sat Mar 1 13:04:13 2008 From: guido at codespeak.net (guido at codespeak.net) Date: Sat, 1 Mar 2008 13:04:13 +0100 (CET) Subject: [py-svn] r51988 - in py/branch/guido-svn-auth/py/path/svn: . testing Message-ID: <20080301120413.A1EA416846A@codespeak.net> Author: guido Date: Sat Mar 1 13:04:13 2008 New Revision: 51988 Modified: py/branch/guido-svn-auth/py/path/svn/testing/test_wccommand.py py/branch/guido-svn-auth/py/path/svn/urlcommand.py py/branch/guido-svn-auth/py/path/svn/wccommand.py Log: fixed some win32 specific issues caused by usernames containing spaces Modified: py/branch/guido-svn-auth/py/path/svn/testing/test_wccommand.py ============================================================================== --- py/branch/guido-svn-auth/py/path/svn/testing/test_wccommand.py (original) +++ py/branch/guido-svn-auth/py/path/svn/testing/test_wccommand.py Sat Mar 1 13:04:13 2008 @@ -4,10 +4,19 @@ 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") +try: + import win32api +except ImportError: + def normpath(p): + return p +else: + import os + def normpath(p): + p = win32api.GetShortPathName(p) + return os.path.normpath(os.path.normcase(p)) class TestWCSvnCommandPath(CommonSvnTests): @@ -253,7 +262,7 @@ try: locked = root.status().locked assert len(locked) == 1 - assert str(locked[0]) == str(somefile) + assert normpath(str(locked[0])) == normpath(str(somefile)) #assert somefile.locked() py.test.raises(Exception, 'somefile.lock()') finally: Modified: py/branch/guido-svn-auth/py/path/svn/urlcommand.py ============================================================================== --- py/branch/guido-svn-auth/py/path/svn/urlcommand.py (original) +++ py/branch/guido-svn-auth/py/path/svn/urlcommand.py Sat Mar 1 13:04:13 2008 @@ -284,7 +284,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\d+) +(?P\S+) +(0? *(?P\d+))? ' + r'^ *(?P\d+) +(?P.+?) +(0? *(?P\d+))? ' '*(?P\w+ +\d{2} +[\d:]+) +(?P.*)$') def __init__(self, line): # this is a typical line from 'svn ls http://...' 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 Sat Mar 1 13:04:13 2008 @@ -208,7 +208,7 @@ """ 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+(.*)') + _rex_status = re.compile(r'\s+(\d+|-)\s+(\S+)\s+(.+?)\s{2,}(.*)') def lock(self): """ set a lock (exclusive) on the resource """ From guido at codespeak.net Sat Mar 1 13:10:28 2008 From: guido at codespeak.net (guido at codespeak.net) Date: Sat, 1 Mar 2008 13:10:28 +0100 (CET) Subject: [py-svn] r51989 - py/branch/guido-svn-auth/py/path/svn Message-ID: <20080301121028.8C34C168452@codespeak.net> Author: guido Date: Sat Mar 1 13:10:28 2008 New Revision: 51989 Modified: py/branch/guido-svn-auth/py/path/svn/wccommand.py Log: Added comment about one of the modified regular expressions (having to do with usernames with spaces on win32). 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 Sat Mar 1 13:10:28 2008 @@ -208,6 +208,9 @@ """ rename this path to target. """ py.process.cmdexec("svn move --force %s %s" %(str(self), str(target))) + # 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): From guido at codespeak.net Sat Mar 1 13:59:12 2008 From: guido at codespeak.net (guido at codespeak.net) Date: Sat, 1 Mar 2008 13:59:12 +0100 (CET) Subject: [py-svn] r51993 - py/branch/guido-svn-auth/py/path/svn/testing Message-ID: <20080301125912.D033E168437@codespeak.net> Author: guido Date: Sat Mar 1 13:59:06 2008 New Revision: 51993 Modified: py/branch/guido-svn-auth/py/path/svn/testing/test_auth.py Log: fixed stupid URL to path conversion issue on win32 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 Sat Mar 1 13:59:06 2008 @@ -258,7 +258,11 @@ 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:]) + 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, From guido at codespeak.net Sat Mar 1 14:00:00 2008 From: guido at codespeak.net (guido at codespeak.net) Date: Sat, 1 Mar 2008 14:00:00 +0100 (CET) Subject: [py-svn] r51994 - py/branch/guido-svn-auth/py/misc/testing Message-ID: <20080301130000.D64E916847F@codespeak.net> Author: guido Date: Sat Mar 1 13:59:59 2008 New Revision: 51994 Modified: py/branch/guido-svn-auth/py/misc/testing/test_oskill.py Log: disabled killproc tests on win32 versions that don't have the taskkill binary Modified: py/branch/guido-svn-auth/py/misc/testing/test_oskill.py ============================================================================== --- py/branch/guido-svn-auth/py/misc/testing/test_oskill.py (original) +++ py/branch/guido-svn-auth/py/misc/testing/test_oskill.py Sat Mar 1 13:59:59 2008 @@ -4,6 +4,10 @@ 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)") From guido at codespeak.net Sat Mar 1 14:10:27 2008 From: guido at codespeak.net (guido at codespeak.net) Date: Sat, 1 Mar 2008 14:10:27 +0100 (CET) Subject: [py-svn] r51997 - py/branch/guido-svn-auth/py/path/svn/testing Message-ID: <20080301131027.DFB0C1684C5@codespeak.net> Author: guido Date: Sat Mar 1 14:10:26 2008 New Revision: 51997 Modified: py/branch/guido-svn-auth/py/path/svn/testing/test_wccommand.py Log: made that tests that depend on win32all are skipped if the lib isn't installed Modified: py/branch/guido-svn-auth/py/path/svn/testing/test_wccommand.py ============================================================================== --- py/branch/guido-svn-auth/py/path/svn/testing/test_wccommand.py (original) +++ py/branch/guido-svn-auth/py/path/svn/testing/test_wccommand.py Sat Mar 1 14:10:26 2008 @@ -1,4 +1,5 @@ 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 @@ -7,16 +8,20 @@ if py.path.local.sysfind('svn') is None: py.test.skip("cannot test py.path.svn, 'svn' binary not found") -try: - import win32api -except ImportError: +if sys.platform != 'win32': def normpath(p): return p else: - import os - def normpath(p): - p = win32api.GetShortPathName(p) - return os.path.normpath(os.path.normcase(p)) + 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): From guido at codespeak.net Sat Mar 1 14:27:30 2008 From: guido at codespeak.net (guido at codespeak.net) Date: Sat, 1 Mar 2008 14:27:30 +0100 (CET) Subject: [py-svn] r51999 - py/branch/guido-svn-auth/py/path/svn Message-ID: <20080301132730.7086E1684C2@codespeak.net> Author: guido Date: Sat Mar 1 14:27:30 2008 New Revision: 51999 Modified: py/branch/guido-svn-auth/py/path/svn/urlcommand.py Log: Removing a bit of redundancy. Modified: py/branch/guido-svn-auth/py/path/svn/urlcommand.py ============================================================================== --- py/branch/guido-svn-auth/py/path/svn/urlcommand.py (original) +++ py/branch/guido-svn-auth/py/path/svn/urlcommand.py Sat Mar 1 14:27:30 2008 @@ -97,6 +97,10 @@ 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" @@ -127,8 +131,7 @@ commit_msg=kwargs.get('msg', "mkdir by py lib invocation") createpath = self.join(*args) createpath._svnwrite('mkdir', '-m', commit_msg) - auth = self.auth and self.auth.makecmdoptions() or None - self._lsnorevcache.delentry((createpath.dirpath().strpath, auth)) + self._norev_delentry(createpath.dirpath()) return createpath def copy(self, target, msg='copied by py lib invocation'): @@ -137,8 +140,7 @@ raise py.error.EINVAL(target, "revisions are immutable") self._svncmdexecauth('svn copy -m "%s" "%s" "%s"' %(msg, self._escape(self), self._escape(target))) - auth = self.auth and self.auth.makecmdoptions() or None - self._lsnorevcache.delentry((target.dirpath().strpath, auth)) + self._norev_delentry(target.dirpath()) def rename(self, target, msg="renamed by py lib invocation"): """ rename this path to target with checkin message msg. """ @@ -146,9 +148,8 @@ raise py.error.EINVAL(self, "revisions are immutable") self._svncmdexecauth('svn move -m "%s" --force "%s" "%s"' %( msg, self._escape(self), self._escape(target))) - auth = self.auth and self.auth.makecmdoptions() or None - self._lsnorevcache.delentry((self.dirpath().strpath, auth)) - self._lsnorevcache.delentry((self.strpath, auth)) + 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 @@ -156,8 +157,7 @@ if self.rev is not None: raise py.error.EINVAL(self, "revisions are immutable") self._svncmdexecauth('svn rm -m "%s" "%s"' %(msg, self._escape(self))) - auth = self.auth and self.auth.makecmdoptions() or None - self._lsnorevcache.delentry((self.dirpath().strpath, auth)) + self._norev_delentry(self.dirpath()) def export(self, topath): """ export to a local path @@ -201,8 +201,7 @@ self._escape(tempdir.join(basename)), x.join(basename)._encodedurl()) self._svncmdexecauth(cmd) - auth = self.auth and self.auth.makecmdoptions() or None - self._lsnorevcache.delentry((x.strpath, auth)) # !!! + self._norev_delentry(x) finally: tempdir.remove() return target From guido at codespeak.net Sat Mar 1 14:43:35 2008 From: guido at codespeak.net (guido at codespeak.net) Date: Sat, 1 Mar 2008 14:43:35 +0100 (CET) Subject: [py-svn] r52000 - in py/trunk/py: . misc/testing path/svn path/svn/testing Message-ID: <20080301134335.6FBA51684C2@codespeak.net> Author: guido Date: Sat Mar 1 14:43:33 2008 New Revision: 52000 Added: py/trunk/py/path/svn/auth.txt - copied unchanged from r51999, py/branch/guido-svn-auth/py/path/svn/auth.txt py/trunk/py/path/svn/testing/test_auth.py - copied unchanged from r51999, py/branch/guido-svn-auth/py/path/svn/testing/test_auth.py Modified: py/trunk/py/__init__.py py/trunk/py/conftest.py py/trunk/py/misc/testing/test_oskill.py py/trunk/py/path/svn/svncommon.py py/trunk/py/path/svn/testing/test_wccommand.py py/trunk/py/path/svn/urlcommand.py py/trunk/py/path/svn/wccommand.py Log: Merging the 'guido-auth-svn' branch back into the trunk. This means there's a new class py.path.SvnAuth of which instances store user credentials and auth config, and can be passed to py.path.svnurl and py.path.svnwc objects to control SVN authentication behaviour. Modified: py/trunk/py/__init__.py ============================================================================== --- py/trunk/py/__init__.py (original) +++ py/trunk/py/__init__.py Sat Mar 1 14:43:33 2008 @@ -67,6 +67,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/trunk/py/conftest.py ============================================================================== --- py/trunk/py/conftest.py (original) +++ py/trunk/py/conftest.py Sat Mar 1 14:43:33 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/trunk/py/misc/testing/test_oskill.py ============================================================================== --- py/trunk/py/misc/testing/test_oskill.py (original) +++ py/trunk/py/misc/testing/test_oskill.py Sat Mar 1 14:43:33 2008 @@ -4,6 +4,10 @@ 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)") Modified: py/trunk/py/path/svn/svncommon.py ============================================================================== --- py/trunk/py/path/svn/svncommon.py (original) +++ py/trunk/py/path/svn/svncommon.py Sat Mar 1 14:43:33 2008 @@ -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 "" %(self.username,) Modified: py/trunk/py/path/svn/testing/test_wccommand.py ============================================================================== --- py/trunk/py/path/svn/testing/test_wccommand.py (original) +++ py/trunk/py/path/svn/testing/test_wccommand.py Sat Mar 1 14:43:33 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): @@ -253,7 +267,7 @@ try: locked = root.status().locked assert len(locked) == 1 - assert str(locked[0]) == str(somefile) + assert normpath(str(locked[0])) == normpath(str(somefile)) #assert somefile.locked() py.test.raises(Exception, 'somefile.lock()') finally: Modified: py/trunk/py/path/svn/urlcommand.py ============================================================================== --- py/trunk/py/path/svn/urlcommand.py (original) +++ py/trunk/py/path/svn/urlcommand.py Sat Mar 1 14:43:33 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,33 @@ 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 @@ -143,7 +170,7 @@ '"%s"' % (self._escape(topath),)] if self.rev is not None: args = ['-r', str(self.rev)] + args - process.cmdexec('svn export %s' % (' '.join(args),)) + self._svncmdexecauth('svn export %s' % (' '.join(args),)) return topath def ensure(self, *args, **kwargs): @@ -173,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) @@ -194,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) @@ -214,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. @@ -234,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 = [] @@ -254,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\d+) +(?P\S+) +(0? *(?P\d+))? ' + r'^ *(?P\d+) +(?P.+?) +(0? *(?P\d+))? ' '*(?P\w+ +\d{2} +[\d:]+) +(?P.*)$') def __init__(self, line): # this is a typical line from 'svn ls http://...' Modified: py/trunk/py/path/svn/wccommand.py ============================================================================== --- py/trunk/py/path/svn/wccommand.py (original) +++ py/trunk/py/path/svn/wccommand.py Sat Mar 1 14:43:33 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. """ @@ -119,11 +129,12 @@ url += "@%d" % rev else: args.append('-r' + str(rev)) - self._svn('co', url, *args) + 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() @@ -197,18 +208,21 @@ """ 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._svn('lock').strip() + 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._svn('unlock').strip() + out = self._authsvn('unlock').strip() if out.startswith('svn:'): # warning or error, raise exception raise Exception(out[4:]) @@ -248,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(): @@ -266,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) @@ -334,10 +350,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): @@ -365,7 +381,7 @@ cmd = 'commit -m "%s" --force-log' % (msg.replace('"', '\\"'),) if not rec: cmd += ' -N' - out = self._svn(cmd) + out = self._authsvn(cmd) try: del cache.info[self] except KeyError: @@ -431,7 +447,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 @@ -440,7 +456,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. """ @@ -483,7 +499,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: @@ -534,11 +550,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: @@ -562,7 +580,7 @@ return self.info().mtime def __hash__(self): - return hash((self.strpath, self.__class__)) + return hash((self.strpath, self.__class__, self.auth)) class WCStatus: From guido at codespeak.net Sat Mar 1 15:14:13 2008 From: guido at codespeak.net (guido at codespeak.net) Date: Sat, 1 Mar 2008 15:14:13 +0100 (CET) Subject: [py-svn] r52001 - in py/trunk/py: doc path/svn Message-ID: <20080301141413.291711684D5@codespeak.net> Author: guido Date: Sat Mar 1 15:14:11 2008 New Revision: 52001 Removed: py/trunk/py/path/svn/auth.txt Modified: py/trunk/py/doc/path.txt Log: Removed (outdated, and perhaps a bit too verbose) document about svn auth, in favour of a short note and code example in the existing path.txt doc in docs. Modified: py/trunk/py/doc/path.txt ============================================================================== --- py/trunk/py/doc/path.txt (original) +++ py/trunk/py/doc/path.txt Sat Mar 1 15:14:11 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 =================================== Deleted: /py/trunk/py/path/svn/auth.txt ============================================================================== --- /py/trunk/py/path/svn/auth.txt Sat Mar 1 15:14:11 2008 +++ (empty file) @@ -1,77 +0,0 @@ -SVN authentication support -========================== - -This document describes authentication support for both py.path.svnwc and -py.path.svnurl (yet in its implemention phase). This feature allows using the -library in a completely automated fashion, without having to provide svn -credentials interactively. - -Current implementation ----------------------- - -The credentials are passed to the constructor of the path objects, and are used -(transparently) for every action that accesses the server. Also, when provided, -they are passed recursively to all child objects created by methods such as -join(), ensure(), etc. (XXX currently only true for svnurl, not for svnwc) - -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 as the 'auth' -keyword argument. (XXX the latter currently only for svnwc, and preferrably -that needs to be removed in favour of an .auth attribute like in svnurl) - -It is configurable whether the credentials are stored on disk. Storing them is -useful in certain situations (executive access to the repository will not -require the credentials to be passed) but might not be desired in others - for -instance if a webserver runs more than one application, one does not want to -pollute the webserver's home directory (if it even has one). This behaviour can -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, - ... interactive=False) - >>> wcpath = py.path.svnwc(path, auth=auth) - >>> urlpath = py.path.svnurl(url, auth=auth) - -Open issues ------------ - -* How do we deal with externals properly? - - It looks like the svn command-line client uses the credentials provided for - all externals, if possible, and either prompts for the password in - interactive mode, or barfs when --non-interactive is passed. I think it makes - sense to copy its behaviour here, pass the credentials to any child svn path - objects (as discussed above), and either let the command-line app ask for - creds or throw an exception when 'interactive' is set to False (see above). - - Current idea: ignore this and let the client handle (so no passing auth - around to the children). - -* Affected methods for svnwc: - - - switch - - checkout - - update - - lock - - unlock - - diff (when using older revisions?) - - commit - - log - - status (for locking, etc.?) - -* Affected methods for svnurl: - - not appropriate - the auth is passed to the constructor rather or set to - path.auth rather than passed to all methods From py-svn at codespeak.net Sun Mar 9 04:22:44 2008 From: py-svn at codespeak.net (py-svn at codespeak.net) Date: Sun, 9 Mar 2008 04:22:44 +0100 (CET) Subject: [py-svn] MedHelp 18331 Message-ID: <20080309092155.4186.qmail@ppp89-110-63-84.pppoe.avangarddsl.ru> An HTML attachment was scrubbed... URL: http://codespeak.net/pipermail/py-svn/attachments/20080309/0efe8315/attachment.htm From py-svn at codespeak.net Tue Mar 11 12:09:17 2008 From: py-svn at codespeak.net (py-svn at codespeak.net) Date: Tue, 11 Mar 2008 12:09:17 +0100 (CET) Subject: [py-svn] MensHealth id 92774 Message-ID: <20080311130834.14051.qmail@cust-194-12.dsl.versateladsl.be> An HTML attachment was scrubbed... URL: http://codespeak.net/pipermail/py-svn/attachments/20080311/02b4b3af/attachment.htm From guido at codespeak.net Fri Mar 14 12:08:23 2008 From: guido at codespeak.net (guido at codespeak.net) Date: Fri, 14 Mar 2008 12:08:23 +0100 (CET) Subject: [py-svn] r52481 - py/trunk/py/apigen/testing Message-ID: <20080314110823.2C906169F6C@codespeak.net> Author: guido Date: Fri Mar 14 12:08:21 2008 New Revision: 52481 Modified: py/trunk/py/apigen/testing/test_apigen_functional.py Log: Python2.3 has no 'sorted()' yet. Modified: py/trunk/py/apigen/testing/test_apigen_functional.py ============================================================================== --- py/trunk/py/apigen/testing/test_apigen_functional.py (original) +++ py/trunk/py/apigen/testing/test_apigen_functional.py Fri Mar 14 12:08:21 2008 @@ -5,7 +5,7 @@ import py from py.__.apigen import apigen -py.test.skip("Apigen functionality temporarily disabled") +#py.test.skip("Apigen functionality temporarily disabled") def setup_module(mod): if py.std.sys.platform == "win32": @@ -117,7 +117,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'] From guido at codespeak.net Fri Mar 14 12:27:22 2008 From: guido at codespeak.net (guido at codespeak.net) Date: Fri, 14 Mar 2008 12:27:22 +0100 (CET) Subject: [py-svn] r52483 - py/trunk/py/apigen/testing Message-ID: <20080314112722.2C478169F6C@codespeak.net> Author: guido Date: Fri Mar 14 12:27:21 2008 New Revision: 52483 Modified: py/trunk/py/apigen/testing/test_apigen_functional.py Log: Skipping apigen test again. Modified: py/trunk/py/apigen/testing/test_apigen_functional.py ============================================================================== --- py/trunk/py/apigen/testing/test_apigen_functional.py (original) +++ py/trunk/py/apigen/testing/test_apigen_functional.py Fri Mar 14 12:27:21 2008 @@ -5,7 +5,7 @@ import py from py.__.apigen import apigen -#py.test.skip("Apigen functionality temporarily disabled") +py.test.skip("Apigen functionality temporarily disabled") def setup_module(mod): if py.std.sys.platform == "win32": From py-svn at codespeak.net Fri Mar 14 19:51:45 2008 From: py-svn at codespeak.net (py-svn at codespeak.net) Date: Fri, 14 Mar 2008 19:51:45 +0100 (CET) Subject: [py-svn] MensHealth id 269129 Message-ID: <20080314084854.9393.qmail@lor34-1-82-240-237-14.fbx.proxad.net> An HTML attachment was scrubbed... URL: http://codespeak.net/pipermail/py-svn/attachments/20080314/25516a66/attachment.htm From py-svn at codespeak.net Sat Mar 15 06:26:42 2008 From: py-svn at codespeak.net (Buyers Choice ® Official Site) Date: Sat, 15 Mar 2008 06:26:42 +0100 (CET) Subject: [py-svn] March %71 OFF Message-ID: <20080315112554.2514.qmail@ppp91-76-69-30.pppoe.mtu-net.ru> An HTML attachment was scrubbed... URL: http://codespeak.net/pipermail/py-svn/attachments/20080315/78a909ec/attachment.htm From guido at codespeak.net Mon Mar 17 20:18:50 2008 From: guido at codespeak.net (guido at codespeak.net) Date: Mon, 17 Mar 2008 20:18:50 +0100 (CET) Subject: [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 Message-ID: <20080317191850.E799C169E16@codespeak.net> 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 "" %(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('''\ + + + +guido +2008-02-11T12:12:18.476481Z +Creating branch to work on auth support for py.path.svn*. + + + +''') + 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:', r