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: 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: 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: 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: 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:', rev1 + baz = foo.join('baz').ensure(file=True) + baz.write('baz\n') + rev2 = foo.commit('testing more') + + exportpath = py.test.ensuretemp('test_export_rev_exportdir') + url = py.path.svnurl(repo + '/foo', rev=rev1) + foo1 = url.export(exportpath.join('foo1')) + assert foo1.check() + assert foo1.join('bar').check() + assert not foo1.join('baz').check() + + url = py.path.svnurl(repo + '/foo', rev=rev2) + foo2 = url.export(exportpath.join('foo2')) + assert foo2.check() + assert foo2.join('bar').check() + assert foo2.join('baz').check() + class TestSvnInfoCommand: def test_svn_1_2(self): Modified: py/branch/bugfix-0.9.0/py/path/svn/testing/test_wccommand.py ============================================================================== --- py/branch/bugfix-0.9.0/py/path/svn/testing/test_wccommand.py (original) +++ py/branch/bugfix-0.9.0/py/path/svn/testing/test_wccommand.py Mon Mar 17 20:18:48 2008 @@ -1,13 +1,27 @@ import py +import sys from py.__.path.svn.testing.svntestbase import CommonSvnTests, getrepowc from py.__.path.svn.wccommand import InfoSvnWCCommand from py.__.path.svn.wccommand import parse_wcinfotime from py.__.path.svn import svncommon - if py.path.local.sysfind('svn') is None: py.test.skip("cannot test py.path.svn, 'svn' binary not found") +if sys.platform != 'win32': + def normpath(p): + return p +else: + try: + import win32api + except ImportError: + def normpath(p): + py.test.skip('this test requires win32api to run on windows') + else: + import os + def normpath(p): + p = win32api.GetShortPathName(p) + return os.path.normpath(os.path.normcase(p)) class TestWCSvnCommandPath(CommonSvnTests): @@ -156,6 +170,15 @@ finally: notexisting.remove() + def test_nonversioned_remove(self): + assert self.root.check(versioned=1) + somefile = self.root.join('nonversioned/somefile') + nonwc = py.path.local(somefile) + nonwc.ensure() + assert somefile.check() + assert not somefile.check(versioned=True) + somefile.remove() # this used to fail because it tried to 'svn rm' + def test_properties(self): try: self.root.propset('gaga', 'this') @@ -217,6 +240,63 @@ p.remove(rec=1) f.remove() + def test_lock_unlock(self): + root = self.root + somefile = root.join('somefile') + somefile.ensure(file=True) + # not yet added to repo + py.test.raises(py.process.cmdexec.Error, 'somefile.lock()') + somefile.write('foo') + somefile.commit('test') + assert somefile.check(versioned=True) + somefile.lock() + try: + locked = root.status().locked + assert len(locked) == 1 + assert normpath(str(locked[0])) == normpath(str(somefile)) + #assert somefile.locked() + py.test.raises(Exception, 'somefile.lock()') + finally: + somefile.unlock() + #assert not somefile.locked() + locked = root.status().locked + assert locked == [] + py.test.raises(Exception, 'somefile,unlock()') + somefile.remove() + + def test_commit_nonrecursive(self): + root = self.root + somedir = root.join('sampledir') + somefile = somedir.join('otherfile') + somefile.write('foo') + somedir.propset('foo', 'bar') + status = somedir.status() + assert len(status.prop_modified) == 1 + assert len(status.modified) == 1 + + somedir.commit('non-recursive commit', rec=0) + status = somedir.status() + assert len(status.prop_modified) == 0 + assert len(status.modified) == 1 + + somedir.commit('recursive commit') + status = somedir.status() + assert len(status.prop_modified) == 0 + assert len(status.modified) == 0 + + def test_commit_return_value(self): + root = self.root + testfile = root.join('test.txt').ensure(file=True) + testfile.write('test') + rev = root.commit('testing') + assert type(rev) == int + + anotherfile = root.join('another.txt').ensure(file=True) + anotherfile.write('test') + rev2 = root.commit('testing more') + assert type(rev2) == int + assert rev2 == rev + 1 + #def test_log(self): # l = self.root.log() # assert len(l) == 3 # might need to be upped if more tests are added Modified: py/branch/bugfix-0.9.0/py/path/svn/urlcommand.py ============================================================================== --- py/branch/bugfix-0.9.0/py/path/svn/urlcommand.py (original) +++ py/branch/bugfix-0.9.0/py/path/svn/urlcommand.py Mon Mar 17 20:18:48 2008 @@ -21,10 +21,11 @@ _lsrevcache = BuildcostAccessCache(maxentries=128) _lsnorevcache = AgingCache(maxentries=1000, maxseconds=60.0) - def __new__(cls, path, rev=None): + def __new__(cls, path, rev=None, auth=None): self = object.__new__(cls) if isinstance(path, cls): rev = path.rev + auth = path.auth path = path.strpath proto, uri = path.split("://", 1) host, uripath = uri.split('/', 1) @@ -36,6 +37,7 @@ path = path.rstrip('/') self.strpath = path self.rev = rev + self.auth = auth return self def __repr__(self): @@ -44,7 +46,8 @@ else: return 'svnurl(%r, %r)' % (self.strpath, self.rev) - def _svn(self, cmd, *args): + def _svnwithrev(self, cmd, *args): + """ execute an svn command, append our own url and revision """ if self.rev is None: return self._svnwrite(cmd, *args) else: @@ -52,16 +55,28 @@ return self._svnwrite(cmd, *args) def _svnwrite(self, cmd, *args): + """ execute an svn command, append our own url """ l = ['svn %s' % cmd] args = ['"%s"' % self._escape(item) for item in args] l.extend(args) l.append('"%s"' % self._encodedurl()) # fixing the locale because we can't otherwise parse - string = svncommon.fixlocale() + " ".join(l) + string = " ".join(l) if DEBUG: print "execing", string + out = self._svncmdexecauth(string) + return out + + def _svncmdexecauth(self, cmd): + """ execute an svn command 'as is' """ + cmd = svncommon.fixlocale() + cmd + if self.auth is not None: + cmd += ' ' + self.auth.makecmdoptions() + return self._cmdexec(cmd) + + def _cmdexec(self, cmd): try: - out = process.cmdexec(string) + out = process.cmdexec(cmd) except py.process.cmdexec.Error, e: if (e.err.find('File Exists') != -1 or e.err.find('File already exists') != -1): @@ -69,21 +84,33 @@ raise return out + def _svnpopenauth(self, cmd): + """ execute an svn command, return a pipe for reading stdin """ + cmd = svncommon.fixlocale() + cmd + if self.auth is not None: + cmd += ' ' + self.auth.makecmdoptions() + return self._popen(cmd) + + def _popen(self, cmd): + return os.popen(cmd) + def _encodedurl(self): return self._escape(self.strpath) + def _norev_delentry(self, path): + auth = self.auth and self.auth.makecmdoptions() or None + self._lsnorevcache.delentry((str(path), auth)) + def open(self, mode='r'): """ return an opened file with the given mode. """ assert 'w' not in mode and 'a' not in mode, "XXX not implemented for svn cmdline" assert self.check(file=1) # svn cat returns an empty file otherwise - def popen(cmd): - return os.popen(cmd) if self.rev is None: - return popen(svncommon.fixlocale() + - 'svn cat "%s"' % (self._escape(self.strpath), )) + return self._svnpopenauth('svn cat "%s"' % ( + self._escape(self.strpath), )) else: - return popen(svncommon.fixlocale() + - 'svn cat -r %s "%s"' % (self.rev, self._escape(self.strpath))) + return self._svnpopenauth('svn cat -r %s "%s"' % ( + self.rev, self._escape(self.strpath))) def dirpath(self, *args, **kwargs): """ return the directory path of the current path joined @@ -104,33 +131,47 @@ commit_msg=kwargs.get('msg', "mkdir by py lib invocation") createpath = self.join(*args) createpath._svnwrite('mkdir', '-m', commit_msg) - self._lsnorevcache.delentry(createpath.dirpath().strpath) + self._norev_delentry(createpath.dirpath()) return createpath def copy(self, target, msg='copied by py lib invocation'): """ copy path to target with checkin message msg.""" if getattr(target, 'rev', None) is not None: raise py.error.EINVAL(target, "revisions are immutable") - process.cmdexec('svn copy -m "%s" "%s" "%s"' %(msg, - self._escape(self), self._escape(target))) - self._lsnorevcache.delentry(target.dirpath().strpath) + self._svncmdexecauth('svn copy -m "%s" "%s" "%s"' %(msg, + self._escape(self), self._escape(target))) + self._norev_delentry(target.dirpath()) def rename(self, target, msg="renamed by py lib invocation"): """ rename this path to target with checkin message msg. """ if getattr(self, 'rev', None) is not None: raise py.error.EINVAL(self, "revisions are immutable") - py.process.cmdexec('svn move -m "%s" --force "%s" "%s"' %( - msg, self._escape(self), self._escape(target))) - self._lsnorevcache.delentry(self.dirpath().strpath) - self._lsnorevcache.delentry(self.strpath) + self._svncmdexecauth('svn move -m "%s" --force "%s" "%s"' %( + msg, self._escape(self), self._escape(target))) + self._norev_delentry(self.dirpath()) + self._norev_delentry(self) def remove(self, rec=1, msg='removed by py lib invocation'): """ remove a file or directory (or a directory tree if rec=1) with checkin message msg.""" if self.rev is not None: raise py.error.EINVAL(self, "revisions are immutable") - process.cmdexec('svn rm -m "%s" "%s"' %(msg, self._escape(self))) - self._lsnorevcache.delentry(self.dirpath().strpath) + self._svncmdexecauth('svn rm -m "%s" "%s"' %(msg, self._escape(self))) + self._norev_delentry(self.dirpath()) + + def export(self, topath): + """ export to a local path + + topath should not exist prior to calling this, returns a + py.path.local instance + """ + topath = py.path.local(topath) + args = ['"%s"' % (self._escape(self),), + '"%s"' % (self._escape(topath),)] + if self.rev is not None: + args = ['-r', str(self.rev)] + args + self._svncmdexecauth('svn export %s' % (' '.join(args),)) + return topath def ensure(self, *args, **kwargs): """ ensure that an args-joined path exists (by default as @@ -159,19 +200,19 @@ "ensure %s" % self._escape(tocreate), self._escape(tempdir.join(basename)), x.join(basename)._encodedurl()) - process.cmdexec(cmd) - self._lsnorevcache.delentry(x.strpath) # !!! + self._svncmdexecauth(cmd) + self._norev_delentry(x) finally: tempdir.remove() return target # end of modifying methods def _propget(self, name): - res = self._svn('propget', name) + res = self._svnwithrev('propget', name) return res[:-1] # strip trailing newline def _proplist(self): - res = self._svn('proplist') + res = self._svnwithrev('proplist') lines = res.split('\n') lines = map(str.strip, lines[1:]) return svncommon.PropListDict(self, lines) @@ -180,7 +221,7 @@ """ return sequence of name-info directory entries of self """ def builder(): try: - res = self._svn('ls', '-v') + res = self._svnwithrev('ls', '-v') except process.cmdexec.Error, e: if e.err.find('non-existent in that revision') != -1: raise py.error.ENOENT(self, e.err) @@ -200,10 +241,13 @@ info = InfoSvnCommand(lsline) nameinfo_seq.append((info._name, info)) return nameinfo_seq + auth = self.auth and self.auth.makecmdoptions() or None if self.rev is not None: - return self._lsrevcache.getorbuild((self.strpath, self.rev), builder) + return self._lsrevcache.getorbuild((self.strpath, self.rev, auth), + builder) else: - return self._lsnorevcache.getorbuild(self.strpath, builder) + return self._lsnorevcache.getorbuild((self.strpath, auth), + builder) def log(self, rev_start=None, rev_end=1, verbose=False): """ return a list of LogEntry instances for this path. @@ -220,9 +264,8 @@ else: rev_opt = "-r %s:%s" % (rev_start, rev_end) verbose_opt = verbose and "-v" or "" - xmlpipe = os.popen(svncommon.fixlocale() + - 'svn log --xml %s %s "%s"' % - (rev_opt, verbose_opt, self.strpath)) + xmlpipe = self._svnpopenauth('svn log --xml %s %s "%s"' % + (rev_opt, verbose_opt, self.strpath)) from xml.dom import minidom tree = minidom.parse(xmlpipe) result = [] @@ -240,7 +283,7 @@ # the '0?' part in the middle is an indication of whether the resource is # locked, see 'svn help ls' lspattern = re.compile( - r'^ *(?P\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/bugfix-0.9.0/py/path/svn/wccommand.py ============================================================================== --- py/branch/bugfix-0.9.0/py/path/svn/wccommand.py (original) +++ py/branch/bugfix-0.9.0/py/path/svn/wccommand.py Mon Mar 17 20:18:48 2008 @@ -25,7 +25,7 @@ """ sep = os.sep - def __new__(cls, wcpath=None): + def __new__(cls, wcpath=None, auth=None): self = object.__new__(cls) if isinstance(wcpath, cls): if wcpath.__class__ == cls: @@ -35,6 +35,7 @@ svncommon.ALLOWED_CHARS): raise ValueError("bad char in wcpath %s" % (wcpath, )) self.localpath = py.path.local(wcpath) + self.auth = auth return self strpath = property(lambda x: str(x.localpath), None, None, "string path") @@ -63,13 +64,22 @@ info = self.info() return py.path.svnurl(info.url) - def __repr__(self): return "svnwc(%r)" % (self.strpath) # , self._url) def __str__(self): return str(self.localpath) + def _makeauthoptions(self): + if self.auth is None: + return '' + return self.auth.makecmdoptions() + + def _authsvn(self, cmd, args=None): + args = args and list(args) or [] + args.append(self._makeauthoptions()) + return self._svn(cmd, *args) + def _svn(self, cmd, *args): l = ['svn %s' % cmd] args = [self._escape(item) for item in args] @@ -101,9 +111,9 @@ raise return out - def switch(self, url): + def switch(self, url): """ switch to given URL. """ - self._svn('switch', url) + self._authsvn('switch', [url]) def checkout(self, url=None, rev=None): """ checkout from url to local wcpath. """ @@ -118,12 +128,13 @@ if svncommon._getsvnversion() == '1.3': url += "@%d" % rev else: - args.append('-r', str(rev)) - self._svn('co', url, *args) + args.append('-r' + str(rev)) + args.append(url) + self._authsvn('co', args) def update(self, rev = 'HEAD'): """ update working copy item to given revision. (None -> HEAD). """ - self._svn('up -r %s' % rev) + self._authsvn('up', ['-r', rev]) def write(self, content, mode='wb'): """ write content into local filesystem wc. """ @@ -131,7 +142,7 @@ def dirpath(self, *args): """ return the directory Path of the current Path. """ - return self.__class__(self.localpath.dirpath(*args)) + return self.__class__(self.localpath.dirpath(*args), auth=self.auth) def _ensuredirs(self): parent = self.dirpath() @@ -180,6 +191,10 @@ underlying svn semantics. """ assert rec, "svn cannot remove non-recursively" + if not self.check(versioned=True): + # not added to svn (anymore?), just remove + py.path.local(self).remove() + return flags = [] if force: flags.append('--force') @@ -193,7 +208,32 @@ """ rename this path to target. """ py.process.cmdexec("svn move --force %s %s" %(str(self), str(target))) - _rex_status = re.compile(r'\s+(\d+|-)\s+(\S+)\s+(\S+)\s+(.*)') + # XXX a bit scary to assume there's always 2 spaces between username and + # path, however with win32 allowing spaces in user names there doesn't + # seem to be a more solid approach :( + _rex_status = re.compile(r'\s+(\d+|-)\s+(\S+)\s+(.+?)\s{2,}(.*)') + + def lock(self): + """ set a lock (exclusive) on the resource """ + out = self._authsvn('lock').strip() + if not out: + # warning or error, raise exception + raise Exception(out[4:]) + + def unlock(self): + """ unset a previously set lock """ + out = self._authsvn('unlock').strip() + if out.startswith('svn:'): + # warning or error, raise exception + raise Exception(out[4:]) + + def cleanup(self): + """ remove any locks from the resource """ + # XXX should be fixed properly!!! + try: + self.unlock() + except: + pass def status(self, updates=0, rec=0, externals=0): """ return (collective) Status object for this file. """ @@ -222,7 +262,8 @@ update_rev = None - out = self._svn('status -v %s %s %s' % (updates, rec, externals)) + cmd = 'status -v %s %s %s' % (updates, rec, externals) + out = self._authsvn(cmd) rootstatus = WCStatus(self) for line in out.split('\n'): if not line.strip(): @@ -230,7 +271,7 @@ #print "processing %r" % line flags, rest = line[:8], line[8:] # first column - c0,c1,c2,c3,c4,x5,x6,c7 = flags + c0,c1,c2,c3,c4,c5,x6,c7 = flags #if '*' in line: # print "flags", repr(flags), "rest", repr(rest) @@ -240,7 +281,8 @@ wcpath = self.join(fn, abs=1) rootstatus.unknown.append(wcpath) elif c0 == 'X': - wcpath = self.__class__(self.localpath.join(fn, abs=1)) + wcpath = self.__class__(self.localpath.join(fn, abs=1), + auth=self.auth) rootstatus.external.append(wcpath) elif c0 == 'I': wcpath = self.join(fn, abs=1) @@ -287,7 +329,8 @@ if c1 == 'M': rootstatus.prop_modified.append(wcpath) - if c2 == 'L': + # XXX do we cover all client versions here? + if c2 == 'L' or c5 == 'K': rootstatus.locked.append(wcpath) if c7 == '*': rootstatus.update_available.append(wcpath) @@ -305,10 +348,10 @@ """ return a diff of the current path against revision rev (defaulting to the last one). """ - if rev is None: - out = self._svn('diff') - else: - out = self._svn('diff -r %d' % rev) + args = [] + if rev is not None: + args.append("-r %d" % rev) + out = self._authsvn('diff', args) return out def blame(self): @@ -329,11 +372,14 @@ return result _rex_commit = re.compile(r'.*Committed revision (\d+)\.$', re.DOTALL) - def commit(self, message=""): - """commit() returns None if there was nothing to commit - and the revision number of the commit otherwise. - """ - out = self._svn('commit -m "%s"' % message) + def commit(self, msg='', rec=1): + """ commit with support for non-recursive commits """ + from py.__.path.svn import cache + # XXX i guess escaping should be done better here?!? + cmd = 'commit -m "%s" --force-log' % (msg.replace('"', '\\"'),) + if not rec: + cmd += ' -N' + out = self._authsvn(cmd) try: del cache.info[self] except KeyError: @@ -399,7 +445,7 @@ localpath = self.localpath.new(**kw) else: localpath = self.localpath - return self.__class__(localpath) + return self.__class__(localpath, auth=self.auth) def join(self, *args, **kwargs): """ return a new Path (with the same revision) which is composed @@ -408,7 +454,7 @@ if not args: return self localpath = self.localpath.join(*args, **kwargs) - return self.__class__(localpath) + return self.__class__(localpath, auth=self.auth) def info(self, usecache=1): """ return an Info structure with svn-provided information. """ @@ -451,7 +497,7 @@ paths = [] for localpath in self.localpath.listdir(notsvn): - p = self.__class__(localpath) + p = self.__class__(localpath, auth=self.auth) paths.append(p) if fil or sort: @@ -502,11 +548,13 @@ else: rev_opt = "-r %s:%s" % (rev_start, rev_end) verbose_opt = verbose and "-v" or "" - s = svncommon.fixlocale() + locale_env = svncommon.fixlocale() # some blather on stderr - stdin, stdout, stderr = os.popen3(s + 'svn log --xml %s %s "%s"' % ( - rev_opt, verbose_opt, - self.strpath)) + auth_opt = self._makeauthoptions() + stdin, stdout, stderr = os.popen3(locale_env + + 'svn log --xml %s %s %s "%s"' % ( + rev_opt, verbose_opt, auth_opt, + self.strpath)) from xml.dom import minidom from xml.parsers.expat import ExpatError try: @@ -530,13 +578,13 @@ return self.info().mtime def __hash__(self): - return hash((self.strpath, self.__class__)) + return hash((self.strpath, self.__class__, self.auth)) class WCStatus: attrnames = ('modified','added', 'conflict', 'unchanged', 'external', 'deleted', 'prop_modified', 'unknown', 'update_available', - 'incomplete', 'kindmismatch', 'ignored' + 'incomplete', 'kindmismatch', 'ignored', 'locked' ) def __init__(self, wcpath, rev=None, modrev=None, author=None): Modified: py/branch/bugfix-0.9.0/py/test/collect.py ============================================================================== --- py/branch/bugfix-0.9.0/py/test/collect.py (original) +++ py/branch/bugfix-0.9.0/py/test/collect.py Mon Mar 17 20:18:48 2008 @@ -46,9 +46,6 @@ self.name = name self.parent = parent self._config = getattr(parent, '_config', py.test.config) - if parent is not None: - if hasattr(parent, 'config'): - py.test.pdb() self.fspath = getattr(parent, 'fspath', None) Module = configproperty('Module') @@ -289,7 +286,7 @@ name2items[name] = res return res -class PyCollectorMixin(object): +class PyCollectorMixin(Collector): def funcnamefilter(self, name): return name.startswith('test') def classnamefilter(self, name): @@ -333,7 +330,7 @@ raise except: self._name2items_exception = py.std.sys.exc_info() - raise + raise def run(self): self._prepare() @@ -350,7 +347,7 @@ def run(self): if getattr(self.obj, 'disabled', 0): return [] - return PyCollectorMixin.run(self) + return super(Module, self).run() def join(self, name): res = super(Module, self).join(name) @@ -442,7 +439,40 @@ Collector.Function.__get__(self)) # XXX for python 2.2 Function = property(Function) -class Generator(PyCollectorMixin, Collector): + +class FunctionMixin(object): + """ mixin for the code common to Function and Generator. + """ + def _getpathlineno(self): + code = py.code.Code(self.obj) + return code.path, code.firstlineno + + def _getsortvalue(self): + return self._getpathlineno() + + def setup(self): + """ perform setup for this test function. """ + if getattr(self.obj, 'im_self', None): + name = 'setup_method' + else: + name = 'setup_function' + obj = self.parent.obj + meth = getattr(obj, name, None) + if meth is not None: + return meth(self.obj) + + def teardown(self): + """ perform teardown for this test function. """ + if getattr(self.obj, 'im_self', None): + name = 'teardown_method' + else: + name = 'teardown_function' + obj = self.parent.obj + meth = getattr(obj, name, None) + if meth is not None: + return meth(self.obj) + +class Generator(FunctionMixin, PyCollectorMixin, Collector): def run(self): self._prepare() itemlist = self._name2items @@ -468,13 +498,6 @@ call, args = obj, () return call, args - def _getpathlineno(self): - code = py.code.Code(self.obj) - return code.path, code.firstlineno - - def _getsortvalue(self): - return self._getpathlineno() - class DoctestFile(PyCollectorMixin, FSCollector): def run(self): return [self.fspath.basename] Modified: py/branch/bugfix-0.9.0/py/test/item.py ============================================================================== --- py/branch/bugfix-0.9.0/py/test/item.py (original) +++ py/branch/bugfix-0.9.0/py/test/item.py Mon Mar 17 20:18:48 2008 @@ -2,6 +2,7 @@ from inspect import isclass, ismodule from py.__.test.outcome import Skipped, Failed, Passed +from py.__.test.collect import FunctionMixin _dummy = object() @@ -37,7 +38,7 @@ def finishcapture(self): self._config._finishcapture(self) -class Function(Item): +class Function(FunctionMixin, Item): """ a Function Item is responsible for setting up and executing a Python callable test object. """ @@ -52,10 +53,6 @@ def __repr__(self): return "<%s %r>" %(self.__class__.__name__, self.name) - def _getpathlineno(self): - code = py.code.Code(self.obj) - return code.path, code.firstlineno - def _getsortvalue(self): if self._sort_value is None: return self._getpathlineno() @@ -70,32 +67,28 @@ """ execute the given test function. """ target(*args) - def setup(self): - """ perform setup for this test function. """ - if getattr(self.obj, 'im_self', None): - name = 'setup_method' - else: - name = 'setup_function' - obj = self.parent.obj - meth = getattr(obj, name, None) - if meth is not None: - return meth(self.obj) - - def teardown(self): - """ perform teardown for this test function. """ - if getattr(self.obj, 'im_self', None): - name = 'teardown_method' - else: - name = 'teardown_function' - obj = self.parent.obj - meth = getattr(obj, name, None) - if meth is not None: - return meth(self.obj) - # # triggering specific outcomes while executing Items # -def skip(msg="unknown reason"): +class BaseReason(object): + def __init__(self, msg="unknown reason", **kwds): + self.msg = msg + self.__dict__.update(kwds) + + def __repr__(self): + return self.msg + +class Broken(BaseReason): + def __repr__(self): + return "Broken: %s" % (self.msg,) + +class _NotImplemented(BaseReason): + def __repr__(self): + return "Not implemented: %s" % (self.msg,) + +# whatever comes here.... + +def skip(msg=BaseReason()): """ skip with the given Message. """ __tracebackhide__ = True raise Skipped(msg=msg) Modified: py/branch/bugfix-0.9.0/py/test/outcome.py ============================================================================== --- py/branch/bugfix-0.9.0/py/test/outcome.py (original) +++ py/branch/bugfix-0.9.0/py/test/outcome.py Mon Mar 17 20:18:48 2008 @@ -9,7 +9,7 @@ def __repr__(self): if self.msg: - return self.msg + return repr(self.msg) return "<%s instance>" %(self.__class__.__name__,) __str__ = __repr__ Modified: py/branch/bugfix-0.9.0/py/test/representation.py ============================================================================== --- py/branch/bugfix-0.9.0/py/test/representation.py (original) +++ py/branch/bugfix-0.9.0/py/test/representation.py Mon Mar 17 20:18:48 2008 @@ -60,8 +60,11 @@ s = str(source.getstatement(len(source)-1)) except KeyboardInterrupt: raise - except: - s = str(source[-1]) + except: + try: + s = str(source[-1]) + except IndexError: + s = "" indent = " " * (4 + (len(s) - len(s.lstrip()))) # get the real exception information out lines = excinfo.exconly(tryshort=True).split('\n') Modified: py/branch/bugfix-0.9.0/py/test/rsession/executor.py ============================================================================== --- py/branch/bugfix-0.9.0/py/test/rsession/executor.py (original) +++ py/branch/bugfix-0.9.0/py/test/rsession/executor.py Mon Mar 17 20:18:48 2008 @@ -40,7 +40,7 @@ raise except: e = sys.exc_info()[1] - if isinstance(e, Failed): + if isinstance(e, Failed) and e.excinfo: excinfo = e.excinfo else: excinfo = py.code.ExceptionInfo() Modified: py/branch/bugfix-0.9.0/py/test/rsession/reporter.py ============================================================================== --- py/branch/bugfix-0.9.0/py/test/rsession/reporter.py (original) +++ py/branch/bugfix-0.9.0/py/test/rsession/reporter.py Mon Mar 17 20:18:48 2008 @@ -14,6 +14,7 @@ from py.__.test.representation import Presenter import sys +import thread class AbstractReporter(object): def __init__(self, config, hosts): @@ -280,6 +281,8 @@ def report_FailedTryiter(self, event): self.out.line("FAILED TO LOAD MODULE: %s\n" % "/".join(event.item.listnames())) self.failed_tests_outcome.append(event) + # argh! bad hack, need to fix it + self.failed[self.hosts[0]] += 1 def report_SkippedTryiter(self, event): self.out.line("Skipped (%s) %s\n" % (str(event.excinfo.value), "/". @@ -301,6 +304,7 @@ #self.show_item(event.item, False) self.out.write("- FAILED TO LOAD MODULE") self.failed_tests_outcome.append(event) + self.failed[self.hosts[0]] += 1 def report_ReceivedItemOutcome(self, event): host = self.hosts[0] Modified: py/branch/bugfix-0.9.0/py/test/rsession/testing/test_executor.py ============================================================================== --- py/branch/bugfix-0.9.0/py/test/rsession/testing/test_executor.py (original) +++ py/branch/bugfix-0.9.0/py/test/rsession/testing/test_executor.py Mon Mar 17 20:18:48 2008 @@ -37,6 +37,10 @@ def run(self): raise Failed(excinfo="3") +class ItemTestFailingExplicitEmpty(Item): + def run(self): + py.test.raises(ValueError, lambda : 123) + class TestExecutor(BasicRsessionTest): def test_run_executor(self): ex = RunExecutor(ItemTestPassing("pass", self.config), config=self.config) @@ -158,3 +162,10 @@ outcome = ex.execute() assert not outcome.passed assert outcome.excinfo == "3" + + def test_executor_explicit_Faile_no_excinfo(self): + ex = RunExecutor(ItemTestFailingExplicitEmpty("failexx", self.config), + config=self.config) + outcome = ex.execute() + assert not outcome.passed + Modified: py/branch/bugfix-0.9.0/py/test/rsession/testing/test_reporter.py ============================================================================== --- py/branch/bugfix-0.9.0/py/test/rsession/testing/test_reporter.py (original) +++ py/branch/bugfix-0.9.0/py/test/rsession/testing/test_reporter.py Mon Mar 17 20:18:48 2008 @@ -18,7 +18,6 @@ import py, os -#py.test.skip("in progress") from py.__.test.rsession.rsession import LocalReporter, AbstractSession,\ RemoteReporter from py.__.test.rsession import repevent @@ -135,11 +134,13 @@ r.report(repevent.RsyncFinished()) list(rootcol._tryiter(reporterror=lambda x : AbstractSession.reporterror(r.report, x))) r.report(repevent.TestFinished()) + return r cap = py.io.StdCaptureFD() - boxfun() + r = boxfun() out, err = cap.reset() assert not err + assert out.find("1 failed in") != -1 assert out.find("NameError: name 'sadsadsa' is not defined") != -1 def _test_still_to_go(self): @@ -171,23 +172,19 @@ reporter = LocalReporter def test_report_received_item_outcome(self): - #py.test.skip("XXX rewrite test to not rely on exact formatting") assert self.report_received_item_outcome() == 'FsF.' def test_module(self): - #py.test.skip("XXX rewrite test to not rely on exact formatting") output = self._test_module() assert output.find("test_one") != -1 assert output.endswith("FsF."), output def test_full_module(self): - #py.test.skip("XXX rewrite test to not rely on exact formatting") received = self._test_full_module() - expected = """ -repmod/test_one.py[1] -repmod/test_three.py[0] - FAILED TO LOAD MODULE -repmod/test_two.py[0] - skipped (reason)""" - assert received.find(expected) != -1 + expected_lst = ["repmod/test_one.py", "FAILED TO LOAD MODULE", + "skipped", "reason"] + for i in expected_lst: + assert received.find(i) != -1 class TestRemoteReporter(AbstractTestReporter): reporter = RemoteReporter @@ -196,28 +193,24 @@ self._test_still_to_go() def test_report_received_item_outcome(self): - py.test.skip("XXX rewrite test to not rely on exact formatting") val = self.report_received_item_outcome() - expected = """ localhost: FAILED py.test.rsession.testing.test_slave.py funcpass - localhost: SKIPPED py.test.rsession.testing.test_slave.py funcpass - localhost: FAILED py.test.rsession.testing.test_slave.py funcpass - localhost: PASSED py.test.rsession.testing.test_slave.py funcpass -""" - assert val.find(expected) != -1 + expected_lst = ["localhost", "FAILED", + "funcpass", "test_one", + "SKIPPED", + "PASSED"] + for expected in expected_lst: + assert val.find(expected) != -1 def test_module(self): - py.test.skip("XXX rewrite test to not rely on exact formatting") val = self._test_module() - print val - expected = """ localhost: FAILED py.test.rsession.testing.test_slave.py funcpass - localhost: SKIPPED py.test.rsession.testing.test_slave.py funcpass - localhost: FAILED py.test.rsession.testing.test_slave.py funcpass - localhost: PASSED py.test.rsession.testing.test_slave.py funcpass -""" - assert val.find(expected) != -1 + expected_lst = ["localhost", "FAILED", + "funcpass", "test_one", + "SKIPPED", + "PASSED"] + for expected in expected_lst: + assert val.find(expected) != -1 def test_full_module(self): - #py.test.skip("XXX rewrite test to not rely on exact formatting") val = self._test_full_module() - assert val.find('FAILED TO LOAD MODULE: repmod/test_three.py\n'\ - '\nSkipped (reason) repmod/test_two.py') != -1 + assert val.find("FAILED TO LOAD MODULE: repmod/test_three.py\n"\ + "\nSkipped ('reason') repmod/test_two.py") != -1 Modified: py/branch/bugfix-0.9.0/py/test/rsession/testing/test_rsession.py ============================================================================== --- py/branch/bugfix-0.9.0/py/test/rsession/testing/test_rsession.py (original) +++ py/branch/bugfix-0.9.0/py/test/rsession/testing/test_rsession.py Mon Mar 17 20:18:48 2008 @@ -29,7 +29,7 @@ rootcol = py.test.collect.Directory(tmpdir) data = list(rootcol._tryiter(reporterror=events.append)) assert len(events) == 2 - assert str(events[1][0].value) == "Reason" + assert str(events[1][0].value).find("Reason") != -1 class TestRSessionRemote(DirSetup, BasicRsessionTest): def test_example_distribution_minus_x(self): Modified: py/branch/bugfix-0.9.0/py/test/rsession/testing/test_web.py ============================================================================== --- py/branch/bugfix-0.9.0/py/test/rsession/testing/test_web.py (original) +++ py/branch/bugfix-0.9.0/py/test/rsession/testing/test_web.py Mon Mar 17 20:18:48 2008 @@ -24,7 +24,7 @@ from py.__.test.rsession import webjs from py.__.test.rsession.web import FUNCTION_LIST, IMPORTED_PYPY - source = rpython2javascript(webjs, FUNCTION_LIST) + source = rpython2javascript(webjs, FUNCTION_LIST, use_pdb=False) assert source def test_parse_args(): Modified: py/branch/bugfix-0.9.0/py/test/rsession/web.py ============================================================================== --- py/branch/bugfix-0.9.0/py/test/rsession/web.py (original) +++ py/branch/bugfix-0.9.0/py/test/rsession/web.py Mon Mar 17 20:18:48 2008 @@ -24,11 +24,10 @@ "show_host", "hide_host", "hide_messagebox", "opt_scroll"] try: - from pypy.rpython.ootypesystem.bltregistry import MethodDesc, BasicExternal,\ - described + from pypy.rpython.ootypesystem.bltregistry import MethodDesc, BasicExternal from pypy.translator.js.main import rpython2javascript from pypy.translator.js import commproxy - from pypy.rpython.extfunc import _callable + from pypy.translator.js.lib.support import callback commproxy.USE_MOCHIKIT = False IMPORTED_PYPY = True @@ -36,14 +35,11 @@ class BasicExternal(object): pass - def described(*args, **kwargs): + def callback(*args, **kwargs): def decorator(func): return func return decorator - def _callable(*args, **kwargs): - pass - IMPORTED_PYPY = False def add_item(event): @@ -153,22 +149,19 @@ for host in self.hosts: to_send[host.hostid] = host.hostname return to_send - show_hosts = described(retval={str:str}, args=[('callback', - _callable([{str:str}]))])(show_hosts) + show_hosts = callback(retval={str:str})(show_hosts) def show_skip(self, item_name="aa"): return {'item_name': item_name, 'reason': self.skip_reasons[item_name]} - show_skip = described(retval={str:str}, args=[('item_name',str),('callback', - _callable([{str:str}]))])(show_skip) + show_skip = callback(retval={str:str})(show_skip) def show_fail(self, item_name="aa"): return {'item_name':item_name, 'traceback':str(self.fail_reasons[item_name]), 'stdout':self.stdout[item_name], 'stderr':self.stderr[item_name]} - show_fail = described(retval={str:str}, args=[('item_name',str),('callback', - _callable([{str:str}]))])(show_fail) + show_fail = callback(retval={str:str})(show_fail) _sessids = None _sesslock = py.std.thread.allocate_lock() @@ -186,8 +179,7 @@ finally: self._sesslock.release() return sessid - show_sessid = described(retval=str, args=[('callback', - _callable([str]))])(show_sessid) + show_sessid = callback(retval=str)(show_sessid) def failed(self, **kwargs): if not 'sessid' in kwargs: @@ -201,14 +193,13 @@ del self._sessids[to_del] self.pending_events._del(kwargs['sessid']) - def show_all_statuses(self, sessid=-1): + def show_all_statuses(self, sessid='xx'): retlist = [self.show_status_change(sessid)] while not self.pending_events.empty_queue(sessid): retlist.append(self.show_status_change(sessid)) retval = retlist return retval - show_all_statuses = described(retval=[{str:str}],args= - [('sessid',str), ('callback',_callable([[{str:str}]]))])(show_all_statuses) + show_all_statuses = callback(retval=[{str:str}])(show_all_statuses) def show_status_change(self, sessid): event = self.pending_events.get(sessid) @@ -276,7 +267,7 @@ def repr_source(self, relline, source): lines = [] - for num, line in enumerate(source.split("\n")): + for num, line in enumerate(str(source).split("\n")): if num == relline: lines.append(">>>>" + line) else: @@ -286,6 +277,14 @@ def report_ReceivedItemOutcome(self, event): self.all += 1 self.pending_events.put(event) + + def report_FailedTryiter(self, event): + fullitemname = "/".join(event.item.listnames()) + self.fail_reasons[fullitemname] = self.repr_failure_tblong( + event.item, event.excinfo, event.excinfo.traceback) + self.stdout[fullitemname] = '' + self.stderr[fullitemname] = '' + self.pending_events.put(event) def report_ItemStart(self, event): if isinstance(event.item, py.test.collect.Module): @@ -400,7 +399,8 @@ def run_jssource(self): js_name = py.path.local(__file__).dirpath("webdata").join("source.js") web_name = py.path.local(__file__).dirpath().join("webjs.py") - if IMPORTED_PYPY and web_name.mtime() > js_name.mtime(): + if IMPORTED_PYPY and web_name.mtime() > js_name.mtime() or \ + (not js_name.check()) or 1: from py.__.test.rsession import webjs javascript_source = rpython2javascript(webjs, Modified: py/branch/bugfix-0.9.0/py/test/rsession/webdata/index.html ============================================================================== --- py/branch/bugfix-0.9.0/py/test/rsession/webdata/index.html (original) +++ py/branch/bugfix-0.9.0/py/test/rsession/webdata/index.html Mon Mar 17 20:18:48 2008 @@ -20,6 +20,10 @@ z-index: 2; } + div.main { + margin-right: 170px; + } + .error { color: #F00; font-weight: bold; @@ -32,6 +36,7 @@ background-color: #ffd; border: 1px solid #003; z-index: 1; + width: 160px; } #navbar tr, #navbar td { Modified: py/branch/bugfix-0.9.0/py/test/rsession/webdata/source.js ============================================================================== Binary files. No diff available. Modified: py/branch/bugfix-0.9.0/py/test/rsession/webjs.py ============================================================================== --- py/branch/bugfix-0.9.0/py/test/rsession/webjs.py (original) +++ py/branch/bugfix-0.9.0/py/test/rsession/webjs.py Mon Mar 17 20:18:48 2008 @@ -156,7 +156,7 @@ td.appendChild(link) exported_methods.show_fail(item_name, fail_come_back) - if counters[msg['fullmodulename']] % MAX_COUNTER == 0: + if counters[msg['fullmodulename']] == 0: tr = create_elem("tr") module_part.appendChild(tr) @@ -212,10 +212,16 @@ return True tr = create_elem("tr") td = create_elem("td") + a = create_elem("a") + a.setAttribute("href", "javascript:show_traceback('%s')" % ( + msg['fullitemname'],)) txt = create_text_elem("- FAILED TO LOAD MODULE") - td.appendChild(txt) + a.appendChild(txt) + td.appendChild(a) tr.appendChild(td) module_part.appendChild(tr) + item_name = msg['fullitemname'] + exported_methods.show_fail(item_name, fail_come_back) elif msg['type'] == 'SkippedTryiter': module_part = get_elem(msg['fullitemname']) if not module_part: Modified: py/branch/bugfix-0.9.0/py/test/session.py ============================================================================== --- py/branch/bugfix-0.9.0/py/test/session.py (original) +++ py/branch/bugfix-0.9.0/py/test/session.py Mon Mar 17 20:18:48 2008 @@ -67,6 +67,7 @@ self.footer(colitems) except Exit, ex: pass + return self.getitemoutcomepairs(Failed) def runtraced(self, colitem): if self.shouldclose(): Modified: py/branch/bugfix-0.9.0/py/test/testing/test_session.py ============================================================================== --- py/branch/bugfix-0.9.0/py/test/testing/test_session.py (original) +++ py/branch/bugfix-0.9.0/py/test/testing/test_session.py Mon Mar 17 20:18:48 2008 @@ -314,3 +314,24 @@ expected_output = '\nE ' + line_to_report + '\n' print 'Looking for:', expected_output assert expected_output in out + + +def test_skip_reasons(): + tmp = py.test.ensuretemp("check_skip_reasons") + tmp.ensure("test_one.py").write(py.code.Source(""" + import py + def test_1(): + py.test.skip(py.test.broken('stuff')) + + def test_2(): + py.test.skip(py.test.notimplemented('stuff')) + """)) + tmp.ensure("__init__.py") + config = py.test.config._reparse([tmp]) + session = config.initsession() + session.main() + skips = session.getitemoutcomepairs(Skipped) + assert len(skips) == 2 + assert repr(skips[0][1]) == 'Broken: stuff' + assert repr(skips[1][1]) == 'Not implemented: stuff' + Modified: py/branch/bugfix-0.9.0/py/test/testing/test_setup_nested.py ============================================================================== --- py/branch/bugfix-0.9.0/py/test/testing/test_setup_nested.py (original) +++ py/branch/bugfix-0.9.0/py/test/testing/test_setup_nested.py Mon Mar 17 20:18:48 2008 @@ -42,14 +42,23 @@ class TestSetupTeardownOnInstance(TestSimpleClassSetup): def setup_method(self, method): - self.clslevel.append(17) + self.clslevel.append(method.__name__) def teardown_method(self, method): x = self.clslevel.pop() - assert x == 17 + assert x == method.__name__ def test_setup(self): - assert self.clslevel[-1] == 17 + assert self.clslevel[-1] == 'test_setup' + + def test_generate(self): + assert self.clslevel[-1] == 'test_generate' + yield self.generated, 5 + assert self.clslevel[-1] == 'test_generate' + + def generated(self, value): + assert value == 5 + assert self.clslevel[-1] == 'test_generate' def test_teardown_method_worked(): assert not TestSetupTeardownOnInstance.clslevel Modified: py/branch/bugfix-0.9.0/py/tool/utestconvert.py ============================================================================== --- py/branch/bugfix-0.9.0/py/tool/utestconvert.py (original) +++ py/branch/bugfix-0.9.0/py/tool/utestconvert.py Mon Mar 17 20:18:48 2008 @@ -225,7 +225,7 @@ if not args: s = '' - for block in blocksplitter(sys.stdin.read()): + for block in blocksplitter(sys.stdin): s += rewrite_utest(block) sys.stdout.write(s) From briandorsey at codespeak.net Mon Mar 17 21:24:21 2008 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Mon, 17 Mar 2008 21:24:21 +0100 (CET) Subject: [py-svn] r52667 - py/branch/bugfix-0.9.0/py/path/svn/testing Message-ID: <20080317202421.EC433169E39@codespeak.net> Author: briandorsey Date: Mon Mar 17 21:24:20 2008 New Revision: 52667 Modified: py/branch/bugfix-0.9.0/py/path/svn/testing/test_auth.py Log: spelling fix: '--uername' --> '--username' Modified: py/branch/bugfix-0.9.0/py/path/svn/testing/test_auth.py ============================================================================== --- py/branch/bugfix-0.9.0/py/path/svn/testing/test_auth.py (original) +++ py/branch/bugfix-0.9.0/py/path/svn/testing/test_auth.py Mon Mar 17 21:24:20 2008 @@ -62,7 +62,7 @@ def test_makecmdoptions_uname_pw_makestr(self): auth = py.path.SvnAuth('foo', 'bar') - assert auth.makecmdoptions() == '--uername="foo" --password="bar"' + assert auth.makecmdoptions() == '--username="foo" --password="bar"' def test_makecmdoptions_quote_escape(self): auth = py.path.SvnAuth('fo"o', '"ba\'r"') From fijal at codespeak.net Tue Mar 18 21:57:45 2008 From: fijal at codespeak.net (fijal at codespeak.net) Date: Tue, 18 Mar 2008 21:57:45 +0100 (CET) Subject: [py-svn] r52707 - py/trunk/py/test Message-ID: <20080318205745.E051B169E7D@codespeak.net> Author: fijal Date: Tue Mar 18 21:57:43 2008 New Revision: 52707 Modified: py/trunk/py/test/reporter.py Log: Allow some mocking. Modified: py/trunk/py/test/reporter.py ============================================================================== --- py/trunk/py/test/reporter.py (original) +++ py/trunk/py/test/reporter.py Tue Mar 18 21:57:43 2008 @@ -49,12 +49,15 @@ return self.flag class AbstractReporter(object): - def __init__(self, config, hosts): + def __init__(self, config, hosts, out=None): self.config = config self.hosts = hosts self.failed_tests_outcome = [] self.skipped_tests_outcome = [] - self.out = getout(py.std.sys.stdout) + if out is None: + self.out = getout(py.std.sys.stdout) + else: + self.out = out self.presenter = Presenter(self.out, config) self.to_rsync = {} From guido at codespeak.net Wed Mar 19 14:31:32 2008 From: guido at codespeak.net (guido at codespeak.net) Date: Wed, 19 Mar 2008 14:31:32 +0100 (CET) Subject: [py-svn] r52719 - py/branch/bugfix-0.9.0/py/doc Message-ID: <20080319133132.C76E2169EDF@codespeak.net> Author: guido Date: Wed Mar 19 14:31:28 2008 New Revision: 52719 Added: py/branch/bugfix-0.9.0/py/doc/changes-0.9.1.txt py/branch/bugfix-0.9.0/py/doc/release-0.9.1.txt Log: Added release message and changes file. Added: py/branch/bugfix-0.9.0/py/doc/changes-0.9.1.txt ============================================================================== --- (empty file) +++ py/branch/bugfix-0.9.0/py/doc/changes-0.9.1.txt Wed Mar 19 14:31:28 2008 @@ -0,0 +1,53 @@ +Changes between 0.9.0 and 0.9.1 +=============================== + +This is a fairly complete list of changes between 0.9 and 0.9.1, which can +serve as a reference for developers. + +* allowing + signs in py.path.svn urls [39106] +* fixed support for Failed exceptions without excinfo in py.test [39340] +* added support for killing processes for Windows (as well as platforms that + support os.kill) in py.misc.killproc [39655] +* added setup/teardown for generative tests to py.test [40702] +* added detection of FAILED TO LOAD MODULE to py.test [40703, 40738, 40739] +* fixed problem with calling .remove() on wcpaths of non-versioned files in + py.path [44248] +* fixed some import and inheritance issues in py.test [41480, 44648, 44655] +* fail to run greenlet tests when pypy is available, but without stackless + [45294] +* small fixes in rsession tests [45295] +* fixed issue with 2.5 type representations in py.test [45483, 45484] +* made that internal reporting issues displaying is done atomically in py.test + [45518] +* made that non-existing files are igored by the py.lookup script [45519] +* improved exception name creation in py.test [45535] +* made that less threads are used in execnet [merge in 45539] +* removed lock required for atomical reporting issue displaying in py.test + [45545] +* removed globals from execnet [45541, 45547] +* refactored cleanup mechanics, made that setDaemon is set to 1 to make atexit + get called in 2.5 (py.execnet) [45548] +* fixed bug in joining threads in py.execnet's servemain [45549] +* refactored py.test.rsession tests to not rely on exact output format anymore + [45646] +* using repr() on test outcome [45647] +* added 'Reason' classes for py.test.skip() [45648, 45649] +* killed some unnecessary sanity check in py.test.collect [45655] +* avoid using os.tmpfile() in py.io.fdcapture because on Windows it's only + usable by Administrators [45901] +* added support for locking and non-recursive commits to py.path.svnwc [45994] +* locking files in py.execnet to prevent CPython from segfaulting [46010] +* added export() method to py.path.svnurl +* fixed -d -x in py.test [47277] +* fixed argument concatenation problem in py.path.svnwc [49423] +* restore py.test behaviour that it exits with code 1 when there are failures + [49974] +* don't fail on html files that don't have an accompanying .txt file [50606] +* fixed 'utestconvert.py < input' [50645] +* small fix for code indentation in py.code.source [50755] +* fix _docgen.py documentation building [51285] +* improved checks for source representation of code blocks in py.test [51292] +* added support for passing authentication to py.path.svn* objects [52000, + 52001] +* removed sorted() call for py.apigen tests in favour of [].sort() to support + Python 2.3 [52481] Added: py/branch/bugfix-0.9.0/py/doc/release-0.9.1.txt ============================================================================== --- (empty file) +++ py/branch/bugfix-0.9.0/py/doc/release-0.9.1.txt Wed Mar 19 14:31:28 2008 @@ -0,0 +1,33 @@ +py lib 0.9.1: bugfix release +============================= + +Welcome to the 0.9.1 py lib release - a library aiming to +support agile and test-driven python development on various levels. + +This is mostly a bugfix release, with a couple of new features sneaked in. +Most important changes: + +* reduced the number of threads used in py.execnet +* some new functionality (authentication, export, locking) in py.path's + Subversion APIs +* stability and segfault fixes in execnet +* numerous small fixes in py.test's rsession (experimental pluggable session) + and generative test features +* some fixes in the py.test core +* added py.misc.killproc, which allows killing processes on (some flavours of) + Windows and UNIX + +For a complete list of changes, see doc/changes-0.9.1.txt in the source +package. + +Download/Install: http://codespeak.net/py/0.9.1/download.html +Documentation/API: http://codespeak.net/py/0.9.1/index.html + +Work on the py lib has been partially funded by the +European Union IST programme and by http://merlinux.de +within the PyPy project. + +best, have fun and let us know what you think! + +holger krekel, Maciej Fijalkowski, +Carl Friedrich Bolz, Guido Wesdorp From guido at codespeak.net Fri Mar 21 11:40:40 2008 From: guido at codespeak.net (guido at codespeak.net) Date: Fri, 21 Mar 2008 11:40:40 +0100 (CET) Subject: [py-svn] r52798 - in py/branch/bugfix-0.9.0/py: . doc Message-ID: <20080321104040.5C7371684D8@codespeak.net> Author: guido Date: Fri Mar 21 11:40:39 2008 New Revision: 52798 Modified: py/branch/bugfix-0.9.0/py/__init__.py py/branch/bugfix-0.9.0/py/doc/download.txt Log: Updated version number for the upcoming 0.9.1 release, updated downloads doc. 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 Fri Mar 21 11:40:39 2008 @@ -7,7 +7,7 @@ """ from initpkg import initpkg -version = "0.9.1-alpha" +version = "0.9.1" initpkg(__name__, description = "py lib: agile development and test support library", Modified: py/branch/bugfix-0.9.0/py/doc/download.txt ============================================================================== --- py/branch/bugfix-0.9.0/py/doc/download.txt (original) +++ py/branch/bugfix-0.9.0/py/doc/download.txt Fri Mar 21 11:40:39 2008 @@ -9,11 +9,11 @@ The latest public release: - `download py-0.9.0.tar.gz`_ - `download py-0.9.0.zip`_ + `download py-0.9.1.tar.gz`_ + `download py-0.9.1.zip`_ -.. _`download py-0.9.0.tar.gz`: http://codespeak.net/download/py/py-0.9.0.tar.gz -.. _`download py-0.9.0.zip`: http://codespeak.net/download/py/py-0.9.0.zip +.. _`download py-0.9.1.tar.gz`: http://codespeak.net/download/py/py-0.9.1.tar.gz +.. _`download py-0.9.1.zip`: http://codespeak.net/download/py/py-0.9.1.zip The py lib can be `globally installed via setup.py`_ or `used locally`_. From py-svn at codespeak.net Sun Mar 23 21:13:51 2008 From: py-svn at codespeak.net (py-svn at codespeak.net) Date: Sun, 23 Mar 2008 21:13:51 +0100 (CET) Subject: [py-svn] MedHelp 84012 Message-ID: <20080323141254.7397.qmail@ppp91-76-158-158.pppoe.mtu-net.ru> An HTML attachment was scrubbed... URL: From guido at codespeak.net Fri Mar 28 11:08:12 2008 From: guido at codespeak.net (guido at codespeak.net) Date: Fri, 28 Mar 2008 11:08:12 +0100 (CET) Subject: [py-svn] r53044 - py/branch/bugfix-0.9.0/py/doc Message-ID: <20080328100812.7F6F8169FB2@codespeak.net> Author: guido Date: Fri Mar 28 11:08:12 2008 New Revision: 53044 Modified: py/branch/bugfix-0.9.0/py/doc/index.txt Log: Last occurrence of 0.9.0 in the docs replaced with 0.9.1. Modified: py/branch/bugfix-0.9.0/py/doc/index.txt ============================================================================== --- py/branch/bugfix-0.9.0/py/doc/index.txt (original) +++ py/branch/bugfix-0.9.0/py/doc/index.txt Fri Mar 28 11:08:12 2008 @@ -7,7 +7,7 @@ `Download and Installation`_ -`0.9.0 release announcement`_ +`0.9.1 release announcement`_ Main tools and API ---------------------- @@ -59,4 +59,4 @@ .. _`Why What how py?`: why_py.html .. _`future`: future.html .. _`miscellaneous features`: misc.html -.. _`0.9.0 release announcement`: release-0.9.0.html +.. _`0.9.1 release announcement`: release-0.9.1.html From guido at codespeak.net Fri Mar 28 11:30:55 2008 From: guido at codespeak.net (guido at codespeak.net) Date: Fri, 28 Mar 2008 11:30:55 +0100 (CET) Subject: [py-svn] r53046 - py/release/0.9.1 Message-ID: <20080328103055.DC29D169FA1@codespeak.net> Author: guido Date: Fri Mar 28 11:30:54 2008 New Revision: 53046 Added: py/release/0.9.1/ - copied from r53045, py/branch/bugfix-0.9.0/ Log: Setting 0.9.1 tag. From guido at codespeak.net Fri Mar 28 11:51:26 2008 From: guido at codespeak.net (guido at codespeak.net) Date: Fri, 28 Mar 2008 11:51:26 +0100 (CET) Subject: [py-svn] r53048 - py/release/0.9.1/py/doc Message-ID: <20080328105126.CA74D169F54@codespeak.net> Author: guido Date: Fri Mar 28 11:51:26 2008 New Revision: 53048 Modified: py/release/0.9.1/py/doc/download.txt Log: Grmbl, there _is_ now a win32 greenlet.pyd in the package... Modified: py/release/0.9.1/py/doc/download.txt ============================================================================== --- py/release/0.9.1/py/doc/download.txt (original) +++ py/release/0.9.1/py/doc/download.txt Fri Mar 28 11:51:26 2008 @@ -18,10 +18,6 @@ The py lib can be `globally installed via setup.py`_ or `used locally`_. -WARNING: win32 there is no pre-packaged c-extension -module (greenlet) yet and thus greenlets will not work -out of the box. - Getting (and updating) via subversion -------------------------------------------- From guido at codespeak.net Fri Mar 28 11:52:16 2008 From: guido at codespeak.net (guido at codespeak.net) Date: Fri, 28 Mar 2008 11:52:16 +0100 (CET) Subject: [py-svn] r53049 - py/branch/bugfix-0.9.0/py/doc Message-ID: <20080328105216.E0955169F54@codespeak.net> Author: guido Date: Fri Mar 28 11:52:16 2008 New Revision: 53049 Modified: py/branch/bugfix-0.9.0/py/doc/download.txt Log: There will be a greenlet.pyd this time. Modified: py/branch/bugfix-0.9.0/py/doc/download.txt ============================================================================== --- py/branch/bugfix-0.9.0/py/doc/download.txt (original) +++ py/branch/bugfix-0.9.0/py/doc/download.txt Fri Mar 28 11:52:16 2008 @@ -18,10 +18,6 @@ The py lib can be `globally installed via setup.py`_ or `used locally`_. -WARNING: win32 there is no pre-packaged c-extension -module (greenlet) yet and thus greenlets will not work -out of the box. - Getting (and updating) via subversion -------------------------------------------- From guido at codespeak.net Sat Mar 29 10:03:17 2008 From: guido at codespeak.net (guido at codespeak.net) Date: Sat, 29 Mar 2008 10:03:17 +0100 (CET) Subject: [py-svn] r53073 - py/branch/bugfix-0.9.0/py/doc Message-ID: <20080329090317.CD016169F2C@codespeak.net> Author: guido Date: Sat Mar 29 10:03:16 2008 New Revision: 53073 Modified: py/branch/bugfix-0.9.0/py/doc/download.txt Log: So, we decided to not add the pyd file... Modified: py/branch/bugfix-0.9.0/py/doc/download.txt ============================================================================== --- py/branch/bugfix-0.9.0/py/doc/download.txt (original) +++ py/branch/bugfix-0.9.0/py/doc/download.txt Sat Mar 29 10:03:16 2008 @@ -18,6 +18,10 @@ The py lib can be `globally installed via setup.py`_ or `used locally`_. +WARNING: win32 there is no pre-packaged c-extension +module (greenlet) yet and thus greenlets will not work +out of the box. + Getting (and updating) via subversion -------------------------------------------- From guido at codespeak.net Sat Mar 29 10:04:00 2008 From: guido at codespeak.net (guido at codespeak.net) Date: Sat, 29 Mar 2008 10:04:00 +0100 (CET) Subject: [py-svn] r53074 - py/release/0.9.1 Message-ID: <20080329090400.18625169F2D@codespeak.net> Author: guido Date: Sat Mar 29 10:03:59 2008 New Revision: 53074 Removed: py/release/0.9.1/ Log: Removing tag for now (changes on the branch) - will add again in a second. From guido at codespeak.net Sat Mar 29 10:04:32 2008 From: guido at codespeak.net (guido at codespeak.net) Date: Sat, 29 Mar 2008 10:04:32 +0100 (CET) Subject: [py-svn] r53075 - py/release/0.9.1 Message-ID: <20080329090432.00857169F31@codespeak.net> Author: guido Date: Sat Mar 29 10:04:32 2008 New Revision: 53075 Added: py/release/0.9.1/ - copied from r53074, py/branch/bugfix-0.9.0/ Log: Re-setting 0.9.1 tag from the bugfix-0.9.0 branch.