From hpk at codespeak.net Sun Oct 3 12:25:47 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 3 Oct 2004 12:25:47 +0200 (MEST) Subject: [py-svn] r6821 - in py: branch dist dist/doc dist/py dist/py/bin dist/py/execnet tag Message-ID: <20041003102547.ED4D45A910@thoth.codespeak.net> Author: hpk Date: Sat Oct 2 12:56:47 2004 New Revision: 6821 Added: py/branch/ py/dist/ py/dist/doc/ py/dist/py/ - copied from r6818, std/trunk/src/std/ py/dist/py/bin/rename_import.py (contents, props changed) py/dist/py/execnet/gateway.py - copied unchanged from r6819, std/trunk/src/std/execnet/gateway.py py/dist/py/execnet/source.py - copied unchanged from r6819, std/trunk/src/std/execnet/source.py py/tag/ Log: copying over the std files into /svn/dist/py along with the usual tag/trunk directory. note that there is no trunk directory because i think it's superflous and adds unneccessary length/nestedness to the svn-url. Added: py/dist/py/bin/rename_import.py ============================================================================== --- (empty file) +++ py/dist/py/bin/rename_import.py Sat Oct 2 12:56:47 2004 @@ -0,0 +1,23 @@ +#!/usr/bin/python + +import std, sys +import bike +import rlcompleter2 +rlcompleter2.setup() + +if __name__ == '__main__': + source = std.path.local(sys.argv[1]) + assert source.check(dir=1) + + #dest = std.path.local(sys.argv[2]) + + #dest.ensure(dir=1) + + import bike + ctx = bike.init().brmctx + + for x in source.visit(std.path.checker(file=1, fnmatch='*.py'), + std.path.checker(dotfile=0)): + ctx.paths.append(str(x)) + + From hpk at codespeak.net Sun Oct 3 12:27:22 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 3 Oct 2004 12:27:22 +0200 (MEST) Subject: [py-svn] r6830 - in py/dist/py: . bin execnet execnet/bin magic path path/local path/py path/pypath path/svn path/svnwc path/test process test test/report test/report/text test/test test/test/data test/tool utest Message-ID: <20041003102722.995815A910@thoth.codespeak.net> Author: hpk Date: Sat Oct 2 23:13:42 2004 New Revision: 6830 Added: py/dist/py/bin/py.test - copied, changed from r6821, py/dist/py/bin/utest py/dist/py/path/pypath/ - copied from r6821, py/dist/py/path/py/ py/dist/py/test/ - copied from r6821, py/dist/py/utest/ Removed: py/dist/py/bin/utest py/dist/py/path/py/ py/dist/py/utest/ Modified: py/dist/py/__init__.py py/dist/py/api_test.py py/dist/py/bin/_findstd.py py/dist/py/bin/check_python_path py/dist/py/bin/rename_import.py py/dist/py/execnet/bin/shell.py py/dist/py/execnet/bin/startserver.py py/dist/py/execnet/gateway.py py/dist/py/execnet/gateway_test.py py/dist/py/execnet/register.py py/dist/py/execnet/source_test.py py/dist/py/initpkg.py py/dist/py/initpkg_test.py py/dist/py/magic/assertion.py py/dist/py/magic/assertion_test.py py/dist/py/magic/autopath.py py/dist/py/magic/autopath_test.py py/dist/py/magic/dyncode.py py/dist/py/magic/dyncode_test.py py/dist/py/magic/exprinfo.py py/dist/py/magic/exprinfo_test.py py/dist/py/magic/invoke.py py/dist/py/magic/invoke_test.py py/dist/py/magic/patch_test.py py/dist/py/magic/viewtype_test.py py/dist/py/path/api_test.py py/dist/py/path/common.py py/dist/py/path/local/api.py py/dist/py/path/local/local.py py/dist/py/path/local/local_test.py py/dist/py/path/pypath/pypath.py py/dist/py/path/pypath/pypath_test.py py/dist/py/path/svn/command.py py/dist/py/path/svn/command_test.py py/dist/py/path/svn/common.py py/dist/py/path/svnwc/command.py py/dist/py/path/svnwc/command_test.py py/dist/py/path/test/_common.py py/dist/py/path/test/_svncommon.py py/dist/py/process/cmdexec_test.py py/dist/py/test/cmdline.py py/dist/py/test/collect.py py/dist/py/test/collect_test.py py/dist/py/test/compat.py py/dist/py/test/compat_test.py py/dist/py/test/config.py py/dist/py/test/config_test.py py/dist/py/test/raises.py py/dist/py/test/raises_test.py py/dist/py/test/report/memo.py py/dist/py/test/report/memo_test.py py/dist/py/test/report/text/reporter.py py/dist/py/test/report/text/summary.py py/dist/py/test/run.py py/dist/py/test/test/data/Collector.py py/dist/py/test/test/demo.py py/dist/py/test/tool/optparse.py py/dist/py/test/tool/outerrcapture_test.py Log: the great rename from std to py (done by a new hack/script, because bicycle repair apparently doesn't even support import renames) Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Sat Oct 2 23:13:42 2004 @@ -5,22 +5,22 @@ 'path.invchecker': './path/common.invchecker', 'path.svnurl': './path/svn/command.XSvnCommandPath', 'path.svnwc': './path/svnwc/command.XSvnWCCommandPath', - 'path.py': './path/py/pypath.PyPath', + 'path.py': './path/pypath/pypath.PyPath', 'path.NotFound': './path/error.FileNotFound', 'path.Denied': './path/error.PermissionDenied', 'path.NoDirectory': './path/error.NoDirectory', 'path.Invalid': './path/error.Invalid', - 'utest.collect': './utest/collect', - 'utest.run': './utest/run', - 'utest.main': './utest/cmdline.main', - 'utest.raises': './utest/raises.raises', - 'utest.config': './utest/config.config', - 'utest.compat': './utest/compat', - 'utest.Unit': './utest/run.Unit', - 'utest.Option': './utest/tool/optparse.Option', - 'utest.TextReporter': './utest/report/text/reporter.TextReporter', - 'utest.MemoReporter': './utest/report/memo.MemoReporter', + 'test.collect': './test/collect', + 'test.run': './test/run', + 'test.main': './test/cmdline.main', + 'test.raises': './test/raises.raises', + 'test.config': './test/config.config', + 'test.compat': './test/compat', + 'test.Unit': './test/run.Unit', + 'test.Option': './test/tool/optparse.Option', + 'test.TextReporter': './test/report/text/reporter.TextReporter', + 'test.MemoReporter': './test/report/memo.MemoReporter', 'process.cmdexec': './process/cmdexec.cmdexec', Modified: py/dist/py/api_test.py ============================================================================== --- py/dist/py/api_test.py (original) +++ py/dist/py/api_test.py Sat Oct 2 23:13:42 2004 @@ -1,41 +1,41 @@ -from std.utest import raises -import std +from py.test import raises +import py import inspect class API_V0_namespace_consistence: def test_path_entrypoints(self): - assert inspect.ismodule(std.path) - assert_class('std.path', 'local') - assert_class('std.path', 'svnwc') - assert_class('std.path', 'svnurl') - assert_class('std.path', 'py') - assert_class('std.path', 'checker') - assert_class('std.path', 'invchecker') - assert_class('std.path', 'NotFound') - assert_class('std.path', 'Denied') + assert inspect.ismodule(py.path) + assert_class('py.path', 'local') + assert_class('py.path', 'svnwc') + assert_class('py.path', 'svnurl') + assert_class('py.path', 'py') + assert_class('py.path', 'checker') + assert_class('py.path', 'invchecker') + assert_class('py.path', 'NotFound') + assert_class('py.path', 'Denied') def test_magic_entrypoints(self): - assert_class('std.magic', 'View') - assert_function('std.magic', 'invoke') - assert_function('std.magic', 'revoke') - assert_function('std.magic', 'patch') - assert_function('std.magic', 'revoke') - - assert inspect.ismodule(std.magic.dyncode) - assert_function('std.magic.dyncode', 'compile') - assert_function('std.magic.dyncode', 'compile2') - assert_function('std.magic.dyncode', 'findsource') - assert_function('std.magic.dyncode', 'getsource') - assert_function('std.magic.dyncode', 'listtb') - assert_function('std.magic.dyncode', 'findsource') + assert_class('py.magic', 'View') + assert_function('py.magic', 'invoke') + assert_function('py.magic', 'revoke') + assert_function('py.magic', 'patch') + assert_function('py.magic', 'revoke') + + assert inspect.ismodule(py.magic.dyncode) + assert_function('py.magic.dyncode', 'compile') + assert_function('py.magic.dyncode', 'compile2') + assert_function('py.magic.dyncode', 'findsource') + assert_function('py.magic.dyncode', 'getsource') + assert_function('py.magic.dyncode', 'listtb') + assert_function('py.magic.dyncode', 'findsource') def test_process_entrypoints(self): - assert_function('std.process', 'cmdexec') + assert_function('py.process', 'cmdexec') def test_utest_entrypoints(self): # XXX TOBECOMPLETED - assert_function('std.utest', 'main') + assert_function('py.test', 'main') #assert_module('std.utest', 'collect') def assert_class(modpath, name): Modified: py/dist/py/bin/_findstd.py ============================================================================== --- py/dist/py/bin/_findstd.py (original) +++ py/dist/py/bin/_findstd.py Sat Oct 2 23:13:42 2004 @@ -8,7 +8,7 @@ last = current initpy = join(current, '__init__.py') if not exists(initpy): - stddir = join(current, 'std') + stddir = join(current, 'py') # recognize std-package and make it importable first thing if exists(stddir) and exists(join(stddir, '__init__.py')): sys.path[:] = [p for p in sys.path if p != current] @@ -22,4 +22,4 @@ if not searchstd(abspath(os.curdir)): if not searchstd(opd(abspath(sys.argv[0]))): if not searchstd(opd(__file__)): - raise SystemExit, "Could not find 'std' package!" + raise SystemExit, "Could not find 'py' package!" Modified: py/dist/py/bin/check_python_path ============================================================================== --- py/dist/py/bin/check_python_path (original) +++ py/dist/py/bin/check_python_path Sat Oct 2 23:13:42 2004 @@ -2,7 +2,7 @@ from __future__ import generators import sys, os -from std.path import local +from py.path import local def search_path(item): dirs = [os.curdir] Copied: py/dist/py/bin/py.test (from r6821, py/dist/py/bin/utest) ============================================================================== --- py/dist/py/bin/utest (original) +++ py/dist/py/bin/py.test Sat Oct 2 23:13:42 2004 @@ -4,5 +4,5 @@ import sys # we now can import (a version of) std -from std.utest import main +from py.test import main main(sys.argv) Modified: py/dist/py/bin/rename_import.py ============================================================================== --- py/dist/py/bin/rename_import.py (original) +++ py/dist/py/bin/rename_import.py Sat Oct 2 23:13:42 2004 @@ -1,12 +1,12 @@ #!/usr/bin/python -import std, sys +import py, sys import bike import rlcompleter2 rlcompleter2.setup() if __name__ == '__main__': - source = std.path.local(sys.argv[1]) + source = py.path.local(sys.argv[1]) assert source.check(dir=1) #dest = std.path.local(sys.argv[2]) @@ -16,8 +16,8 @@ import bike ctx = bike.init().brmctx - for x in source.visit(std.path.checker(file=1, fnmatch='*.py'), - std.path.checker(dotfile=0)): + for x in source.visit(py.path.checker(file=1, fnmatch='*.py'), + py.path.checker(dotfile=0)): ctx.paths.append(str(x)) Deleted: /py/dist/py/bin/utest ============================================================================== --- /py/dist/py/bin/utest Sat Oct 2 23:13:42 2004 +++ (empty file) @@ -1,8 +0,0 @@ -#!/usr/bin/env python - -import _findstd -import sys -# we now can import (a version of) std - -from std.utest import main -main(sys.argv) Modified: py/dist/py/execnet/bin/shell.py ============================================================================== --- py/dist/py/execnet/bin/shell.py (original) +++ py/dist/py/execnet/bin/shell.py Sat Oct 2 23:13:42 2004 @@ -64,7 +64,7 @@ sys.stdout, sys.stderr = clientfile, clientfile try: try: - exec compile(line + '\n','', 'single') + exec compile(line + '\n','', 'single') except: print_exc() finally: Modified: py/dist/py/execnet/bin/startserver.py ============================================================================== --- py/dist/py/execnet/bin/startserver.py (original) +++ py/dist/py/execnet/bin/startserver.py Sat Oct 2 23:13:42 2004 @@ -12,7 +12,7 @@ import sys, socket, os if debug: # and not os.isatty(sys.stdin.fileno()): - f = open('/tmp/execnet-socket-stdout.log', 'a', 0) + f = open('/tmp/execnet-socket-pyout.log', 'a', 0) old = sys.stdout, sys.stderr sys.stdout = sys.stderr = f Modified: py/dist/py/execnet/gateway.py ============================================================================== --- py/dist/py/execnet/gateway.py (original) +++ py/dist/py/execnet/gateway.py Sat Oct 2 23:13:42 2004 @@ -1,7 +1,7 @@ import sys, os, threading, struct, Queue, traceback import atexit -from std.__impl__.execnet.source import Source +from py.__impl__.execnet.source import Source debug = 0 sysex = (KeyboardInterrupt, SystemExit) Modified: py/dist/py/execnet/gateway_test.py ============================================================================== --- py/dist/py/execnet/gateway_test.py (original) +++ py/dist/py/execnet/gateway_test.py Sat Oct 2 23:13:42 2004 @@ -1,12 +1,12 @@ import os, sys -import std -from std.__impl__.execnet.source import Source -autopath = std.magic.autopath() +import py +from py.__impl__.execnet.source import Source +autopath = py.magic.autopath() class PopenGatewayTestSetup: disabled = True def setup_class(cls): - cls.gw = std.execnet.PopenGateway() + cls.gw = py.execnet.PopenGateway() def teardown_class(cls): cls.gw.exit() @@ -16,7 +16,7 @@ def setup_class(cls): portrange = (7770, 7800) - cls.proxygw = std.execnet.PopenGateway() + cls.proxygw = py.execnet.PopenGateway() s = Source( autopath.thisdir.join('bin', 'startserver.py').read(), """ @@ -39,7 +39,7 @@ cls.proxygw.wait_exec(timeout=5) if not cls.proxygw._listenport: raise IOError, "could not setup remote SocketServer" - cls.gw = std.execnet.SocketGateway('localhost', cls.proxygw._listenport) + cls.gw = py.execnet.SocketGateway('localhost', cls.proxygw._listenport) print "initialized socket gateway on port", cls.proxygw._listenport def teardown_class(cls): @@ -78,7 +78,7 @@ num = 4 l = [] for i in range(num): - l.append(std.execnet.PopenGateway()) + l.append(py.execnet.PopenGateway()) stati = [] for gw in l: status = gw.remote_exec_async("gateway.remote_exec_oneshot('back=42')") Modified: py/dist/py/execnet/register.py ============================================================================== --- py/dist/py/execnet/register.py (original) +++ py/dist/py/execnet/register.py Sat Oct 2 23:13:42 2004 @@ -1,11 +1,11 @@ -from std.magic import autopath ; autopath = autopath() +from py.magic import autopath ; autopath = autopath() import os, inspect, socket -import std -from std.__impl__.execnet import inputoutput, gateway -std.magic.invoke(dyncode=True) +import py +from py.__impl__.execnet import inputoutput, gateway +py.magic.invoke(dyncode=True) class InstallableGateway(gateway.Gateway): def __init__(self, io, ns = None): Modified: py/dist/py/execnet/source_test.py ============================================================================== --- py/dist/py/execnet/source_test.py (original) +++ py/dist/py/execnet/source_test.py Sat Oct 2 23:13:42 2004 @@ -1,5 +1,5 @@ -from std.__impl__.execnet.source import Source +from py.__impl__.execnet.source import Source def test_source_str_function(): x = Source("3") Modified: py/dist/py/initpkg.py ============================================================================== --- py/dist/py/initpkg.py (original) +++ py/dist/py/initpkg.py Sat Oct 2 23:13:42 2004 @@ -56,13 +56,13 @@ return self.exportdefs.items() def getpath(self): - from std.path import local + from py.path import local base = local(self.implmodule.__file__).dirpath() assert base.check() return base def _iterfiles(self): - from std.path import checker + from py.path import checker base = self.getpath() for x in base.visit(checker(file=1, notext='.pyc'), rec=checker(dotfile=0)): @@ -82,10 +82,10 @@ def getzipdata(self): """ return string representing a zipfile containing the package. """ import zipfile - import std + import py try: from cStringIO import StringIO except ImportError: from StringIO import StringIO - base = std.__package__.getpath().dirpath() + base = py.__package__.getpath().dirpath() outf = StringIO() f = zipfile.ZipFile(outf, 'w', compression=zipfile.ZIP_DEFLATED) try: Modified: py/dist/py/initpkg_test.py ============================================================================== --- py/dist/py/initpkg_test.py (original) +++ py/dist/py/initpkg_test.py Sat Oct 2 23:13:42 2004 @@ -1,26 +1,26 @@ -import std +import py import types def test_dir(): - for name in dir(std): + for name in dir(py): if not name.startswith('_'): - obj = getattr(std, name) + obj = getattr(py, name) if isinstance(obj, types.ModuleType): keys = dir(obj) assert len(keys) > 0 assert getattr(obj, '__map__') == {} def test_virtual_module_identity(): - from std import path as path1 - from std import path as path2 + from py import path as path1 + from py import path as path2 assert path1 is path2 - from std.path import local as local1 - from std.path import local as local2 + from py.path import local as local1 + from py.path import local as local2 assert local1 is local2 def test_importing_all_implementations(): - base = std.path.local(std.__file__).dirpath() - for p in base.visit('*.py', std.path.checker(dotfile=0)): + base = py.path.local(py.__file__).dirpath() + for p in base.visit('*.py', py.path.checker(dotfile=0)): relpath = p.new(ext='').relto(base) if base.sep in relpath: # not std/*.py itself if relpath.find('test/data') != -1: @@ -28,23 +28,23 @@ if relpath.find('bin/') != -1: continue relpath = relpath.replace(base.sep, '.') - modpath = 'std.__impl__.%s' % relpath + modpath = 'py.__impl__.%s' % relpath assert __import__(modpath) def test_shahexdigest(): - hex = std.__package__.shahexdigest() + hex = py.__package__.shahexdigest() assert len(hex) == 40 def test_getzipdata(): - s = std.__package__.getzipdata() + s = py.__package__.getzipdata() # the following test should abasically work in the future def XXXtest_virtual_on_the_fly(): - std.initpkg('my', { + py.initpkg('my', { 'x.abspath' : 'os.path.abspath', - 'x.local' : 'std.path.local', + 'x.local' : 'py.path.local', 'y' : 'smtplib', - 'z.cmdexec' : 'std.process.cmdexec', + 'z.cmdexec' : 'py.process.cmdexec', }) from my.x import abspath from my.x import local @@ -52,7 +52,7 @@ from my import y assert y is smtplib from my.z import cmdexec - from std.process import cmdexec as cmdexec2 + from py.process import cmdexec as cmdexec2 assert cmdexec is cmdexec2 ##def test_help(): Modified: py/dist/py/magic/assertion.py ============================================================================== --- py/dist/py/magic/assertion.py (original) +++ py/dist/py/magic/assertion.py Sat Oct 2 23:13:42 2004 @@ -1,11 +1,11 @@ import __builtin__, sys -from std import magic -from std.__impl__.magic import exprinfo, dyncode +from py import magic +from py.__impl__.magic import exprinfo, dyncode BuiltinAssertionError = __builtin__.AssertionError class AssertionError(BuiltinAssertionError): - __module__ = 'std.magic' + __module__ = 'py.magic' def __init__(self, *args): BuiltinAssertionError.__init__(self, *args) Modified: py/dist/py/magic/assertion_test.py ============================================================================== --- py/dist/py/magic/assertion_test.py (original) +++ py/dist/py/magic/assertion_test.py Sat Oct 2 23:13:42 2004 @@ -1,6 +1,6 @@ -from std.utest import main -from std.__impl__.magic import assertion +from py.test import main +from py.__impl__.magic import assertion def f(): return 2 Modified: py/dist/py/magic/autopath.py ============================================================================== --- py/dist/py/magic/autopath.py (original) +++ py/dist/py/magic/autopath.py Sat Oct 2 23:13:42 2004 @@ -1,5 +1,5 @@ import os, sys -from std.path import local +from py.path import local class autopath: def __init__(self, globs=None, basefile='__init__.py'): Modified: py/dist/py/magic/autopath_test.py ============================================================================== --- py/dist/py/magic/autopath_test.py (original) +++ py/dist/py/magic/autopath_test.py Sat Oct 2 23:13:42 2004 @@ -1,11 +1,11 @@ -import std +import py import sys -from std.__impl__.path.test.setuptestfs import setuptestfs +from py.__impl__.path.test.setuptestfs import setuptestfs class TestAutoPath: - getauto = "from std.magic import autopath ; autopath = autopath()" + getauto = "from py.magic import autopath ; autopath = autopath()" def __init__(self): - self.root = std.utest.config.tmpdir.ensure('autoconfigure', dir=1) + self.root = py.test.config.tmpdir.ensure('autoconfigure', dir=1) self.initdir = self.root.ensure('pkgdir', dir=1) self.initdir.ensure('__init__.py') self.initdir2 = self.initdir.ensure('initdir2', dir=1) @@ -64,7 +64,7 @@ sys.argv = [''] oldsyspath = sys.path[:] try: - std.utest.raises(ValueError, ''' + py.test.raises(ValueError,''' d = {} exec self.getauto in d ''') @@ -72,4 +72,4 @@ sys.path[:] = oldsyspath sys.argv = sys.argv -std.utest.main() +py.test.main() Modified: py/dist/py/magic/dyncode.py ============================================================================== --- py/dist/py/magic/dyncode.py (original) +++ py/dist/py/magic/dyncode.py Sat Oct 2 23:13:42 2004 @@ -14,7 +14,7 @@ import linecache import __builtin__ from __builtin__ import compile as oldcompile -from std import magic +from py import magic def gettb(tb, n=-1): """ return the n'th traceback. """ Modified: py/dist/py/magic/dyncode_test.py ============================================================================== --- py/dist/py/magic/dyncode_test.py (original) +++ py/dist/py/magic/dyncode_test.py Sat Oct 2 23:13:42 2004 @@ -1,8 +1,8 @@ import sys import os #print "dyncode_test: __name__ ==", __name__ -from std import utest -from std.__impl__.magic import dyncode +from py import test +from py.__impl__.magic import dyncode def test_dyncode_trace(): source = """ @@ -11,7 +11,7 @@ """ co = dyncode.compile2(source) exec co - excinfo = utest.raises(ValueError, f) + excinfo = test.raises(ValueError, f) filename, lineno = dyncode.tbinfo(excinfo[2]) line = dyncode.getline(filename, lineno) assert line.strip() == 'raise ValueError' @@ -26,7 +26,7 @@ assert fn1 != fn2 def test_syntaxerror_rerepresentation(): - ex = utest.raises(SyntaxError, dyncode.compile2, 'x x')[1] + ex = test.raises(SyntaxError, dyncode.compile2, 'x x')[1] assert ex.lineno == 1 assert ex.offset == 3 assert ex.text.strip(), 'x x' @@ -74,5 +74,5 @@ assert len(l) == 4 if __name__ == '__main__': - utest.main() + test.main() Modified: py/dist/py/magic/exprinfo.py ============================================================================== --- py/dist/py/magic/exprinfo.py (original) +++ py/dist/py/magic/exprinfo.py Sat Oct 2 23:13:42 2004 @@ -1,6 +1,6 @@ from compiler import parse, ast, pycodegen -from std import magic -from std.__impl__.magic import dyncode +from py import magic +from py.__impl__.magic import dyncode import __builtin__, sys passthroughex = (KeyboardInterrupt, SystemExit, MemoryError) Modified: py/dist/py/magic/exprinfo_test.py ============================================================================== --- py/dist/py/magic/exprinfo_test.py (original) +++ py/dist/py/magic/exprinfo_test.py Sat Oct 2 23:13:42 2004 @@ -1,7 +1,7 @@ import sys -from std.utest import main -from std.__impl__.magic.exprinfo import getmsg, interpret +from py.test import main +from py.__impl__.magic.exprinfo import getmsg, interpret def getexcinfo(exc, obj, *args, **kwargs): try: Modified: py/dist/py/magic/invoke.py ============================================================================== --- py/dist/py/magic/invoke.py (original) +++ py/dist/py/magic/invoke.py Sat Oct 2 23:13:42 2004 @@ -13,18 +13,18 @@ a useful error message. """ if dyncode: - from std.__impl__.magic import dyncode + from py.__impl__.magic import dyncode dyncode.invoke() if assertion: - from std.__impl__.magic import assertion + from py.__impl__.magic import assertion assertion.invoke() def revoke(dyncode=False, assertion=False): """ revoke previously invoked magic (see invoke()).""" if dyncode: - from std.__impl__.magic import dyncode + from py.__impl__.magic import dyncode dyncode.revoke() if assertion: - from std.__impl__.magic import assertion + from py.__impl__.magic import assertion assertion.revoke() Modified: py/dist/py/magic/invoke_test.py ============================================================================== --- py/dist/py/magic/invoke_test.py (original) +++ py/dist/py/magic/invoke_test.py Sat Oct 2 23:13:42 2004 @@ -1,6 +1,6 @@ import __builtin__ as bltin -from std.magic import invoke, revoke -from std.utest import raises +from py.magic import invoke, revoke +from py.test import raises import inspect def check_dyncode(): Modified: py/dist/py/magic/patch_test.py ============================================================================== --- py/dist/py/magic/patch_test.py (original) +++ py/dist/py/magic/patch_test.py Sat Oct 2 23:13:42 2004 @@ -1,5 +1,5 @@ -from std.utest import raises -from std.magic import patch, revert +from py.test import raises +from py.magic import patch, revert def test_patch_revert(): class a: Modified: py/dist/py/magic/viewtype_test.py ============================================================================== --- py/dist/py/magic/viewtype_test.py (original) +++ py/dist/py/magic/viewtype_test.py Sat Oct 2 23:13:42 2004 @@ -1,5 +1,5 @@ -from std import utest -from std.magic import View +from py import test +from py.magic import View def test_class_dispatch(): ### Use a custom class hierarchy with existing instances @@ -55,5 +55,5 @@ codelines = [PyOp(op).generate() for op in existing] assert codelines == ["4 + 5", "getitem('', 'join')", "setattr('x', 'y', 3)", "12 - 1"] -utest.main() +test.main() Modified: py/dist/py/path/api_test.py ============================================================================== --- py/dist/py/path/api_test.py (original) +++ py/dist/py/path/api_test.py Sat Oct 2 23:13:42 2004 @@ -1,13 +1,13 @@ -from std import path, utest -from std.__impl__.path.svnwc.command_test import getrepowc +from py import path, test +from py.__impl__.path.svnwc.command_test import getrepowc class TestAPI: def __init__(self): - self.root = utest.config.tmpdir.ensure('local', dir=1) + self.root = test.config.tmpdir.ensure('local', dir=1) def repr_eval_test(self, p): r = repr(p) - from std.path import local,svnurl, svnwc, py + from py.path import local,svnurl, svnwc, py y = eval(r) assert y == p @@ -33,7 +33,7 @@ self.repr_eval_test(p) def test_svnurl(self): - p = path.svnurl('http://codespeak.net/svn/std.path') + p = path.svnurl('http://codespeak.net/svn/py.path') assert p.check(svnurl=1) self.repr_eval_test(p) @@ -55,5 +55,5 @@ if __name__ == '__main__': - utest.main() + test.main() Modified: py/dist/py/path/common.py ============================================================================== --- py/dist/py/path/common.py (original) +++ py/dist/py/path/common.py Sat Oct 2 23:13:42 2004 @@ -4,7 +4,7 @@ """ from __future__ import generators import sys -from std import path +from py import path def checktype(pathinstance, kw): names = ('local', 'svnwc', 'svnurl', 'py') @@ -98,7 +98,7 @@ class PathBase(object): """ shared implementation for filesystem path objects.""" Checkers = Checkers - from std.path import NotFound + from py.path import NotFound def check(self, **kw): if kw: Modified: py/dist/py/path/local/api.py ============================================================================== --- py/dist/py/path/local/api.py (original) +++ py/dist/py/path/local/api.py Sat Oct 2 23:13:42 2004 @@ -1,7 +1,7 @@ """ Tool functions regarding local filesystem paths """ -from std import path +from py import path def get_temproot(): """ return the system's temporary directory (where tempfiles are usually created in)""" Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Sat Oct 2 23:13:42 2004 @@ -6,14 +6,14 @@ """ import sys, os, stat -from std import path -from std.__impl__.path import error -from std.__impl__.path import common +from py import path +from py.__impl__.path import error +from py.__impl__.path import common if sys.platform == 'win32': - from std.__impl__.path.local._win import WinMixin as PlatformMixin + from py.__impl__.path.local._win import WinMixin as PlatformMixin else: - from std.__impl__.path.local._posix import PosixMixin as PlatformMixin + from py.__impl__.path.local._posix import PosixMixin as PlatformMixin class LocalPath(common.PathBase, PlatformMixin): """ the fully specialized local path implementation. Modified: py/dist/py/path/local/local_test.py ============================================================================== --- py/dist/py/path/local/local_test.py (original) +++ py/dist/py/path/local/local_test.py Sat Oct 2 23:13:42 2004 @@ -1,8 +1,8 @@ import sys, os -from std.utest import main, raises, config -from std.path import local, checker -from std.__impl__.path.test._common import CommonFSTests -from std.__impl__.path.test.setuptestfs import setuptestfs +from py.test import main, raises, config +from py.path import local, checker +from py.__impl__.path.test._common import CommonFSTests +from py.__impl__.path.test.setuptestfs import setuptestfs class TestLocalPath(CommonFSTests): def __init__(self): Modified: py/dist/py/path/pypath/pypath.py ============================================================================== --- py/dist/py/path/py/pypath.py (original) +++ py/dist/py/path/pypath/pypath.py Sat Oct 2 23:13:42 2004 @@ -5,9 +5,9 @@ for the first stable release! """ from __future__ import generators -from std import path +from py import path -from std.__impl__.path import common +from py.__impl__.path import common import sys, os import inspect, imp Modified: py/dist/py/path/pypath/pypath_test.py ============================================================================== --- py/dist/py/path/py/pypath_test.py (original) +++ py/dist/py/path/pypath/pypath_test.py Sat Oct 2 23:13:42 2004 @@ -1,6 +1,6 @@ import sys, os -from std import path, utest -from std.__impl__.path.py import pypath +from py import path, test +from py.__impl__.path.pypath import pypath class TestPyPath: root = path.py(pypath.__name__) @@ -38,7 +38,7 @@ l = list(self.root.visit(path.checker(basename='PyPath'))) assert len(l) == 1 obj = l[0] - assert str(obj).endswith('path.py.pypath.PyPath') + assert str(obj).endswith('path.pypath.pypath.PyPath') assert obj.resolve() is PyPath def test_visit_fnmatch(self): @@ -46,7 +46,7 @@ l = list(self.root.visit('PyPath')) assert len(l) == 1 obj = l[0] - assert str(obj).endswith('path.py.pypath.PyPath') + assert str(obj).endswith('path.pypath.pypath.PyPath') assert obj.resolve() is PyPath def test_specified_base_empty_path(self): @@ -141,22 +141,22 @@ def test_invalid1(self): p = path.py('os.path.qwe("a", "b")') - s = utest.raises(path.NotFound, "p.resolve()") + s = test.raises(path.NotFound, "p.resolve()") def test_syntaxerror(self): p = path.py('os.path.qwe("a", ') - s = utest.raises(ValueError, "p.resolve()") + s = test.raises(ValueError, "p.resolve()") class ErrorTests: def test_FileNotFound(self): p = path.py('somesuch') - utest.raises(path.NotFound, p.resolve) + test.raises(path.NotFound, p.resolve) p = path.py('email.whatever') - utest.raises(path.NotFound, p.resolve) + test.raises(path.NotFound, p.resolve) def test_attributeerror(self): p = path.py('os.path.qabspath') - utest.raises(path.NotFound, p.resolve) + test.raises(path.NotFound, p.resolve) #def test_ImportError(): # p = path.py('__std.utest.test.data.failingimport.someattr') @@ -165,4 +165,4 @@ class ExampleClass: testattr = 1 -utest.main() +test.main() Modified: py/dist/py/path/svn/command.py ============================================================================== --- py/dist/py/path/svn/command.py (original) +++ py/dist/py/path/svn/command.py Sat Oct 2 23:13:42 2004 @@ -6,10 +6,10 @@ """ import os, sys, time, re -from std import path, process -from std.__impl__.path import common -from std.__impl__.path import error -from std.__impl__.path.svn import common as svncommon +from py import path, process +from py.__impl__.path import common +from py.__impl__.path import error +from py.__impl__.path.svn import common as svncommon class XSvnCommandPath(svncommon.SvnPathBase): def __repr__(self): Modified: py/dist/py/path/svn/command_test.py ============================================================================== --- py/dist/py/path/svn/command_test.py (original) +++ py/dist/py/path/svn/command_test.py Sat Oct 2 23:13:42 2004 @@ -1,7 +1,7 @@ import sys, os -from std import utest, path -from std.__impl__.path.test import _svncommon -from std.__impl__.path.svnwc.command_test import getrepowc +from py import test, path +from py.__impl__.path.test import _svncommon +from py.__impl__.path.svnwc.command_test import getrepowc class TestSvnCommandPath(_svncommon.CommonCommandAndBindingTests): def __init__(self): Modified: py/dist/py/path/svn/common.py ============================================================================== --- py/dist/py/path/svn/common.py (original) +++ py/dist/py/path/svn/common.py Sat Oct 2 23:13:42 2004 @@ -2,10 +2,10 @@ module with a base subversion path object. """ import os, sys, time, re -from std import path, process -from std.__impl__.path import error -from std.__impl__.path import common -from std.__impl__.path.svn import cache +from py import path, process +from py.__impl__.path import error +from py.__impl__.path import common +from py.__impl__.path.svn import cache #_______________________________________________________________ Modified: py/dist/py/path/svnwc/command.py ============================================================================== --- py/dist/py/path/svnwc/command.py (original) +++ py/dist/py/path/svnwc/command.py Sat Oct 2 23:13:42 2004 @@ -9,10 +9,10 @@ """ import os, sys, time, re -from std import path, process -from std.__impl__.path import common -from std.__impl__.path.svn import cache -from std.__impl__.path.svn import common as svncommon +from py import path, process +from py.__impl__.path import common +from py.__impl__.path.svn import cache +from py.__impl__.path.svn import common as svncommon DEBUG = 0 @@ -364,7 +364,7 @@ return self.svnwcpath in s.allpath(ignored=0,unknown=0, deleted=0) def log(self, rev_start=None, rev_end=1, verbose=False): - from std.__impl__.path.svn.command import _Head, LogEntry + from py.__impl__.path.svn.command import _Head, LogEntry assert self.check() # make it simpler for the pipe rev_start = rev_start is None and _Head or rev_start rev_end = rev_end is None and _Head or rev_end Modified: py/dist/py/path/svnwc/command_test.py ============================================================================== --- py/dist/py/path/svnwc/command_test.py (original) +++ py/dist/py/path/svnwc/command_test.py Sat Oct 2 23:13:42 2004 @@ -1,16 +1,16 @@ import sys, os -from std import utest, path, process -from std.__impl__.path.test._svncommon import CommonSvnTests -from std.__impl__.path.test.setuptestfs import setuptestfs +from py import test, path, process +from py.__impl__.path.test._svncommon import CommonSvnTests +from py.__impl__.path.test.setuptestfs import setuptestfs # make a wc directory out of a given root url # cache previously obtained wcs! # -tmpdir = path.local.make_numbered_dir(base='std.pathtestwc-') +tmpdir = path.local.make_numbered_dir(base='py.pathtestwc-') def getrepowc(): - repo = utest.config.tmpdir / 'path' / 'repo' - wcdir = utest.config.tmpdir / 'path' / 'wc' + repo = test.config.tmpdir / 'path' / 'repo' + wcdir = test.config.tmpdir / 'path' / 'wc' if not repo.check(): assert not wcdir.check() repo.ensure(dir=1) @@ -18,7 +18,7 @@ process.cmdexec('svnadmin create %s' % repo) except process.cmdexec.Error: repo.remove() - raise utest.run.Skipped(msg='could not create temporary svn test repository') + raise test.run.Skipped(msg='could not create temporary svn test repository') wcdir.ensure(dir=1) print "created svn repository", repo wc = path.svnwc(wcdir, url='file://%s' % repo) @@ -201,7 +201,7 @@ class TestWCSvnCommandPathSpecial: disabled = True - rooturl = 'http://codespeak.net/svn/std.path/trunk/dist/std.path/test/data' + rooturl = 'http://codespeak.net/svn/py.path/trunk/dist/py.path/test/data' #def test_update_none_rev(self): # path = tmpdir.join('checkouttest') # wcpath = newpath(xsvnwc=str(path), url=self.rooturl) @@ -213,4 +213,4 @@ # wcpath.localpath.remove(rec=1) if __name__ == '__main__': - utest.main() + test.main() Modified: py/dist/py/path/test/_common.py ============================================================================== --- py/dist/py/path/test/_common.py (original) +++ py/dist/py/path/test/_common.py Sat Oct 2 23:13:42 2004 @@ -1,4 +1,4 @@ -from std.path import checker, NotFound +from py.path import checker, NotFound class CommonFSTests: root = None # subclasses have to provide a current 'root' attribute Modified: py/dist/py/path/test/_svncommon.py ============================================================================== --- py/dist/py/path/test/_svncommon.py (original) +++ py/dist/py/path/test/_svncommon.py Sat Oct 2 23:13:42 2004 @@ -1,9 +1,9 @@ import sys, os -from std import path, utest, process -from std.__impl__.path.test import _common -from std.__impl__.path.test import _common -from std.__impl__.path.svn import cache +from py import path, test, process +from py.__impl__.path.test import _common +from py.__impl__.path.test import _common +from py.__impl__.path.svn import cache class CommonSvnTests(_common.CommonFSTests): Modified: py/dist/py/process/cmdexec_test.py ============================================================================== --- py/dist/py/process/cmdexec_test.py (original) +++ py/dist/py/process/cmdexec_test.py Sat Oct 2 23:13:42 2004 @@ -1,5 +1,5 @@ -from std import utest -from std.process import cmdexec +from py import test +from py.process import cmdexec class Test_exec_cmd: def test_simple(self): @@ -7,7 +7,7 @@ assert out.strip() == 'hallo' def test_simple_error(self): - utest.raises (cmdexec.Error, cmdexec, 'exit 1') + test.raises (cmdexec.Error, cmdexec, 'exit 1') def test_simple_error_exact_status(self): try: @@ -23,4 +23,4 @@ assert hasattr(e, 'err') assert hasattr(e, 'out') assert e.err or e.out -utest.main() +test.main() Modified: py/dist/py/test/cmdline.py ============================================================================== --- py/dist/py/utest/cmdline.py (original) +++ py/dist/py/test/cmdline.py Sat Oct 2 23:13:42 2004 @@ -1,6 +1,6 @@ from __future__ import generators -from std import utest, path -from std.utest import config +from py import test, path +from py.test import config import os, sys # @@ -18,7 +18,7 @@ if name != '__main__': return # called from an imported test file #raise RuntimeError, "Must provide 'argv' parameter" - collectors.append(utest.collect.Module('__main__')) + collectors.append(test.collect.Module('__main__')) args = argv[1:] for x in getanchors(args): config.readconfiguration(x) @@ -28,10 +28,10 @@ filenames = config.parseargs(args) collectors.extend(getcollectors(filenames)) if not collectors: - collectors.append(utest.collect.Directory(path.local())) + collectors.append(test.collect.Directory(path.local())) reporter = config.reporter - runner = utest.run.Runner(reporter) + runner = test.run.Runner(reporter) runner.setup() try: try: @@ -39,7 +39,7 @@ try: for collector in collectors: runner.run(collector) - except utest.run.Exit: + except test.run.Exit: pass finally: runner.teardown() @@ -57,9 +57,9 @@ for fn in filenames: fullfn = current.join(fn, abs=1) if fullfn.check(file=1): - yield utest.collect.Execfile(fullfn) + yield test.collect.Execfile(fullfn) elif fullfn.check(dir=1): - yield utest.collect.Directory(fullfn) + yield test.collect.Directory(fullfn) else: raise RuntimeError, "%r does not exist" % fn yielded = True Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/utest/collect.py (original) +++ py/dist/py/test/collect.py Sat Oct 2 23:13:42 2004 @@ -1,7 +1,7 @@ from __future__ import generators import sys, inspect, types, imp -from std import path, utest +from py import path, test class Error(object): """ represents a non-fatal exception while collecting. """ @@ -21,7 +21,7 @@ """ instances are *restartable iterators*. They will yield Units or Collector instances during iteration. """ - Unit = utest.run.Unit + Unit = test.run.Unit def iterunits(self): """ yield all units of the Collector instance. """ Modified: py/dist/py/test/collect_test.py ============================================================================== --- py/dist/py/utest/collect_test.py (original) +++ py/dist/py/test/collect_test.py Sat Oct 2 23:13:42 2004 @@ -1,43 +1,43 @@ from __future__ import generators -from std import utest, path -from std.magic import autopath ; autopath = autopath() +from py import test, path +from py.magic import autopath ; autopath = autopath() testdir = autopath.thisdir / 'test' assert testdir.check(dir=1) datadir = testdir / 'data' def test_failing_import_execfile(): fn = datadir / 'failingimport.py' - l = list(utest.collect.Execfile(fn)) + l = list(test.collect.Execfile(fn)) assert l ex, = l assert issubclass(ex.excinfo[0], ImportError) def test_failing_import_directory(): - class MyDirectory(utest.collect.Directory): + class MyDirectory(test.collect.Directory): fil = path.checker(basestarts="testspecial_", ext='.py') l = list(MyDirectory(datadir)) assert len(l) == 1 - assert isinstance(l[0], utest.collect.Execfile) + assert isinstance(l[0], test.collect.Execfile) l2 = list(l[0]) assert l2 exc = l2[0] - assert isinstance(exc, utest.collect.Error) + assert isinstance(exc, test.collect.Error) assert issubclass(exc.excinfo[0], ImportError) def test_execfile_file_not_found(): fn = testdir.join('nada','no') - l = list(utest.collect.Execfile(fn)) + l = list(test.collect.Execfile(fn)) assert len(l) == 1 - assert isinstance(l[0], utest.collect.Error) + assert isinstance(l[0], test.collect.Error) import traceback print traceback.print_exception(*l[0].excinfo) assert issubclass(l[0].excinfo[0], (IOError, OSError)) def test_syntax_error_in_module(): - modpath = 'std.__impl__.utest.test.data.syntax_error.whatever' - l2 = list(utest.collect.Module(modpath)) + modpath = 'py.__impl__.utest.test.data.syntax_error.whatever' + l2 = list(test.collect.Module(modpath)) assert len(l2) == 1 - assert isinstance(l2[0], utest.collect.Error) + assert isinstance(l2[0], test.collect.Error) assert issubclass(l2[0].excinfo[0], path.Invalid) def test_disabled_class(): @@ -45,15 +45,15 @@ class y: disabled = True def test_method(self): pass - l = list(utest.collect.Class(path.py('', ns=x))) + l = list(test.collect.Class(path.py('', ns=x))) assert not l class TestCustomCollector: def test_custom_collect(self): - l = list(utest.collect.Execfile(datadir.join('Collector.py'))) + l = list(test.collect.Execfile(datadir.join('Collector.py'))) assert len(l) == 3 for unit in l: - assert isinstance(unit, utest.Unit) + assert isinstance(unit, test.Unit) #for x in l2: # assert isinstance(x, Unit) # x.execute() @@ -81,4 +81,4 @@ def test_4(self): assert mygroup.reslist == [1,2,3] -utest.main() +test.main() Modified: py/dist/py/test/compat.py ============================================================================== --- py/dist/py/utest/compat.py (original) +++ py/dist/py/test/compat.py Sat Oct 2 23:13:42 2004 @@ -1,7 +1,7 @@ from __future__ import generators -from std import utest, magic +from py import test, magic -class TestCaseUnit(utest.run.Unit): +class TestCaseUnit(test.run.Unit): """ compatibility Unit executor for TestCase methods honouring setUp and tearDown semantics. """ @@ -14,7 +14,7 @@ unboundmethod(instance) finally: instance.tearDown() - return utest.run.Passed() + return test.run.Passed() class TestCase: """compatibility class of unittest's TestCase. """ @@ -28,10 +28,10 @@ def fail(self, msg=None): """ fail immediate with given message. """ - raise utest.run.Failed(msg=msg) + raise test.run.Failed(msg=msg) def assertRaises(self, excclass, func, *args, **kwargs): - utest.raises(excclass, func, *args, **kwargs) + test.raises(excclass, func, *args, **kwargs) failUnlessRaises = assertRaises # dynamically construct (redundant) methods @@ -49,7 +49,7 @@ items.append(""" def %(name)s(self, %(sig)s): if %(expr)s: - raise utest.run.Failed(tbindex=-2, msg=%(sigsubst)r %% (%(sig)s)) + raise test.run.Failed(tbindex=-2, msg=%(sigsubst)r %% (%(sig)s)) """ % locals() ) source = "".join(items) Modified: py/dist/py/test/compat_test.py ============================================================================== --- py/dist/py/utest/compat_test.py (original) +++ py/dist/py/test/compat_test.py Sat Oct 2 23:13:42 2004 @@ -1,7 +1,7 @@ from __future__ import generators -from std import utest, magic +from py import test, magic -class CompatTestCaseSetupSemantics(utest.compat.TestCase): +class CompatTestCaseSetupSemantics(test.compat.TestCase): globlist = [] def setUp(self): @@ -27,7 +27,7 @@ for x,y in zip(self.globlist, self.globlist[1:]): assert x is not y -class DynTestCase(utest.compat.TestCase): +class DynTestCase(test.compat.TestCase): nameparamdef = { 'failUnlessEqual,assertEqual,assertEquals': ('1, 1', '1, 0'), 'assertNotEquals,failIfEqual': ('0, 1', '0,0'), @@ -44,10 +44,10 @@ #self.%(name)s(%(paramfail)s) def test_%(name)s_failing(self): - self.assertRaises(utest.run.Failed, + self.assertRaises(test.run.Failed, self.%(name)s, %(paramfail)s) """ % locals() co = magic.dyncode.compile2(source) exec co -utest.main() +test.main() Modified: py/dist/py/test/config.py ============================================================================== --- py/dist/py/utest/config.py (original) +++ py/dist/py/test/config.py Sat Oct 2 23:13:42 2004 @@ -1,6 +1,6 @@ from __future__ import generators -from std import utest, path -from std.__impl__.utest.tool import optparse +from py import test, path +from py.__impl__.test.tool import optparse import os, sys, new dummy = object() @@ -16,14 +16,14 @@ self.option = optparse.Values() def getoptions(self): - Option = utest.Option + Option = test.Option return ('utest standard options', [ Option('-c', '--collectonly', action="store_true", dest="collectonly", default=False, help="only collect tests, don't execute them. "), Option('-S', '--nocapture', action="store_true", dest="nocapture", default=False, - help="disable catching of sys.stdout/stderr output."), + help="disable catching of sys.pyout/pyerr output."), Option('-v', '--verbose', action="count", dest="verbose", default=0, help="increase verbosity"), @@ -119,7 +119,7 @@ try: return self._reporter except AttributeError: - self._reporter = utest.TextReporter() + self._reporter = test.TextReporter() return self._reporter reporter = property(_getreporter, None, None) Modified: py/dist/py/test/config_test.py ============================================================================== --- py/dist/py/utest/config_test.py (original) +++ py/dist/py/test/config_test.py Sat Oct 2 23:13:42 2004 @@ -1,6 +1,6 @@ from __future__ import generators -from std import utest -from std.utest import config +from py import test +from py.test import config class MyClass: def getoptions(self): @@ -27,4 +27,4 @@ d2 = config.tmpdir assert d1 == d2 -utest.main() +test.main() Modified: py/dist/py/test/raises.py ============================================================================== --- py/dist/py/utest/raises.py (original) +++ py/dist/py/test/raises.py Sat Oct 2 23:13:42 2004 @@ -1,5 +1,5 @@ import sys -from std import utest, magic +from py import test, magic def raises(ExpectedException, *args, **kwargs): """ raise AssertionError, if target code does not raise the expected @@ -23,7 +23,7 @@ excinfo = sys.exc_info() else: excinfo = None - raise utest.run.ExceptionFailure(expr=expr, expected=ExpectedException, + raise test.run.ExceptionFailure(expr=expr, expected=ExpectedException, innerexcinfo=excinfo, tbindex = -2) else: func = args[0] @@ -40,5 +40,5 @@ if k: k = ', ' + k expr = '%s(%r%s)' %(func.__name__, args, k) - raise utest.run.ExceptionFailure(expr=args, expected=ExpectedException, + raise test.run.ExceptionFailure(expr=args, expected=ExpectedException, innerexcinfo=excinfo, tbindex = -2) Modified: py/dist/py/test/raises_test.py ============================================================================== --- py/dist/py/utest/raises_test.py (original) +++ py/dist/py/test/raises_test.py Sat Oct 2 23:13:42 2004 @@ -1,17 +1,17 @@ -from std import utest +from py import test def somefunc(x, y): assert x == y class classes_are_arbitrary_grouping: def test_raises(self): - utest.raises(ValueError, "int('qwe')") + test.raises(ValueError, "int('qwe')") def test_raises_syntax_error(self): - utest.raises(SyntaxError, "qwe qwe qwe") + test.raises(SyntaxError, "qwe qwe qwe") def test_raises_function(self): - utest.raises(ValueError, int, 'hello') + test.raises(ValueError, int, 'hello') -utest.main() +test.main() Modified: py/dist/py/test/report/memo.py ============================================================================== --- py/dist/py/utest/report/memo.py (original) +++ py/dist/py/test/report/memo.py Sat Oct 2 23:13:42 2004 @@ -1,9 +1,9 @@ from __future__ import generators import sys import os -from std import utest +from py import test -run = utest.run +run = test.run class MemoReporter: typemap = { Modified: py/dist/py/test/report/memo_test.py ============================================================================== --- py/dist/py/utest/report/memo_test.py (original) +++ py/dist/py/test/report/memo_test.py Sat Oct 2 23:13:42 2004 @@ -1,6 +1,6 @@ -from std import utest, path +from py import test, path -datadir = utest.config.tmpdir +datadir = test.config.tmpdir #def test_equal_should_raise(): # check.equal(1,2) @@ -10,14 +10,14 @@ # try: # def test_memoreporter(): - reporter = utest.MemoReporter() + reporter = test.MemoReporter() p = datadir.join('memoimport.py') p.write('raise IOError') - collector = utest.collect.Execfile(p) + collector = test.collect.Execfile(p) #main(collector=collector, reporter=reporter) #collect_errors = reporter.getlist(collect.Error) #assert len(collect_errors) == 1 ##print collect_errors if __name__=='__main__': - utest.main() + test.main() Modified: py/dist/py/test/report/text/reporter.py ============================================================================== --- py/dist/py/utest/report/text/reporter.py (original) +++ py/dist/py/test/report/text/reporter.py Sat Oct 2 23:13:42 2004 @@ -5,9 +5,9 @@ import traceback from time import time as now -from std import utest, magic -from std.utest import collect, run -from std.magic import dyncode +from py import test, magic +from py.test import collect, run +from py.magic import dyncode # lazy relative Implementation imports from summary import Summary @@ -30,7 +30,7 @@ self.out = getout(f) self._started = {} self.summary = self.Summary() - self.summary.option = self.option = utest.config.option + self.summary.option = self.option = test.config.option def start(self, conf=None): self.summary.starttime = now() @@ -95,7 +95,7 @@ def startunit(self, unit): if not self.option.nocapture: - from std.__impl__.utest.tool.outerrcapture import SimpleOutErrCapture + from py.__impl__.test.tool.outerrcapture import SimpleOutErrCapture unit.iocapture = SimpleOutErrCapture() if self.out.tty: realpath, lineno = unit.pypath.getfilelineno() Modified: py/dist/py/test/report/text/summary.py ============================================================================== --- py/dist/py/utest/report/text/summary.py (original) +++ py/dist/py/test/report/text/summary.py Sat Oct 2 23:13:42 2004 @@ -3,9 +3,9 @@ import os import traceback -from std.utest import collect, run -from std.magic import dyncode -from std.__impl__.magic import exprinfo, assertion +from py.test import collect, run +from py.magic import dyncode +from py.__impl__.magic import exprinfo, assertion class Summary(object): def __init__(self): @@ -98,10 +98,10 @@ pass else: if out.strip(): - self.out.sep("- ", "recorded stdout") + self.out.sep("- ", "recorded pyout") self.out.line(out.strip()) if err.strip(): - self.out.sep("- ", "recorded stderr") + self.out.sep("- ", "recorded pyerr") self.out.line(err.strip()) #self.out.line() Modified: py/dist/py/test/run.py ============================================================================== --- py/dist/py/utest/run.py (original) +++ py/dist/py/test/run.py Sat Oct 2 23:13:42 2004 @@ -1,10 +1,10 @@ from __future__ import generators import sys, inspect -from std import utest +from py import test class Runner: - option = utest.config.option + option = test.config.option def __init__(self, reporter): self.reporter = reporter @@ -13,7 +13,7 @@ def run(self, obj): """ run (possibly many) testitems and/or collectors. """ - collect = utest.collect + collect = test.collect if isinstance(obj, Unit): if not self.option.collectonly: #if self.option.args: Modified: py/dist/py/test/test/data/Collector.py ============================================================================== --- py/dist/py/utest/test/data/Collector.py (original) +++ py/dist/py/test/test/data/Collector.py Sat Oct 2 23:13:42 2004 @@ -1,5 +1,5 @@ -from std.utest import collect +from py.test import collect class Collector(collect.PyCollector): def collect_function(self, pypath): Modified: py/dist/py/test/test/demo.py ============================================================================== --- py/dist/py/utest/test/demo.py (original) +++ py/dist/py/test/test/demo.py Sat Oct 2 23:13:42 2004 @@ -1,4 +1,4 @@ -from std.utest import raises, main +from py.test import raises, main def otherfunc(a,b): assert a==b Modified: py/dist/py/test/tool/optparse.py ============================================================================== --- py/dist/py/utest/tool/optparse.py (original) +++ py/dist/py/test/tool/optparse.py Sat Oct 2 23:13:42 2004 @@ -69,7 +69,7 @@ import sys, os import types -from std.__impl__.utest.tool import textwrap +from py.__impl__.test.tool import textwrap class OptParseError (Exception): def __init__ (self, msg): Modified: py/dist/py/test/tool/outerrcapture_test.py ============================================================================== --- py/dist/py/utest/tool/outerrcapture_test.py (original) +++ py/dist/py/test/tool/outerrcapture_test.py Sat Oct 2 23:13:42 2004 @@ -1,6 +1,6 @@ import sys -from std import utest -from std.__impl__.utest.tool.outerrcapture import SimpleOutErrCapture +from py import test +from py.__impl__.test.tool.outerrcapture import SimpleOutErrCapture def test_capturing_simple(): cap = SimpleOutErrCapture() @@ -14,13 +14,13 @@ cap = SimpleOutErrCapture() print "hello" cap.reset() - utest.raises(AttributeError, "cap.reset()") + test.raises(AttributeError, "cap.reset()") def test_capturing_error_recursive(): cap = SimpleOutErrCapture() cap2 = SimpleOutErrCapture() print "hello" cap2.reset() - utest.raises(AttributeError, "cap2.reset()") + test.raises(AttributeError, "cap2.reset()") -utest.main() +test.main() From hpk at codespeak.net Sun Oct 3 12:49:39 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 3 Oct 2004 12:49:39 +0200 (MEST) Subject: [py-svn] r6832 - py/dist/doc Message-ID: <20041003104939.6125C5A910@thoth.codespeak.net> Author: hpk Date: Sun Oct 3 12:49:38 2004 New Revision: 6832 Added: py/dist/doc/execnet.txt Log: a draft in pseudo-rest for a document describing the py.execnet library. Armin, you wanted to have a look at the py.execnet stuff and i think looking into this document and improving it is the way to go (apart from looking what is already there). Added: py/dist/doc/execnet.txt ============================================================================== --- (empty file) +++ py/dist/doc/execnet.txt Sun Oct 3 12:49:38 2004 @@ -0,0 +1,160 @@ +The py.execnet library 0.8.0 +============================ + +Execnet deals with letting your python programs execute and +communicate across process and computer barriers. At the +core it is a very simple and powerful mechanism: executing +source code at "the other side". The other side is reachable +through Gateways, currently + +- py.execnet.PopenGateway for executing code within a child process + on the same computer + +- py.execnet.SocketGateway for executing code across a socket connection. + +For now, see the py/gateway/gateway_test.py file for how it works +currently. + +High Level Interface for executing code +--------------------------------------- + +These gateways offer the following "high level interface" + + [[docfunc(py.__impl__.execnet.gateway.Gateway.remote_exec_oneshot)]] + + [[docfunc(py.__impl__.execnet.gateway.Gateway.remote_exec_async)]] + + [[docfunc(py.__impl__.execnet.gateway.Gateway.remote_exec_sync)]] + + [[docfunc(py.__impl__.execnet.gateway.Gateway.remote_import)]] + +(editors note: the mechanism to substitute those "docfunc" calls +is not there yet, but i want such documentation to be automatically +connected to the real code, showing its docstrings etc.pp) + +The most important property of execnet gateways is that +the protocol they speak with each other ``are determined by +the client side``. If you open a gateway on some server +in order to coordinate with some other programs you +determine your own protocol, not affecting all the others. +Multiple clients can run their own gateway versions in +the same remote process (e.g. connecting through their +version of a SocketGateway). + +You should not need to maintain software on the other sides +you are running your code at. + +... + +(Proposed "Channel" VHL interface for exchanging data) +------------------------------------------------------ + +While executing custom strings on "the other side" is simple enough +it is often tricky to deal with. Therefore we want a way +to send data items to and fro between the distributedly running +program. The idea is to inject a Channel object for each +execution of source code. This Channel object allows two +program parts to send data to each other. + +Here is the rough idea for the interface: + + channel.gateway # the gateway through which this channel operates + + channel.send(*items, timeout=None): + sends the given items to the other side of the channel, + possibly blocking if the sender queue is full. + Note that each value V of the items needs to have the + following property (all basic types in python have it): + eval(repr(V)) == V. + + channel.receive(timeout=None): + receives an item that was sent from the other side, + possibly blocking if there is none. + + channel.set(**namevaluepairs): + set the given namevaluepairs on the channel object. + These bindings will appear on both sides of the channel. + + channel.get(name, ignore=_dummy, timeout=None): + return the value from a name binding, possibly blocking + until it appears. If ignore is provided then its value + will be ignored and the function will only return if + a different value is received. Obviously, set/get can be + used for sending named signals to each other. + + channel.subscribe(itemcallback=None, namevaluecallback=None): + register the given callbacks. itemcallback will be called + with every received item. namevaluecallback will be called + with every received set of namevaluepairs as keyword arguments. + You can subscribe multiple callbacks which will be executed + in the order of their subscription. Note that the execution + of callbacks is serialized and all callbacks will run in the + same thread. + + channel.unsubscribe(itemcallback=None, namevaluecallback=None): + unregister the given callback from their subscription. + If a callback is registered multiple times all instances + will be deleted. + + + +An Example for Channels +----------------------- + +The following proposed example opens a PopenGateway, i.e. a +child process, starts a socket server within that process and +then opens a SocketGateway to the freshly started +socketserver. The "startserver.py" is a small script that +basically listens and accepts socket connections, receives one +liners and executes them. This socketserver stuff is supposed +to never change. Probably it should be a script, callable like +py.execnet.socketserver from the command line. + + import py + socketserverbootstrap = py.code.Source( + py.script.getpath('py.execnet.socketserver').read() + """ + import socket + portrange = channel.get("portrange") + for i in portrange: + try: + sock = listen(("localhost", i)) + except socket.error: + continue + else: + channel.set(listenport=i) + startserver(sock) + print "started server with socket" + break + else: + channel.set(listenport=None) + """) + + # open a gateway to a fresh child process + proxygw = py.execnet.PopenGateway() + + # execute asynchronously the above socketserverbootstrap on the other + channel = proxygw.remote_exec_async(socketserverbootstrap) + + # send parameters for the for-loop + channel.set(portrange=(7770, 7800)) + # + # the other side should start the for loop now, we + # wait for the result + # + listenport = channel.get('listenport', timeout=3) + if listenport is None: + raise IOError, "could not setup remote SocketServer" + + # open another gateway to the freshly installed socket server + socketgw = py.execnet.SocketGateway('localhost', listenport) + print "initialized socket gateway on port", listenport + + # later you can exit / close down the gateways + socketgw.exit() + proxygw.exit() + +the example is slightly futuristic but apart from the +channel interface the code for the above functionality +is there. + From hpk at codespeak.net Sun Oct 3 22:33:33 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 3 Oct 2004 22:33:33 +0200 (MEST) Subject: [py-svn] r6833 - py/dist/doc Message-ID: <20041003203333.A5F1A5A910@thoth.codespeak.net> Author: hpk Date: Sun Oct 3 22:33:32 2004 New Revision: 6833 Added: py/dist/doc/test.txt Log: a first draft of some documentation about the py.test part. Added: py/dist/doc/test.txt ============================================================================== --- (empty file) +++ py/dist/doc/test.txt Sun Oct 3 22:33:32 2004 @@ -0,0 +1,206 @@ +============================ +The py.test tool and library +============================ + +This is very much a draft version. + +The ``py.test`` command line tool +================================= + +``py.test`` is the command line tool to run tests. If you +provide it a file that contains functions or methods starting +with ``test_`` then it will run those methods. Assertions +about test outcomes are done via the standard ``assert`` +statement. + +The existence of a standalone ``py.test`` tool allows +to write tests without any boilerplate: + + def test_simple(): + assert 42 == 43 + +this would be the complete content of a test script +and you can simply execute the tests by invoking: + + py.test filename.py + +... + +Managing Test state across test modules, classes and methods +============================================================ + +Often you want to create some test files, db-connections or +other state. With ``py.test`` there are three scopes for +which you can provide hooks. These hooks will get called by +the runner: + + def setup_module(cls, module): + """ setup up any objects specific to the execution + of the given module. + """ + def teardown_module(cls, module): + """ teardown any objects that were previously setup + with a setup_module method. + """ + + def setup_class(cls): + """ setup up any objects specific to the execution + of the given class (which usually contains tests). + """ + def teardown_class(cls): + """ teardown any objects that were previously setup + with a call to setup_class + """ + + def setup_method(self, method): + """ setup up objects tied to the execution of the given + method in a class. setup_method is invoked for every + test method of a class. + """ + def teardown_method(self, method): + """ teardown any objects that were previously setup + with a setup_method call. + """ + +While the test runner guarantees that for every ``setup`` a +``teardown`` will be invoked (it exists) it does *not* guarantee +that it only happens once. For example, the runner might +decide to call the setup_module/teardown_module pair more than once +during the execution of a test module. + +Note also that all setup/teardown methods are optional. For +example, you can have a setup_module but no teardown_module +and the other way round. + + + +how to write assertions +----------------------- + +writing assertions is very simple in py.test and this +is one of its most noticeable features: + + You can simply use the ``assert`` statement. + +For example you can write: + + assert hasattr(x, 'something') + +and in case this fails the ``test reporter`` will provide +you with a very helpful analysis and a clean traceback. + +Please note that in order to display helpful analysis +of a failing ``assert`` expression some magic takes +place behind the scenes. For now, you only need to +know that if something looks strange or you suspect +a bug in that behind-the-scenes-magic you may turn +of the magic by providing the ``--nomagic`` option. + +In order to write assertions regarding exceptions, you use +one of two forms: + + py.test.assert_raises(Exception, func, *args, **kwargs) + py.test.assert_raises(Exception, "func(*args, **kwargs)") + +both of which executes the given function with args and kwargs +and asserts that the given ``Exception`` is raised. Again, +in case of the two possible failures (no exception or wrong +exception) the reporter provides you with helpful output. + + +The three components of ``py.test`` +=================================== + +In order to customize py.test you need to understand +the basic architure of ``py.test``: + + ___________________ + | | + | Collector | + |___________________| + / \ + | unit.execute(runner) + | / + collect/get units / + | /called for each + | / + ___________________/ ________________ + | | send events | | + | Driver |----------------------->| Reporter | + |___________________| |________________| + + ....................... + . "utest.conf" . + . cmdline options . + ....................... + + +The *Driver* basically receives test *Units* from *Collector*. +executes them via the ``Unit.execute()`` method, and takes the +outcome (possibly an exception) and sends it over to the +*reporter*. + +The test collection process +=========================== + +The collecting process is iterative, i.e. the driver +does not get a complete list of all test units but +can start immediately with the first tests that +were collected. + +Also, the collectors are acompletly separated from any +driving, execution or reporting details. Collectors +have an __iter__ method, yielding more (sub) collectors or +Test *Units*. They should usually not raise exceptions but +yield back a specific CollectError. This is to avoid that a +collecting error interrupts the whole collection process. + +Usually, ``py.test`` collects test files that match +the glob patterns ``test_*.py`` or ``*_test.py`` and +it recurses into any directory that doesn't start +with a leading dot (e.g. ``.svn`` is ignored). + +It then recurses into the test files and collects +functions and methods that have a leading ``test_`` name, +unless you provide a custom collector in your module. + +Customizing the collection process in a module +---------------------------------------------- + +If you have a module where you want to take responsibility for +collecting specific test units and possibly even executing a +test then you can provide your own ``Collector`` at module +level. The default ModuleCollector looks for the name +``Collector`` in the modules namespace and turns over +reponsibility by invoking it with the "module-path". + +The module path is a ``py.path.py()`` instance and carries +information needed to get to the module. Generally, a +pypath allows to address a python object in the filesystem. +It has two parts, a filesystem path and a dotted path leading +to the python object. Among other niceties, this allows +to easily invoke setup/teardown operations along the path +leading up to the python object. *Addressability of test Units* +is a major concern ... + +Customizing execution of Units +------------------------------ + +- Units allow total control of executing test methods + +- by providing custom collectors you can easily produce + your own unit class and instances + +- unitinstance.execute() gets called in order to execute a test + +- Unit.execute() methods can provide custom paramters + (a db-connection, some initialized application entity, whatever) + to the actual underlying method being invoked. + +- lift some of the arbitrary restrictions of unittest.py: allow + test functions and allow classes to just provide "grouping" for + tests. + + + + From hpk at codespeak.net Sun Oct 3 22:46:25 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 3 Oct 2004 22:46:25 +0200 (MEST) Subject: [py-svn] r6834 - py/dist/doc Message-ID: <20041003204625.54C875A910@thoth.codespeak.net> Author: hpk Date: Sun Oct 3 22:46:24 2004 New Revision: 6834 Modified: py/dist/doc/test.txt Log: - rename 'Unit' to 'Item' to avoid the ever-lasting confusion with unittest.py nomen clatura. This is so far only done in documentation but i intend to do it in the code as well. I don't want to explain all the time and all over the documentation how 'py.test' is different from unittest.py because i think that unittest.py's is pretty broken. Also 'Unit' is misleading because we want test Items to be unittests, bugtests, doctests and possibly many other things. For the same reason i introduced 'Driver' instead of the dreaded 'Runner' which everybody uses to refer to anything having to do with unittest.py's way of doing things, i.e. doing it all together and not separating any responsibilities. Sorry for the rant ... but let's not worry too much about unittest.py's idea of things. Modified: py/dist/doc/test.txt ============================================================================== --- py/dist/doc/test.txt (original) +++ py/dist/doc/test.txt Sun Oct 3 22:46:24 2004 @@ -119,10 +119,10 @@ | Collector | |___________________| / \ - | unit.execute(runner) - | / - collect/get units / - | /called for each + | Item.execute(runner) + | ^ + receive test Items / + | /execute test Item | / ___________________/ ________________ | | send events | | @@ -135,8 +135,8 @@ ....................... -The *Driver* basically receives test *Units* from *Collector*. -executes them via the ``Unit.execute()`` method, and takes the +The *Driver* basically receives test *Items* from *Collector*. +executes them via the ``Item.execute()`` method, and takes the outcome (possibly an exception) and sends it over to the *reporter*. @@ -144,14 +144,13 @@ =========================== The collecting process is iterative, i.e. the driver -does not get a complete list of all test units but -can start immediately with the first tests that -were collected. +get test Items one by one and can thus start to execute +the first collected test Item. Also, the collectors are acompletly separated from any driving, execution or reporting details. Collectors have an __iter__ method, yielding more (sub) collectors or -Test *Units*. They should usually not raise exceptions but +Test *Items*. They should usually not raise exceptions but yield back a specific CollectError. This is to avoid that a collecting error interrupts the whole collection process. @@ -168,39 +167,37 @@ ---------------------------------------------- If you have a module where you want to take responsibility for -collecting specific test units and possibly even executing a -test then you can provide your own ``Collector`` at module +collecting your own test Items and possibly even for executing +a test then you can provide your own ``Collector`` at module level. The default ModuleCollector looks for the name -``Collector`` in the modules namespace and turns over +``Collector`` in the modules namespace and turns over reponsibility by invoking it with the "module-path". The module path is a ``py.path.py()`` instance and carries -information needed to get to the module. Generally, a -pypath allows to address a python object in the filesystem. -It has two parts, a filesystem path and a dotted path leading -to the python object. Among other niceties, this allows -to easily invoke setup/teardown operations along the path -leading up to the python object. *Addressability of test Units* -is a major concern ... +information needed to get to the module. Generally, a pypath +allows to address a python object in the filesystem. +*Addressability of test Items* is a major concern because we +want to memorize failing tests across py.test invocations. A +``pypath`` has two parts, a filesystem path and a dotted path +leading to the python object. Another benefits apart from +from addressability is that invoking setup/teardown operations +can simply be implemented by walking the path path to the python +object. -Customizing execution of Units +Customizing execution of Items ------------------------------ -- Units allow total control of executing test methods +- Items allow total control of executing test methods - by providing custom collectors you can easily produce - your own unit class and instances + your own Item class and instances -- unitinstance.execute() gets called in order to execute a test +- Iteminstance.execute() gets called in order to execute a test -- Unit.execute() methods can provide custom paramters +- Item.execute() methods can provide custom paramters (a db-connection, some initialized application entity, whatever) to the actual underlying method being invoked. - lift some of the arbitrary restrictions of unittest.py: allow test functions and allow classes to just provide "grouping" for tests. - - - - From hpk at codespeak.net Mon Oct 4 15:49:22 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 4 Oct 2004 15:49:22 +0200 (MEST) Subject: [py-svn] r6845 - py/dist/doc Message-ID: <20041004134922.8AA975C38D@thoth.codespeak.net> Author: hpk Date: Mon Oct 4 15:49:22 2004 New Revision: 6845 Modified: py/dist/doc/test.txt Log: small rest and content fixes here and there Modified: py/dist/doc/test.txt ============================================================================== --- py/dist/doc/test.txt (original) +++ py/dist/doc/test.txt Mon Oct 4 15:49:22 2004 @@ -26,18 +26,20 @@ ... -Managing Test state across test modules, classes and methods -============================================================ +Managing test data across test modules, classes and methods +=========================================================== Often you want to create some test files, db-connections or -other state. With ``py.test`` there are three scopes for -which you can provide hooks. These hooks will get called by -the runner: +other state in order to run tests in a certain environment. +With ``py.test`` there are three scopes for which you can +provide hooks to manage such state. These hooks will get +called by the driver:: def setup_module(cls, module): """ setup up any objects specific to the execution of the given module. """ + def teardown_module(cls, module): """ teardown any objects that were previously setup with a setup_module method. @@ -62,9 +64,9 @@ with a setup_method call. """ -While the test runner guarantees that for every ``setup`` a -``teardown`` will be invoked (it exists) it does *not* guarantee -that it only happens once. For example, the runner might +While the test driver guarantees that for every ``setup`` a +``teardown`` will be invoked (if it exists) it does *not* guarantee +that it only happens once. For example, the driver might decide to call the setup_module/teardown_module pair more than once during the execution of a test module. @@ -97,7 +99,7 @@ of the magic by providing the ``--nomagic`` option. In order to write assertions regarding exceptions, you use -one of two forms: +one of two forms:: py.test.assert_raises(Exception, func, *args, **kwargs) py.test.assert_raises(Exception, "func(*args, **kwargs)") @@ -112,14 +114,14 @@ =================================== In order to customize py.test you need to understand -the basic architure of ``py.test``: +the basic architure of ``py.test``:: ___________________ | | | Collector | |___________________| / \ - | Item.execute(runner) + | Item.execute(driver) | ^ receive test Items / | /execute test Item @@ -135,33 +137,35 @@ ....................... -The *Driver* basically receives test *Items* from *Collector*. +The *Driver* basically receives test *Items* from a *Collector*, executes them via the ``Item.execute()`` method, and takes the outcome (possibly an exception) and sends it over to the -*reporter*. +*reporter* instance. + The test collection process =========================== The collecting process is iterative, i.e. the driver -get test Items one by one and can thus start to execute -the first collected test Item. +get test Items one by one and can thus start immediately +to execute the first collected test Item. + +Also, the collectors are completly separated from any driving, +execution or reporting details. Collectors have an __iter__ +method, yielding more (sub) collectors or Test *Items*. They +should usually not raise exceptions but yield back a specific +CollectError. This is to avoid that a collecting error +interrupts the whole collection process. + +Usually, ``py.test`` collects test files that match the glob +patterns ``test_*.py`` or ``*_test.py`` and it recurses into +any directory that doesn't start with a leading dot (e.g. +``.svn`` is ignored). + +It then recurses into the test files and collects functions +and methods that have a leading ``test_`` name, unless you +provide a custom collector in your module. -Also, the collectors are acompletly separated from any -driving, execution or reporting details. Collectors -have an __iter__ method, yielding more (sub) collectors or -Test *Items*. They should usually not raise exceptions but -yield back a specific CollectError. This is to avoid that a -collecting error interrupts the whole collection process. - -Usually, ``py.test`` collects test files that match -the glob patterns ``test_*.py`` or ``*_test.py`` and -it recurses into any directory that doesn't start -with a leading dot (e.g. ``.svn`` is ignored). - -It then recurses into the test files and collects -functions and methods that have a leading ``test_`` name, -unless you provide a custom collector in your module. Customizing the collection process in a module ---------------------------------------------- @@ -174,30 +178,30 @@ reponsibility by invoking it with the "module-path". The module path is a ``py.path.py()`` instance and carries -information needed to get to the module. Generally, a pypath -allows to address a python object in the filesystem. +information needed to traverse to to the module. In general, +a pypath allows to address a python object on the filesystem. *Addressability of test Items* is a major concern because we want to memorize failing tests across py.test invocations. A ``pypath`` has two parts, a filesystem path and a dotted path -leading to the python object. Another benefits apart from -from addressability is that invoking setup/teardown operations -can simply be implemented by walking the path path to the python +leading to the python object. Another benefits, apart from +from addressability, is that invoking setup/teardown operations +can simply be implemented by walking the path to the python object. Customizing execution of Items ------------------------------ -- Items allow total control of executing test methods - -- by providing custom collectors you can easily produce - your own Item class and instances - -- Iteminstance.execute() gets called in order to execute a test +- Items allow total control of executing their contained test + method. iteminstance.execute() gets called in order to + execute a test. - Item.execute() methods can provide custom paramters (a db-connection, some initialized application entity, whatever) to the actual underlying method being invoked. -- lift some of the arbitrary restrictions of unittest.py: allow - test functions and allow classes to just provide "grouping" for - tests. +- by providing custom collectors you can easily produce + your own Item instances + +- lift some of the arbitrary restrictions of unittest.py: + allow plain test functions (without being in a class) and + allow classes to simply mean "grouping" of tests. From hpk at codespeak.net Mon Oct 4 16:04:36 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 4 Oct 2004 16:04:36 +0200 (MEST) Subject: [py-svn] r6846 - py/dist/doc Message-ID: <20041004140436.DF1C45C38D@thoth.codespeak.net> Author: hpk Date: Mon Oct 4 16:04:36 2004 New Revision: 6846 Modified: py/dist/doc/test.txt Log: more fixes, clarifications Modified: py/dist/doc/test.txt ============================================================================== --- py/dist/doc/test.txt (original) +++ py/dist/doc/test.txt Mon Oct 4 16:04:36 2004 @@ -1,6 +1,6 @@ -============================ -The py.test tool and library -============================ +================================ +The ``py.test`` tool and library +================================ This is very much a draft version. @@ -16,13 +16,13 @@ The existence of a standalone ``py.test`` tool allows to write tests without any boilerplate: - def test_simple(): + def test_answer(): assert 42 == 43 -this would be the complete content of a test script -and you can simply execute the tests by invoking: +this would be the complete content of a ``test_sample.py`` +test script and you can simply execute the tests by invoking: - py.test filename.py + py.test test_sample.py ... @@ -35,12 +35,11 @@ provide hooks to manage such state. These hooks will get called by the driver:: - def setup_module(cls, module): + def setup_module(module): """ setup up any objects specific to the execution of the given module. """ - - def teardown_module(cls, module): + def teardown_module(module): """ teardown any objects that were previously setup with a setup_module method. """ @@ -65,10 +64,11 @@ """ While the test driver guarantees that for every ``setup`` a -``teardown`` will be invoked (if it exists) it does *not* guarantee -that it only happens once. For example, the driver might -decide to call the setup_module/teardown_module pair more than once -during the execution of a test module. +corresponding ``teardown`` will be invoked (if it exists) it +does *not* guarantee that it only happens once. For example, +the driver might decide to call the +setup_module/teardown_module pair more than once during the +execution of a test module. Note also that all setup/teardown methods are optional. For example, you can have a setup_module but no teardown_module @@ -79,7 +79,7 @@ how to write assertions ----------------------- -writing assertions is very simple in py.test and this +writing assertions is very simple and this is one of its most noticeable features: You can simply use the ``assert`` statement. @@ -104,7 +104,7 @@ py.test.assert_raises(Exception, func, *args, **kwargs) py.test.assert_raises(Exception, "func(*args, **kwargs)") -both of which executes the given function with args and kwargs +both of which execute the given function with args and kwargs and asserts that the given ``Exception`` is raised. Again, in case of the two possible failures (no exception or wrong exception) the reporter provides you with helpful output. @@ -113,8 +113,8 @@ The three components of ``py.test`` =================================== -In order to customize py.test you need to understand -the basic architure of ``py.test``:: +In order to customize ``py.test`` it's good to understand +its basic architure:: ___________________ | | @@ -148,23 +148,26 @@ The collecting process is iterative, i.e. the driver get test Items one by one and can thus start immediately -to execute the first collected test Item. - -Also, the collectors are completly separated from any driving, -execution or reporting details. Collectors have an __iter__ -method, yielding more (sub) collectors or Test *Items*. They -should usually not raise exceptions but yield back a specific -CollectError. This is to avoid that a collecting error -interrupts the whole collection process. - -Usually, ``py.test`` collects test files that match the glob -patterns ``test_*.py`` or ``*_test.py`` and it recurses into -any directory that doesn't start with a leading dot (e.g. -``.svn`` is ignored). - -It then recurses into the test files and collects functions -and methods that have a leading ``test_`` name, unless you -provide a custom collector in your module. +to execute the first collected test Item. At the same time, +the collectors are completly shielded from any driving, +execution or reporting details. + +The ``driver`` invokes the iteration protocol, i.e. the +``__iter__`` method of Collectors. These methods yield more (sub) +collectors or Test *Items*. They should usually not raise +exceptions but yield back a specific CollectError. This is to +avoid that a collecting error breaks the whole collection +chain. It is at the drivers discretion to react to errors +from collectors. + +The default DirectoryCollector collects test files that +match the glob patterns ``test_*.py`` or ``*_test.py`` and it +recurses into any directory that doesn't start with a leading +dot (e.g. ``.svn`` direcotries is ignored). + +Another ``PyCollector`` then recurses into the test files and +collects functions and methods that have a leading ``test_`` +name, unless you provide a custom collector in your module. Customizing the collection process in a module From hpk at codespeak.net Tue Oct 5 18:12:17 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 5 Oct 2004 18:12:17 +0200 (MEST) Subject: [py-svn] r6857 - py/dist/doc Message-ID: <20041005161217.4611E5AB66@thoth.codespeak.net> Author: hpk Date: Tue Oct 5 18:12:16 2004 New Revision: 6857 Modified: py/dist/doc/execnet.txt Log: new revision of the execnet Channel interface after some discussions with Jum regarding threads and asynchronously handling stuff ... Modified: py/dist/doc/execnet.txt ============================================================================== --- py/dist/doc/execnet.txt (original) +++ py/dist/doc/execnet.txt Tue Oct 5 18:12:16 2004 @@ -60,43 +60,47 @@ channel.gateway # the gateway through which this channel operates - channel.send(*items, timeout=None): + channel.timeout # the default timeout is no timeout, i.e. + # all operations block, setting the + # timeout to a float values indicates + # the timeout in seconds for all + # operations. + # + # API for sending and receiving anonymous values + # + channel.send(*items): sends the given items to the other side of the channel, possibly blocking if the sender queue is full. Note that each value V of the items needs to have the following property (all basic types in python have it): eval(repr(V)) == V. - channel.receive(timeout=None): + channel.receive(): receives an item that was sent from the other side, possibly blocking if there is none. + # + # API for setting and getting named values ("named signals") + # channel.set(**namevaluepairs): set the given namevaluepairs on the channel object. - These bindings will appear on both sides of the channel. + These binding will only appear on the remote side of the channel. - channel.get(name, ignore=_dummy, timeout=None): + channel.get(name): return the value from a name binding, possibly blocking - until it appears. If ignore is provided then its value - will be ignored and the function will only return if - a different value is received. Obviously, set/get can be - used for sending named signals to each other. - - channel.subscribe(itemcallback=None, namevaluecallback=None): - register the given callbacks. itemcallback will be called - with every received item. namevaluecallback will be called - with every received set of namevaluepairs as keyword arguments. - You can subscribe multiple callbacks which will be executed - in the order of their subscription. Note that the execution - of callbacks is serialized and all callbacks will run in the - same thread. - - channel.unsubscribe(itemcallback=None, namevaluecallback=None): - unregister the given callback from their subscription. - If a callback is registered multiple times all instances - will be deleted. - - + until it appears. Note that the name is immediately + deleted from the channels namespace. + Obviously, set/get can be used for sending named signals + to each other. + + # use with caution: + channel.peek(name=None): + return a tuple (isvalue, value) where isvalue indicates + if the named value was available. Callers may only assume + a meaningful value if isvalue is True. Otherwise the value + is undefined. Please note that the peek function is + a so called "polling" interface and thus can cause scheduling + penalties that may considerably slow down your process. An Example for Channels ----------------------- @@ -104,11 +108,9 @@ The following proposed example opens a PopenGateway, i.e. a child process, starts a socket server within that process and then opens a SocketGateway to the freshly started -socketserver. The "startserver.py" is a small script that +socketserver. The "py.execnet.socketserver" is a small script that basically listens and accepts socket connections, receives one -liners and executes them. This socketserver stuff is supposed -to never change. Probably it should be a script, callable like -py.execnet.socketserver from the command line. +liners and executes them. import py socketserverbootstrap = py.code.Source( @@ -158,3 +160,25 @@ channel interface the code for the above functionality is there. +Another Example for Channels +---------------------------- + +problem: retrieving contents of remote files + + import py + contentserverbootstrap = py.code.Source( + """ + for fn in channel.receive(): + channel.send(open(fn).read()) + """ + # open a gateway to a fresh child process + contentgateway = py.execnet.SSHGateway('codespeak.net', identity) + channel = contentgateway.remote_exec_async(contentserverbootstrap) + + for fn in filelist: + channel.send(fn) + content = channel.receive() + # process content + + # later you can exit / close down the gateway + contentgateway.exit() From hpk at codespeak.net Thu Oct 7 23:56:10 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 7 Oct 2004 23:56:10 +0200 (MEST) Subject: [py-svn] r6870 - py/dist/doc Message-ID: <20041007215610.55E6B5C39E@thoth.codespeak.net> Author: hpk Date: Thu Oct 7 23:56:08 2004 New Revision: 6870 Modified: py/dist/doc/execnet.txt Log: a couple of small fixes Modified: py/dist/doc/execnet.txt ============================================================================== --- py/dist/doc/execnet.txt (original) +++ py/dist/doc/execnet.txt Thu Oct 7 23:56:08 2004 @@ -1,5 +1,5 @@ -The py.execnet library 0.8.0 -============================ +The py.execnet library +====================== Execnet deals with letting your python programs execute and communicate across process and computer barriers. At the @@ -57,14 +57,15 @@ program parts to send data to each other. Here is the rough idea for the interface: + # + # attributes API + # channel.gateway # the gateway through which this channel operates - channel.timeout # the default timeout is no timeout, i.e. - # all operations block, setting the - # timeout to a float values indicates - # the timeout in seconds for all - # operations. + channel.timeout # the default timeout is None, i.e. all operations block + # setting the timeout to a number value indicates the + # timeout in seconds for all operations. # # API for sending and receiving anonymous values # @@ -114,13 +115,13 @@ import py socketserverbootstrap = py.code.Source( - py.script.getpath('py.execnet.socketserver').read() + py.script.getpath('py.execnet.socketserver').read(), """ import socket portrange = channel.get("portrange") for i in portrange: try: - sock = listen(("localhost", i)) + sock = bind_and_listen(("localhost", i)) except socket.error: continue else: @@ -144,7 +145,7 @@ # the other side should start the for loop now, we # wait for the result # - listenport = channel.get('listenport', timeout=3) + listenport = channel.get('listenport') if listenport is None: raise IOError, "could not setup remote SocketServer" @@ -158,7 +159,7 @@ the example is slightly futuristic but apart from the channel interface the code for the above functionality -is there. +is there albeit in slightly different namespaces. Another Example for Channels ---------------------------- @@ -169,7 +170,11 @@ contentserverbootstrap = py.code.Source( """ for fn in channel.receive(): - channel.send(open(fn).read()) + f = open(f, 'rb') + try: + channel.send(f.read()) + finally: + f.close() """ # open a gateway to a fresh child process contentgateway = py.execnet.SSHGateway('codespeak.net', identity) From hpk at codespeak.net Sun Oct 10 14:02:12 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 10 Oct 2004 14:02:12 +0200 (MEST) Subject: [py-svn] r6881 - py/dist/doc Message-ID: <20041010120212.7B7285A0BB@thoth.codespeak.net> Author: hpk Date: Sun Oct 10 14:02:11 2004 New Revision: 6881 Added: py/dist/doc/why_py.txt Modified: py/dist/doc/execnet.txt py/dist/doc/test.txt Log: M doc/execnet.txt M doc/test.txt mostly fixes to rest A doc/why_py.txt a new document describing some of the background/history of the py lib Modified: py/dist/doc/execnet.txt ============================================================================== --- py/dist/doc/execnet.txt (original) +++ py/dist/doc/execnet.txt Sun Oct 10 14:02:11 2004 @@ -56,7 +56,7 @@ execution of source code. This Channel object allows two program parts to send data to each other. -Here is the rough idea for the interface: +Here is the rough idea for the interface:: # # attributes API # @@ -111,7 +111,7 @@ then opens a SocketGateway to the freshly started socketserver. The "py.execnet.socketserver" is a small script that basically listens and accepts socket connections, receives one -liners and executes them. +liners and executes them. Here is the code:: import py socketserverbootstrap = py.code.Source( @@ -164,7 +164,7 @@ Another Example for Channels ---------------------------- -problem: retrieving contents of remote files +problem: retrieving contents of remote files:: import py contentserverbootstrap = py.code.Source( Modified: py/dist/doc/test.txt ============================================================================== --- py/dist/doc/test.txt (original) +++ py/dist/doc/test.txt Sun Oct 10 14:02:11 2004 @@ -154,7 +154,7 @@ The ``driver`` invokes the iteration protocol, i.e. the ``__iter__`` method of Collectors. These methods yield more (sub) -collectors or Test *Items*. They should usually not raise +*collectors* or Test *Items*. They should usually not raise exceptions but yield back a specific CollectError. This is to avoid that a collecting error breaks the whole collection chain. It is at the drivers discretion to react to errors Added: py/dist/doc/why_py.txt ============================================================================== --- (empty file) +++ py/dist/doc/why_py.txt Sun Oct 10 14:02:11 2004 @@ -0,0 +1,131 @@ +============================================== +Why, who, what and how do you do *the py lib*? +============================================== + +Why do we do the py lib? +------------------------ + +Among the main motivations for writing the py lib is +frustration at existing python modules and packages, +among them: + +- there is no standard way of testing python applications, + scripts or modules. everybody does its own hack around + the unittest.py if testing happens at all. + +- due to python's "export every name" policy for modules + and packages it is hard to refactor them across + releases becaue you might break uses of your code + +- distributed applications are implemented using + Remote Method Invocation (RMI) which carries a + lot of problems and is often difficult to deploy + with respect to different platforms and versions. + +- there is no _automated_ way of installing, maintaining + and upgrading applications + +- often modules/packages are implemented in java-style + +- the python "batteries included" are tied to the python + release process. You can't easily benefit from new python + module in older python versions which you might have + to use for whatever reason. + +The py lib tries to address these problems step by step. +Of course, we can't solve them all at once but you will +find that the above points currently drive the development +of the py lib. + +What is your current focus? +--------------------------- + +Currently, the main focus of the py lib is to get a decent +`test environment`_. Automated tests fit very well to the +dynamism of Python. Automated tests ease development and +allow fast refactoring cycles. So we are trying to develop +the best python `test environment`_ to makes writing +tests as easy as possible. And fun. + +Most importantly, we try to allow test scripts with minimal +boilerplate code or - mostly - no boilerplate at all. With +the py lib you can simply use ``assert`` statements in order +to - well - assert something about objects in your code. +No ``assertEqual(s)`` and all the other kinds of funny names +which only cover part of what you want to assert about an +expression, anyway. + +In order, to allow a fast development pace across versions of +the py lib there is '''explicit name export control'''. You +will only see names which make sense to use from the outside +and which the py lib developers want to guarantee across major +versions. No '''tested feature''' of this tightly controled API +will vanish across major releases until it is marked +deprecated. But if deprecated an API could go with every +following major release. Much thought is given to reduce the +exported *name complexity*. This is an area where the python +"batteries" lack a lot. They expose so many names that it +becomes very hard to change APIs across releases. This kills +the fun of refactoring and improving things. + +Another focus is a well tested Path implementation that +supports different backends, currently a local filesystem and +subversion working copies and subversion remote URLs. +Moreover, it provides an experimental fspython path to address +a Python object on the filesystem. + +How does py development work? +----------------------------- + +We are discussing things on our `py-dev mailing list`_ +and collaborate via the codespeak subversion repository. + +We follow a `coding style`_ which builds on `PEP 8`_, the basic +python coding style document. It's easy to get commit rights +especially if you are an experienced python developer +and share some of the frustrations described above. + +Moreover, the world has been granted svn commit rights to all +py test files so that you can easily add bug-tests or tests +for behaviour to make sure the tested behaviour persists +across releases. If you are itching to actually fix or +refactor any implementation code you can likely get commit +rights to do it. However, it is then a good idea to follow +py-dev at codespeak.net and grasp some of the things that are +going on. Oh right, and you should also agree to release +your code under an ``MIT license``. Maybe we can compute +the individual copyrights from the subversion blame +command. Would be a fun way to handle it. Basically, nobody +should have a problem to use the py lib under any OSI approved +license and also for commercial purposes. + +Who is "we"? Some history ... +----------------------------- + +Some of the initial code was written from Jens-Uwe Mager +and Holger Krekel, after which Holger continued on an +iteration of the py.test tool (known first as 'utest', then as +'std.utest', now 'py.test'). Helpful discussions took place +with Martijn Faassen, and then Armin Rigo who contributed +important parts. He and Holger came up with a couple of +iterations of the testing-code that reduced the API to almost +nothing: just the assert statement and an assert_raises +method. + +Now recently, after Holgers `talk at EP2004`_ more people +were interested and there were some discussions with Jim Fulton +and Marius Gedminas and more recently, Ian Bicking. However, +there is no real core development team as such. Also we +are somewhat lacking in the win32 area. Every now and +then the py lib is tested on windows but it's currently +not a continous concern of one of the current developers. +However, one of the improvements to the testing code is to +allow running tests across multiple python versions and +computers. Then we can run tests without having to walk +up or boot up a windows machine :-) + +.. _`talk at EP2004`: http://codespeak.net/svn/user/hpk/talks/std-talk.txt +.. _`coding style`: coding-style.html +.. _`PEP 8`: http://www.python.org/peps/pep-0008.html +.. _`py-dev mailing list`: http://codespeak.net/mailman/listinfo/py-dev +.. _`test environment`: test.html From hpk at codespeak.net Sun Oct 10 14:26:28 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 10 Oct 2004 14:26:28 +0200 (MEST) Subject: [py-svn] r6882 - py/dist/doc Message-ID: <20041010122628.809B75A0BB@thoth.codespeak.net> Author: hpk Date: Sun Oct 10 14:26:27 2004 New Revision: 6882 Modified: py/dist/doc/execnet.txt py/dist/doc/test.txt Log: improvements and some restructuring for test.txt Modified: py/dist/doc/execnet.txt ============================================================================== --- py/dist/doc/execnet.txt (original) +++ py/dist/doc/execnet.txt Sun Oct 10 14:26:27 2004 @@ -57,6 +57,7 @@ program parts to send data to each other. Here is the rough idea for the interface:: + # # attributes API # Modified: py/dist/doc/test.txt ============================================================================== --- py/dist/doc/test.txt (original) +++ py/dist/doc/test.txt Sun Oct 10 14:26:27 2004 @@ -2,10 +2,13 @@ The ``py.test`` tool and library ================================ +.. contents:: +.. sectnum:: + This is very much a draft version. -The ``py.test`` command line tool -================================= +starting point: ``py.test`` command line tool +============================================= ``py.test`` is the command line tool to run tests. If you provide it a file that contains functions or methods starting @@ -24,7 +27,11 @@ py.test test_sample.py -... +For seeing various options you can use: + + py.test -h + +to see the list of options. Managing test data across test modules, classes and methods =========================================================== @@ -143,8 +150,8 @@ *reporter* instance. -The test collection process -=========================== +Collectors and the test collection process +------------------------------------------ The collecting process is iterative, i.e. the driver get test Items one by one and can thus start immediately @@ -170,6 +177,36 @@ name, unless you provide a custom collector in your module. +Drivers bind it all together +---------------------------- + +Drivers serve as the glue code which call Collectors +in order to receive test Items, call their ``Item.execute()`` +method and send events/results to the ``Reporter``. + +Reporters process events +------------------------ + +A driver typically invokes certain methods of a Reporter +for various points in the execution process:: + + start(), end() are invoked only from the outmost driver + to allow a reporter to write headers and + footers + + open(collector) is called when a collector is about + to be iterated. This method should return + a "close" method (or None) which will be + called when the collector is finished iterating. + + startitem(item) is called before executing a single test item + enditem(result) is called when execution of a single test item + finished. The result.item attribute points back + to the Test item the result object belongs to. + +Customizing the py.test process +=============================== + Customizing the collection process in a module ---------------------------------------------- From hpk at codespeak.net Sun Oct 10 14:35:53 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 10 Oct 2004 14:35:53 +0200 (MEST) Subject: [py-svn] r6883 - py/dist/doc Message-ID: <20041010123553.781235A0BB@thoth.codespeak.net> Author: hpk Date: Sun Oct 10 14:35:52 2004 New Revision: 6883 Modified: py/dist/doc/test.txt Log: some more work on the test text. Modified: py/dist/doc/test.txt ============================================================================== --- py/dist/doc/test.txt (original) +++ py/dist/doc/test.txt Sun Oct 10 14:35:52 2004 @@ -5,8 +5,6 @@ .. contents:: .. sectnum:: -This is very much a draft version. - starting point: ``py.test`` command line tool ============================================= @@ -33,8 +31,55 @@ to see the list of options. -Managing test data across test modules, classes and methods -=========================================================== + +Basic features for writing tests +================================ + +assert with the ``assert statement`` +------------------------------------ + +writing assertions is very simple and this +is one of its most noticeable features: + + You can simply use the ``assert`` statement. + +For example you can write: + + assert hasattr(x, 'something') + +and in case this fails the ``test reporter`` will provide +you with a very helpful analysis and a clean traceback. + +Please note that in order to display helpful analysis +of a failing ``assert`` expression some magic takes +place behind the scenes. For now, you only need to +know that if something looks strange or you suspect +a bug in that behind-the-scenes-magic you may turn +of the magic by providing the ``--nomagic`` option. + +how to write assertions about execptions +---------------------------------------- + +In order to write assertions about exceptions, you use +one of two forms:: + + py.test.assert_raises(Exception, func, *args, **kwargs) + py.test.assert_raises(Exception, "func(*args, **kwargs)") + +both of which execute the given function with args and kwargs +and asserts that the given ``Exception`` is raised. Again, +in case of the possible failures (*no exception** or *wrong +exception*) the reporter provides you with helpful output. + +debug with the ``print statement`` +---------------------------------- + +By default, the py lib catches stdout/stderr while executing +tests. This output is only displayed when the test fails, +otherwise you will not see it. + +Managing test state across test modules, classes and methods +------------------------------------------------------------ Often you want to create some test files, db-connections or other state in order to run tests in a certain environment. @@ -83,40 +128,6 @@ -how to write assertions ------------------------ - -writing assertions is very simple and this -is one of its most noticeable features: - - You can simply use the ``assert`` statement. - -For example you can write: - - assert hasattr(x, 'something') - -and in case this fails the ``test reporter`` will provide -you with a very helpful analysis and a clean traceback. - -Please note that in order to display helpful analysis -of a failing ``assert`` expression some magic takes -place behind the scenes. For now, you only need to -know that if something looks strange or you suspect -a bug in that behind-the-scenes-magic you may turn -of the magic by providing the ``--nomagic`` option. - -In order to write assertions regarding exceptions, you use -one of two forms:: - - py.test.assert_raises(Exception, func, *args, **kwargs) - py.test.assert_raises(Exception, "func(*args, **kwargs)") - -both of which execute the given function with args and kwargs -and asserts that the given ``Exception`` is raised. Again, -in case of the two possible failures (no exception or wrong -exception) the reporter provides you with helpful output. - - The three components of ``py.test`` =================================== From hpk at codespeak.net Sun Oct 10 14:41:41 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 10 Oct 2004 14:41:41 +0200 (MEST) Subject: [py-svn] r6884 - py/dist/doc Message-ID: <20041010124141.7DB8F5A0BB@thoth.codespeak.net> Author: hpk Date: Sun Oct 10 14:41:41 2004 New Revision: 6884 Modified: py/dist/doc/test.txt Log: small rest fixes Modified: py/dist/doc/test.txt ============================================================================== --- py/dist/doc/test.txt (original) +++ py/dist/doc/test.txt Sun Oct 10 14:41:41 2004 @@ -35,11 +35,11 @@ Basic features for writing tests ================================ -assert with the ``assert statement`` +assert with the ``assert`` statement ------------------------------------ writing assertions is very simple and this -is one of its most noticeable features: +is one of py.tests most noticeable features: You can simply use the ``assert`` statement. @@ -71,7 +71,7 @@ in case of the possible failures (*no exception** or *wrong exception*) the reporter provides you with helpful output. -debug with the ``print statement`` +debug with the ``print`` statement ---------------------------------- By default, the py lib catches stdout/stderr while executing From hpk at codespeak.net Sun Oct 10 14:45:52 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 10 Oct 2004 14:45:52 +0200 (MEST) Subject: [py-svn] r6885 - py/dist/doc Message-ID: <20041010124552.1D8465A0BB@thoth.codespeak.net> Author: hpk Date: Sun Oct 10 14:45:51 2004 New Revision: 6885 Modified: py/dist/doc/why_py.txt Log: reorder frustrations according to priority Modified: py/dist/doc/why_py.txt ============================================================================== --- py/dist/doc/why_py.txt (original) +++ py/dist/doc/why_py.txt Sun Oct 10 14:45:51 2004 @@ -17,21 +17,21 @@ and packages it is hard to refactor them across releases becaue you might break uses of your code +- the python "batteries included" are tied to the python + release process. You can't easily benefit from new python + module in older python versions which you might have + to use for whatever reason. + +- often modules/packages are implemented in java-style + - distributed applications are implemented using - Remote Method Invocation (RMI) which carries a - lot of problems and is often difficult to deploy + a variant of Remote Method Invocation (RMI) which carries + a lot of problems and is often difficult to deploy with respect to different platforms and versions. - there is no _automated_ way of installing, maintaining and upgrading applications -- often modules/packages are implemented in java-style - -- the python "batteries included" are tied to the python - release process. You can't easily benefit from new python - module in older python versions which you might have - to use for whatever reason. - The py lib tries to address these problems step by step. Of course, we can't solve them all at once but you will find that the above points currently drive the development From hpk at codespeak.net Sun Oct 10 16:01:43 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 10 Oct 2004 16:01:43 +0200 (MEST) Subject: [py-svn] r6888 - in py/dist/py/path: local svn svnwc test Message-ID: <20041010140143.35D865A0BB@thoth.codespeak.net> Author: hpk Date: Sun Oct 10 16:01:42 2004 New Revision: 6888 Modified: py/dist/py/path/local/local.py py/dist/py/path/svn/common.py py/dist/py/path/svnwc/command.py py/dist/py/path/test/_common.py Log: allow dirpath() to receive arguments where x.dirpath(*args) == x.dirpath().join(*args) holds true. Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Sun Oct 10 16:01:42 2004 @@ -189,8 +189,12 @@ def __eq__(self, other): return str(self) == str(other) - def dirpath(self): - return self.new(basename='') + def dirpath(self, *args): + x = self.new(basename='') + if args: + return x.join(*args) + else: + return x def open(self, mode='r'): """ return an opened file with the given mode. """ Modified: py/dist/py/path/svn/common.py ============================================================================== --- py/dist/py/path/svn/common.py (original) +++ py/dist/py/path/svn/common.py Sun Oct 10 16:01:42 2004 @@ -149,9 +149,9 @@ newpath = self.__class__(self.sep.join(parts), self._rev) return newpath - def dirpath(self): + def dirpath(self, *args): """ return the directory Path of the current Path. """ - return self.new(basename='') + return self.new(basename='').join(*args) def propget(self, name): """ return the content of the given property. """ Modified: py/dist/py/path/svnwc/command.py ============================================================================== --- py/dist/py/path/svnwc/command.py (original) +++ py/dist/py/path/svnwc/command.py Sun Oct 10 16:01:42 2004 @@ -90,9 +90,9 @@ """ write content into local filesystem wc. """ self.localpath.write(content) - def dirpath(self): + def dirpath(self, *args): """ return the directory Path of the current Path. """ - return self.__class__(self.localpath.dirpath()) + return self.__class__(self.localpath.dirpath(*args)) def _ensuredirs(self): parent = self.dirpath() Modified: py/dist/py/path/test/_common.py ============================================================================== --- py/dist/py/path/test/_common.py (original) +++ py/dist/py/path/test/_common.py Sun Oct 10 16:01:42 2004 @@ -70,6 +70,10 @@ newpath = self.root.join('sampledir') assert newpath.dirpath() == self.root + def test_dirpath_with_args(self): + newpath = self.root.join('sampledir') + assert newpath.dirpath('x') == self.root.join('x') + def test_purebasename(self): newpath = self.root.join('samplefile.py') assert newpath.purebasename == 'samplefile' From hpk at codespeak.net Sun Oct 10 16:13:25 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 10 Oct 2004 16:13:25 +0200 (MEST) Subject: [py-svn] r6889 - in py/dist/py: execnet magic test Message-ID: <20041010141325.C2B745A0BB@thoth.codespeak.net> Author: hpk Date: Sun Oct 10 16:13:25 2004 New Revision: 6889 Modified: py/dist/py/execnet/gateway_test.py py/dist/py/magic/autopath.py py/dist/py/magic/autopath_test.py py/dist/py/test/collect_test.py Log: changed autopath() to directly return a (local) path object to the file from which you execute autopath(). the usage from py.magic import autopath autopath = autopath() results in you having mypath pointing to your .py file you are executing the autopath() from. If you want to get another modules path you can call autopath(othermodule.__dict__) to receive the path pointing to that other module. Modified: py/dist/py/execnet/gateway_test.py ============================================================================== --- py/dist/py/execnet/gateway_test.py (original) +++ py/dist/py/execnet/gateway_test.py Sun Oct 10 16:13:25 2004 @@ -18,7 +18,7 @@ portrange = (7770, 7800) cls.proxygw = py.execnet.PopenGateway() s = Source( - autopath.thisdir.join('bin', 'startserver.py').read(), + autopath.dirpath('bin', 'startserver.py').read(), """ import socket for i in range%(portrange)r: Modified: py/dist/py/magic/autopath.py ============================================================================== --- py/dist/py/magic/autopath.py (original) +++ py/dist/py/magic/autopath.py Sun Oct 10 16:13:25 2004 @@ -1,33 +1,36 @@ import os, sys from py.path import local -class autopath: - def __init__(self, globs=None, basefile='__init__.py'): - """ provide help for getting/setting bootstrap paths. - attributes: +def autopath(globs=None, basefile='__init__.py'): + """ return the (local) path of the "current" file pointed to by globals + or - if it is none - alternatively the callers frame globals. - thisdir is the directory path of the module where the import occurs - or for __main__ modules it's the dir of sys.argv[0] + the path will always point to a .py file or to None. + the path will have the following payload: pkgdir is the last parent directory path containing 'basefile' - starting backwards from thisdir - """ - if globs is None: - globs = sys._getframe(1).f_globals - try: - __file__ = globs['__file__'] - except KeyError: - if not sys.argv[0]: - raise ValueError, "cannot compute autopath in interactive mode" - __file__ = os.path.abspath(sys.argv[0]) - self.thisdir = local(__file__).dirpath() + starting backwards from the current file. + """ + if globs is None: + globs = sys._getframe(1).f_globals + try: + __file__ = globs['__file__'] + except KeyError: + if not sys.argv[0]: + raise ValueError, "cannot compute autopath in interactive mode" + __file__ = os.path.abspath(sys.argv[0]) - current = self.pkgdir = self.thisdir - while 1: - if basefile in current: - self.pkgdir = current - current = current.dirpath() - if self.pkgdir != current: - continue - elif str(current) not in sys.path: - sys.path.insert(0, str(current)) - break + ret = local(__file__) + if ret.ext in ('.pyc', '.pyo'): + ret = ret.new(ext='.py') + current = pkgdir = ret.dirpath() + while 1: + if basefile in current: + pkgdir = current + current = current.dirpath() + if pkgdir != current: + continue + elif str(current) not in sys.path: + sys.path.insert(0, str(current)) + break + ret.pkgdir = pkgdir + return ret Modified: py/dist/py/magic/autopath_test.py ============================================================================== --- py/dist/py/magic/autopath_test.py (original) +++ py/dist/py/magic/autopath_test.py Sun Oct 10 16:13:25 2004 @@ -18,7 +18,7 @@ try: exec self.getauto in d conf = d['autopath'] - assert conf.thisdir == self.initdir2 + assert conf.dirpath() == self.initdir2 assert conf.pkgdir == self.initdir assert str(self.root) in sys.path exec self.getauto in d @@ -26,6 +26,22 @@ finally: sys.path[:] = oldsyspath + def test_import_autoconfigure__file__with_py_exts(self): + for ext in '.pyc', '.pyo': + testpath = self.initdir2 / ('autoconfiguretest' + ext) + d = {'__file__' : str(testpath)} + oldsyspath = sys.path[:] + try: + exec self.getauto in d + conf = d['autopath'] + assert conf == self.initdir2.join('autoconfiguretest.py') + assert conf.pkgdir == self.initdir + assert str(self.root) in sys.path + exec self.getauto in d + assert conf is not d['autopath'] + finally: + sys.path[:] = oldsyspath + def test_import_autoconfigure___file__without_init(self): testpath = self.root / 'autoconfiguretest.py' d = {'__file__' : str(testpath)} @@ -33,7 +49,7 @@ try: exec self.getauto in d conf = d['autopath'] - assert conf.thisdir == self.root + assert conf.dirpath() == self.root assert conf.pkgdir == self.root syspath = sys.path[:] assert str(self.root) in syspath @@ -51,7 +67,7 @@ d = {} exec self.getauto in d conf = d['autopath'] - assert conf.thisdir == self.initdir2 + assert conf.dirpath() == self.initdir2 assert conf.pkgdir == self.initdir syspath = sys.path[:] assert str(self.root) in syspath @@ -59,6 +75,7 @@ sys.path[:] = oldsyspath sys.argv = sys.argv + def test_import_autoconfigure__nofile_interactive(self): oldsysarg = sys.argv sys.argv = [''] Modified: py/dist/py/test/collect_test.py ============================================================================== --- py/dist/py/test/collect_test.py (original) +++ py/dist/py/test/collect_test.py Sun Oct 10 16:13:25 2004 @@ -1,7 +1,7 @@ from __future__ import generators from py import test, path from py.magic import autopath ; autopath = autopath() -testdir = autopath.thisdir / 'test' +testdir = autopath.dirpath('test') assert testdir.check(dir=1) datadir = testdir / 'data' From hpk at codespeak.net Sun Oct 10 22:39:13 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 10 Oct 2004 22:39:13 +0200 (MEST) Subject: [py-svn] r6897 - in py/dist: doc py/path/local Message-ID: <20041010203913.C98B05A0BB@thoth.codespeak.net> Author: hpk Date: Sun Oct 10 22:39:13 2004 New Revision: 6897 Modified: py/dist/doc/why_py.txt py/dist/py/path/local/_posix.py py/dist/py/path/local/local_test.py Log: added chown() function to local path along with some tests Modified: py/dist/doc/why_py.txt ============================================================================== --- py/dist/doc/why_py.txt (original) +++ py/dist/doc/why_py.txt Sun Oct 10 22:39:13 2004 @@ -22,6 +22,8 @@ module in older python versions which you might have to use for whatever reason. +- non-interactive environments. + - often modules/packages are implemented in java-style - distributed applications are implemented using Modified: py/dist/py/path/local/_posix.py ============================================================================== --- py/dist/py/path/local/_posix.py (original) +++ py/dist/py/path/local/_posix.py Sun Oct 10 22:39:13 2004 @@ -4,6 +4,7 @@ """ import os, sys, stat +import py #__________________________________________________________ # # Local Path Posix Mixin @@ -49,6 +50,23 @@ except: self._except(sys.exc_info()) + def chown(self, user, group, rec=0): + """ change ownership to the given user and group. + user and group may be specified by a number or + by a name. if rec is True change ownership + recursively. + """ + uid = getuserid(user) + gid = getgroupid(group) + try: + if rec: + for x in self.visit(rec=py.path.checker(link=0)): + os.chown(str(self), uid, gid) + else: + os.chown(str(self), uid, gid) + except: + self._except(sys.exc_info()) + def readlink(self): """ return value of a symbolic link. """ try: @@ -81,3 +99,17 @@ except: self._except(sys.exc_info()) + +def getuserid(user): + import pwd + if isinstance(user, int): + return user + entry = pwd.getpwnam(user) + return entry[2] + +def getgroupid(group): + import grp + if isinstance(group, int): + return group + entry = grp.getgrnam(group) + return entry[2] Modified: py/dist/py/path/local/local_test.py ============================================================================== --- py/dist/py/path/local/local_test.py (original) +++ py/dist/py/path/local/local_test.py Sun Oct 10 22:39:13 2004 @@ -318,6 +318,16 @@ for x,y in oldmodes.items(): x.chmod(y) + def test_chown_identity(self): + owner = self.root.owner() + group = self.root.group() + self.root.chown(owner, group) + + def test_chown_identity_rec_mayfail(self): + owner = self.root.owner() + group = self.root.group() + self.root.chown(owner, group) + class TestMisc: root = local(TestLocalPath.root) From hpk at codespeak.net Sun Oct 10 23:09:17 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 10 Oct 2004 23:09:17 +0200 (MEST) Subject: [py-svn] r6899 - py/dist/py/path/local Message-ID: <20041010210917.948975A0BB@thoth.codespeak.net> Author: hpk Date: Sun Oct 10 23:09:17 2004 New Revision: 6899 Modified: py/dist/py/path/local/_posix.py Log: fix recursively chowning Modified: py/dist/py/path/local/_posix.py ============================================================================== --- py/dist/py/path/local/_posix.py (original) +++ py/dist/py/path/local/_posix.py Sun Oct 10 23:09:17 2004 @@ -61,9 +61,8 @@ try: if rec: for x in self.visit(rec=py.path.checker(link=0)): - os.chown(str(self), uid, gid) - else: - os.chown(str(self), uid, gid) + os.chown(str(x), uid, gid) + os.chown(str(self), uid, gid) except: self._except(sys.exc_info()) From hpk at codespeak.net Mon Oct 11 00:59:26 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 11 Oct 2004 00:59:26 +0200 (MEST) Subject: [py-svn] r6903 - py/dist/py/path/local Message-ID: <20041010225926.02D7E5A0BB@thoth.codespeak.net> Author: hpk Date: Mon Oct 11 00:59:26 2004 New Revision: 6903 Modified: py/dist/py/path/local/_posix.py Log: include path in path.chmod(rec=1) Modified: py/dist/py/path/local/_posix.py ============================================================================== --- py/dist/py/path/local/_posix.py (original) +++ py/dist/py/path/local/_posix.py Mon Oct 11 00:59:26 2004 @@ -45,8 +45,7 @@ if rec: for x in self.visit(): os.chmod(str(x), mode) - else: - os.chmod(str(self), mode) + os.chmod(str(self), mode) except: self._except(sys.exc_info()) From hpk at codespeak.net Wed Oct 13 05:01:04 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 13 Oct 2004 05:01:04 +0200 (MEST) Subject: [py-svn] r6909 - in py/dist/py/execnet: . bin Message-ID: <20041013030104.41E945AAB6@thoth.codespeak.net> Author: hpk Date: Wed Oct 13 05:01:02 2004 New Revision: 6909 Modified: py/dist/py/execnet/bin/startserver.py py/dist/py/execnet/gateway.py py/dist/py/execnet/gateway_test.py py/dist/py/execnet/register.py Log: - the first implementation of the "channel interface" as described in doc/execnet - added lots of tests - gateway.py should be split up into multiple files, probably Modified: py/dist/py/execnet/bin/startserver.py ============================================================================== --- py/dist/py/execnet/bin/startserver.py (original) +++ py/dist/py/execnet/bin/startserver.py Wed Oct 13 05:01:02 2004 @@ -42,7 +42,7 @@ # import traceback # traceback.print_exc() -def listen(hostport): +def bind_and_listen(hostport): if isinstance(hostport, str): host, port = hostport.split(':') hostport = (host, int(port)) @@ -64,6 +64,6 @@ hostport = sys.argv[1] else: hostport = ':8888' - serversock = listen(hostport) + serversock = bind_and_listen(hostport) startserver(serversock) Modified: py/dist/py/execnet/gateway.py ============================================================================== --- py/dist/py/execnet/gateway.py (original) +++ py/dist/py/execnet/gateway.py Wed Oct 13 05:01:02 2004 @@ -1,35 +1,60 @@ import sys, os, threading, struct, Queue, traceback import atexit +import time from py.__impl__.execnet.source import Source debug = 0 sysex = (KeyboardInterrupt, SystemExit) -class Gateway: - def __init__(self, io, ns = None): - if ns is None: - ns = {} - ns['gateway'] = self +class Gateway(object): + num_worker_threads = 2 + + def __init__(self, io, startcount=2): self.io = io - self.ns = ns - self._incoming = Queue.Queue() + self._execqueue = Queue.Queue() self._outgoing = Queue.Queue() - self._execevent = threading.Event() - - self.running = 1 - self._replylock = threading.RLock() - self._replies = {} - - for method in self._receiver, self._executor, self._sender: - t = threading.Thread(target=method, name=method.func_name) - setattr(self, 'thread'+method.func_name, t) - t.setDaemon(0) - t.start() + self.channelfactory = ChannelFactory(self, startcount) + self.iothreads = [ + threading.Thread(target=self.thread_receiver, name='receiver'), + threading.Thread(target=self.thread_sender, name='sender'), + ] + for x in self.iothreads: + x.start() + self.workerthreads = w = [] + for x in range(self.num_worker_threads): + w.append(threading.Thread(target=self.thread_executor, + name='executor %d' % x)) + for x in w: + x.start() if not _gateways: atexit.register(cleanup_atexit) _gateways.append(self) + def _stopexec(self): + if self.workerthreads: + for x in self.workerthreads: + self._execqueue.put(None) + for x in self.workerthreads: + if x.isAlive(): + self.trace("joining %r" % x) + x.join() + self.workerthreads[:] = [] + + def exit(self): + if self.workerthreads: + self._stopexec() + self._outgoing.put(Message.EXIT_GATEWAY()) + else: + self.trace("exit() called, but gateway has not threads anymore!") + + def join(self): + current = threading.currentThread() + for x in self.iothreads: + if x != current and x.isAlive(): + print "joining", x + x.join() + def trace(self, *args): if debug: try: @@ -47,179 +72,258 @@ errortext = "".join(l) self.trace(errortext) - def _receiver(self): - """ read sourcecode strings into the incoming queue. """ + def thread_receiver(self): + """ thread to read and handle Messages half-sync-half-async. """ try: while 1: try: - header = self.io.read(struct.calcsize("!i")) - stringlen, = struct.unpack("!i", header) - string = self.io.read(stringlen) - except self.io.error: + msg = Message.readfrom(self.io) + self.trace("received <- %r" % msg) + msg.handle(self) + except SystemExit: + self.io.close_read() + return + except: self.traceex(sys.exc_info()) - break - else: - if not string: # empty string is signal to shutdown - break - self._incoming.put(string) - finally: - self.io.close_read() - self.trace("receiver leaving") - self.exit() + break + finally: + self.trace('leaving %r' % threading.currentThread()) - def _sender(self): - """ send source objects from the outgoing queue to the remote side. """ + def thread_sender(self): + """ thread to send Messages over the wire. """ try: while 1: - obj = self._outgoing.get() - if obj is None: - # if we are finished then we try to send an - # empty string #to the other side in order to signal - # it to shutdown its receiver loop - obj = '' - else: - obj = str(obj) - data = struct.pack("!i", len(obj)) + obj + msg = self._outgoing.get() try: - self.io.write(data) + msg.writeto(self.io) except self.io.error: self.traceex(sys.exc_info()) - break else: - self.trace('sent -> %d' % len(obj)) - if not obj: - break + self.trace('sent -> %r' % msg) + if isinstance(msg, (Message.STOP_RECEIVING, Message.EXIT_GATEWAY)): + self.io.close_write() + break finally: - self.trace('sender leaving') - self.io.close_write() + self.trace('leaving %r' % threading.currentThread()) - def _executor(self): - """ execute source objects from the incoming queue. """ + def thread_executor(self): + """ worker thread to execute source objects from the execution queue. """ try: while 1: - source = self._incoming.get() - if source is None: - self._execevent.set() + task = self._execqueue.get() + if task is None: break + channel, source = task try: - self.trace("executing starts:", repr(source)[:50]) + loc = { 'channel' : channel } + self.trace("execution starts:", repr(source)[:50]) try: co = compile(source, '', 'exec') - exec co in self.ns # globals(), self.ns + exec co in loc finally: - self._execevent.set() self.trace("execution finished:", repr(source)[:50]) except (KeyboardInterrupt, SystemExit): raise except: - self.traceex(sys.exc_info()) - finally: - self.trace("executor leaving") - - def wait_exec(self, timeout=10.0): - """ wait until the next execution event (i.e. sourcecode is executed). """ - self._execevent.wait(timeout) - if not self._execevent.isSet(): - raise IOError, "timeout waiting for execevent" - self.clear_exec() - - def clear_exec(self): - """ clear execution event. """ - self._execevent.clear() - - def exit(self): - if self.running: - self.running = 0 - self._incoming.put(None) - self._outgoing.put(None) - for thread in (self.thread_receiver, - self.thread_sender, - self.thread_executor): - if thread != threading.currentThread(): - self.trace("joining %s" % str(thread)) - thread.join(10.0) + excinfo = sys.exc_info() + l = traceback.format_exception(*excinfo) + errortext = "".join(l) + self._outgoing.put(Message.CHANNEL_CLOSE_ERROR(channel.id, errortext)) + self.trace(errortext) else: - self.trace("joining %s which is current" % str(thread)) - self.trace("exit finished successfully") - else: - self.trace("exit() called, but gateway was not running anymore!") - - def __nonzero__(self): - return self.running + self._outgoing.put(Message.CHANNEL_CLOSE(channel.id)) + finally: + self.trace('leaving %r' % threading.currentThread()) - def _get_status(self, id=None): - self._replylock.acquire() - try: - if id is None: - id = len(self._replies) - self._replies[id] = x = Reply(self, id) - return x - return self._replies[id] - finally: - self._replylock.release() # _____________________________________________________________________ # # High Level Interface # _____________________________________________________________________ - def remote_exec_oneshot(self, source): - """ execute source on the other side of the gateway disregarding feedback. """ - if source is not None: - source = Source(source) - self._outgoing.put(source) - def remote_exec_async(self, source): - """ return reply object for the asynchornous execution of the - given sourcecode. + """ return channel object for communicating with the asynchronously + executing source code which can use a corresponding 'channel' + object in turn. """ - source = Source(source) - reply = self._get_status() - source.putaround( - "try:", - Source(""" - except: - msg = gateway._format_exception(sys.exc_info()) - else: - msg = '' - gateway.remote_exec_oneshot( - 'gateway._get_status(%r).set(%%r)' %% (msg, )) - """ % reply.id)) - self.trace(str(source)) - self.remote_exec_oneshot(source) - return reply + source = str(Source(source)) + if debug: + import parser + parser.suite(source) + channel = self.channelfactory.new() + self._outgoing.put(Message.CHANNEL_OPEN(channel.id, source)) + return channel def remote_exec_sync(self, source, timeout=10): """ synchronously execute source on the other side of the gateway return after execution of the source finishes on the other side. """ - reply = self.remote_exec_async(source) - reply.wait(timeout=timeout) + channel = self.remote_exec_async(source) + channel.waitclose(timeout=timeout) return -class Reply: - """ Reply objects let you determine if a remote_exec_async method - finished on the other site and possibly retrieve error information. - """ +class Channel(object): def __init__(self, gateway, id): + assert isinstance(id, int) self.gateway = gateway self.id = id - self._setevent = threading.Event() - - def set(self, msg): - self.msg = msg - self._setevent.set() - - def wait(self, timeout=10.0): - self._setevent.wait(timeout=timeout) - if not self._setevent.isSet(): - raise IOError, "timeout waiting to status reply" - self._setevent.clear() + self._items = Queue.Queue() + self._closeevent = threading.Event() + + def _close(self, error=None): + self._error = error + self._closeevent.set() + + def __repr__(self): + flag = self._closeevent.isSet() and "closed" or "open" + return "" % (self.id, flag) + + def waitclose(self, timeout=None): + """ return error message (None if no error) after waiting for close event. """ + self._closeevent.wait(timeout) + if not self._closeevent.isSet(): + raise IOError, "Timeout" + return self._error + def send(self, item): + """sends the given item to the other side of the channel, + possibly blocking if the sender queue is full. + Note that each value V of the items needs to have the + following property (all basic types in python have it): + eval(repr(V)) == V.""" + self.gateway._outgoing.put(Message.CHANNEL_DATA(self.id, repr(item))) + + def receive(self, timeout=None): + """receives an item that was sent from the other side, + possibly blocking if there is none.""" + return self._items.get(timeout=timeout) # -# helper functions +# helpers +# + +class ChannelFactory(object): + def __init__(self, gateway, startcount=1): + self._dict = dict() + self._lock = threading.RLock() + self.gateway = gateway + self.count = startcount + + def new(self): + self._lock.acquire() + try: + channel = Channel(self.gateway, self.count) + self._dict[self.count] = channel + return channel + finally: + self.count += 2 + self._lock.release() + + def __getitem__(self, key): + self._lock.acquire() + try: + return self._dict[key] + finally: + self._lock.release() + def __setitem__(self, key, value): + self._lock.acquire() + try: + self._dict[key] = value + finally: + self._lock.release() + def __delitem__(self, key): + self._lock.acquire() + try: + del self._dict[key] + finally: + self._lock.release() + +# ___________________________________________________________________________ # +# Messages +# ___________________________________________________________________________ +# the size of a number on the wire +numsize = struct.calcsize("!i") +# header of a packet +# int message_type: 0==exitgateway, +# 1==channelfinished_ok, +# 2==channelfinished_err, +# 3==channelopen # executes source code +# 4==channelsend # marshals obj +class Message: + """ encapsulates Messages and their wire protocol. """ + _types = {} + def __init__(self, channelid=0, data=''): + self.channelid = channelid + self.data = str(data) + + def writeto(self, io): + data = str(self.data) + header = struct.pack("!iii", self.msgtype, self.channelid, len(data)) + io.write(header) + io.write(data) + + def readfrom(cls, io): + header = io.read(numsize*3) + msgtype, senderid, stringlen = struct.unpack("!iii", header) + if stringlen: + string = io.read(stringlen) + else: + string = '' + msg = cls._types[msgtype](senderid, string) + return msg + readfrom = classmethod(readfrom) + + def __repr__(self): + if len(self.data) > 50: + return "" %(self.__class__.__name__, + self.channelid, len(self.data)) + else: + return "" %(self.__class__.__name__, + self.channelid, self.data) + +def _setupmessages(): + class EXIT_GATEWAY(Message): + def handle(self, gateway): + gateway._stopexec() + gateway._outgoing.put(self.STOP_RECEIVING()) + raise SystemExit + class STOP_RECEIVING(Message): + def handle(self, gateway): + raise SystemExit + class CHANNEL_OPEN(Message): + def handle(self, gateway): + channel = Channel(gateway, self.channelid) + gateway.channelfactory[self.channelid] = channel + gateway._execqueue.put((channel, self.data)) + class CHANNEL_DATA(Message): + def handle(self, gateway): + channel = gateway.channelfactory[self.channelid] + channel._items.put(eval(self.data)) + class CHANNEL_CLOSE(Message): + def handle(self, gateway): + channel = gateway.channelfactory[self.channelid] + channel._close() + del gateway.channelfactory[channel.id] + class CHANNEL_CLOSE_ERROR(Message): + def handle(self, gateway): + channel = gateway.channelfactory[self.channelid] + channel._close(self.data) + if debug: + for line in self.data.split('\n'): + gateway.trace("remote error: " + line) + classes = [x for x in locals().values() if hasattr(x, '__bases__')] + classes.sort(lambda x,y : cmp(x.__name__, y.__name__)) + i = 0 + for cls in classes: + Message._types[i] = cls + cls.msgtype = i + setattr(Message, cls.__name__, cls) + i+=1 + +_setupmessages() + + def getid(gw, cache={}): name = gw.__class__.__name__ try: @@ -230,7 +334,7 @@ _gateways = [] def cleanup_atexit(): + print "="*20 + "cleaning up" + "=" * 20 for x in _gateways: - if x.running: + if x.workerthreads: x.exit() - Modified: py/dist/py/execnet/gateway_test.py ============================================================================== --- py/dist/py/execnet/gateway_test.py (original) +++ py/dist/py/execnet/gateway_test.py Wed Oct 13 05:01:02 2004 @@ -1,8 +1,66 @@ import os, sys import py from py.__impl__.execnet.source import Source -autopath = py.magic.autopath() +from py.__impl__.execnet import gateway +mypath = py.magic.autopath() +from StringIO import StringIO + +class TestMessage: + def test_wire_protocol(self): + for cls in gateway.Message._types.values(): + one = StringIO() + cls(42, '23').writeto(one) + two = StringIO(one.getvalue()) + msg = gateway.Message.readfrom(two) + assert isinstance(msg, cls) + assert msg.channelid == 42 + assert msg.data == '23' + assert isinstance(repr(msg), str) + # == "" %(msg.__class__.__name__, ) + +class TestChannel: + def setup_method(self, method): + self.fac = gateway.ChannelFactory(None) + + def test_factory_create(self): + chan1 = self.fac.new() + assert chan1.id == 1 + chan2 = self.fac.new() + assert chan2.id == 3 + + def test_factory_getitem(self): + chan1 = self.fac.new() + assert self.fac[chan1.id] == chan1 + chan2 = self.fac.new() + assert self.fac[chan2.id] == chan2 + + def test_factory_delitem(self): + chan1 = self.fac.new() + assert self.fac[chan1.id] == chan1 + del self.fac[chan1.id] + py.test.raises(KeyError, self.fac.__getitem__, chan1.id) + + def test_factory_setitem(self): + channel = gateway.Channel(None, 12) + self.fac[channel.id] = channel + assert self.fac[channel.id] == channel + + def test_channel_timeouterror(self): + channel = self.fac.new() + py.test.raises(IOError, channel.waitclose, timeout=0.1) + + def test_channel_close(self): + channel = self.fac.new() + channel._close() + channel.waitclose(0.1) + + def test_channel_close_error(self): + channel = self.fac.new() + channel._close("error") + err = channel.waitclose(0.1) + assert err == "error" + class PopenGatewayTestSetup: disabled = True def setup_class(cls): @@ -11,90 +69,112 @@ def teardown_class(cls): cls.gw.exit() + +class BasicRemoteExecution: + disabled = True + + def test_correct_setup(self): + assert self.gw.workerthreads and self.gw.iothreads + + def test_syntax_error_in_debug_mode(self): + debug = gateway.debug + try: + gateway.debug = 1 + py.test.raises(SyntaxError, self.gw.remote_exec_async, "a='") + finally: + gateway.debug = debug + + def test_remote_exec_async_waitclose(self): + channel = self.gw.remote_exec_async('pass') + channel.waitclose(timeout=3.0) + + def test_remote_exec_async_channel_anonymous(self): + channel = self.gw.remote_exec_async(''' + obj = channel.receive() + channel.send(obj) + ''') + channel.send(42) + result = channel.receive(timeout=3.0) + assert result == 42 + +class TestBasicPopenGateway(PopenGatewayTestSetup, BasicRemoteExecution): + disabled = False + def test_many_popen(self): + num = 4 + l = [] + for i in range(num): + l.append(py.execnet.PopenGateway()) + channels = [] + for gw in l: + channel = gw.remote_exec_async("""channel.send(42)""") + channels.append(channel) + try: + while channels: + channel = channels.pop() + try: + ret = channel.receive() + assert ret == 42 + finally: + channel.gateway.exit() + finally: + for x in channels: + x.gateway.exit() + class SocketGatewayTestSetup: disabled = True def setup_class(cls): portrange = (7770, 7800) cls.proxygw = py.execnet.PopenGateway() - s = Source( - autopath.dirpath('bin', 'startserver.py').read(), - """ - import socket - for i in range%(portrange)r: - try: - sock = listen(("localhost", i)) - except socket.error: - continue - else: - gateway.remote_exec_oneshot("gateway._listenport=" + str(i)) - startserver(sock) - print "started server with socket" - break + socketserverbootstrap = Source( + mypath.dirpath('bin', 'startserver.py').read(), + """ + import socket + portrange = channel.receive() + for i in portrange: + try: + sock = bind_and_listen(("localhost", i)) + except socket.error: + continue else: - gateway.remote_exec_oneshot("gateway._listenport=None")""" % locals(), - ) - - cls.proxygw.remote_exec_oneshot(s) - cls.proxygw.wait_exec(timeout=5) - if not cls.proxygw._listenport: + channel.send(i) + startserver(sock) + print "started server with socket" + break + else: + channel.send(None) + """) + # open a gateway to a fresh child process + cls.proxygw = py.execnet.PopenGateway() + + # execute asynchronously the above socketserverbootstrap on the other + channel = cls.proxygw.remote_exec_async(socketserverbootstrap) + + # send parameters for the for-loop + channel.send((7770, 7800)) + # + # the other side should start the for loop now, we + # wait for the result + # + cls.listenport = channel.receive() + if cls.listenport is None: raise IOError, "could not setup remote SocketServer" - cls.gw = py.execnet.SocketGateway('localhost', cls.proxygw._listenport) - print "initialized socket gateway on port", cls.proxygw._listenport + cls.gw = py.execnet.SocketGateway('localhost', cls.listenport) + print "initialized socket gateway on port", cls.listenport def teardown_class(cls): print "trying to tear down remote socket gateway" cls.gw.exit() - if cls.proxygw._listenport: + if cls.gw.port: print "trying to tear down remote socket loop" import socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.connect(('localhost', cls.proxygw._listenport)) + sock.connect(('localhost', cls.listenport)) sock.sendall('"raise KeyboardInterrupt"') sock.shutdown(2) print "trying to tear proxy gateway" cls.proxygw.exit() -class BasicRemoteExecution: - disabled = True - def test_remote_exec_oneshot(self): - self.gw.clear_exec() - self.gw.remote_exec_oneshot('gateway.remote_exec_oneshot("test_1_back = 42")') - self.gw.wait_exec() - assert self.gw.ns['test_1_back'] == 42 - - def test_remote_exec_async_with_handle(self): - handle = self.gw.remote_exec_async('gateway.remote_exec_oneshot("test_2_back = 17")') - handle.wait(timeout=10.0) - assert self.gw.ns['test_2_back'] == 17 - - def test_remote_exec_sync(self): - self.gw.remote_exec_sync('gateway.remote_exec_oneshot("test_3_back = 43")') - assert self.gw.ns['test_3_back'] == 43 - -class BasicPopenGatewayTest(PopenGatewayTestSetup, BasicRemoteExecution): - disabled = False - def test_many_popen(self): - num = 4 - l = [] - for i in range(num): - l.append(py.execnet.PopenGateway()) - stati = [] - for gw in l: - status = gw.remote_exec_async("gateway.remote_exec_oneshot('back=42')") - stati.append(status) - try: - while stati: - status = stati.pop() - try: - status.wait(timeout=3.0) - assert status.gateway.ns['back'] == 42 - finally: - status.gateway.exit() - finally: - for x in stati: - x.gateway.exit() - class BasicSocketGatewayTest(SocketGatewayTestSetup, BasicRemoteExecution): disabled = False Modified: py/dist/py/execnet/register.py ============================================================================== --- py/dist/py/execnet/register.py (original) +++ py/dist/py/execnet/register.py Wed Oct 13 05:01:02 2004 @@ -1,5 +1,6 @@ from py.magic import autopath ; autopath = autopath() +import Queue import os, inspect, socket @@ -8,40 +9,56 @@ py.magic.invoke(dyncode=True) class InstallableGateway(gateway.Gateway): - def __init__(self, io, ns = None): + """ initialize gateways on both sides of a inputoutput object. """ + def __init__(self, io): self.remote_bootstrap_gateway(io) - gateway.Gateway.__init__(self, io, ns) + gateway.Gateway.__init__(self, io=io, startcount=1) def remote_bootstrap_gateway(self, io): """ return Gateway with a asynchronously remotely initialized counterpart Gateway (which may or may not succeed). + Note that the other sides gateways starts enumerating + its channels with even numbers while the sender + gateway starts with odd numbers. This allows to + uniquely identify channels across both sides. """ bootstrap = [ inspect.getsource(inputoutput), - io.server_stmt, inspect.getsource(gateway), - "gateway = Gateway(io)", - "gateway.thread_executor.join()" + io.server_stmt, + "Gateway(io=io, startcount=2).join()", + "print 'exiting gateway'", ] source = "\n".join(bootstrap) self.trace("sending gateway bootstrap code") io.write('%r\n' % source) class PopenGateway(InstallableGateway): - def __init__(self, python="python", ns=None): + def __init__(self, python="python"): cmd = '%s -u -c "exec input()"' % python infile, outfile = os.popen2(cmd) io = inputoutput.Popen2IO(infile, outfile) - InstallableGateway.__init__(self, io, ns) + InstallableGateway.__init__(self, io=io) + self._pidchannel = self.remote_exec_async("import os ; channel.send(os.getpid())") + + def exit(self): + super(PopenGateway, self).exit() + try: + pid = self._pidchannel.receive(0.5) + except Queue.Empty(): + self.trace("could not receive child PID") + else: + self.trace("waiting for pid %s" % pid) + os.waitpid(pid, 0) class SocketGateway(InstallableGateway): - def __init__(self, host, port, ns=None): + def __init__(self, host, port): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - host = str(host) - port = int(port) + self.host = host = str(host) + self.port = port = int(port) sock.connect((host, port)) io = inputoutput.SocketIO(sock) - InstallableGateway.__init__(self, io, ns) + InstallableGateway.__init__(self, io=io) class ExecGateway(PopenGateway): def remote_exec_sync_stdcapture(self, lines, callback): From hpk at codespeak.net Wed Oct 13 17:58:29 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 13 Oct 2004 17:58:29 +0200 (MEST) Subject: [py-svn] r6916 - in py/dist: doc py py/execnet py/execnet/bin py/test py/test/test/data Message-ID: <20041013155829.718585AAB6@thoth.codespeak.net> Author: hpk Date: Wed Oct 13 17:58:28 2004 New Revision: 6916 Modified: py/dist/doc/execnet.txt py/dist/py/api_test.py py/dist/py/execnet/bin/startserver.py py/dist/py/execnet/gateway.py py/dist/py/execnet/gateway_test.py py/dist/py/execnet/register.py py/dist/py/execnet/source_test.py py/dist/py/test/collect.py py/dist/py/test/collect_test.py py/dist/py/test/test/data/Collector.py Log: - fixes for python2.2 - removed timeout parameter from many execnet methods - introduced documentation for upcoming Channel improvements (reraising remote exceptions) Modified: py/dist/doc/execnet.txt ============================================================================== --- py/dist/doc/execnet.txt (original) +++ py/dist/doc/execnet.txt Wed Oct 13 17:58:28 2004 @@ -80,6 +80,16 @@ channel.receive(): receives an item that was sent from the other side, possibly blocking if there is none. + Note that exceptions from the other side will be + reraised as gateway.ExecutionFailed exceptions containing + a textual representation of the remote traceback. + + channel.waitclose(timeout=None): + wait until this channel is closed. Note that a closed + channel may still hold items that will be received or + send. Note that exceptions from the other side will be + reraised as gateway.ExecutionFailed exceptions containing + a textual representation of the remote traceback. # # API for setting and getting named values ("named signals") Modified: py/dist/py/api_test.py ============================================================================== --- py/dist/py/api_test.py (original) +++ py/dist/py/api_test.py Wed Oct 13 17:58:28 2004 @@ -1,6 +1,7 @@ from py.test import raises import py +import sys import inspect class API_V0_namespace_consistence: @@ -43,7 +44,8 @@ obj = getattr(mod, name) fullpath = modpath + '.' + name assert obj.__module__ == modpath - assert obj.__name__ == name + if sys.version_info >= (2,3): + assert obj.__name__ == name def assert_function(modpath, name): mod = __import__(modpath, None, None, [name]) Modified: py/dist/py/execnet/bin/startserver.py ============================================================================== --- py/dist/py/execnet/bin/startserver.py (original) +++ py/dist/py/execnet/bin/startserver.py Wed Oct 13 17:58:28 2004 @@ -7,7 +7,7 @@ # progname = 'socket_readline_exec_server-1.2' -debug = 1 +debug = 0 import sys, socket, os @@ -19,7 +19,7 @@ def execloop(serversock): while 1: try: - print progname, 'Entering Accept loop', sock.getsockname() + print progname, 'Entering Accept loop', serversock.getsockname() clientsock,address = serversock.accept() print progname, 'got new connection from %s %s' % address clientfile = clientsock.makefile('r+b',0) Modified: py/dist/py/execnet/gateway.py ============================================================================== --- py/dist/py/execnet/gateway.py (original) +++ py/dist/py/execnet/gateway.py Wed Oct 13 17:58:28 2004 @@ -1,7 +1,7 @@ import sys, os, threading, struct, Queue, traceback import atexit -import time +# XXX the following line should not be here from py.__impl__.execnet.source import Source debug = 0 @@ -19,13 +19,11 @@ threading.Thread(target=self.thread_receiver, name='receiver'), threading.Thread(target=self.thread_sender, name='sender'), ] - for x in self.iothreads: - x.start() self.workerthreads = w = [] for x in range(self.num_worker_threads): w.append(threading.Thread(target=self.thread_executor, name='executor %d' % x)) - for x in w: + for x in self.iothreads + w: x.start() if not _gateways: atexit.register(cleanup_atexit) @@ -118,7 +116,7 @@ loc = { 'channel' : channel } self.trace("execution starts:", repr(source)[:50]) try: - co = compile(source, '', 'exec') + co = compile(source+'\n', '', 'exec') exec co in loc finally: self.trace("execution finished:", repr(source)[:50]) @@ -146,20 +144,17 @@ object in turn. """ source = str(Source(source)) - if debug: - import parser - parser.suite(source) channel = self.channelfactory.new() self._outgoing.put(Message.CHANNEL_OPEN(channel.id, source)) return channel - def remote_exec_sync(self, source, timeout=10): + def remote_exec_sync(self, source): """ synchronously execute source on the other side of the gateway return after execution of the source finishes on the other side. """ channel = self.remote_exec_async(source) - channel.waitclose(timeout=timeout) + channel.waitclose() return class Channel(object): @@ -178,9 +173,14 @@ flag = self._closeevent.isSet() and "closed" or "open" return "" % (self.id, flag) - def waitclose(self, timeout=None): - """ return error message (None if no error) after waiting for close event. """ - self._closeevent.wait(timeout) + def waitclose(self, timeout): + """ wait until this channel is closed. Note that a closed + channel may still hold items that will be received or + send. Note that exceptions from the other side will be + reraised as gateway.ExecutionFailed exceptions containing + a textual representation of the remote traceback. + """ + self._closeevent.wait(timeout=timeout) if not self._closeevent.isSet(): raise IOError, "Timeout" return self._error @@ -193,10 +193,14 @@ eval(repr(V)) == V.""" self.gateway._outgoing.put(Message.CHANNEL_DATA(self.id, repr(item))) - def receive(self, timeout=None): + def receive(self): """receives an item that was sent from the other side, - possibly blocking if there is none.""" - return self._items.get(timeout=timeout) + possibly blocking if there is none. + Note that exceptions from the other side will be + reraised as gateway.ExecutionFailed exceptions containing + a textual representation of the remote traceback. + """ + return self._items.get() # # helpers Modified: py/dist/py/execnet/gateway_test.py ============================================================================== --- py/dist/py/execnet/gateway_test.py (original) +++ py/dist/py/execnet/gateway_test.py Wed Oct 13 17:58:28 2004 @@ -76,14 +76,6 @@ def test_correct_setup(self): assert self.gw.workerthreads and self.gw.iothreads - def test_syntax_error_in_debug_mode(self): - debug = gateway.debug - try: - gateway.debug = 1 - py.test.raises(SyntaxError, self.gw.remote_exec_async, "a='") - finally: - gateway.debug = debug - def test_remote_exec_async_waitclose(self): channel = self.gw.remote_exec_async('pass') channel.waitclose(timeout=3.0) @@ -94,7 +86,7 @@ channel.send(obj) ''') channel.send(42) - result = channel.receive(timeout=3.0) + result = channel.receive() assert result == 42 class TestBasicPopenGateway(PopenGatewayTestSetup, BasicRemoteExecution): @@ -135,6 +127,9 @@ try: sock = bind_and_listen(("localhost", i)) except socket.error: + print "got error" + import traceback + traceback.print_exc() continue else: channel.send(i) Modified: py/dist/py/execnet/register.py ============================================================================== --- py/dist/py/execnet/register.py (original) +++ py/dist/py/execnet/register.py Wed Oct 13 17:58:28 2004 @@ -1,8 +1,7 @@ from py.magic import autopath ; autopath = autopath() -import Queue - import os, inspect, socket +import sys import py from py.__impl__.execnet import inputoutput, gateway @@ -27,14 +26,13 @@ inspect.getsource(gateway), io.server_stmt, "Gateway(io=io, startcount=2).join()", - "print 'exiting gateway'", ] source = "\n".join(bootstrap) self.trace("sending gateway bootstrap code") io.write('%r\n' % source) class PopenGateway(InstallableGateway): - def __init__(self, python="python"): + def __init__(self, python=sys.executable): cmd = '%s -u -c "exec input()"' % python infile, outfile = os.popen2(cmd) io = inputoutput.Popen2IO(infile, outfile) @@ -44,8 +42,9 @@ def exit(self): super(PopenGateway, self).exit() try: - pid = self._pidchannel.receive(0.5) - except Queue.Empty(): + self._pidchannel.waitclose(timeout=0.5) + pid = self._pidchannel.receive() + except IOError: self.trace("could not receive child PID") else: self.trace("waiting for pid %s" % pid) Modified: py/dist/py/execnet/source_test.py ============================================================================== --- py/dist/py/execnet/source_test.py (original) +++ py/dist/py/execnet/source_test.py Wed Oct 13 17:58:28 2004 @@ -38,4 +38,3 @@ #x.indent("try:", # """except: - Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Wed Oct 13 17:58:28 2004 @@ -34,7 +34,7 @@ def _except(self): excinfo = sys.exc_info() - if issubclass(excinfo[0], (KeyboardInterrupt, SystemExit)): + if isinstance(excinfo[1], (KeyboardInterrupt, SystemExit)): raise excinfo # KeyboardInterrupt return Error(excinfo) Modified: py/dist/py/test/collect_test.py ============================================================================== --- py/dist/py/test/collect_test.py (original) +++ py/dist/py/test/collect_test.py Wed Oct 13 17:58:28 2004 @@ -31,7 +31,7 @@ assert isinstance(l[0], test.collect.Error) import traceback print traceback.print_exception(*l[0].excinfo) - assert issubclass(l[0].excinfo[0], (IOError, OSError)) + assert isinstance(l[0].excinfo[1], (IOError, OSError)) def test_syntax_error_in_module(): modpath = 'py.__impl__.utest.test.data.syntax_error.whatever' Modified: py/dist/py/test/test/data/Collector.py ============================================================================== --- py/dist/py/test/test/data/Collector.py (original) +++ py/dist/py/test/test/data/Collector.py Wed Oct 13 17:58:28 2004 @@ -1,4 +1,4 @@ - +from __future__ import generators from py.test import collect class Collector(collect.PyCollector): From hpk at codespeak.net Wed Oct 13 18:44:30 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 13 Oct 2004 18:44:30 +0200 (MEST) Subject: [py-svn] r6917 - in py/dist: doc py/execnet Message-ID: <20041013164430.8AF195AAB6@thoth.codespeak.net> Author: hpk Date: Wed Oct 13 18:44:29 2004 New Revision: 6917 Modified: py/dist/doc/execnet.txt py/dist/py/execnet/gateway.py py/dist/py/execnet/gateway_test.py Log: - introduced reraising of RemoteError in Channel.waitclose() and Channel.receive(). - there still is a minor problem in the shutdown procedure of the SocketGateway Modified: py/dist/doc/execnet.txt ============================================================================== --- py/dist/doc/execnet.txt (original) +++ py/dist/doc/execnet.txt Wed Oct 13 18:44:29 2004 @@ -81,14 +81,14 @@ receives an item that was sent from the other side, possibly blocking if there is none. Note that exceptions from the other side will be - reraised as gateway.ExecutionFailed exceptions containing + reraised as gateway.RemoteError exceptions containing a textual representation of the remote traceback. channel.waitclose(timeout=None): wait until this channel is closed. Note that a closed channel may still hold items that will be received or send. Note that exceptions from the other side will be - reraised as gateway.ExecutionFailed exceptions containing + reraised as gateway.RemoteError exceptions containing a textual representation of the remote traceback. # Modified: py/dist/py/execnet/gateway.py ============================================================================== --- py/dist/py/execnet/gateway.py (original) +++ py/dist/py/execnet/gateway.py Wed Oct 13 18:44:29 2004 @@ -7,8 +7,21 @@ debug = 0 sysex = (KeyboardInterrupt, SystemExit) +class RemoteError(Exception): + """ Contains an Exceptions from the other side. """ + def __init__(self, formatted): + Exception.__init__(self) + self.formatted = formatted + + def __str__(self): + return self.formatted + + def __repr__(self): + return "%s: %s" %(self.__class__.__name__, self.formatted) + class Gateway(object): num_worker_threads = 2 + RemoteError = RemoteError def __init__(self, io, startcount=2): self.io = io @@ -166,7 +179,11 @@ self._closeevent = threading.Event() def _close(self, error=None): - self._error = error + if error is not None: + self._error = RemoteError(error) + self._items.put(self._error) + else: + self._error = None self._closeevent.set() def __repr__(self): @@ -183,7 +200,8 @@ self._closeevent.wait(timeout=timeout) if not self._closeevent.isSet(): raise IOError, "Timeout" - return self._error + if self._error: + raise self._error def send(self, item): """sends the given item to the other side of the channel, @@ -200,7 +218,10 @@ reraised as gateway.ExecutionFailed exceptions containing a textual representation of the remote traceback. """ - return self._items.get() + x = self._items.get() + if isinstance(x, RemoteError): + raise x + return x # # helpers @@ -286,6 +307,7 @@ return "" %(self.__class__.__name__, self.channelid, self.data) + def _setupmessages(): class EXIT_GATEWAY(Message): def handle(self, gateway): @@ -333,7 +355,7 @@ try: return cache.setdefault(name, {})[id(gw)] except KeyError: - cache[name][id(gw)] = x = "%s.%d" %(gw.__class__.__name__, len(cache[name])) + cache[name][id(gw)] = x = "%s:%s.%d" %(os.getpid(), gw.__class__.__name__, len(cache[name])) return x _gateways = [] Modified: py/dist/py/execnet/gateway_test.py ============================================================================== --- py/dist/py/execnet/gateway_test.py (original) +++ py/dist/py/execnet/gateway_test.py Wed Oct 13 18:44:29 2004 @@ -48,7 +48,7 @@ def test_channel_timeouterror(self): channel = self.fac.new() - py.test.raises(IOError, channel.waitclose, timeout=0.1) + py.test.raises(IOError, channel.waitclose, timeout=0.01) def test_channel_close(self): channel = self.fac.new() @@ -58,9 +58,8 @@ def test_channel_close_error(self): channel = self.fac.new() channel._close("error") - err = channel.waitclose(0.1) - assert err == "error" - + py.test.raises(gateway.RemoteError, channel.waitclose, 0.1) + class PopenGatewayTestSetup: disabled = True def setup_class(cls): @@ -69,7 +68,6 @@ def teardown_class(cls): cls.gw.exit() - class BasicRemoteExecution: disabled = True @@ -89,6 +87,18 @@ result = channel.receive() assert result == 42 + def test_channel_close_and_then_receive_error(self): + channel = self.gw.remote_exec_async('raise ValueError') + py.test.raises(gateway.RemoteError, channel.receive) + + def test_channel_close_and_then_receive_error_multiple(self): + channel = self.gw.remote_exec_async('channel.send(42) ; raise ValueError') + import time + time.sleep(0.1) + x = channel.receive() + assert x == 42 + py.test.raises(gateway.RemoteError, channel.receive) + class TestBasicPopenGateway(PopenGatewayTestSetup, BasicRemoteExecution): disabled = False def test_many_popen(self): @@ -134,7 +144,6 @@ else: channel.send(i) startserver(sock) - print "started server with socket" break else: channel.send(None) From hpk at codespeak.net Wed Oct 13 20:25:38 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 13 Oct 2004 20:25:38 +0200 (MEST) Subject: [py-svn] r6918 - in py/dist: doc py/execnet Message-ID: <20041013182538.5BC7C5AAB6@thoth.codespeak.net> Author: hpk Date: Wed Oct 13 20:25:35 2004 New Revision: 6918 Modified: py/dist/doc/execnet.txt py/dist/py/execnet/gateway.py py/dist/py/execnet/gateway_test.py py/dist/py/execnet/register.py Log: removed remote_exec_async and sync in favour of a single asynchronous remote_exec(source) method. If you want to wait for the other side to finish you can simply - due to the new channels - do: channel = remote_exec(source) channel.waitclose(timeout=None) or in short: remote_exec(source).waitclose() Modified: py/dist/doc/execnet.txt ============================================================================== --- py/dist/doc/execnet.txt (original) +++ py/dist/doc/execnet.txt Wed Oct 13 20:25:35 2004 @@ -1,39 +1,47 @@ The py.execnet library ====================== +this is very much a draft version. + Execnet deals with letting your python programs execute and communicate across process and computer barriers. At the core it is a very simple and powerful mechanism: executing -source code at "the other side". The other side is reachable -through Gateways, currently - -- py.execnet.PopenGateway for executing code within a child process - on the same computer +source code at "the other side" and communicating with +the remote part of your program. -- py.execnet.SocketGateway for executing code across a socket connection. +The main benefit of taking the view of '''asynchronously executing +code fragments''' for the problem of remote executio is that ''the +communication protocols can be defined by the client side'''. +Usually, with server/client apps and especially RMI systems you +often have to upgrade your server if you upgrade your client. + +Basic Features +============== + +With ''py.execnet'' you get the means + +- to navigate through the network with Process, Thread, SSH + and Socket- gateways that allow you ... + +- to distribute your program across a network and define + communication protocols from the client side, making + "server" upgrades superflous. For now, see the py/gateway/gateway_test.py file for how it works currently. -High Level Interface for executing code ---------------------------------------- - -These gateways offer the following "high level interface" - - [[docfunc(py.__impl__.execnet.gateway.Gateway.remote_exec_oneshot)]] - - [[docfunc(py.__impl__.execnet.gateway.Gateway.remote_exec_async)]] - - [[docfunc(py.__impl__.execnet.gateway.Gateway.remote_exec_sync)]] - - [[docfunc(py.__impl__.execnet.gateway.Gateway.remote_import)]] +High Level Interface for remote execution +----------------------------------------- -(editors note: the mechanism to substitute those "docfunc" calls -is not there yet, but i want such documentation to be automatically -connected to the real code, showing its docstrings etc.pp) +These gateways offer one main high level interface: + def exec_remote(source): + """return channel object for communicating with the asynchronously + executing 'source' code which will have a corresponding 'channel' + object in its executing namespace. + The most important property of execnet gateways is that -the protocol they speak with each other ``are determined by +the protocol they speak with each other ``is determined by the client side``. If you open a gateway on some server in order to coordinate with some other programs you determine your own protocol, not affecting all the others. Modified: py/dist/py/execnet/gateway.py ============================================================================== --- py/dist/py/execnet/gateway.py (original) +++ py/dist/py/execnet/gateway.py Wed Oct 13 20:25:35 2004 @@ -4,7 +4,7 @@ # XXX the following line should not be here from py.__impl__.execnet.source import Source -debug = 0 +debug = 1 sysex = (KeyboardInterrupt, SystemExit) class RemoteError(Exception): @@ -90,10 +90,9 @@ try: msg = Message.readfrom(self.io) self.trace("received <- %r" % msg) - msg.handle(self) - except SystemExit: - self.io.close_read() - return + msg.received(self) + except sysex: + raise except: self.traceex(sys.exc_info()) break @@ -107,13 +106,14 @@ msg = self._outgoing.get() try: msg.writeto(self.io) - except self.io.error: - self.traceex(sys.exc_info()) + except: + excinfo = sys.exc_info() + self.traceex(excinfo) + msg.post_sent(self, excinfo) + raise else: self.trace('sent -> %r' % msg) - if isinstance(msg, (Message.STOP_RECEIVING, Message.EXIT_GATEWAY)): - self.io.close_write() - break + msg.post_sent(self) finally: self.trace('leaving %r' % threading.currentThread()) @@ -151,26 +151,18 @@ # High Level Interface # _____________________________________________________________________ - def remote_exec_async(self, source): + def remote_exec(self, source): """ return channel object for communicating with the asynchronously - executing source code which can use a corresponding 'channel' - object in turn. + executing 'source' code which will have a corresponding 'channel' + object in its executing namespace. """ source = str(Source(source)) channel = self.channelfactory.new() self._outgoing.put(Message.CHANNEL_OPEN(channel.id, source)) return channel - def remote_exec_sync(self, source): - """ synchronously execute source on the other side of the gateway - return after execution of the source finishes on the other - side. - """ - channel = self.remote_exec_async(source) - channel.waitclose() - return - class Channel(object): + """Communication channel between two possibly remote threads of code. """ def __init__(self, gateway, id): assert isinstance(id, int) self.gateway = gateway @@ -299,6 +291,9 @@ return msg readfrom = classmethod(readfrom) + def post_sent(self, gateway, excinfo=None): + pass + def __repr__(self): if len(self.data) > 50: return "" %(self.__class__.__name__, @@ -309,35 +304,51 @@ def _setupmessages(): + # + # EXIT_GATEWAY and STOP_RECEIVING are messages to cleanly + # bring down the IO connection, i.e. it shouldn't die + # unexpectedly. + # + # First an EXIT_GATEWAY message is send which results + # on the other side's receive_handle + # which cleanly class EXIT_GATEWAY(Message): - def handle(self, gateway): + def received(self, gateway): gateway._stopexec() gateway._outgoing.put(self.STOP_RECEIVING()) raise SystemExit + def post_sent(self, gateway, excinfo=None): + gateway.io.close_write() + raise SystemExit class STOP_RECEIVING(Message): - def handle(self, gateway): + def received(self, gateway): + # note that we don't need to close io.close_read() + # as the sender side will have closed the io + # already. With sockets closing it would raise + # a Transport Not Connected exception raise SystemExit + def post_sent(self, gateway, excinfo=None): + gateway.io.close_write() + raise SystemExit + class CHANNEL_OPEN(Message): - def handle(self, gateway): + def received(self, gateway): channel = Channel(gateway, self.channelid) gateway.channelfactory[self.channelid] = channel gateway._execqueue.put((channel, self.data)) class CHANNEL_DATA(Message): - def handle(self, gateway): + def received(self, gateway): channel = gateway.channelfactory[self.channelid] channel._items.put(eval(self.data)) class CHANNEL_CLOSE(Message): - def handle(self, gateway): + def received(self, gateway): channel = gateway.channelfactory[self.channelid] channel._close() del gateway.channelfactory[channel.id] class CHANNEL_CLOSE_ERROR(Message): - def handle(self, gateway): + def received(self, gateway): channel = gateway.channelfactory[self.channelid] channel._close(self.data) - if debug: - for line in self.data.split('\n'): - gateway.trace("remote error: " + line) classes = [x for x in locals().values() if hasattr(x, '__bases__')] classes.sort(lambda x,y : cmp(x.__name__, y.__name__)) i = 0 Modified: py/dist/py/execnet/gateway_test.py ============================================================================== --- py/dist/py/execnet/gateway_test.py (original) +++ py/dist/py/execnet/gateway_test.py Wed Oct 13 20:25:35 2004 @@ -58,7 +58,7 @@ def test_channel_close_error(self): channel = self.fac.new() channel._close("error") - py.test.raises(gateway.RemoteError, channel.waitclose, 0.1) + py.test.raises(gateway.RemoteError, channel.waitclose, 0.01) class PopenGatewayTestSetup: disabled = True @@ -74,12 +74,12 @@ def test_correct_setup(self): assert self.gw.workerthreads and self.gw.iothreads - def test_remote_exec_async_waitclose(self): - channel = self.gw.remote_exec_async('pass') + def test_remote_exec_waitclose(self): + channel = self.gw.remote_exec('pass') channel.waitclose(timeout=3.0) - def test_remote_exec_async_channel_anonymous(self): - channel = self.gw.remote_exec_async(''' + def test_remote_exec_channel_anonymous(self): + channel = self.gw.remote_exec(''' obj = channel.receive() channel.send(obj) ''') @@ -88,11 +88,11 @@ assert result == 42 def test_channel_close_and_then_receive_error(self): - channel = self.gw.remote_exec_async('raise ValueError') + channel = self.gw.remote_exec('raise ValueError') py.test.raises(gateway.RemoteError, channel.receive) def test_channel_close_and_then_receive_error_multiple(self): - channel = self.gw.remote_exec_async('channel.send(42) ; raise ValueError') + channel = self.gw.remote_exec('channel.send(42) ; raise ValueError') import time time.sleep(0.1) x = channel.receive() @@ -108,7 +108,7 @@ l.append(py.execnet.PopenGateway()) channels = [] for gw in l: - channel = gw.remote_exec_async("""channel.send(42)""") + channel = gw.remote_exec("""channel.send(42)""") channels.append(channel) try: while channels: @@ -152,7 +152,7 @@ cls.proxygw = py.execnet.PopenGateway() # execute asynchronously the above socketserverbootstrap on the other - channel = cls.proxygw.remote_exec_async(socketserverbootstrap) + channel = cls.proxygw.remote_exec(socketserverbootstrap) # send parameters for the for-loop channel.send((7770, 7800)) Modified: py/dist/py/execnet/register.py ============================================================================== --- py/dist/py/execnet/register.py (original) +++ py/dist/py/execnet/register.py Wed Oct 13 20:25:35 2004 @@ -37,7 +37,7 @@ infile, outfile = os.popen2(cmd) io = inputoutput.Popen2IO(infile, outfile) InstallableGateway.__init__(self, io=io) - self._pidchannel = self.remote_exec_async("import os ; channel.send(os.getpid())") + self._pidchannel = self.remote_exec("import os ; channel.send(os.getpid())") def exit(self): super(PopenGateway, self).exit() From hpk at codespeak.net Wed Oct 13 20:27:46 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 13 Oct 2004 20:27:46 +0200 (MEST) Subject: [py-svn] r6919 - py/dist/doc Message-ID: <20041013182746.D32E75AAB6@thoth.codespeak.net> Author: hpk Date: Wed Oct 13 20:27:46 2004 New Revision: 6919 Modified: py/dist/doc/execnet.txt Log: small fix to the doc Modified: py/dist/doc/execnet.txt ============================================================================== --- py/dist/doc/execnet.txt (original) +++ py/dist/doc/execnet.txt Wed Oct 13 20:27:46 2004 @@ -27,15 +27,14 @@ communication protocols from the client side, making "server" upgrades superflous. -For now, see the py/gateway/gateway_test.py file for how it works -currently. +See file:py/gateway/gateway_test.py for how it works currently. High Level Interface for remote execution ----------------------------------------- These gateways offer one main high level interface: - def exec_remote(source): + def remote_exec(source): """return channel object for communicating with the asynchronously executing 'source' code which will have a corresponding 'channel' object in its executing namespace. From hpk at codespeak.net Thu Oct 14 02:04:03 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 14 Oct 2004 02:04:03 +0200 (MEST) Subject: [py-svn] r6920 - in py/dist: doc py/execnet py/path py/path/local py/path/pypath py/path/svn py/path/svnwc py/path/test py/test Message-ID: <20041014000403.AF5E25AAB6@thoth.codespeak.net> Author: hpk Date: Thu Oct 14 02:04:02 2004 New Revision: 6920 Removed: py/dist/py/path/test/test_svn_binding.py Modified: py/dist/doc/test.txt py/dist/py/execnet/gateway.py py/dist/py/execnet/gateway_test.py py/dist/py/path/common.py py/dist/py/path/local/local.py py/dist/py/path/local/local_test.py py/dist/py/path/pypath/pypath_test.py py/dist/py/path/svn/command.py py/dist/py/path/svn/command_test.py py/dist/py/path/svn/common.py py/dist/py/path/svnwc/command.py py/dist/py/path/svnwc/command_test.py py/dist/py/path/test/_common.py py/dist/py/path/test/_svncommon.py py/dist/py/test/collect.py py/dist/py/test/collect_test.py py/dist/py/test/compat_test.py py/dist/py/test/raises_test.py Log: huge commit - added copy method to filesystem path objects - py.test: by default only collect classes that start with 'Test' - py.test: fix bug that previously resulted in running tests twice! - small cleanups here and there Modified: py/dist/doc/test.txt ============================================================================== --- py/dist/doc/test.txt (original) +++ py/dist/doc/test.txt Thu Oct 14 02:04:02 2004 @@ -184,7 +184,10 @@ dot (e.g. ``.svn`` direcotries is ignored). Another ``PyCollector`` then recurses into the test files and -collects functions and methods that have a leading ``test_`` +collects functions and methods that have a leading ``test_``. +By default, methods are only collected if their class starts +with ``Test``. + name, unless you provide a custom collector in your module. Modified: py/dist/py/execnet/gateway.py ============================================================================== --- py/dist/py/execnet/gateway.py (original) +++ py/dist/py/execnet/gateway.py Thu Oct 14 02:04:02 2004 @@ -4,7 +4,7 @@ # XXX the following line should not be here from py.__impl__.execnet.source import Source -debug = 1 +debug = 0 sysex = (KeyboardInterrupt, SystemExit) class RemoteError(Exception): Modified: py/dist/py/execnet/gateway_test.py ============================================================================== --- py/dist/py/execnet/gateway_test.py (original) +++ py/dist/py/execnet/gateway_test.py Thu Oct 14 02:04:02 2004 @@ -61,7 +61,6 @@ py.test.raises(gateway.RemoteError, channel.waitclose, 0.01) class PopenGatewayTestSetup: - disabled = True def setup_class(cls): cls.gw = py.execnet.PopenGateway() @@ -69,8 +68,6 @@ cls.gw.exit() class BasicRemoteExecution: - disabled = True - def test_correct_setup(self): assert self.gw.workerthreads and self.gw.iothreads @@ -100,7 +97,6 @@ py.test.raises(gateway.RemoteError, channel.receive) class TestBasicPopenGateway(PopenGatewayTestSetup, BasicRemoteExecution): - disabled = False def test_many_popen(self): num = 4 l = [] @@ -122,9 +118,7 @@ for x in channels: x.gateway.exit() -class SocketGatewayTestSetup: - disabled = True - +class SocketGatewaySetup: def setup_class(cls): portrange = (7770, 7800) cls.proxygw = py.execnet.PopenGateway() @@ -179,6 +173,5 @@ print "trying to tear proxy gateway" cls.proxygw.exit() -class BasicSocketGatewayTest(SocketGatewayTestSetup, BasicRemoteExecution): - disabled = False - +class TestSocketGateway(SocketGatewaySetup, BasicRemoteExecution): + pass Modified: py/dist/py/path/common.py ============================================================================== --- py/dist/py/path/common.py (original) +++ py/dist/py/path/common.py Thu Oct 14 02:04:02 2004 @@ -172,19 +172,20 @@ except: self._except(sys.exc_info()) - def visit(self, fil=None, rec=None, ignore=None): + def visit(self, fil=None, rec=None, ignore=None): if isinstance(fil, str): fil = fnmatch(fil) if isinstance(rec, str): rec = fnmatch(fil) - - reclist = [] - try: + if ignore: + try: + dirlist = self.listdir() + except ignore: + return + else: dirlist = self.listdir() - except ignore: - return - checkdir = path.checker(dir=1) + reclist = [] for p in dirlist: if fil is None or fil(p): yield p Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Thu Oct 14 02:04:02 2004 @@ -53,15 +53,10 @@ Note also that passing in a local path object will simply return the exact same path object. """ - #if isinstance(path, common.VPath): - # path = path._impl - - if isinstance(path, LocalPath): + if isinstance(path, cls): if path.__class__ == cls: return path path = path.strpath - - # initialize the path self = object.__new__(cls) if path is None: @@ -246,10 +241,11 @@ if self.check(dir=1, link=0): if rec: import shutil - def call(*args): - print args + #def call(*args): + # print args - shutil.rmtree(self.strpath, onerror=call) + shutil.rmtree(self.strpath) + #, onerror=call) else: os.rmdir(self.strpath) else: @@ -257,6 +253,30 @@ except: self._except(sys.exc_info()) + def copy(self, target, archive=False): + try: + assert not archive + if self.check(file=1): + if target.check(dir=1): + target = target.join(self.basename) + assert self!=target + copychunked(self, target) + else: + target.ensure(dir=1) + def rec(p): + return rec.check(link=0) + for x in self.visit(rec=rec): + relpath = x.relto(self) + newx = target.join(relpath) + if x.check(link=1): + newx.mksymlinkto(x.readlink()) + elif x.check(file=1): + copychunked(x, newx) + elif x.check(dir=1): + newx.ensure(dir=1) + except: + self._except(sys.exc_info()) + def dumpobj(self, obj): """ pickle object into path location""" try: @@ -364,7 +384,7 @@ #""" - #Tool functions regarding local filesystem paths + #special class constructors for local filesystem paths #""" def get_temproot(cls): """ return the system's temporary directory (where tempfiles are usually created in)""" @@ -444,3 +464,18 @@ # current = current.dirpath() #parentdirmatch = classmethod(parentdirmatch) +def copychunked(src, dest): + chunksize = 524288 # bytes + fsrc = src.open('rb') + try: + fdest = dest.open('wb') + try: + while 1: + buf = fsrc.read(chunksize) + if not buf: + break + fdest.write(buf) + finally: + fdest.close() + finally: + fsrc.close() Modified: py/dist/py/path/local/local_test.py ============================================================================== --- py/dist/py/path/local/local_test.py (original) +++ py/dist/py/path/local/local_test.py Thu Oct 14 02:04:02 2004 @@ -310,10 +310,10 @@ oldmodes = {} for x in self.root.visit(rec=recfilter): oldmodes[x] = x.mode() - self.root.chmod(0577, rec=1) + self.root.chmod(0772, rec=1) try: for x in self.root.visit(rec=recfilter): - assert x.mode() & 0777 == 0577 + assert x.mode() & 0777 == 0772 finally: for x,y in oldmodes.items(): x.chmod(y) Modified: py/dist/py/path/pypath/pypath_test.py ============================================================================== --- py/dist/py/path/pypath/pypath_test.py (original) +++ py/dist/py/path/pypath/pypath_test.py Thu Oct 14 02:04:02 2004 @@ -132,7 +132,7 @@ assert path.py('os.path') == l[2] assert path.py('os.path.abspath') == l[3] -class EvalTest: +class TestEval: disabled = True def test_funccall(self): p = path.py('os.path.join("a", "b")') @@ -147,7 +147,7 @@ p = path.py('os.path.qwe("a", ') s = test.raises(ValueError, "p.resolve()") -class ErrorTests: +class TestErrors: def test_FileNotFound(self): p = path.py('somesuch') test.raises(path.NotFound, p.resolve) Modified: py/dist/py/path/svn/command.py ============================================================================== --- py/dist/py/path/svn/command.py (original) +++ py/dist/py/path/svn/command.py Thu Oct 14 02:04:02 2004 @@ -56,6 +56,13 @@ else: self._svnwrite('mkdir') + def copy(self, target, msg=None): + #if getattr(target, '_rev', -1) !=-1: + # raise path.Invalid("target can't have a revision: %r" % target) + if not msg: + msg = "auto" + process.cmdexec("svn copy -m %r %s %s" %(msg, str(self), str(target))) + def _propget(self, name): res = self._svn('propget', name) return res[:-1] # strip trailing newline Modified: py/dist/py/path/svn/command_test.py ============================================================================== --- py/dist/py/path/svn/command_test.py (original) +++ py/dist/py/path/svn/command_test.py Thu Oct 14 02:04:02 2004 @@ -1,12 +1,24 @@ import sys, os -from py import test, path +import py from py.__impl__.path.test import _svncommon from py.__impl__.path.svnwc.command_test import getrepowc +count = 0 class TestSvnCommandPath(_svncommon.CommonCommandAndBindingTests): def __init__(self): repo, wc = getrepowc() - self.root = path.svnurl(repo) + self.root = py.path.svnurl(repo) + + def test_copy_file(self): + global count + count += 1 + if count == 2: + raise ValueError + raise py.test.run.Skipped(msg="XXX fix svnurl first") + + def test_copy_dir(self): + raise py.test.run.Skipped(msg="XXX fix svnurl first") + pass def XXXtest_info_log(self): url = self.root.join("samplefile") @@ -15,6 +27,3 @@ from time import gmtime t = gmtime(res[0].date) assert t.tm_year == 2003 and t.tm_mon == 7 and t.tm_mday == 17 - -if __name__ == '__main__': - main() Modified: py/dist/py/path/svn/common.py ============================================================================== --- py/dist/py/path/svn/common.py (original) +++ py/dist/py/path/svn/common.py Thu Oct 14 02:04:02 2004 @@ -223,7 +223,6 @@ If the Path is not relative to the given base, return an empty string. """ - relpath = rel.strpath if self.strpath.startswith(relpath): return self.strpath[len(relpath)+1:] Modified: py/dist/py/path/svnwc/command.py ============================================================================== --- py/dist/py/path/svnwc/command.py (original) +++ py/dist/py/path/svnwc/command.py Thu Oct 14 02:04:02 2004 @@ -9,7 +9,8 @@ """ import os, sys, time, re -from py import path, process +from py import path +import py from py.__impl__.path import common from py.__impl__.path.svn import cache from py.__impl__.path.svn import common as svncommon @@ -20,8 +21,6 @@ sep = os.sep def __new__(cls, wcpath=None, url=None): - if isinstance(wcpath, XSvnWCCommandPath): - return wcpath self = object.__new__(cls) self.localpath = path.local(wcpath) self._url = url @@ -30,13 +29,7 @@ strpath = property(lambda x: str(x.localpath), None, None, "string path") def __eq__(self, other): - if self.localpath == other.localpath: - if self._url == other._url: - return True - else: - if self.url == other.url: - return True - return False + return self.localpath == getattr(other, 'localpath', None) def _geturl(self): if self._url is None: @@ -70,7 +63,7 @@ string = svncommon.fixlocale() + " ".join(l) if DEBUG: print "execing", string - out = process.cmdexec(string) + out = py.process.cmdexec(string) return out def checkout(self, rev = None, url=None): @@ -129,7 +122,7 @@ def add(self): self._svn('add') - def remove(self, force=0, rec=1): + def remove(self, rec=1, force=1): """ remove a file or a directory tree. 'rec'ursive is ignored and considered always true (because of underlying svn semantics. @@ -139,6 +132,9 @@ flags.append('--force') self._svn('remove', *flags) + def copy(self, target): + py.process.cmdexec("svn copy %s %s" %(str(self), str(target))) + def status(self, updates=0, rec=0): """ return (collective) Status object for this file. """ # http://svnbook.red-bean.com/book.html#svn-ch-3-sect-4.3.1 @@ -305,7 +301,7 @@ if not info: try: output = self._svn('info') - except process.cmdexec.Error, e: + except py.process.cmdexec.Error, e: if e.err.find('Path is not a working copy directory') != -1: raise path.NotFound, e.err raise @@ -356,7 +352,7 @@ def versioned(self): try: s = self.svnwcpath.status() - except process.cmdexec.Error, e: + except py.process.cmdexec.Error, e: if e.err.find('is not a working copy')!=-1: return False raise Modified: py/dist/py/path/svnwc/command_test.py ============================================================================== --- py/dist/py/path/svnwc/command_test.py (original) +++ py/dist/py/path/svnwc/command_test.py Thu Oct 14 02:04:02 2004 @@ -198,8 +198,7 @@ # l = self.root.log() # assert len(l) == 3 # might need to be upped if more tests are added -class TestWCSvnCommandPathSpecial: - disabled = True +class XTestWCSvnCommandPathSpecial: rooturl = 'http://codespeak.net/svn/py.path/trunk/dist/py.path/test/data' #def test_update_none_rev(self): @@ -211,6 +210,3 @@ # assert wcpath.info().rev > 2100 # finally: # wcpath.localpath.remove(rec=1) - -if __name__ == '__main__': - test.main() Modified: py/dist/py/path/test/_common.py ============================================================================== --- py/dist/py/path/test/_common.py (original) +++ py/dist/py/path/test/_common.py Thu Oct 14 02:04:02 2004 @@ -56,10 +56,11 @@ assert newpath.check(notbasename='xyz') assert newpath.basename == 'sampledir' - #def test_ext_direct(self): - # newpath = self.root.join('sampledir.ext') - # check.true(newpath.check(basename='sampledir.ext')) - # check.equal(newpath.ext(), '.ext') + def test_ext(self): + newpath = self.root.join('sampledir.ext') + assert newpath.ext == '.ext' + newpath = self.root.join('sampledir') + assert not newpath.ext def test_basename(self): newpath = self.root.join('sampledir') @@ -293,3 +294,31 @@ for name in dir(local.Checkers): if name[0] != '_': assert name in doc + + def test_copy_file(self): + otherdir = self.root.join('otherdir') + initpy = otherdir.join('__init__.py') + copied = otherdir.join('copied') + initpy.copy(copied) + try: + assert copied.check() + s1 = initpy.read() + s2 = copied.read() + assert s1 == s2 + finally: + if copied.check(): + copied.remove() + + def test_copy_dir(self): + otherdir = self.root.join('otherdir') + copied = self.root.join('newdir') + try: + otherdir.copy(copied) + assert copied.check(dir=1) + assert copied.join('__init__.py').check(file=1) + s1 = otherdir.join('__init__.py').read() + s2 = copied.join('__init__.py').read() + assert s1 == s2 + finally: + if copied.check(dir=1): + copied.remove(rec=1) Modified: py/dist/py/path/test/_svncommon.py ============================================================================== --- py/dist/py/path/test/_svncommon.py (original) +++ py/dist/py/path/test/_svncommon.py Thu Oct 14 02:04:02 2004 @@ -2,11 +2,9 @@ from py import path, test, process from py.__impl__.path.test import _common -from py.__impl__.path.test import _common from py.__impl__.path.svn import cache class CommonSvnTests(_common.CommonFSTests): - def test_propget(self): url = self.root.join("samplefile") value = url.propget('svn:eol-style') @@ -31,7 +29,6 @@ assert t.tm_year == 2003 and t.tm_mon == 7 and t.tm_mday == 17 class CommonCommandAndBindingTests(CommonSvnTests): - def test_trailing_slash_is_stripped(self): # XXX we need to test more normalizing properties url = self.root.join("/") Deleted: /py/dist/py/path/test/test_svn_binding.py ============================================================================== --- /py/dist/py/path/test/test_svn_binding.py Thu Oct 14 02:04:02 2004 +++ (empty file) @@ -1 +0,0 @@ -import sys, os Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Thu Oct 14 02:04:02 2004 @@ -67,7 +67,7 @@ def __iter__(self): try: self.fspath.stat() # to throw correct filenotfound errors - name = self.fspath.get('purebasename') + name = "%s_%s" % (self.fspath.dirpath().basename, self.fspath.purebasename ) mod = imp.load_source(name, str(self.fspath)) mod.__name__ = name pypath = path.py('', ns=mod) @@ -145,7 +145,7 @@ def collect_class(self, pypath): #print "checking %r (pypath: %r)" % (pypath.resolve(), pypath) - if self.samemodule(pypath): + if pypath.check(basestarts='Test') and self.samemodule(pypath): obj = pypath.resolve() if inspect.isclass(obj) and not getattr(obj, 'disabled', 0): yield Class(pypath) Modified: py/dist/py/test/collect_test.py ============================================================================== --- py/dist/py/test/collect_test.py (original) +++ py/dist/py/test/collect_test.py Thu Oct 14 02:04:02 2004 @@ -58,7 +58,7 @@ # assert isinstance(x, Unit) # x.execute() -class someclass: +class Testsomeclass: disabled = True def test_something(): raise ValueError @@ -70,15 +70,15 @@ l.append(2) def test_3(): assert l == [1,2] -class mygroup: +class Testmygroup: reslist = [] def test_1(self): - mygroup.reslist.append(1) + self.reslist.append(1) def test_2(self): - mygroup.reslist.append(2) + self.reslist.append(2) def test_3(self): - mygroup.reslist.append(3) + self.reslist.append(3) def test_4(self): - assert mygroup.reslist == [1,2,3] + assert self.reslist == [1,2,3] test.main() Modified: py/dist/py/test/compat_test.py ============================================================================== --- py/dist/py/test/compat_test.py (original) +++ py/dist/py/test/compat_test.py Thu Oct 14 02:04:02 2004 @@ -1,7 +1,7 @@ from __future__ import generators from py import test, magic -class CompatTestCaseSetupSemantics(test.compat.TestCase): +class TestCompatTestCaseSetupSemantics(test.compat.TestCase): globlist = [] def setUp(self): @@ -27,7 +27,7 @@ for x,y in zip(self.globlist, self.globlist[1:]): assert x is not y -class DynTestCase(test.compat.TestCase): +class TestCompatAssertions(test.compat.TestCase): nameparamdef = { 'failUnlessEqual,assertEqual,assertEquals': ('1, 1', '1, 0'), 'assertNotEquals,failIfEqual': ('0, 1', '0,0'), Modified: py/dist/py/test/raises_test.py ============================================================================== --- py/dist/py/test/raises_test.py (original) +++ py/dist/py/test/raises_test.py Thu Oct 14 02:04:02 2004 @@ -3,7 +3,7 @@ def somefunc(x, y): assert x == y -class classes_are_arbitrary_grouping: +class TestClass: def test_raises(self): test.raises(ValueError, "int('qwe')") From hpk at codespeak.net Thu Oct 14 02:15:59 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 14 Oct 2004 02:15:59 +0200 (MEST) Subject: [py-svn] r6921 - in py/dist/py/path: svn svnwc Message-ID: <20041014001559.EF7B75AAB6@thoth.codespeak.net> Author: hpk Date: Thu Oct 14 02:15:59 2004 New Revision: 6921 Modified: py/dist/py/path/svn/command.py py/dist/py/path/svn/command_test.py py/dist/py/path/svn/common.py py/dist/py/path/svnwc/command_test.py Log: - small fixes and refactorings Modified: py/dist/py/path/svn/command.py ============================================================================== --- py/dist/py/path/svn/command.py (original) +++ py/dist/py/path/svn/command.py Thu Oct 14 02:15:59 2004 @@ -12,6 +12,26 @@ from py.__impl__.path.svn import common as svncommon class XSvnCommandPath(svncommon.SvnPathBase): + def __new__(cls, path, rev=-1): + if isinstance(path, cls): + return path + + # initialize the instance + self = object.__new__(cls) + + import urllib + path, attrs = urllib.splitattr(path) + while path and path.endswith('/'): + path = path[:-1] + self.strpath = path + for attr in attrs: + key, value = urllib.splitvalue(attr) + if key == "rev": + rev = value + break + self._rev = rev + return self + def __repr__(self): if self._rev == -1: return 'svnurl(%r)' % self.strpath Modified: py/dist/py/path/svn/command_test.py ============================================================================== --- py/dist/py/path/svn/command_test.py (original) +++ py/dist/py/path/svn/command_test.py Thu Oct 14 02:15:59 2004 @@ -3,22 +3,16 @@ from py.__impl__.path.test import _svncommon from py.__impl__.path.svnwc.command_test import getrepowc -count = 0 class TestSvnCommandPath(_svncommon.CommonCommandAndBindingTests): def __init__(self): repo, wc = getrepowc() self.root = py.path.svnurl(repo) def test_copy_file(self): - global count - count += 1 - if count == 2: - raise ValueError raise py.test.run.Skipped(msg="XXX fix svnurl first") def test_copy_dir(self): raise py.test.run.Skipped(msg="XXX fix svnurl first") - pass def XXXtest_info_log(self): url = self.root.join("samplefile") Modified: py/dist/py/path/svn/common.py ============================================================================== --- py/dist/py/path/svn/common.py (original) +++ py/dist/py/path/svn/common.py Thu Oct 14 02:15:59 2004 @@ -12,26 +12,6 @@ class SvnPathBase(common.PathBase): """ Base implementation for SvnPath implementations. """ sep = '/' - def __new__(cls, path, rev=-1): - if isinstance(path, SvnPathBase): - return path - - # initialize the instance - self = object.__new__(cls) - - import urllib - path, attrs = urllib.splitattr(path) - while path and path.endswith('/'): - path = path[:-1] - self.strpath = path - for attr in attrs: - key, value = urllib.splitvalue(attr) - if key == "rev": - rev = value - break - self._rev = rev - return self - def _getrev(self): if self._rev == -1: url, rev = cache.repositories.get(self.strpath) @@ -54,7 +34,7 @@ #return s def __hash__(self): - return hash((self.strpath, self.rev, self.__class__)) + return hash(self.strpath) def new(self, **kw): """ create a modified version of this path. A 'rev' argument Modified: py/dist/py/path/svnwc/command_test.py ============================================================================== --- py/dist/py/path/svnwc/command_test.py (original) +++ py/dist/py/path/svnwc/command_test.py Thu Oct 14 02:15:59 2004 @@ -6,8 +6,6 @@ # make a wc directory out of a given root url # cache previously obtained wcs! # -tmpdir = path.local.make_numbered_dir(base='py.pathtestwc-') - def getrepowc(): repo = test.config.tmpdir / 'path' / 'repo' wcdir = test.config.tmpdir / 'path' / 'wc' From hpk at codespeak.net Thu Oct 14 18:42:02 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 14 Oct 2004 18:42:02 +0200 (MEST) Subject: [py-svn] r6931 - in py/dist/py: . path path/svn path/svnwc path/test Message-ID: <20041014164202.5C8145A165@thoth.codespeak.net> Author: hpk Date: Thu Oct 14 18:42:01 2004 New Revision: 6931 Modified: py/dist/py/__init__.py py/dist/py/path/common.py py/dist/py/path/svn/cache.py py/dist/py/path/svn/command.py py/dist/py/path/svn/command_test.py py/dist/py/path/svn/common.py py/dist/py/path/svnwc/command.py py/dist/py/path/test/_svncommon.py Log: - lots of small fixes - the path.svnurl() implementation now behaves much more consistent with the other impls but much much slower because it doesn't use caching now. I/We may have to rethink the svnurl() implementation at some point ... or maybe implement it via svn-bindings first and see how that works. A slow "svn" commandline binding wouldn't hurt so much then ... - added simple copy() and remove() operations to FSPaths. (which caused the above bigger consistency change) Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Thu Oct 14 18:42:01 2004 @@ -3,8 +3,8 @@ 'path.local': './path/local/local.LocalPath', 'path.checker': './path/common.checker', 'path.invchecker': './path/common.invchecker', - 'path.svnurl': './path/svn/command.XSvnCommandPath', - 'path.svnwc': './path/svnwc/command.XSvnWCCommandPath', + 'path.svnurl': './path/svn/command.SvnCommandPath', + 'path.svnwc': './path/svnwc/command.SvnWCCommandPath', 'path.py': './path/pypath/pypath.PyPath', 'path.NotFound': './path/error.FileNotFound', 'path.Denied': './path/error.PermissionDenied', Modified: py/dist/py/path/common.py ============================================================================== --- py/dist/py/path/common.py (original) +++ py/dist/py/path/common.py Thu Oct 14 18:42:01 2004 @@ -139,7 +139,7 @@ purebasename = property(purebasename, None, None, 'basename without extension') def read(self, num=-1): - """ read up to 'num' bytes. (if num==-1 then read all)""" + #""" read up to 'num' bytes. (if num==-1 then read all)""" try: f = self.open() try: @@ -224,8 +224,8 @@ return repr(str(self)) def _except(self, excinfo): - """ default exception handling is to raise it. """ - raise excinfo + """ default exception handling is to reraise it. """ + raise excinfo[0], excinfo[1], excinfo[2] class fnmatch: def __init__(self, pattern): Modified: py/dist/py/path/svn/cache.py ============================================================================== --- py/dist/py/path/svn/cache.py (original) +++ py/dist/py/path/svn/cache.py Thu Oct 14 18:42:01 2004 @@ -40,8 +40,11 @@ self.repos = [] def put(self, url, rev, timestamp=None): + if rev is None: + return if timestamp is None: timestamp = time.time() + for entry in self.repos: if url == entry.url: entry.timestamp = timestamp Modified: py/dist/py/path/svn/command.py ============================================================================== --- py/dist/py/path/svn/command.py (original) +++ py/dist/py/path/svn/command.py Thu Oct 14 18:42:01 2004 @@ -11,36 +11,24 @@ from py.__impl__.path import error from py.__impl__.path.svn import common as svncommon -class XSvnCommandPath(svncommon.SvnPathBase): - def __new__(cls, path, rev=-1): +class SvnCommandPath(svncommon.SvnPathBase): + def __new__(cls, path, rev=None): if isinstance(path, cls): - return path - - # initialize the instance + if path.rev == rev: + return path self = object.__new__(cls) - - import urllib - path, attrs = urllib.splitattr(path) - while path and path.endswith('/'): - path = path[:-1] - self.strpath = path - for attr in attrs: - key, value = urllib.splitvalue(attr) - if key == "rev": - rev = value - break - self._rev = rev + self.strpath = path.rstrip('/') + self.rev = rev return self def __repr__(self): - if self._rev == -1: + if self.rev == -1: return 'svnurl(%r)' % self.strpath else: - return 'svnurl(%r, %r)' % (self.strpath, self._rev) + return 'svnurl(%r, %r)' % (self.strpath, self.rev) def _svn(self, cmd, *args): - assert self._rev is None or self._rev >=-1 - if self.rev is None or self.rev == -1: + if self.rev is None: l = ['svn %s' % cmd] else: l = ['svn %s -r %d' % (cmd, self.rev)] @@ -65,10 +53,16 @@ return out def open(self, mode='r'): - assert 'w' not in mode and 'a' not in 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 - return os.popen(svncommon.fixlocale() + - 'svn cat -r %s "%s"' % (self.rev, self.strpath)) + def popen(cmd): + return os.popen(cmd) + if self.rev is None: + return popen(svncommon.fixlocale() + + 'svn cat "%s"' % (self.strpath, )) + else: + return popen(svncommon.fixlocale() + + 'svn cat -r %s "%s"' % (self.rev, self.strpath)) def mkdir(self, commit_msg=None): if commit_msg: @@ -76,13 +70,16 @@ else: self._svnwrite('mkdir') - def copy(self, target, msg=None): - #if getattr(target, '_rev', -1) !=-1: - # raise path.Invalid("target can't have a revision: %r" % target) - if not msg: - msg = "auto" + def copy(self, target, msg='auto'): + if getattr(target, 'rev', None) is not None: + raise path.Invalid("target can't have a revision: %r" % target) process.cmdexec("svn copy -m %r %s %s" %(msg, str(self), str(target))) + def remove(self, rec=1, msg='auto'): + if self.rev is not None: + raise path.Invalid("cannot remove revisioned object: %r" % self) + process.cmdexec("svn rm -m %r %s" %(msg, str(self))) + def _propget(self, name): res = self._svn('propget', name) return res[:-1] # strip trailing newline Modified: py/dist/py/path/svn/command_test.py ============================================================================== --- py/dist/py/path/svn/command_test.py (original) +++ py/dist/py/path/svn/command_test.py Thu Oct 14 18:42:01 2004 @@ -8,10 +8,10 @@ repo, wc = getrepowc() self.root = py.path.svnurl(repo) - def test_copy_file(self): + def xtest_copy_file(self): raise py.test.run.Skipped(msg="XXX fix svnurl first") - def test_copy_dir(self): + def xtest_copy_dir(self): raise py.test.run.Skipped(msg="XXX fix svnurl first") def XXXtest_info_log(self): Modified: py/dist/py/path/svn/common.py ============================================================================== --- py/dist/py/path/svn/common.py (original) +++ py/dist/py/path/svn/common.py Thu Oct 14 18:42:01 2004 @@ -5,23 +5,12 @@ from py import path, process from py.__impl__.path import error from py.__impl__.path import common -from py.__impl__.path.svn import cache #_______________________________________________________________ class SvnPathBase(common.PathBase): """ Base implementation for SvnPath implementations. """ sep = '/' - def _getrev(self): - if self._rev == -1: - url, rev = cache.repositories.get(self.strpath) - #reasonably up-to-date info? - if rev == -1: - rev = self._getlatestrevision() - self._rev = rev - return self._rev - - rev = property(_getrev, None, None, "revision of this svn-path.") def _geturl(self): return self.strpath @@ -30,8 +19,6 @@ def __str__(self): """ return a string representation (including rev-number) """ return self.strpath - #s = self.strpath + ";rev=%s" % self._rev - #return s def __hash__(self): return hash(self.strpath) @@ -48,7 +35,7 @@ |--| ext """ obj = object.__new__(self.__class__) - obj._rev = kw.get('rev', self._rev) + obj.rev = kw.get('rev', self.rev) if 'basename' in kw: if 'purebasename' in kw or 'ext' in kw: @@ -111,8 +98,7 @@ def __eq__(self, other): """ return true if path and rev attributes each match """ return (str(self) == str(other) and - (self._rev == other._rev or self.rev == other.rev)) - #getattr(self, '_rev', None) == getattr(other, '_rev', None)) + (self.rev == other.rev or self.rev == other.rev)) def __ne__(self, other): return not self == other @@ -126,7 +112,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) return newpath def dirpath(self, *args): @@ -135,23 +121,13 @@ def propget(self, name): """ return the content of the given property. """ - try: - return cache.prop[(self.strpath, self.rev, name)] - except KeyError: - value = self._propget(name) - if self.rev is not None: - cache.prop[(self.strpath, self.rev, name)] = value - return value + value = self._propget(name) + return value def proplist(self): """ list all property names. """ - try: - return cache.proplist[self] - except KeyError: - content = self._proplist() - if self.rev is not None: - cache.proplist[self] = content - return content + content = self._proplist() + return content # XXX unify argument naming with LocalPath.listdir def listdir(self, fil=None, sort=None): @@ -162,11 +138,8 @@ """ if isinstance(fil, str): fil = common.fnmatch(fil) - try: - paths = cache.entries[self] - except KeyError: - nameinfo_seq = self._listdir_nameinfo() - cache.entries[self] = paths = self._make_path_tuple(nameinfo_seq) + nameinfo_seq = self._listdir_nameinfo() + paths = self._make_path_tuple(nameinfo_seq) if fil or sort: paths = filter(fil, paths) @@ -179,16 +152,13 @@ def info(self): """ return an Info structure with svn-provided information. """ - try: - return cache.info[self] - except KeyError: - parent = self.dirpath() - melist = parent.listdir(self.__eq__) - #print "melist", melist - if len(melist)!=1: - raise error.FileNotFound(self) - cache.info[self] = info = melist[0].info() # this must be in the cache - return info + parent = self.dirpath() + nameinfo_seq = parent._listdir_nameinfo() + bn = self.basename + for name, info in nameinfo_seq: + if name == bn: + return info + raise error.FileNotFound(self) def size(self): """ Return the size of the file content of the Path. """ @@ -214,45 +184,37 @@ def _make_path_tuple(self, nameinfo_seq): """ return a tuple of paths from a nameinfo-tuple sequence. """ - assert self.rev is not None, "revision of %s should not be None here" % self + #assert self.rev is not None, "revision of %s should not be None here" % self res = [] for name, info in nameinfo_seq: child = self.join(name) res.append(child) - cache.info[child] = info return tuple(res) - def _childmaxrev(self): - """ return maximum revision number of childs (or self.rev if no childs) """ - rev = self.rev - for name, info in self._listdir_nameinfo(): - rev = max(rev, info.created_rev) - return rev - - def _getlatestrevision(self): - """ return latest repo-revision for this path. """ - url, rev = cache.repositories.get(self.strpath) - #print "refetching repo revision in class %r" % self.__class__ - path = self.__class__(url, None) - - # we got a repo-url but need updating the rev? - if self.strpath != url: - rev = path._childmaxrev() - cache.repositories.put(url, rev) - else: - # we need a long walk to find the root-repo and revision - while 1: - try: - rev = max(rev, path._childmaxrev()) - previous = path - path = path.dirpath() - except (IOError, process.cmdexec.Error): - break - if rev == -1: - raise IOError, "could not determine newest repo revision for %s" % self - cache.repositories.put(previous.strpath, rev) - return rev + #def _childmaxrev(self): + # """ return maximum revision number of childs (or self.rev if no childs) """ + # rev = self.rev + # for name, info in self._listdir_nameinfo(): + # rev = max(rev, info.created_rev) + # return rev + + #def _getlatestrevision(self): + # """ return latest repo-revision for this path. """ + # url = self.strpath + # path = self.__class__(url, None) + # + # # we need a long walk to find the root-repo and revision + # while 1: + # try: + # rev = max(rev, path._childmaxrev()) + # previous = path + # path = path.dirpath() + # except (IOError, process.cmdexec.Error): + # break + # if rev is None: + # raise IOError, "could not determine newest repo revision for %s" % self + # return rev class Checkers(common.Checkers): def _info(self): Modified: py/dist/py/path/svnwc/command.py ============================================================================== --- py/dist/py/path/svnwc/command.py (original) +++ py/dist/py/path/svnwc/command.py Thu Oct 14 18:42:01 2004 @@ -2,9 +2,9 @@ svn-Command based Implementation of a Subversion WorkingCopy Path. - XSvnWCCommandPath is the main class. + SvnWCCommandPath is the main class. - XSvnWC is an alias to this class. + SvnWC is an alias to this class. """ @@ -17,7 +17,7 @@ DEBUG = 0 -class XSvnWCCommandPath(common.PathBase): +class SvnWCCommandPath(common.PathBase): sep = os.sep def __new__(cls, wcpath=None, url=None): Modified: py/dist/py/path/test/_svncommon.py ============================================================================== --- py/dist/py/path/test/_svncommon.py (original) +++ py/dist/py/path/test/_svncommon.py Thu Oct 14 18:42:01 2004 @@ -65,9 +65,9 @@ # the following tests are easier if we have a path class def test_repocache_simple(self): repocache = cache.RepoCache() - repocache.put(self.root.strpath, self.root.rev) + repocache.put(self.root.strpath, 42) url, rev = repocache.get(self.root.join('test').strpath) - assert rev == self.root.rev + assert rev == 42 assert url == self.root.strpath def test_repocache_notimeout(self): From hpk at codespeak.net Thu Oct 14 18:49:20 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 14 Oct 2004 18:49:20 +0200 (MEST) Subject: [py-svn] r6932 - py/dist/py/test Message-ID: <20041014164920.0446B5A165@thoth.codespeak.net> Author: hpk Date: Thu Oct 14 18:49:20 2004 New Revision: 6932 Modified: py/dist/py/test/config.py Log: - removed short options '-c' and '-D' for --pdb and --collectonly because they are rarely needed i would say. Modified: py/dist/py/test/config.py ============================================================================== --- py/dist/py/test/config.py (original) +++ py/dist/py/test/config.py Thu Oct 14 18:49:20 2004 @@ -18,7 +18,7 @@ def getoptions(self): Option = test.Option return ('utest standard options', [ - Option('-c', '--collectonly', + Option('', '--collectonly', action="store_true", dest="collectonly", default=False, help="only collect tests, don't execute them. "), Option('-S', '--nocapture', @@ -39,7 +39,7 @@ Option('-n', '--nomagic', action="store_true", dest="nomagic", default=False, help="don't invoke magic to e.g. beautify failing/error statements."), - Option('-D', '--pdb', + Option('', '--pdb', action="store_true", dest="usepdb", default=False, help="Start pdb (the Python debugger) on errors."), ]) From hpk at codespeak.net Thu Oct 14 19:23:13 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 14 Oct 2004 19:23:13 +0200 (MEST) Subject: [py-svn] r6933 - in py/dist/py/path: local svnwc test Message-ID: <20041014172313.074775A165@thoth.codespeak.net> Author: hpk Date: Thu Oct 14 19:23:07 2004 New Revision: 6933 Modified: py/dist/py/path/local/local.py py/dist/py/path/local/local_test.py py/dist/py/path/svnwc/command_test.py py/dist/py/path/test/_common.py py/dist/py/path/test/_svncommon.py Log: - shifted test_remove_file() test - some fixes Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Thu Oct 14 19:23:07 2004 @@ -264,7 +264,7 @@ else: target.ensure(dir=1) def rec(p): - return rec.check(link=0) + return p.check(link=0) for x in self.visit(rec=rec): relpath = x.relto(self) newx = target.join(relpath) Modified: py/dist/py/path/local/local_test.py ============================================================================== --- py/dist/py/path/local/local_test.py (original) +++ py/dist/py/path/local/local_test.py Thu Oct 14 19:23:07 2004 @@ -92,16 +92,6 @@ finally: d.remove(rec=1) - def test_remove_file(self): - tempdir = local.mkdtemp() - try: - testfile = tempdir.join('test') - testfile.write('test') - testfile.remove() - assert not testfile.check() - finally: - tempdir.remove(rec=1) - def test_ensure_filepath_withdir(self): tmpdir = local.mkdtemp() try: Modified: py/dist/py/path/svnwc/command_test.py ============================================================================== --- py/dist/py/path/svnwc/command_test.py (original) +++ py/dist/py/path/svnwc/command_test.py Thu Oct 14 19:23:07 2004 @@ -1,5 +1,4 @@ -import sys, os -from py import test, path, process +import py from py.__impl__.path.test._svncommon import CommonSvnTests from py.__impl__.path.test.setuptestfs import setuptestfs @@ -7,19 +6,19 @@ # cache previously obtained wcs! # def getrepowc(): - repo = test.config.tmpdir / 'path' / 'repo' - wcdir = test.config.tmpdir / 'path' / 'wc' + repo = py.test.config.tmpdir / 'path' / 'repo' + wcdir = py.test.config.tmpdir / 'path' / 'wc' if not repo.check(): assert not wcdir.check() repo.ensure(dir=1) try: - process.cmdexec('svnadmin create %s' % repo) - except process.cmdexec.Error: + py.process.cmdexec('svnadmin create %s' % repo) + except py.process.cmdexec.Error: repo.remove() - raise test.run.Skipped(msg='could not create temporary svn test repository') + raise py.test.run.Skipped(msg='could not create temporary svn test repository') wcdir.ensure(dir=1) print "created svn repository", repo - wc = path.svnwc(wcdir, url='file://%s' % repo) + wc = py.path.svnwc(wcdir, url='file://%s' % repo) wc.checkout() print "checked out new repo into", wc setuptestfs(wc) @@ -31,7 +30,7 @@ wc.commit('third rev') else: print "using repository at %s" % repo - wc = path.svnwc(wcdir) + wc = py.path.svnwc(wcdir) return ("file://%s" % repo, wc) class TestWCSvnCommandPath(CommonSvnTests): Modified: py/dist/py/path/test/_common.py ============================================================================== --- py/dist/py/path/test/_common.py (original) +++ py/dist/py/path/test/_common.py Thu Oct 14 19:23:07 2004 @@ -1,4 +1,5 @@ from py.path import checker, NotFound +import py class CommonFSTests: root = None # subclasses have to provide a current 'root' attribute @@ -322,3 +323,14 @@ finally: if copied.check(dir=1): copied.remove(rec=1) + + def test_remove_file(self): + d = self.root.ensure('todeleted') + assert d.check() + d.remove() + assert not d.check() + + # !!!! + # don't insert any tests below because the previous test is + # desctructive! + # !!!! Modified: py/dist/py/path/test/_svncommon.py ============================================================================== --- py/dist/py/path/test/_svncommon.py (original) +++ py/dist/py/path/test/_svncommon.py Thu Oct 14 19:23:07 2004 @@ -1,10 +1,13 @@ -import sys, os - +import py from py import path, test, process from py.__impl__.path.test import _common from py.__impl__.path.svn import cache class CommonSvnTests(_common.CommonFSTests): + + def test_remove_file(self): + py.test.run.Skipped(msg="need better svn state management") + def test_propget(self): url = self.root.join("samplefile") value = url.propget('svn:eol-style') From hpk at codespeak.net Thu Oct 14 19:33:47 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 14 Oct 2004 19:33:47 +0200 (MEST) Subject: [py-svn] r6934 - in py/dist/py: magic path/local path/svn path/svnwc path/test Message-ID: <20041014173347.438365A165@thoth.codespeak.net> Author: hpk Date: Thu Oct 14 19:33:43 2004 New Revision: 6934 Added: py/dist/py/path/test/commonfs.py - copied, changed from r6933, py/dist/py/path/test/_common.py py/dist/py/path/test/svncommonfs.py - copied, changed from r6933, py/dist/py/path/test/_svncommon.py Removed: py/dist/py/path/test/_common.py py/dist/py/path/test/_svncommon.py py/dist/py/path/test/setuptestfs.py Modified: py/dist/py/magic/autopath_test.py py/dist/py/path/local/local_test.py py/dist/py/path/svn/command_test.py py/dist/py/path/svnwc/command_test.py Log: - some implementation renaming (getting rid of ugly _names) not externally visible Modified: py/dist/py/magic/autopath_test.py ============================================================================== --- py/dist/py/magic/autopath_test.py (original) +++ py/dist/py/magic/autopath_test.py Thu Oct 14 19:33:43 2004 @@ -1,6 +1,5 @@ import py import sys -from py.__impl__.path.test.setuptestfs import setuptestfs class TestAutoPath: getauto = "from py.magic import autopath ; autopath = autopath()" Modified: py/dist/py/path/local/local_test.py ============================================================================== --- py/dist/py/path/local/local_test.py (original) +++ py/dist/py/path/local/local_test.py Thu Oct 14 19:33:43 2004 @@ -1,8 +1,7 @@ import sys, os from py.test import main, raises, config from py.path import local, checker -from py.__impl__.path.test._common import CommonFSTests -from py.__impl__.path.test.setuptestfs import setuptestfs +from py.__impl__.path.test.commonfs import CommonFSTests, setuptestfs class TestLocalPath(CommonFSTests): def __init__(self): Modified: py/dist/py/path/svn/command_test.py ============================================================================== --- py/dist/py/path/svn/command_test.py (original) +++ py/dist/py/path/svn/command_test.py Thu Oct 14 19:33:43 2004 @@ -1,9 +1,9 @@ import sys, os import py -from py.__impl__.path.test import _svncommon +from py.__impl__.path.test import svncommonfs from py.__impl__.path.svnwc.command_test import getrepowc -class TestSvnCommandPath(_svncommon.CommonCommandAndBindingTests): +class TestSvnCommandPath(svncommonfs.CommonCommandAndBindingTests): def __init__(self): repo, wc = getrepowc() self.root = py.path.svnurl(repo) Modified: py/dist/py/path/svnwc/command_test.py ============================================================================== --- py/dist/py/path/svnwc/command_test.py (original) +++ py/dist/py/path/svnwc/command_test.py Thu Oct 14 19:33:43 2004 @@ -1,6 +1,6 @@ import py -from py.__impl__.path.test._svncommon import CommonSvnTests -from py.__impl__.path.test.setuptestfs import setuptestfs +from py.__impl__.path.test.svncommonfs import CommonSvnTests +from py.__impl__.path.test.commonfs import setuptestfs # make a wc directory out of a given root url # cache previously obtained wcs! Deleted: /py/dist/py/path/test/_common.py ============================================================================== --- /py/dist/py/path/test/_common.py Thu Oct 14 19:33:43 2004 +++ (empty file) @@ -1,336 +0,0 @@ -from py.path import checker, NotFound -import py - -class CommonFSTests: - root = None # subclasses have to provide a current 'root' attribute - - def test_constructor_equality(self): - p = self.root.__class__(self.root) - assert p == self.root - - def test_eq_nonstring(self): - path1 = self.root.join('sampledir') - path2 = self.root.join('sampledir') - assert path1 == path2 - - def test_new_identical(self): - assert self.root == self.root.new() - - def test_join(self): - p = self.root.join('sampledir') - assert str(p) == self.root.sep.join([str(self.root), 'sampledir']) - - def test_join_normalized(self): - newpath = self.root.join(self.root.sep+'sampledir') - assert str(newpath) == self.root.sep.join([str(self.root), 'sampledir']) - newpath = self.root.join((self.root.sep*2) + 'sampledir') - assert str(newpath) == self.root.sep.join([str(self.root), 'sampledir']) - - def test_join_noargs(self): - newpath = self.root.join() - assert self.root == newpath - - def test_join_div_operator(self): - newpath = self.root / '/sampledir' / '/test//' - newpath2 = self.root.join('sampledir', 'test') - assert newpath == newpath2 - - def test_parts(self): - newpath = self.root / 'sampledir' / 'otherfile' - par = newpath.parts()[-3:] - assert par == [self.root, self.root/'sampledir', newpath] - - #def test_parents_nonexisting_file(self): - # newpath = self.root / 'dirnoexist' / 'nonexisting file' - # par = list(newpath.parents()) - # assert par[:2] == [self.root / 'dirnoexist', self.root] - - # following feature is disabled - #def test_getitem(self): - # newpath = self.root['sampledir']['test'] - # newpath2 = self.root.join('sampledir', 'test') - # assert newpath == newpath2 - - def test_basename_checks(self): - newpath = self.root.join('sampledir') - assert newpath.check(basename='sampledir') - assert newpath.check(notbasename='xyz') - assert newpath.basename == 'sampledir' - - def test_ext(self): - newpath = self.root.join('sampledir.ext') - assert newpath.ext == '.ext' - newpath = self.root.join('sampledir') - assert not newpath.ext - - def test_basename(self): - newpath = self.root.join('sampledir') - assert newpath.check(basename='sampledir') - assert newpath.basename, 'sampledir' - - def test_dirpath(self): - newpath = self.root.join('sampledir') - assert newpath.dirpath() == self.root - - def test_dirpath_with_args(self): - newpath = self.root.join('sampledir') - assert newpath.dirpath('x') == self.root.join('x') - - def test_purebasename(self): - newpath = self.root.join('samplefile.py') - assert newpath.purebasename == 'samplefile' - - def test_multiple_parts(self): - newpath = self.root.join('samplefile.py') - dirname, purebasename, basename, ext = newpath.get( - 'dirname,purebasename,basename,ext') - assert dirname == str(self.root) - assert purebasename == 'samplefile' - assert basename == 'samplefile.py' - assert ext == '.py' - - def test_dotted_name_ext(self): - newpath = self.root.join('a.b.c') - ext = newpath.get('ext') - assert ext == '.c' - assert newpath.ext == '.c' - - def test_newext(self): - newpath = self.root.join('samplefile.py') - newext = newpath.new(ext='.txt') - assert newext.basename == "samplefile.txt" - assert newext.purebasename == "samplefile" - - def test_newbasename(self): - newpath = self.root.join('samplefile.py') - newbase = newpath.new(basename="sampledict.pickle") - assert newbase.basename == "sampledict.pickle" - assert newbase.dirpath() == newpath.dirpath() - - def test_iteration(self): - l = [] - for i in self.root: - l.append(i.basename) - assert 'sampledir' in l - - def test_read(self): - url = self.root.join("samplefile") - contents = url.read() - assert contents.find("samplefile") != -1 - - def test_readlines(self): - fn = self.root.join('samplefile') - contents = fn.readlines() - assert contents == ['samplefile\n'] - - def test_readlines_nocr(self): - fn = self.root.join('samplefile') - contents = fn.readlines(cr=0) - assert contents == ['samplefile', ''] - - def test_not_exists(self): - assert not self.root.join('does_not_exist').check() - assert self.root.join('does_not_exist').check(exists=0) - - def test_exists(self): - assert self.root.join("samplefile").check() - assert self.root.join("samplefile").check(exists=1) - - def test_dir(self): - #print repr(self.root.join("sampledir")) - assert self.root.join("sampledir").check(dir=1) - assert self.root.join('samplefile').check(notdir=1) - assert not self.root.join("samplefile").check(dir=1) - - def test_filter_dir(self): - assert checker(dir=1)(self.root.join("sampledir")) - - def test_file(self): - assert self.root.join('samplefile').check(file=1) - - def test_not_file(self): - assert not self.root.join("sampledir").check(file=1) - assert self.root.join("sampledir").check(file=0) - - def test_filter_file(self): - assert checker(file=1)(self.root.join("samplefile")) - - def test_fnmatch_file(self): - assert self.root.join("samplefile").check(fnmatch='s*e') - assert self.root.join("samplefile").check(notfnmatch='s*x') - assert not self.root.join("samplefile").check(fnmatch='s*x') - - #def test_fnmatch_dir(self): - - def test_non_existent(self): - assert self.root.join("sampledir.nothere").check(dir=0) - assert self.root.join("sampledir.nothere").check(file=0) - assert self.root.join("sampledir.nothere").check(notfile=1) - assert self.root.join("sampledir.nothere").check(notdir=1) - assert self.root.join("sampledir.nothere").check(notexists=1) - assert not self.root.join("sampledir.nothere").check(notfile=0) - - # pattern = self.root.sep.join(['s*file']) - # sfile = self.root.join("samplefile") - # assert sfile.check(fnmatch=pattern) - - def test_size(self): - url = self.root.join("samplefile") - assert url.size() > len("samplefile") - - def test_mtime(self): - url = self.root.join("samplefile") - assert url.mtime() > 0 - - def test_relto(self): - l=self.root.join("sampledir", "otherfile") - assert l.relto(self.root) == l.sep.join(["sampledir", "otherfile"]) - assert l.check(relto=self.root) - assert self.root.check(notrelto=l) - assert not self.root.check(relto=l) - - def test_relto_not_relative(self): - l1=self.root.join("sampledir") - l2=self.root.join("samplefile") - assert not l1.relto(l2) - - def test_listdir(self): - l = self.root.listdir() - assert self.root.join('sampledir') in l - assert self.root.join('samplefile') in l - - def test_listdir_fnmatchstring(self): - l = self.root.listdir('s*dir') - assert len(l), 1 - assert l[0], self.root.join('sampledir') - - def test_listdir_filter(self): - l = self.root.listdir(checker(dir=1)) - assert self.root.join('sampledir') in l - assert not self.root.join('samplefile') in l - - def test_listdir_sorted(self): - l = self.root.listdir(checker(basestarts="samplefile"), sort=True) - assert self.root.join('samplefile') == l[0] - assert self.root.join('samplefile.py') == l[1] - - def test_visit_nofilter(self): - l = [] - for i in self.root.visit(): - l.append(i.relto(self.root)) - assert "sampledir" in l - assert self.root.sep.join(["sampledir", "otherfile"]) in l - - def test_visit_filesonly(self): - l = [] - for i in self.root.visit(checker(file=1)): - l.append(i.relto(self.root)) - assert not "sampledir" in l - assert self.root.sep.join(["sampledir", "otherfile"]) in l - - def test_visit_norecurse(self): - l = [] - for i in self.root.visit(None, lambda x: not x.basename == "sampledir"): - l.append(i.relto(self.root)) - assert "sampledir" in l - assert not self.root.sep.join(["sampledir", "otherfile"]) in l - - def test_visit_filterfunc_is_string(self): - l = [] - for i in self.root.visit('*dir'): - l.append(i.relto(self.root)) - assert len(l), 2 - assert "sampledir" in l - assert "otherdir" in l - - def test_visit_ignore(self): - p = self.root / 'nonexisting' - assert list(p.visit(ignore=NotFound)) == [] - - def test_load(self): - p = self.root.join('sampledict.pickle') - obj = p.loadobj() - assert type(obj) is dict - assert obj.get('answer',None) == 42 - - def test_visit_nodotfiles(self): - l = [] - for i in self.root.visit(checker(dotfile=0)): - l.append(i.relto(self.root)) - assert "sampledir" in l - assert self.root.sep.join(["sampledir", "otherfile"]) in l - assert not ".dotfile" in l - - def test_visit_endswith(self): - l = [] - for i in self.root.visit(checker(endswith=".py")): - l.append(i.relto(self.root)) - assert not self.root.sep.join(["sampledir", "otherfile"]) in l - assert "samplefile.py" in l - - def test_endswith(self): - assert self.root.check(notendswith='.py') - x = self.root.join('samplefile.py') - assert x.check(endswith='.py') - - def test_cmp(self): - path1 = self.root.join('samplefile') - path2 = self.root.join('samplefile.py') - assert cmp(path1, path2) == cmp('samplefile', 'samplefile.py') - assert cmp(path1, path1) == 0 - - def test_contains_path(self): - path1 = self.root .join('samplefile') - assert path1 in self.root - assert not self.root.join('not existing') in self.root - - def test_contains_path_with_basename(self): - assert 'samplefile' in self.root - assert 'not existing' not in self.root - - def featuretest_check_docstring(self): - here = self.root.__class__ - assert here.check.__doc__ - doc = here.check.__doc__ - for name in dir(local.Checkers): - if name[0] != '_': - assert name in doc - - def test_copy_file(self): - otherdir = self.root.join('otherdir') - initpy = otherdir.join('__init__.py') - copied = otherdir.join('copied') - initpy.copy(copied) - try: - assert copied.check() - s1 = initpy.read() - s2 = copied.read() - assert s1 == s2 - finally: - if copied.check(): - copied.remove() - - def test_copy_dir(self): - otherdir = self.root.join('otherdir') - copied = self.root.join('newdir') - try: - otherdir.copy(copied) - assert copied.check(dir=1) - assert copied.join('__init__.py').check(file=1) - s1 = otherdir.join('__init__.py').read() - s2 = copied.join('__init__.py').read() - assert s1 == s2 - finally: - if copied.check(dir=1): - copied.remove(rec=1) - - def test_remove_file(self): - d = self.root.ensure('todeleted') - assert d.check() - d.remove() - assert not d.check() - - # !!!! - # don't insert any tests below because the previous test is - # desctructive! - # !!!! Deleted: /py/dist/py/path/test/_svncommon.py ============================================================================== --- /py/dist/py/path/test/_svncommon.py Thu Oct 14 19:33:43 2004 +++ (empty file) @@ -1,103 +0,0 @@ -import py -from py import path, test, process -from py.__impl__.path.test import _common -from py.__impl__.path.svn import cache - -class CommonSvnTests(_common.CommonFSTests): - - def test_remove_file(self): - py.test.run.Skipped(msg="need better svn state management") - - def test_propget(self): - url = self.root.join("samplefile") - value = url.propget('svn:eol-style') - assert value == 'native' - - def test_proplist(self): - url = self.root.join("samplefile") - res = url.proplist() - assert res['svn:eol-style'] == 'native' - - def test_info(self): - url = self.root.join("samplefile") - res = url.info() - assert res.size > len("samplefile") and res.created_rev >= 0 - - def xxxtest_info_log(self): - url = self.root.join("samplefile") - res = url.log(rev_start=1155, rev_end=1155, verbose=True) - assert res[0].revision == 1155 and res[0].author == "jum" - from time import gmtime - t = gmtime(res[0].date) - assert t.tm_year == 2003 and t.tm_mon == 7 and t.tm_mday == 17 - -class CommonCommandAndBindingTests(CommonSvnTests): - def test_trailing_slash_is_stripped(self): - # XXX we need to test more normalizing properties - url = self.root.join("/") - assert self.root == url - - #def test_different_revs_compare_unequal(self): - # newpath = self.root.new(rev=1199) - # assert newpath != self.root - - def test_exists_svn_root(self): - assert self.root.check() - - #def test_not_exists_rev(self): - # url = self.root.__class__(self.rooturl, rev=500) - # assert url.check(exists=0) - - #def test_nonexisting_listdir_rev(self): - # url = self.root.__class__(self.rooturl, rev=500) - # raises(error.FileNotFound, url.listdir) - - #def test_newrev(self): - # url = self.root.new(rev=None) - # assert url.rev == None - # assert url.strpath == self.root.strpath - # url = self.root.new(rev=10) - # assert url.rev == 10 - - #def test_info_rev(self): - # url = self.root.__class__(self.rooturl, rev=1155) - # url = url.join("samplefile") - # res = url.info() - # assert res.size > len("samplefile") and res.created_rev == 1155 - - # the following tests are easier if we have a path class - def test_repocache_simple(self): - repocache = cache.RepoCache() - repocache.put(self.root.strpath, 42) - url, rev = repocache.get(self.root.join('test').strpath) - assert rev == 42 - assert url == self.root.strpath - - def test_repocache_notimeout(self): - repocache = cache.RepoCache() - repocache.timeout = 0 - repocache.put(self.root.strpath, self.root.rev) - url, rev = repocache.get(self.root.strpath) - assert rev == -1 - assert url == self.root.strpath - - def test_repocache_outdated(self): - repocache = cache.RepoCache() - repocache.put(self.root.strpath, 42, timestamp=0) - url, rev = repocache.get(self.root.join('test').strpath) - assert rev == -1 - assert url == self.root.strpath - - def _test_getreporev(self): - """ this test runs so slow it's usually disabled """ - old = cache.repositories.repos - try: - _repocache.clear() - root = self.root.new(rev=-1) - url, rev = cache.repocache.get(root.strpath) - assert rev>=0 - assert url == svnrepourl - finally: - repositories.repos = old - -#cache.repositories.put(svnrepourl, 1200, 0) Copied: py/dist/py/path/test/commonfs.py (from r6933, py/dist/py/path/test/_common.py) ============================================================================== --- py/dist/py/path/test/_common.py (original) +++ py/dist/py/path/test/commonfs.py Thu Oct 14 19:33:43 2004 @@ -1,6 +1,26 @@ from py.path import checker, NotFound import py +def setuptestfs(path): + if path.join('samplefile').check(): + return + + #print "setting up test fs for", repr(path) + + samplefile = path.ensure('samplefile') + samplefile.write('samplefile\n') + + path.ensure('samplefile.py').write('import os') + + d = {1:2, 'hello': 'world', 'answer': 42} + path.ensure('sampledict.pickle').dumpobj(d) + + sampledir = path.ensure('sampledir', dir=1) + sampledir.ensure('otherfile') + + otherdir = path.ensure('otherdir', dir=1) + otherdir.ensure('__init__.py') + class CommonFSTests: root = None # subclasses have to provide a current 'root' attribute @@ -334,3 +354,5 @@ # don't insert any tests below because the previous test is # desctructive! # !!!! + # + Deleted: /py/dist/py/path/test/setuptestfs.py ============================================================================== --- /py/dist/py/path/test/setuptestfs.py Thu Oct 14 19:33:43 2004 +++ (empty file) @@ -1,20 +0,0 @@ - -def setuptestfs(path): - if path.join('samplefile').check(): - return - - #print "setting up test fs for", repr(path) - - samplefile = path.ensure('samplefile') - samplefile.write('samplefile\n') - - path.ensure('samplefile.py').write('import os') - - d = {1:2, 'hello': 'world', 'answer': 42} - path.ensure('sampledict.pickle').dumpobj(d) - - sampledir = path.ensure('sampledir', dir=1) - sampledir.ensure('otherfile') - - otherdir = path.ensure('otherdir', dir=1) - otherdir.ensure('__init__.py') Copied: py/dist/py/path/test/svncommonfs.py (from r6933, py/dist/py/path/test/_svncommon.py) ============================================================================== --- py/dist/py/path/test/_svncommon.py (original) +++ py/dist/py/path/test/svncommonfs.py Thu Oct 14 19:33:43 2004 @@ -1,9 +1,9 @@ import py from py import path, test, process -from py.__impl__.path.test import _common +from py.__impl__.path.test.commonfs import CommonFSTests from py.__impl__.path.svn import cache -class CommonSvnTests(_common.CommonFSTests): +class CommonSvnTests(CommonFSTests): def test_remove_file(self): py.test.run.Skipped(msg="need better svn state management") From hpk at codespeak.net Thu Oct 14 19:41:26 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 14 Oct 2004 19:41:26 +0200 (MEST) Subject: [py-svn] r6935 - in py/dist/py/path: local test Message-ID: <20041014174126.CDC965A165@thoth.codespeak.net> Author: hpk Date: Thu Oct 14 19:41:23 2004 New Revision: 6935 Modified: py/dist/py/path/local/local.py py/dist/py/path/test/commonfs.py py/dist/py/path/test/svncommonfs.py Log: - added a common test for recursively removing paths - note that remove() always defaults to recursive removes now. With the keyword arg rec=False you can inhibit recursively removing something. Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Thu Oct 14 19:41:23 2004 @@ -235,8 +235,8 @@ return self.strpath[len(relpath)+1:] return "" - def remove(self, rec=0): - """ remove a file or directory (or a directory tree if rec=1).""" + def remove(self, rec=1): + """ remove a file or directory (or a directory tree if rec=1). """ try: if self.check(dir=1, link=0): if rec: Modified: py/dist/py/path/test/commonfs.py ============================================================================== --- py/dist/py/path/test/commonfs.py (original) +++ py/dist/py/path/test/commonfs.py Thu Oct 14 19:41:23 2004 @@ -350,9 +350,11 @@ d.remove() assert not d.check() - # !!!! - # don't insert any tests below because the previous test is - # desctructive! - # !!!! - # + def test_remove_dir_recursive_by_default(self): + d = self.root.ensure('to', 'be', 'deleted') + assert d.check() + p = self.root.join('to') + p.remove() + assert not p.check() + Modified: py/dist/py/path/test/svncommonfs.py ============================================================================== --- py/dist/py/path/test/svncommonfs.py (original) +++ py/dist/py/path/test/svncommonfs.py Thu Oct 14 19:41:23 2004 @@ -7,6 +7,9 @@ def test_remove_file(self): py.test.run.Skipped(msg="need better svn state management") + + def test_remove_dir_recursive_by_default(self): + py.test.run.Skipped(msg="need better svn state management") def test_propget(self): url = self.root.join("samplefile") From hpk at codespeak.net Thu Oct 14 20:59:33 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 14 Oct 2004 20:59:33 +0200 (MEST) Subject: [py-svn] r6936 - in py/dist: doc example example/test Message-ID: <20041014185933.2AE835A165@thoth.codespeak.net> Author: hpk Date: Thu Oct 14 20:59:31 2004 New Revision: 6936 Added: py/dist/example/ py/dist/example/test/ py/dist/example/test/test_setup_flow_example.py Modified: py/dist/doc/test.txt Log: - added example directory - added test-example for setup_flow - modified test.txt documentation to include this new example, inspired by a suggestion from Ian Bicking Modified: py/dist/doc/test.txt ============================================================================== --- py/dist/doc/test.txt (original) +++ py/dist/doc/test.txt Thu Oct 14 20:59:31 2004 @@ -78,6 +78,14 @@ tests. This output is only displayed when the test fails, otherwise you will not see it. +order of execution is guaranteed +-------------------------------- + +Great care is taken that by default all tests run in the order +in which they appear in the files. If you run tests multiple times +you will find that they execute in exactly the same order +within each file. + Managing test state across test modules, classes and methods ------------------------------------------------------------ @@ -118,15 +126,66 @@ While the test driver guarantees that for every ``setup`` a corresponding ``teardown`` will be invoked (if it exists) it does *not* guarantee that it only happens once. For example, -the driver might decide to call the -setup_module/teardown_module pair more than once during the -execution of a test module. - -Note also that all setup/teardown methods are optional. For -example, you can have a setup_module but no teardown_module -and the other way round. +the driver might decide to call the ``setup_module`` / +``teardown_module`` pair more than once during the execution +of a test module. + +Note also that all setup/teardown methods are optional. You could +have a ``setup_module`` but no ``teardown_module`` and the other way round. + +Working Examples +================ + +Example for managing state at module, class and method level +------------------------------------------------------------ + +Here is a working example for what goes on when you setup modules, +classes and methods:: + + # [[from example/test/test_setup_flow_example.py]] + + def setup_module(module): + module.TestStateFullThing.classcount = 0 + + class TestStateFullThing: + def setup_class(cls): + cls.classcount += 1 + + def teardown_class(cls): + cls.classcount -= 1 + + def setup_method(self, method): + self.id = eval(method.func_name[5:]) + + def test_42(self): + assert self.classcount == 1 + assert self.id == 42 + + def test_23(self): + assert self.classcount == 1 + assert self.id == 23 + + def teardown_module(module): + assert module.TestStateFullThing.classcount == 0 +For this example the control flow happens as follows:: + import test_setup_flow_example + setup_module(test_setup_flow_example) + setup_class(TestStateFullThing) + instance = TestStateFullThing() + setup_method(instance, instance.test_42) + instance.test_42() + setup_method(instance, instance.test_23) + instance.test_23() + teardown_class(TestStateFullThing) + teardown_module(test_setup_flow_example) + + +Note that ``setup_class(TestStateFullThing)`` is called and not +``TestStateFullThing.setup_class()`` which would require you +to insert ``setup_class = classmethod(setup_class)`` to make +your setup function callable. The three components of ``py.test`` =================================== Added: py/dist/example/test/test_setup_flow_example.py ============================================================================== --- (empty file) +++ py/dist/example/test/test_setup_flow_example.py Thu Oct 14 20:59:31 2004 @@ -0,0 +1,42 @@ +def setup_module(module): + module.TestStateFullThing.classcount = 0 + +class TestStateFullThing: + def setup_class(cls): + cls.classcount += 1 + + def teardown_class(cls): + cls.classcount -= 1 + + def setup_method(self, method): + self.id = eval(method.func_name[5:]) + + def test_42(self): + assert self.classcount == 1 + assert self.id == 42 + + def test_23(self): + assert self.classcount == 1 + assert self.id == 23 + +def teardown_module(module): + assert module.TestStateFullThing.classcount == 0 + +""" For this example the control flow happens as follows:: + import test_setup_flow_example + setup_module(test_setup_flow_example) + setup_class(TestStateFullThing) + instance = TestStateFullThing() + setup_method(instance, instance.test_42) + instance.test_42() + setup_method(instance, instance.test_23) + instance.test_23() + teardown_class(TestStateFullThing) + teardown_module(test_setup_flow_example) + +Note that ``setup_class(TestStateFullThing)`` is called and not +``TestStateFullThing.setup_class()`` which would require you +to insert ``setup_class = classmethod(setup_class)`` to make +your setup function callable. +""" + From hpk at codespeak.net Thu Oct 14 21:16:30 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 14 Oct 2004 21:16:30 +0200 (MEST) Subject: [py-svn] r6937 - in py/dist/py/path: local test Message-ID: <20041014191630.737995A165@thoth.codespeak.net> Author: hpk Date: Thu Oct 14 21:16:29 2004 New Revision: 6937 Modified: py/dist/py/path/local/local.py py/dist/py/path/test/commonfs.py py/dist/py/path/test/svncommonfs.py Log: added move() and rename() operations for local filesystems Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Thu Oct 14 21:16:29 2004 @@ -6,7 +6,7 @@ """ import sys, os, stat -from py import path +import py from py.__impl__.path import error from py.__impl__.path import common @@ -276,7 +276,22 @@ newx.ensure(dir=1) except: self._except(sys.exc_info()) - + + def move(self, target): + if target.relto(self): + raise py.path.Invalid("cannot move path into a subdirectory of itself") + try: + self.rename(target) + except py.path.Invalid: + self.copy(target) + self.remove() + + def rename(self, target): + try: + os.rename(str(self), str(target)) + except: + self._except(sys.exc_info()) + def dumpobj(self, obj): """ pickle object into path location""" try: Modified: py/dist/py/path/test/commonfs.py ============================================================================== --- py/dist/py/path/test/commonfs.py (original) +++ py/dist/py/path/test/commonfs.py Thu Oct 14 21:16:29 2004 @@ -357,4 +357,18 @@ p.remove() assert not p.check() + def test_move_file(self): + p = self.root.ensure('tomove') + newp = p.new(basename='moved') + p.move(newp) + assert newp.check(file=1) + assert not p.check() + def test_move_directory(self): + source = self.root.join('to') + source.ensure('moved', 'somefile').write("42") + dest = source.new(basename='moveddir') + source.move(dest) + assert dest.check(dir=1) + assert dest.join('moved', 'somefile').read() == '42' + assert not source.check() Modified: py/dist/py/path/test/svncommonfs.py ============================================================================== --- py/dist/py/path/test/svncommonfs.py (original) +++ py/dist/py/path/test/svncommonfs.py Thu Oct 14 21:16:29 2004 @@ -5,12 +5,12 @@ class CommonSvnTests(CommonFSTests): - def test_remove_file(self): - py.test.run.Skipped(msg="need better svn state management") + def setup_method(self, meth): + bn = meth.func_name + if bn.startswith('test_remove') or bn.startswith('test_move'): + raise py.test.run.Skipped(msg= + "tests for (re)move require better svn state management") - def test_remove_dir_recursive_by_default(self): - py.test.run.Skipped(msg="need better svn state management") - def test_propget(self): url = self.root.join("samplefile") value = url.propget('svn:eol-style') From hpk at codespeak.net Thu Oct 14 21:19:02 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 14 Oct 2004 21:19:02 +0200 (MEST) Subject: [py-svn] r6938 - in py/dist/example: . test Message-ID: <20041014191902.CF5E55A165@thoth.codespeak.net> Author: hpk Date: Thu Oct 14 21:19:02 2004 New Revision: 6938 Modified: py/dist/example/ (props changed) py/dist/example/test/ (props changed) Log: set svn:ignore for pyc files From hpk at codespeak.net Thu Oct 14 21:33:28 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 14 Oct 2004 21:33:28 +0200 (MEST) Subject: [py-svn] r6939 - py/dist/py/path/svnwc Message-ID: <20041014193328.014575A165@thoth.codespeak.net> Author: hpk Date: Thu Oct 14 21:33:28 2004 New Revision: 6939 Modified: py/dist/py/path/svnwc/command.py Log: simple move() impl for svn path Modified: py/dist/py/path/svnwc/command.py ============================================================================== --- py/dist/py/path/svnwc/command.py (original) +++ py/dist/py/path/svnwc/command.py Thu Oct 14 21:33:28 2004 @@ -135,6 +135,9 @@ def copy(self, target): py.process.cmdexec("svn copy %s %s" %(str(self), str(target))) + def move(self, target): + py.process.cmdexec("svn move --force %s %s" %(str(self), str(target))) + def status(self, updates=0, rec=0): """ return (collective) Status object for this file. """ # http://svnbook.red-bean.com/book.html#svn-ch-3-sect-4.3.1 From hpk at codespeak.net Thu Oct 14 22:00:11 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 14 Oct 2004 22:00:11 +0200 (MEST) Subject: [py-svn] r6940 - in py/dist/py: . execnet magic path path/local path/pypath path/svn path/svnwc process test test/report test/tool Message-ID: <20041014200011.514F35A165@thoth.codespeak.net> Author: hpk Date: Thu Oct 14 22:00:10 2004 New Revision: 6940 Added: py/dist/py/execnet/test_gateway.py - copied unchanged from r6931, py/dist/py/execnet/gateway_test.py py/dist/py/execnet/test_source.py - copied unchanged from r6931, py/dist/py/execnet/source_test.py py/dist/py/magic/test_assertion.py - copied unchanged from r6931, py/dist/py/magic/assertion_test.py py/dist/py/magic/test_autopath.py - copied unchanged from r6934, py/dist/py/magic/autopath_test.py py/dist/py/magic/test_dyncode.py - copied unchanged from r6931, py/dist/py/magic/dyncode_test.py py/dist/py/magic/test_exprinfo.py - copied unchanged from r6931, py/dist/py/magic/exprinfo_test.py py/dist/py/magic/test_invoke.py - copied, changed from r6931, py/dist/py/magic/invoke_test.py py/dist/py/magic/test_patch.py - copied unchanged from r6931, py/dist/py/magic/patch_test.py py/dist/py/magic/test_viewtype.py - copied unchanged from r6931, py/dist/py/magic/viewtype_test.py py/dist/py/path/local/test_local.py - copied unchanged from r6934, py/dist/py/path/local/local_test.py py/dist/py/path/pypath/test_pypath.py - copied unchanged from r6931, py/dist/py/path/pypath/pypath_test.py py/dist/py/path/svn/test_command.py - copied, changed from r6934, py/dist/py/path/svn/command_test.py py/dist/py/path/svnwc/test_command.py - copied unchanged from r6934, py/dist/py/path/svnwc/command_test.py py/dist/py/path/test_api.py - copied, changed from r6931, py/dist/py/path/api_test.py py/dist/py/process/test_cmdexec.py - copied unchanged from r6931, py/dist/py/process/cmdexec_test.py py/dist/py/test/report/test_memo.py - copied unchanged from r6931, py/dist/py/test/report/memo_test.py py/dist/py/test/test_collect.py - copied unchanged from r6931, py/dist/py/test/collect_test.py py/dist/py/test/test_compat.py - copied unchanged from r6931, py/dist/py/test/compat_test.py py/dist/py/test/test_config.py - copied unchanged from r6931, py/dist/py/test/config_test.py py/dist/py/test/test_raises.py - copied unchanged from r6931, py/dist/py/test/raises_test.py py/dist/py/test/tool/test_outerrcapture.py - copied unchanged from r6931, py/dist/py/test/tool/outerrcapture_test.py py/dist/py/test_api.py - copied unchanged from r6931, py/dist/py/api_test.py py/dist/py/test_initpkg.py - copied unchanged from r6931, py/dist/py/initpkg_test.py Removed: py/dist/py/api_test.py py/dist/py/execnet/gateway_test.py py/dist/py/execnet/source_test.py py/dist/py/initpkg_test.py py/dist/py/magic/assertion_test.py py/dist/py/magic/autopath_test.py py/dist/py/magic/dyncode_test.py py/dist/py/magic/exprinfo_test.py py/dist/py/magic/invoke_test.py py/dist/py/magic/patch_test.py py/dist/py/magic/viewtype_test.py py/dist/py/path/api_test.py py/dist/py/path/local/local_test.py py/dist/py/path/pypath/pypath_test.py py/dist/py/path/svn/command_test.py py/dist/py/path/svnwc/command_test.py py/dist/py/process/cmdexec_test.py py/dist/py/test/collect_test.py py/dist/py/test/compat_test.py py/dist/py/test/config_test.py py/dist/py/test/raises_test.py py/dist/py/test/report/memo_test.py py/dist/py/test/tool/outerrcapture_test.py Modified: py/dist/py/execnet/register.py Log: - move XX_test.py to test_XX.py for all testfiles. i was getting tired of having "completion" collissions and it seems more consistent to always require a test or "Test" prefix for test files, classes and methods Deleted: /py/dist/py/api_test.py ============================================================================== --- /py/dist/py/api_test.py Thu Oct 14 22:00:10 2004 +++ (empty file) @@ -1,54 +0,0 @@ - -from py.test import raises -import py -import sys -import inspect - -class API_V0_namespace_consistence: - def test_path_entrypoints(self): - assert inspect.ismodule(py.path) - assert_class('py.path', 'local') - assert_class('py.path', 'svnwc') - assert_class('py.path', 'svnurl') - assert_class('py.path', 'py') - assert_class('py.path', 'checker') - assert_class('py.path', 'invchecker') - assert_class('py.path', 'NotFound') - assert_class('py.path', 'Denied') - - def test_magic_entrypoints(self): - assert_class('py.magic', 'View') - assert_function('py.magic', 'invoke') - assert_function('py.magic', 'revoke') - assert_function('py.magic', 'patch') - assert_function('py.magic', 'revoke') - - assert inspect.ismodule(py.magic.dyncode) - assert_function('py.magic.dyncode', 'compile') - assert_function('py.magic.dyncode', 'compile2') - assert_function('py.magic.dyncode', 'findsource') - assert_function('py.magic.dyncode', 'getsource') - assert_function('py.magic.dyncode', 'listtb') - assert_function('py.magic.dyncode', 'findsource') - - def test_process_entrypoints(self): - assert_function('py.process', 'cmdexec') - - def test_utest_entrypoints(self): - # XXX TOBECOMPLETED - assert_function('py.test', 'main') - #assert_module('std.utest', 'collect') - -def assert_class(modpath, name): - mod = __import__(modpath, None, None, [name]) - obj = getattr(mod, name) - fullpath = modpath + '.' + name - assert obj.__module__ == modpath - if sys.version_info >= (2,3): - assert obj.__name__ == name - -def assert_function(modpath, name): - mod = __import__(modpath, None, None, [name]) - obj = getattr(mod, name) - assert hasattr(obj, 'func_doc') - #assert obj.func_name == name Deleted: /py/dist/py/execnet/gateway_test.py ============================================================================== --- /py/dist/py/execnet/gateway_test.py Thu Oct 14 22:00:10 2004 +++ (empty file) @@ -1,177 +0,0 @@ -import os, sys -import py -from py.__impl__.execnet.source import Source -from py.__impl__.execnet import gateway -mypath = py.magic.autopath() - -from StringIO import StringIO - -class TestMessage: - def test_wire_protocol(self): - for cls in gateway.Message._types.values(): - one = StringIO() - cls(42, '23').writeto(one) - two = StringIO(one.getvalue()) - msg = gateway.Message.readfrom(two) - assert isinstance(msg, cls) - assert msg.channelid == 42 - assert msg.data == '23' - assert isinstance(repr(msg), str) - # == "" %(msg.__class__.__name__, ) - -class TestChannel: - def setup_method(self, method): - self.fac = gateway.ChannelFactory(None) - - def test_factory_create(self): - chan1 = self.fac.new() - assert chan1.id == 1 - chan2 = self.fac.new() - assert chan2.id == 3 - - def test_factory_getitem(self): - chan1 = self.fac.new() - assert self.fac[chan1.id] == chan1 - chan2 = self.fac.new() - assert self.fac[chan2.id] == chan2 - - def test_factory_delitem(self): - chan1 = self.fac.new() - assert self.fac[chan1.id] == chan1 - del self.fac[chan1.id] - py.test.raises(KeyError, self.fac.__getitem__, chan1.id) - - def test_factory_setitem(self): - channel = gateway.Channel(None, 12) - self.fac[channel.id] = channel - assert self.fac[channel.id] == channel - - def test_channel_timeouterror(self): - channel = self.fac.new() - py.test.raises(IOError, channel.waitclose, timeout=0.01) - - def test_channel_close(self): - channel = self.fac.new() - channel._close() - channel.waitclose(0.1) - - def test_channel_close_error(self): - channel = self.fac.new() - channel._close("error") - py.test.raises(gateway.RemoteError, channel.waitclose, 0.01) - -class PopenGatewayTestSetup: - def setup_class(cls): - cls.gw = py.execnet.PopenGateway() - - def teardown_class(cls): - cls.gw.exit() - -class BasicRemoteExecution: - def test_correct_setup(self): - assert self.gw.workerthreads and self.gw.iothreads - - def test_remote_exec_waitclose(self): - channel = self.gw.remote_exec('pass') - channel.waitclose(timeout=3.0) - - def test_remote_exec_channel_anonymous(self): - channel = self.gw.remote_exec(''' - obj = channel.receive() - channel.send(obj) - ''') - channel.send(42) - result = channel.receive() - assert result == 42 - - def test_channel_close_and_then_receive_error(self): - channel = self.gw.remote_exec('raise ValueError') - py.test.raises(gateway.RemoteError, channel.receive) - - def test_channel_close_and_then_receive_error_multiple(self): - channel = self.gw.remote_exec('channel.send(42) ; raise ValueError') - import time - time.sleep(0.1) - x = channel.receive() - assert x == 42 - py.test.raises(gateway.RemoteError, channel.receive) - -class TestBasicPopenGateway(PopenGatewayTestSetup, BasicRemoteExecution): - def test_many_popen(self): - num = 4 - l = [] - for i in range(num): - l.append(py.execnet.PopenGateway()) - channels = [] - for gw in l: - channel = gw.remote_exec("""channel.send(42)""") - channels.append(channel) - try: - while channels: - channel = channels.pop() - try: - ret = channel.receive() - assert ret == 42 - finally: - channel.gateway.exit() - finally: - for x in channels: - x.gateway.exit() - -class SocketGatewaySetup: - def setup_class(cls): - portrange = (7770, 7800) - cls.proxygw = py.execnet.PopenGateway() - socketserverbootstrap = Source( - mypath.dirpath('bin', 'startserver.py').read(), - """ - import socket - portrange = channel.receive() - for i in portrange: - try: - sock = bind_and_listen(("localhost", i)) - except socket.error: - print "got error" - import traceback - traceback.print_exc() - continue - else: - channel.send(i) - startserver(sock) - break - else: - channel.send(None) - """) - # open a gateway to a fresh child process - cls.proxygw = py.execnet.PopenGateway() - - # execute asynchronously the above socketserverbootstrap on the other - channel = cls.proxygw.remote_exec(socketserverbootstrap) - - # send parameters for the for-loop - channel.send((7770, 7800)) - # - # the other side should start the for loop now, we - # wait for the result - # - cls.listenport = channel.receive() - if cls.listenport is None: - raise IOError, "could not setup remote SocketServer" - cls.gw = py.execnet.SocketGateway('localhost', cls.listenport) - print "initialized socket gateway on port", cls.listenport - - def teardown_class(cls): - print "trying to tear down remote socket gateway" - cls.gw.exit() - if cls.gw.port: - print "trying to tear down remote socket loop" - import socket - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.connect(('localhost', cls.listenport)) - sock.sendall('"raise KeyboardInterrupt"') - sock.shutdown(2) - print "trying to tear proxy gateway" - cls.proxygw.exit() - -class TestSocketGateway(SocketGatewaySetup, BasicRemoteExecution): - pass Modified: py/dist/py/execnet/register.py ============================================================================== --- py/dist/py/execnet/register.py (original) +++ py/dist/py/execnet/register.py Thu Oct 14 22:00:10 2004 @@ -5,7 +5,6 @@ import py from py.__impl__.execnet import inputoutput, gateway -py.magic.invoke(dyncode=True) class InstallableGateway(gateway.Gateway): """ initialize gateways on both sides of a inputoutput object. """ Deleted: /py/dist/py/execnet/source_test.py ============================================================================== --- /py/dist/py/execnet/source_test.py Thu Oct 14 22:00:10 2004 +++ (empty file) @@ -1,40 +0,0 @@ - -from py.__impl__.execnet.source import Source - -def test_source_str_function(): - x = Source("3") - assert str(x) == "3" - - x = Source(" 3") - assert str(x) == "3" - - x = Source(""" - 3 - """) - assert str(x) == "3" - -def test_source_indent_simple(): - source = Source("raise ValueError") - source.putaround( - "try:", - """ - except ValueError: - x = 42 - else: - x = 23""") - assert str(source)=="""\ -try: - raise ValueError -except ValueError: - x = 42 -else: - x = 23""" - -def XXXtest_source_indent_simple(): - x = StrSource("x=3") - assert not x.isindented() - x.indent("try:", "except: pass") - assert x.read() == "try:\n x=3\nexcept: pass" - - #x.indent("try:", - # """except: Deleted: /py/dist/py/initpkg_test.py ============================================================================== --- /py/dist/py/initpkg_test.py Thu Oct 14 22:00:10 2004 +++ (empty file) @@ -1,60 +0,0 @@ -import py -import types - -def test_dir(): - for name in dir(py): - if not name.startswith('_'): - obj = getattr(py, name) - if isinstance(obj, types.ModuleType): - keys = dir(obj) - assert len(keys) > 0 - assert getattr(obj, '__map__') == {} - -def test_virtual_module_identity(): - from py import path as path1 - from py import path as path2 - assert path1 is path2 - from py.path import local as local1 - from py.path import local as local2 - assert local1 is local2 - -def test_importing_all_implementations(): - base = py.path.local(py.__file__).dirpath() - for p in base.visit('*.py', py.path.checker(dotfile=0)): - relpath = p.new(ext='').relto(base) - if base.sep in relpath: # not std/*.py itself - if relpath.find('test/data') != -1: - continue - if relpath.find('bin/') != -1: - continue - relpath = relpath.replace(base.sep, '.') - modpath = 'py.__impl__.%s' % relpath - assert __import__(modpath) - -def test_shahexdigest(): - hex = py.__package__.shahexdigest() - assert len(hex) == 40 - -def test_getzipdata(): - s = py.__package__.getzipdata() - -# the following test should abasically work in the future -def XXXtest_virtual_on_the_fly(): - py.initpkg('my', { - 'x.abspath' : 'os.path.abspath', - 'x.local' : 'py.path.local', - 'y' : 'smtplib', - 'z.cmdexec' : 'py.process.cmdexec', - }) - from my.x import abspath - from my.x import local - import smtplib - from my import y - assert y is smtplib - from my.z import cmdexec - from py.process import cmdexec as cmdexec2 - assert cmdexec is cmdexec2 - -##def test_help(): -# help(std.path) -# #assert False Deleted: /py/dist/py/magic/assertion_test.py ============================================================================== --- /py/dist/py/magic/assertion_test.py Thu Oct 14 22:00:10 2004 +++ (empty file) @@ -1,43 +0,0 @@ - -from py.test import main -from py.__impl__.magic import assertion - -def f(): - return 2 - -def test_assert(): - assertion.invoke() - try: - try: - assert f() == 3 - except AssertionError, e: - s = str(e) - assert s.startswith('assert 2 == 3\n') - finally: - assertion.revoke() - -def test_assert_multiline_1(): - assertion.invoke() - try: - try: - assert (f() == - 3) - except AssertionError, e: - s = str(e) - assert s.startswith('assert 2 == 3\n') - finally: - assertion.revoke() - -def test_assert_multiline_2(): - assertion.invoke() - try: - try: - assert (f() == (4, - 3)[-1]) - except AssertionError, e: - s = str(e) - assert s.startswith('assert 2 ==') - finally: - assertion.revoke() - -main() Deleted: /py/dist/py/magic/autopath_test.py ============================================================================== --- /py/dist/py/magic/autopath_test.py Thu Oct 14 22:00:10 2004 +++ (empty file) @@ -1,91 +0,0 @@ -import py -import sys - -class TestAutoPath: - getauto = "from py.magic import autopath ; autopath = autopath()" - def __init__(self): - self.root = py.test.config.tmpdir.ensure('autoconfigure', dir=1) - self.initdir = self.root.ensure('pkgdir', dir=1) - self.initdir.ensure('__init__.py') - self.initdir2 = self.initdir.ensure('initdir2', dir=1) - self.initdir2.ensure('__init__.py') - - def test_import_autoconfigure__file__with_init(self): - testpath = self.initdir2 / 'autoconfiguretest.py' - d = {'__file__' : str(testpath)} - oldsyspath = sys.path[:] - try: - exec self.getauto in d - conf = d['autopath'] - assert conf.dirpath() == self.initdir2 - assert conf.pkgdir == self.initdir - assert str(self.root) in sys.path - exec self.getauto in d - assert conf is not d['autopath'] - finally: - sys.path[:] = oldsyspath - - def test_import_autoconfigure__file__with_py_exts(self): - for ext in '.pyc', '.pyo': - testpath = self.initdir2 / ('autoconfiguretest' + ext) - d = {'__file__' : str(testpath)} - oldsyspath = sys.path[:] - try: - exec self.getauto in d - conf = d['autopath'] - assert conf == self.initdir2.join('autoconfiguretest.py') - assert conf.pkgdir == self.initdir - assert str(self.root) in sys.path - exec self.getauto in d - assert conf is not d['autopath'] - finally: - sys.path[:] = oldsyspath - - def test_import_autoconfigure___file__without_init(self): - testpath = self.root / 'autoconfiguretest.py' - d = {'__file__' : str(testpath)} - oldsyspath = sys.path[:] - try: - exec self.getauto in d - conf = d['autopath'] - assert conf.dirpath() == self.root - assert conf.pkgdir == self.root - syspath = sys.path[:] - assert str(self.root) in syspath - exec self.getauto in d - assert conf is not d['autopath'] - finally: - sys.path[:] = oldsyspath - - def test_import_autoconfigure__nofile(self): - p = self.initdir2 / 'autoconfiguretest.py' - oldsysarg = sys.argv - sys.argv = [str(p)] - oldsyspath = sys.path[:] - try: - d = {} - exec self.getauto in d - conf = d['autopath'] - assert conf.dirpath() == self.initdir2 - assert conf.pkgdir == self.initdir - syspath = sys.path[:] - assert str(self.root) in syspath - finally: - sys.path[:] = oldsyspath - sys.argv = sys.argv - - - def test_import_autoconfigure__nofile_interactive(self): - oldsysarg = sys.argv - sys.argv = [''] - oldsyspath = sys.path[:] - try: - py.test.raises(ValueError,''' - d = {} - exec self.getauto in d - ''') - finally: - sys.path[:] = oldsyspath - sys.argv = sys.argv - -py.test.main() Deleted: /py/dist/py/magic/dyncode_test.py ============================================================================== --- /py/dist/py/magic/dyncode_test.py Thu Oct 14 22:00:10 2004 +++ (empty file) @@ -1,78 +0,0 @@ -import sys -import os -#print "dyncode_test: __name__ ==", __name__ -from py import test -from py.__impl__.magic import dyncode - -def test_dyncode_trace(): - source = """ - def f(): - raise ValueError - """ - co = dyncode.compile2(source) - exec co - excinfo = test.raises(ValueError, f) - filename, lineno = dyncode.tbinfo(excinfo[2]) - line = dyncode.getline(filename, lineno) - assert line.strip() == 'raise ValueError' - -def test_dyncode_trace_multiple(): - test_dyncode_trace() - test_dyncode_trace() - -def test_unique_filenames(): - fn1 = dyncode._makedynfilename('fn','source') - fn2 = dyncode._makedynfilename('fn','source') - assert fn1 != fn2 - -def test_syntaxerror_rerepresentation(): - ex = test.raises(SyntaxError, dyncode.compile2, 'x x')[1] - assert ex.lineno == 1 - assert ex.offset == 3 - assert ex.text.strip(), 'x x' - -def test_getfuncsource_dynamic(): - source = """ - def f(): - raise ValueError - - def g(): pass - """ - co = dyncode.compile2(source) - exec co - source = dyncode.getsource(f) - assert dyncode.getsource(f) == 'def f():\n raise ValueError\n' - assert dyncode.getsource(g) == 'def g(): pass\n' - -def test_getpyfile(): - fn = dyncode.getpyfile(dyncode) - assert os.path.exists(fn) - -def test_getstartingblock_singleline(): - class A: - def __init__(self, *args): - frame = sys._getframe(1) - self.source = dyncode.getparseablestartingblock(frame) - - x = A('x', 'y') - - l = [i for i in x.source.split('\n') if i.strip()] - assert len(l) == 1 - -def test_getstartingblock_multiline(): - class A: - def __init__(self, *args): - frame = sys._getframe(1) - self.source = dyncode.getparseablestartingblock(frame) - - x = A('x', - 'y' \ - , - 'z') - - l = [i for i in x.source.split('\n') if i.strip()] - assert len(l) == 4 - -if __name__ == '__main__': - test.main() - Deleted: /py/dist/py/magic/exprinfo_test.py ============================================================================== --- /py/dist/py/magic/exprinfo_test.py Thu Oct 14 22:00:10 2004 +++ (empty file) @@ -1,54 +0,0 @@ - -import sys -from py.test import main -from py.__impl__.magic.exprinfo import getmsg, interpret - -def getexcinfo(exc, obj, *args, **kwargs): - try: - obj(*args, **kwargs) - except KeyboardInterrupt: - raise - except exc: - return sys.exc_info() - else: - raise AssertionError, "%r(*%r, **%r) did not raise" %( - obj, args, kwargs) - -def test_assert_exprinfo(): - def g(): - a = 1 - b = 2 - assert a == b - excinfo = getexcinfo(AssertionError, g) - msg = getmsg(excinfo) - assert msg == 'AssertionError: assert 1 == 2' - -def global_f(): - return 42 - -def test_exprinfo_funccall(): - def g(): - assert global_f() == 43 - excinfo = getexcinfo(AssertionError, g) - msg = getmsg(excinfo) - assert msg == 'AssertionError: assert 42 == 43\n + where 42 = global_f()' - -def test_keyboard_interrupt(): - # XXX this test is slightly strange because it is not - # clear that "interpret" should execute "raise" statements - # ... but it apparently currently does and it's nice to - # exercise the code because the exprinfo-machinery is - # not much executed when all tests pass ... - - class DummyFrame: - f_globals = f_locals = {} - for exstr in "SystemExit", "KeyboardInterrupt", "MemoryError": - ex = eval(exstr) - try: - interpret("raise %s" % exstr, DummyFrame) - except ex: - pass - else: - raise AssertionError, "ex %s didn't pass through" %(exstr, ) - -main() Deleted: /py/dist/py/magic/invoke_test.py ============================================================================== --- /py/dist/py/magic/invoke_test.py Thu Oct 14 22:00:10 2004 +++ (empty file) @@ -1,34 +0,0 @@ -import __builtin__ as bltin -from py.magic import invoke, revoke -from py.test import raises -import inspect - -def check_dyncode(): - co = compile('x=3', 'bogus', 'exec') - s = inspect.getfile(co) - assert s - line = inspect.getsource(co) - assert line.strip() == "x=3" - -def check_assertion(): - excinfo = raises(AssertionError, "assert 1 == 2") - value = excinfo[1] - print value - assert str(value) == "assert 1 == 2" - -def test_invoke_dyncode(): - old = compile - invoke(dyncode=True) - try: - assert compile != old - check_dyncode() - finally: - revoke(dyncode=True) - -def test_invoke_assertion(): - invoke(assertion=True) - try: - check_assertion() - finally: - revoke(assertion=True) - Deleted: /py/dist/py/magic/patch_test.py ============================================================================== --- /py/dist/py/magic/patch_test.py Thu Oct 14 22:00:10 2004 +++ (empty file) @@ -1,31 +0,0 @@ -from py.test import raises -from py.magic import patch, revert - -def test_patch_revert(): - class a: - pass - raises(AttributeError, "patch(a, 'i', 42)") - - a.i = 42 - patch(a, 'i', 23) - assert a.i == 23 - revert(a, 'i') - assert a.i == 42 - -def test_double_patch(): - class a: - i = 42 - assert patch(a, 'i', 2) == 42 - assert patch(a, 'i', 3) == 2 - assert a.i == 3 - assert revert(a, 'i') == 3 - assert a.i == 2 - assert revert(a, 'i') == 2 - assert a.i == 42 - -def test_valueerror(): - class a: - i = 2 - pass - raises(ValueError, "revert(a, 'i')") - Copied: py/dist/py/magic/test_invoke.py (from r6931, py/dist/py/magic/invoke_test.py) ============================================================================== --- py/dist/py/magic/invoke_test.py (original) +++ py/dist/py/magic/test_invoke.py Thu Oct 14 22:00:10 2004 @@ -1,6 +1,5 @@ import __builtin__ as bltin -from py.magic import invoke, revoke -from py.test import raises +import py import inspect def check_dyncode(): @@ -11,24 +10,24 @@ assert line.strip() == "x=3" def check_assertion(): - excinfo = raises(AssertionError, "assert 1 == 2") + excinfo = py.test.raises(AssertionError, "assert 1 == 2") value = excinfo[1] print value assert str(value) == "assert 1 == 2" def test_invoke_dyncode(): old = compile - invoke(dyncode=True) + py.magic.invoke(dyncode=True) try: assert compile != old check_dyncode() finally: - revoke(dyncode=True) + py.magic.revoke(dyncode=True) def test_invoke_assertion(): - invoke(assertion=True) + py.magic.invoke(assertion=True) try: check_assertion() finally: - revoke(assertion=True) + py.magic.revoke(assertion=True) Deleted: /py/dist/py/magic/viewtype_test.py ============================================================================== --- /py/dist/py/magic/viewtype_test.py Thu Oct 14 22:00:10 2004 +++ (empty file) @@ -1,59 +0,0 @@ -from py import test -from py.magic import View - -def test_class_dispatch(): - ### Use a custom class hierarchy with existing instances - - class Picklable(View): - pass - - class Simple(Picklable): - __view__ = object - def pickle(self): - return repr(self.__obj__) - - class Seq(Picklable): - __view__ = list, tuple, dict - def pickle(self): - return ';'.join([Picklable(item).pickle() for item in self.__obj__]) - - class Dict(Seq): - __view__ = dict - def pickle(self): - return Seq.pickle(self) + '!' + Seq(self.values()).pickle() - - assert Picklable(123).pickle() == '123' - assert Picklable([1,[2,3],4]).pickle() == '1;2;3;4' - assert Picklable({1:2}).pickle() == '1!2' - - -def test_custom_class_hierarchy(): - ### Use a custom class hierarchy based on attributes of existing instances - - class Operation: - "Existing class that I don't want to change." - def __init__(self, opname, *args): - self.opname = opname - self.args = args - - existing = [Operation('+', 4, 5), - Operation('getitem', '', 'join'), - Operation('setattr', 'x', 'y', 3), - Operation('-', 12, 1)] - - class PyOp(View): - def __viewkey__(self): - return self.opname - def generate(self): - return '%s(%s)' % (self.opname, ', '.join(map(repr, self.args))) - - class PyBinaryOp(PyOp): - __view__ = ('+', '-', '*', '/') - def generate(self): - return '%s %s %s' % (self.args[0], self.opname, self.args[1]) - - codelines = [PyOp(op).generate() for op in existing] - assert codelines == ["4 + 5", "getitem('', 'join')", "setattr('x', 'y', 3)", "12 - 1"] - -test.main() - Deleted: /py/dist/py/path/api_test.py ============================================================================== --- /py/dist/py/path/api_test.py Thu Oct 14 22:00:10 2004 +++ (empty file) @@ -1,59 +0,0 @@ -from py import path, test -from py.__impl__.path.svnwc.command_test import getrepowc - -class TestAPI: - def __init__(self): - self.root = test.config.tmpdir.ensure('local', dir=1) - - def repr_eval_test(self, p): - r = repr(p) - from py.path import local,svnurl, svnwc, py - y = eval(r) - assert y == p - - def test_defaultlocal(self): - p = path.local() - assert hasattr(p, 'atime') - assert hasattr(p, 'group') - assert hasattr(p, 'setmtime') - assert p.check() - assert p.check(local=1) - assert p.check(svnwc=0) - assert not p.check(svnwc=1) - self.repr_eval_test(p) - - #assert p.std.path() - - def test_xlocal(self): - p = path.local() - assert hasattr(p, 'atime') - assert hasattr(p, 'setmtime') - assert p.check() - assert p.check(local=1) - self.repr_eval_test(p) - - def test_svnurl(self): - p = path.svnurl('http://codespeak.net/svn/py.path') - assert p.check(svnurl=1) - self.repr_eval_test(p) - - def test_svnwc(self): - p = path.svnwc(self.root) - assert p.check(svnwc=1) - self.repr_eval_test(p) - - def test_passing_svnurl(self): - repourl, rootwc = getrepowc() - p = path.svnurl(repourl) - assert p.check() - w = path.svnwc(None, p) - self.repr_eval_test(p) - - def test_pypath(self): - p = path.py('smtplib.SMTP') - self.repr_eval_test(p) - - -if __name__ == '__main__': - test.main() - Deleted: /py/dist/py/path/local/local_test.py ============================================================================== --- /py/dist/py/path/local/local_test.py Thu Oct 14 22:00:10 2004 +++ (empty file) @@ -1,359 +0,0 @@ -import sys, os -from py.test import main, raises, config -from py.path import local, checker -from py.__impl__.path.test.commonfs import CommonFSTests, setuptestfs - -class TestLocalPath(CommonFSTests): - def __init__(self): - print "tmpdir is", config.tmpdir - self.root = config.tmpdir / 'local' - self.root.ensure(dir=1) - setuptestfs(self.root) - - def test_initialize_curdir(self): - assert str(local()) == os.getcwd() - - def test_initialize_reldir(self): - curdir = os.curdir - try: - os.chdir(str(self.root)) - p = local('samplefile') - assert p.check() - finally: - os.chdir(curdir) - - def test_eq_with_strings(self): - path1 = self.root.join('sampledir') - path2 = str(path1) - assert path1 == path2 - assert path2 == path1 - path3 = self.root.join('samplefile') - assert path3 != path2 - assert path2 != path3 - - def test_dump(self): - import tempfile - try: - fd, name = tempfile.mkstemp() - f = os.fdopen(fd) - except AttributeError: - name = tempfile.mktemp() - f = open(name, 'w+') - try: - d = {'answer' : 42} - path = local(name) - path.dumpobj(d) - from cPickle import load - dnew = load(f) - assert d == dnew - finally: - f.close() - os.remove(name) - - def test_setmtime(self): - import tempfile - import time - try: - fd, name = tempfile.mkstemp() - os.close(fd) - except AttributeError: - name = tempfile.mktemp() - open(name, 'w').close() - try: - mtime = int(time.time())-100 - path = local(name) - assert path.mtime() != mtime - path.setmtime(mtime) - assert path.mtime() == mtime - path.setmtime() - assert path.mtime() != mtime - finally: - os.remove(name) - - def test_normpath(self): - new1 = self.root.join("/otherdir") - new2 = self.root.join("otherdir") - assert str(new1) == str(new2) - - def test_mkdtemp_creation(self): - d = local.mkdtemp() - try: - assert d.check(dir=1) - finally: - d.remove(rec=1) - - def test_tmproot(self): - d = local.mkdtemp() - tmproot = local.get_temproot() - try: - assert d.check(dir=1) - assert d.dirpath() == tmproot - finally: - d.remove(rec=1) - - def test_ensure_filepath_withdir(self): - tmpdir = local.mkdtemp() - try: - newfile = tmpdir.join('test1','test2') - newfile.ensure() - assert newfile.check(file=1) - finally: - tmpdir.remove(rec=1) - - def test_ensure_filepath_withoutdir(self): - tmpdir = local.mkdtemp() - try: - newfile = tmpdir.join('test1') - t = newfile.ensure() - assert t == newfile - assert newfile.check(file=1) - finally: - tmpdir.remove(rec=1) - - def test_ensure_dirpath(self): - tmpdir = local.mkdtemp() - try: - newfile = tmpdir.join('test1','test2') - t = newfile.ensure(dir=1) - assert t == newfile - assert newfile.check(dir=1) - finally: - tmpdir.remove(rec=1) - - def test_mkdir(self): - tmpdir = local.mkdtemp() - try: - new = tmpdir.join('test1') - new.mkdir() - assert new.check(dir=1) - - new = tmpdir.mkdir('test2') - assert new.check(dir=1) - assert tmpdir.join('test2') == new - finally: - tmpdir.remove(rec=1) - - def test_chdir(self): - import os - old = local() - tmpdir = local.mkdtemp() - try: - res = tmpdir.chdir() - assert str(res) == str(old) - assert os.getcwd() == str(tmpdir) - finally: - old.chdir() - tmpdir.remove(rec=1) - - -class TestPOSIXLocalPath: - #root = local(TestLocalPath.root) - disabled = sys.platform == 'win32' - - def __init__(self): - print "tmpdir is", config.tmpdir - self.root = config.tmpdir / 'local' - self.root.ensure(dir=1) - setuptestfs(self.root) - - def test_hardlink(self): - tmpdir = local(local.mkdtemp()) - try: - linkpath = tmpdir.join('test') - filepath = tmpdir.join('file') - filepath.write("Hello") - linkpath.mklinkto(filepath) - assert filepath.read() == linkpath.read() - finally: - tmpdir.remove(rec=1) - - def test_symlink_are_identical(self): - tmpdir = local(local.mkdtemp()) - try: - filepath = tmpdir.join('file') - filepath.write("Hello") - linkpath = tmpdir.join('test') - linkpath.mksymlinkto(filepath) - assert filepath.read() == linkpath.read() - finally: - tmpdir.remove(rec=1) - - def test_symlink_isfile(self): - tmpdir = local(local.mkdtemp()) - try: - linkpath = tmpdir.join('test') - filepath = tmpdir.join('file') - filepath.write("") - linkpath.mksymlinkto(filepath) - assert linkpath.check(file=1) - assert not linkpath.check(link=0, file=1) - finally: - tmpdir.remove(rec=1) - - def test_symlink_relative(self): - tmpdir = local(local.mkdtemp()) - try: - linkpath = tmpdir.join('test') - filepath = tmpdir.join('file') - filepath.write("Hello") - linkpath.mksymlinkto(filepath, absolute=False) - assert linkpath.readlink() == "file" - assert filepath.read() == linkpath.read() - finally: - tmpdir.remove(rec=1) - - def test_visit_recursive_symlink(self): - tmpdir = local.mkdtemp() - try: - linkpath = tmpdir.join('test') - linkpath.mksymlinkto(tmpdir) - visitor = tmpdir.visit(None, checker(link=0)) - assert list(visitor) == [linkpath] - #check.equal(list(tmpdir.visit()), [linkpath]) - finally: - tmpdir.remove(rec=1) - - def test_symlink_isdir(self): - tmpdir = local.mkdtemp() - try: - linkpath = tmpdir.join('test') - linkpath.mksymlinkto(tmpdir) - assert linkpath.check(dir=1) - assert not linkpath.check(link=0, dir=1) - finally: - tmpdir.remove(rec=1) - - def test_symlink_remove(self): - tmpdir = local.mkdtemp() - try: - linkpath = tmpdir.join('test') - linkpath.mksymlinkto(linkpath) # point to itself - assert linkpath.check(dir=0) - assert linkpath.check(link=1) - linkpath.remove() - assert not linkpath.check() - finally: - tmpdir.remove(rec=1) - - def test_realpath_file(self): - tmpdir = local.mkdtemp() - try: - linkpath = tmpdir.join('test') - filepath = tmpdir.join('file') - filepath.write("") - linkpath.mksymlinkto(filepath) - realpath = linkpath.realpath() - assert realpath.get('basename') == 'file' - finally: - tmpdir.remove(rec=1) - - def test_owner(self): - from pwd import getpwuid - assert getpwuid(self.root.stat().st_uid)[0] == self.root.owner() - - def test_group(self): - from grp import getgrgid - assert getgrgid(self.root.stat().st_gid)[0] == self.root.group() - - def XXXtest_atime(self): - # XXX disabled. this test is just not platform independent enough - # because acesstime resolution is very different through - # filesystems even on one platform. - import time - path = self.root.join('samplefile') - atime = path.atime() - time.sleep(1) - path.read(1) - assert path.atime() != atime - - def testcommondir(self): - # XXX This is here in local until we find a way to implement this - # using the subversion command line api. - p1 = self.root.join('something') - p2 = self.root.join('otherthing') - assert p1.commondir(p2) == self.root - assert p2.commondir(p1) == self.root - - def testcommondir_nocommon(self): - # XXX This is here in local until we find a way to implement this - # using the subversion command line api. - p1 = self.root.join('something') - p2 = local(os.sep+'blabla') - assert p1.commondir(p2) is None - - - def test_chmod_simple_int(self): - print "self.root is", self.root - mode = self.root.mode() - self.root.chmod(mode/2) - try: - assert self.root.mode() != mode - finally: - self.root.chmod(mode) - assert self.root.mode() == mode - - def test_chmod_rec_int(self): - # XXX fragile test - print "self.root is", self.root - recfilter = checker(dotfile=0) - oldmodes = {} - for x in self.root.visit(rec=recfilter): - oldmodes[x] = x.mode() - self.root.chmod(0772, rec=1) - try: - for x in self.root.visit(rec=recfilter): - assert x.mode() & 0777 == 0772 - finally: - for x,y in oldmodes.items(): - x.chmod(y) - - def test_chown_identity(self): - owner = self.root.owner() - group = self.root.group() - self.root.chown(owner, group) - - def test_chown_identity_rec_mayfail(self): - owner = self.root.owner() - group = self.root.group() - self.root.chown(owner, group) - -class TestMisc: - root = local(TestLocalPath.root) - - def test_make_numbered_dir(self): - root = local.mkdtemp() - try: - for i in range(10): - numdir = local.make_numbered_dir(root, 'base.', keep=2) - assert numdir.check() - assert numdir.get('basename') == 'base.%d' %i - if i>=1: - assert numdir.new(ext=str(i-1)).check() - if i>=2: - assert numdir.new(ext=str(i-2)).check() - if i>=3: - assert not numdir.new(ext=str(i-3)).check() - finally: - #print "root was", root - root.remove(rec=1) - - def test_error_preservation(self): - raises (OSError, self.root.join('qwoeqiwe').mtime) - raises (IOError, self.root.join('qwoeqiwe').read) - - #def test_parentdirmatch(self): - # local.parentdirmatch('std', startmodule=__name__) - -#class XTestLocalPath(TestLocalPath): -# def __init__(self): -# TestLocalPath.__init__(self) -# self.root = local(self.root) -# -#class XXTestLocalPath(TestLocalPath): -# def __init__(self): -# TestLocalPath.__init__(self) -# self.root = local(self.root) - -if __name__ == '__main__': - main() - Deleted: /py/dist/py/path/pypath/pypath_test.py ============================================================================== --- /py/dist/py/path/pypath/pypath_test.py Thu Oct 14 22:00:10 2004 +++ (empty file) @@ -1,168 +0,0 @@ -import sys, os -from py import path, test -from py.__impl__.path.pypath import pypath - -class TestPyPath: - root = path.py(pypath.__name__) - - def test_join(self): - p = self.root.join('PyPath') - obj = p.resolve() - assert obj is path.py - - def test_listdir_module(self): - l = self.root.listdir() - basenames = [x.basename for x in l] - dlist = dir(pypath) - for name in dlist: - assert name in basenames - for name in basenames: - assert name in dlist - - def test_listdir_class(self): - l = self.root.join('PyPath').listdir() - basenames = [x.basename for x in l] - dlist = dir(pypath.PyPath) - for name in dlist: - assert name in basenames - for name in basenames: - assert name in dlist - - def listobj(self): - l = self.root.listobj(basestarts='path') - assert len(l) == 1 - assert l[0] == path - - def test_visit(self): - PyPath = pypath.PyPath - l = list(self.root.visit(path.checker(basename='PyPath'))) - assert len(l) == 1 - obj = l[0] - assert str(obj).endswith('path.pypath.pypath.PyPath') - assert obj.resolve() is PyPath - - def test_visit_fnmatch(self): - PyPath = pypath.PyPath - l = list(self.root.visit('PyPath')) - assert len(l) == 1 - obj = l[0] - assert str(obj).endswith('path.pypath.pypath.PyPath') - assert obj.resolve() is PyPath - - def test_specified_base_empty_path(self): - p = path.py('', ns=ExampleClass) - l = p.listdir() - basenames = [x.basename for x in l] - assert 'testattr' in basenames - - def test_join_from_empty(self): - p = path.py('') - n = p.join('tokenize') - assert str(n) == 'tokenize' - - p = path.py('', ns=os) - n = p.join('getlogin') - assert str(n) == 'getlogin' - - def test_unspecifiedpypath_lists_modules(self): - p = path.py('') - l = p.listdir() - for i in l: - assert '.' not in str(i) - - for j in sys.modules: - for i in l: - if j.startswith(str(i)): - break - else: - self.fail("%s is not in sys.modules") - - def test_main_works(self): - m = path.py('__main__') - import __main__ - assert m.resolve() is __main__ - - def test_relto(self): - m1 = path.py('a.b.c.d') - m2 = path.py('a.b') - m3 = path.py('') - res = m1.relto(m2) - assert str(res) == 'c.d' - assert m2.relto(m3) == str(m2) - - def test_basename(self): - m1 = path.py('a.b.hello') - assert m1.basename == 'hello' - assert m1.check(basename='hello') - assert not m1.check(basename='nono') - assert m1.check(basestarts='he') - assert not m1.check(basestarts='42') - - def test_dirpath(self): - m1 = path.py('a.b.hello') - m2 = path.py('a.b') - m3 = path.py('a') - m4 = path.py() - assert m1.dirpath() == m2 - assert m2.dirpath() == m3 - assert m3.dirpath() == m4 - - def test_function(self): - class A: - i = 3 - def func(self): - pass - p = path.py('func', ns=A) - assert p.check(func=1) - p = path.py('i', ns=A) - assert p.check(func=0) - - def test_hashing_equality(self): - x = path.py('os.path') - y = path.py('os.path') - assert x == y - assert hash(x) == hash(y) - - def test_parents(self): - x = path.py('os.path.abspath') - l = x.parts() - assert len(l) == 4 - assert path.py('') == l[0] - assert path.py('os') == l[1] - assert path.py('os.path') == l[2] - assert path.py('os.path.abspath') == l[3] - -class TestEval: - disabled = True - def test_funccall(self): - p = path.py('os.path.join("a", "b")') - s = p.resolve() - assert s == os.path.join("a", "b") - - def test_invalid1(self): - p = path.py('os.path.qwe("a", "b")') - s = test.raises(path.NotFound, "p.resolve()") - - def test_syntaxerror(self): - p = path.py('os.path.qwe("a", ') - s = test.raises(ValueError, "p.resolve()") - -class TestErrors: - def test_FileNotFound(self): - p = path.py('somesuch') - test.raises(path.NotFound, p.resolve) - p = path.py('email.whatever') - test.raises(path.NotFound, p.resolve) - - def test_attributeerror(self): - p = path.py('os.path.qabspath') - test.raises(path.NotFound, p.resolve) - - #def test_ImportError(): - # p = path.py('__std.utest.test.data.failingimport.someattr') - # utest.raises(ImportError, p.resolve) - -class ExampleClass: - testattr = 1 - -test.main() Deleted: /py/dist/py/path/svn/command_test.py ============================================================================== --- /py/dist/py/path/svn/command_test.py Thu Oct 14 22:00:10 2004 +++ (empty file) @@ -1,23 +0,0 @@ -import sys, os -import py -from py.__impl__.path.test import svncommonfs -from py.__impl__.path.svnwc.command_test import getrepowc - -class TestSvnCommandPath(svncommonfs.CommonCommandAndBindingTests): - def __init__(self): - repo, wc = getrepowc() - self.root = py.path.svnurl(repo) - - def xtest_copy_file(self): - raise py.test.run.Skipped(msg="XXX fix svnurl first") - - def xtest_copy_dir(self): - raise py.test.run.Skipped(msg="XXX fix svnurl first") - - def XXXtest_info_log(self): - url = self.root.join("samplefile") - res = url.log(rev_start=1155, rev_end=1155, verbose=True) - assert res[0].revision == 1155 and res[0].author == "jum" - from time import gmtime - t = gmtime(res[0].date) - assert t.tm_year == 2003 and t.tm_mon == 7 and t.tm_mday == 17 Copied: py/dist/py/path/svn/test_command.py (from r6934, py/dist/py/path/svn/command_test.py) ============================================================================== --- py/dist/py/path/svn/command_test.py (original) +++ py/dist/py/path/svn/test_command.py Thu Oct 14 22:00:10 2004 @@ -1,7 +1,7 @@ import sys, os import py from py.__impl__.path.test import svncommonfs -from py.__impl__.path.svnwc.command_test import getrepowc +from py.__impl__.path.svnwc.test_command import getrepowc class TestSvnCommandPath(svncommonfs.CommonCommandAndBindingTests): def __init__(self): Deleted: /py/dist/py/path/svnwc/command_test.py ============================================================================== --- /py/dist/py/path/svnwc/command_test.py Thu Oct 14 22:00:10 2004 +++ (empty file) @@ -1,209 +0,0 @@ -import py -from py.__impl__.path.test.svncommonfs import CommonSvnTests -from py.__impl__.path.test.commonfs import setuptestfs - -# make a wc directory out of a given root url -# cache previously obtained wcs! -# -def getrepowc(): - repo = py.test.config.tmpdir / 'path' / 'repo' - wcdir = py.test.config.tmpdir / 'path' / 'wc' - if not repo.check(): - assert not wcdir.check() - repo.ensure(dir=1) - try: - py.process.cmdexec('svnadmin create %s' % repo) - except py.process.cmdexec.Error: - repo.remove() - raise py.test.run.Skipped(msg='could not create temporary svn test repository') - wcdir.ensure(dir=1) - print "created svn repository", repo - wc = py.path.svnwc(wcdir, url='file://%s' % repo) - wc.checkout() - print "checked out new repo into", wc - setuptestfs(wc) - wc.join('samplefile').propset('svn:eol-style', 'native') - wc.commit("testrepo setup rev 1") - wc.ensure('anotherfile').write('hello') - wc.commit('second rev') - wc.join('anotherfile').write('world') - wc.commit('third rev') - else: - print "using repository at %s" % repo - wc = py.path.svnwc(wcdir) - return ("file://%s" % repo, wc) - -class TestWCSvnCommandPath(CommonSvnTests): - def __init__(self): - repo, self.root = getrepowc() - - def test_status_attributes_simple(self): - def assert_nochange(p): - s = p.status() - assert not s.modified - assert not s.prop_modified - assert not s.added - assert not s.deleted - - dpath = self.root.join('sampledir') - assert_nochange(self.root.join('sampledir')) - assert_nochange(self.root.join('samplefile')) - - def test_status_added(self): - nf = self.root.join('newfile') - nf.write('hello') - nf.add() - try: - s = nf.status() - assert s.added - assert not s.modified - assert not s.prop_modified - finally: - nf.revert() - - def test_status_change(self): - nf = self.root.join('samplefile') - try: - nf.write(nf.read() + 'change') - s = nf.status() - assert not s.added - assert s.modified - assert not s.prop_modified - finally: - nf.revert() - - def test_status_added_ondirectory(self): - sampledir = self.root.join('sampledir') - try: - t2 = sampledir.mkdir('t2') - t1 = t2.join('t1') - t1.write('test') - t1.add() - s = sampledir.status(rec=1) - assert t1 in s.added - assert t2 in s.added - finally: - t2.revert(rec=1) - t2.localpath.remove(rec=1) - - def test_status_unknown(self): - t1 = self.root.join('un1') - try: - t1.write('test') - s = self.root.status() - assert t1 in s.unknown - finally: - t1.localpath.remove() - - def test_status_unchanged(self): - r = self.root - s = self.root.status(rec=1) - assert r.join('samplefile') in s.unchanged - assert r.join('sampledir') in s.unchanged - assert r.join('sampledir/otherfile') in s.unchanged - - def test_status_update(self): - r = self.root - try: - r.update(rev=1) - s = r.status(updates=1, rec=1) - assert r.join('anotherfile') in s.update_available - assert len(s.update_available) == 1 - finally: - r.update() - - def test_diff(self): - p = self.root / 'anotherfile' - out = p.diff(rev=2) - assert out.find('hello') != -1 - - def test_join_abs(self): - s = str(self.root.localpath) - n = self.root.join(s, abs=1) - assert self.root == n - - def test_join_abs2(self): - assert self.root.join('samplefile', abs=1) == self.root.join('samplefile') - - def test_str_gives_localpath(self): - assert str(self.root) == str(self.root.localpath) - - def test_versioned(self): - assert self.root.check(versioned=1) - assert self.root.join('samplefile').check(versioned=1) - assert not self.root.join('notexisting').check(versioned=1) - notexisting = self.root.join('hello').localpath - try: - notexisting.write("") - assert self.root.join('hello').check(versioned=0) - finally: - notexisting.remove() - - def test_properties(self): - try: - self.root.propset('gaga', 'this') - assert self.root.propget('gaga') == 'this' - assert self.root in self.root.status().prop_modified - assert 'gaga' in self.root.proplist() - assert self.root.proplist()['gaga'] == 'this' - - finally: - self.root.propdel('gaga') - - def test_proplist_recursive(self): - s = self.root.join('samplefile') - s.propset('gugu', 'that') - try: - p = self.root.proplist(rec=1) - assert self.root / 'samplefile' in p - finally: - s.propdel('gugu') - - def test_long_properties(self): - value = """ - vadm:posix : root root 0100755 - Properties on 'chroot/dns/var/bind/db.net.xots': - """ - try: - self.root.propset('gaga', value) - backvalue = self.root.propget('gaga') - assert backvalue == value - #assert len(backvalue.split('\n')) == 1 - finally: - self.root.propdel('gaga') - - - def test_ensure(self): - newpath = self.root.ensure('a', 'b', 'c') - try: - assert newpath.check(exists=1, versioned=1) - finally: - self.root.join('a').remove(force=1) - - def test_not_versioned(self): - p = self.root.localpath.mkdir('whatever') - f = self.root.localpath.ensure('testcreatedfile') - try: - assert self.root.join('whatever').check(versioned=0) - assert self.root.join('testcreatedfile').check(versioned=0) - assert not self.root.join('testcreatedfile').check(versioned=1) - finally: - p.remove(rec=1) - f.remove() - - #def test_log(self): - # l = self.root.log() - # assert len(l) == 3 # might need to be upped if more tests are added - -class XTestWCSvnCommandPathSpecial: - - rooturl = 'http://codespeak.net/svn/py.path/trunk/dist/py.path/test/data' - #def test_update_none_rev(self): - # path = tmpdir.join('checkouttest') - # wcpath = newpath(xsvnwc=str(path), url=self.rooturl) - # try: - # wcpath.checkout(rev=2100) - # wcpath.update() - # assert wcpath.info().rev > 2100 - # finally: - # wcpath.localpath.remove(rec=1) Copied: py/dist/py/path/test_api.py (from r6931, py/dist/py/path/api_test.py) ============================================================================== --- py/dist/py/path/api_test.py (original) +++ py/dist/py/path/test_api.py Thu Oct 14 22:00:10 2004 @@ -1,5 +1,5 @@ from py import path, test -from py.__impl__.path.svnwc.command_test import getrepowc +from py.__impl__.path.svnwc.test_command import getrepowc class TestAPI: def __init__(self): Deleted: /py/dist/py/process/cmdexec_test.py ============================================================================== --- /py/dist/py/process/cmdexec_test.py Thu Oct 14 22:00:10 2004 +++ (empty file) @@ -1,26 +0,0 @@ -from py import test -from py.process import cmdexec - -class Test_exec_cmd: - def test_simple(self): - out = cmdexec('echo hallo') - assert out.strip() == 'hallo' - - def test_simple_error(self): - test.raises (cmdexec.Error, cmdexec, 'exit 1') - - def test_simple_error_exact_status(self): - try: - cmdexec('exit 1') - except cmdexec.Error, e: - assert e.status == 1 - - def test_err(self): - try: - cmdexec('echoqweqwe123 hallo') - raise AssertionError, "command succeeded but shouldn't" - except cmdexec.Error, e: - assert hasattr(e, 'err') - assert hasattr(e, 'out') - assert e.err or e.out -test.main() Deleted: /py/dist/py/test/collect_test.py ============================================================================== --- /py/dist/py/test/collect_test.py Thu Oct 14 22:00:10 2004 +++ (empty file) @@ -1,84 +0,0 @@ -from __future__ import generators -from py import test, path -from py.magic import autopath ; autopath = autopath() -testdir = autopath.dirpath('test') -assert testdir.check(dir=1) -datadir = testdir / 'data' - -def test_failing_import_execfile(): - fn = datadir / 'failingimport.py' - l = list(test.collect.Execfile(fn)) - assert l - ex, = l - assert issubclass(ex.excinfo[0], ImportError) - -def test_failing_import_directory(): - class MyDirectory(test.collect.Directory): - fil = path.checker(basestarts="testspecial_", ext='.py') - l = list(MyDirectory(datadir)) - assert len(l) == 1 - assert isinstance(l[0], test.collect.Execfile) - l2 = list(l[0]) - assert l2 - exc = l2[0] - assert isinstance(exc, test.collect.Error) - assert issubclass(exc.excinfo[0], ImportError) - -def test_execfile_file_not_found(): - fn = testdir.join('nada','no') - l = list(test.collect.Execfile(fn)) - assert len(l) == 1 - assert isinstance(l[0], test.collect.Error) - import traceback - print traceback.print_exception(*l[0].excinfo) - assert isinstance(l[0].excinfo[1], (IOError, OSError)) - -def test_syntax_error_in_module(): - modpath = 'py.__impl__.utest.test.data.syntax_error.whatever' - l2 = list(test.collect.Module(modpath)) - assert len(l2) == 1 - assert isinstance(l2[0], test.collect.Error) - assert issubclass(l2[0].excinfo[0], path.Invalid) - -def test_disabled_class(): - class x: - class y: - disabled = True - def test_method(self): pass - l = list(test.collect.Class(path.py('', ns=x))) - assert not l - -class TestCustomCollector: - def test_custom_collect(self): - l = list(test.collect.Execfile(datadir.join('Collector.py'))) - assert len(l) == 3 - for unit in l: - assert isinstance(unit, test.Unit) - #for x in l2: - # assert isinstance(x, Unit) - # x.execute() - -class Testsomeclass: - disabled = True - def test_something(): - raise ValueError - -l = [] -def test_1(): - l.append(1) -def test_2(): - l.append(2) -def test_3(): - assert l == [1,2] -class Testmygroup: - reslist = [] - def test_1(self): - self.reslist.append(1) - def test_2(self): - self.reslist.append(2) - def test_3(self): - self.reslist.append(3) - def test_4(self): - assert self.reslist == [1,2,3] - -test.main() Deleted: /py/dist/py/test/compat_test.py ============================================================================== --- /py/dist/py/test/compat_test.py Thu Oct 14 22:00:10 2004 +++ (empty file) @@ -1,53 +0,0 @@ -from __future__ import generators -from py import test, magic - -class TestCompatTestCaseSetupSemantics(test.compat.TestCase): - globlist = [] - - def setUp(self): - self.__dict__.setdefault('l', []).append(42) - self.globlist.append(self) - - def tearDown(self): - self.l.pop() - - def test_issetup(self): - l = self.l - assert len(l) == 1 - assert l[-1] == 42 - self.checkmultipleinstances() - - def test_issetup2(self): - l = self.l - assert len(l) == 1 - assert l[-1] == 42 - self.checkmultipleinstances() - - def checkmultipleinstances(self): - for x,y in zip(self.globlist, self.globlist[1:]): - assert x is not y - -class TestCompatAssertions(test.compat.TestCase): - nameparamdef = { - 'failUnlessEqual,assertEqual,assertEquals': ('1, 1', '1, 0'), - 'assertNotEquals,failIfEqual': ('0, 1', '0,0'), - 'failUnless,assert_': ('1', 'None'), - 'failIf': ('0', '1'), - } - - sourcelist = [] - for names, (paramok, paramfail) in nameparamdef.items(): - for name in names.split(','): - source = """ - def test_%(name)s(self): - self.%(name)s(%(paramok)s) - #self.%(name)s(%(paramfail)s) - - def test_%(name)s_failing(self): - self.assertRaises(test.run.Failed, - self.%(name)s, %(paramfail)s) - """ % locals() - co = magic.dyncode.compile2(source) - exec co - -test.main() Deleted: /py/dist/py/test/config_test.py ============================================================================== --- /py/dist/py/test/config_test.py Thu Oct 14 22:00:10 2004 +++ (empty file) @@ -1,30 +0,0 @@ -from __future__ import generators -from py import test -from py.test import config - -class MyClass: - def getoptions(self): - yield config.Option('-v', action="count", dest="verbose", help="verbose") - -def xtest_verbose(): - obj = MyClass() - args = config.parseargs(['-v', 'hello'], obj) - assert args == ['hello'] - assert hasattr(obj, 'option') - assert hasattr(obj.option, 'verbose') - assert obj.option.verbose - -def xtest_verbose_default(): - obj = MyClass() - args = config.parseargs(['hello'], obj) - assert args, ['hello'] - assert hasattr(obj, 'option') - assert hasattr(obj.option, 'verbose') - assert not obj.option.verbose - -def test_tmpdir(): - d1 = config.tmpdir - d2 = config.tmpdir - assert d1 == d2 - -test.main() Deleted: /py/dist/py/test/raises_test.py ============================================================================== --- /py/dist/py/test/raises_test.py Thu Oct 14 22:00:10 2004 +++ (empty file) @@ -1,17 +0,0 @@ -from py import test - -def somefunc(x, y): - assert x == y - -class TestClass: - def test_raises(self): - test.raises(ValueError, "int('qwe')") - - def test_raises_syntax_error(self): - test.raises(SyntaxError, "qwe qwe qwe") - - def test_raises_function(self): - test.raises(ValueError, int, 'hello') - - -test.main() Deleted: /py/dist/py/test/report/memo_test.py ============================================================================== --- /py/dist/py/test/report/memo_test.py Thu Oct 14 22:00:10 2004 +++ (empty file) @@ -1,23 +0,0 @@ -from py import test, path - -datadir = test.config.tmpdir - -#def test_equal_should_raise(): -# check.equal(1,2) - -#class MyUnit(collect.Auto, collect.Unit): -# def execute(self, runner): -# try: -# -def test_memoreporter(): - reporter = test.MemoReporter() - p = datadir.join('memoimport.py') - p.write('raise IOError') - collector = test.collect.Execfile(p) - #main(collector=collector, reporter=reporter) - #collect_errors = reporter.getlist(collect.Error) - #assert len(collect_errors) == 1 - ##print collect_errors - -if __name__=='__main__': - test.main() Deleted: /py/dist/py/test/tool/outerrcapture_test.py ============================================================================== --- /py/dist/py/test/tool/outerrcapture_test.py Thu Oct 14 22:00:10 2004 +++ (empty file) @@ -1,26 +0,0 @@ -import sys -from py import test -from py.__impl__.test.tool.outerrcapture import SimpleOutErrCapture - -def test_capturing_simple(): - cap = SimpleOutErrCapture() - print "hello world" - print >>sys.stderr, "hello error" - out, err = cap.reset() - assert out == "hello world\n" - assert err == "hello error\n" - -def test_capturing_error(): - cap = SimpleOutErrCapture() - print "hello" - cap.reset() - test.raises(AttributeError, "cap.reset()") - -def test_capturing_error_recursive(): - cap = SimpleOutErrCapture() - cap2 = SimpleOutErrCapture() - print "hello" - cap2.reset() - test.raises(AttributeError, "cap2.reset()") - -test.main() From hpk at codespeak.net Fri Oct 15 00:02:18 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 15 Oct 2004 00:02:18 +0200 (MEST) Subject: [py-svn] r6941 - in py/dist/py: . path test Message-ID: <20041014220218.B98EA5A165@thoth.codespeak.net> Author: hpk Date: Fri Oct 15 00:02:18 2004 New Revision: 6941 Modified: py/dist/py/__init__.py py/dist/py/path/test_api.py py/dist/py/test/test_collect.py Log: restricted the exported namespace some more (in the end plain modules should not be exportable at all). Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Fri Oct 15 00:02:18 2004 @@ -11,12 +11,16 @@ 'path.NoDirectory': './path/error.NoDirectory', 'path.Invalid': './path/error.Invalid', - 'test.collect': './test/collect', + 'test.collect.Collector': './test/collect.Collector', + 'test.collect.Directory': './test/collect.Directory', + 'test.collect.Execfile': './test/collect.Execfile', + 'test.collect.PyCollector': './test/collect.PyCollector', + 'test.collect.Error': './test/collect.Error', 'test.run': './test/run', 'test.main': './test/cmdline.main', 'test.raises': './test/raises.raises', 'test.config': './test/config.config', - 'test.compat': './test/compat', + 'test.compat.TestCase': './test/compat.TestCase', 'test.Unit': './test/run.Unit', 'test.Option': './test/tool/optparse.Option', 'test.TextReporter': './test/report/text/reporter.TextReporter', Modified: py/dist/py/path/test_api.py ============================================================================== --- py/dist/py/path/test_api.py (original) +++ py/dist/py/path/test_api.py Fri Oct 15 00:02:18 2004 @@ -24,7 +24,7 @@ #assert p.std.path() - def test_xlocal(self): + def test_local(self): p = path.local() assert hasattr(p, 'atime') assert hasattr(p, 'setmtime') Modified: py/dist/py/test/test_collect.py ============================================================================== --- py/dist/py/test/test_collect.py (original) +++ py/dist/py/test/test_collect.py Fri Oct 15 00:02:18 2004 @@ -1,43 +1,46 @@ from __future__ import generators from py import test, path from py.magic import autopath ; autopath = autopath() +from py.__impl__.test import collect + testdir = autopath.dirpath('test') assert testdir.check(dir=1) datadir = testdir / 'data' + def test_failing_import_execfile(): fn = datadir / 'failingimport.py' - l = list(test.collect.Execfile(fn)) + l = list(collect.Execfile(fn)) assert l ex, = l assert issubclass(ex.excinfo[0], ImportError) def test_failing_import_directory(): - class MyDirectory(test.collect.Directory): + class MyDirectory(collect.Directory): fil = path.checker(basestarts="testspecial_", ext='.py') l = list(MyDirectory(datadir)) assert len(l) == 1 - assert isinstance(l[0], test.collect.Execfile) + assert isinstance(l[0], collect.Execfile) l2 = list(l[0]) assert l2 exc = l2[0] - assert isinstance(exc, test.collect.Error) + assert isinstance(exc, collect.Error) assert issubclass(exc.excinfo[0], ImportError) def test_execfile_file_not_found(): fn = testdir.join('nada','no') - l = list(test.collect.Execfile(fn)) + l = list(collect.Execfile(fn)) assert len(l) == 1 - assert isinstance(l[0], test.collect.Error) + assert isinstance(l[0], collect.Error) import traceback print traceback.print_exception(*l[0].excinfo) assert isinstance(l[0].excinfo[1], (IOError, OSError)) def test_syntax_error_in_module(): modpath = 'py.__impl__.utest.test.data.syntax_error.whatever' - l2 = list(test.collect.Module(modpath)) + l2 = list(collect.Module(modpath)) assert len(l2) == 1 - assert isinstance(l2[0], test.collect.Error) + assert isinstance(l2[0], collect.Error) assert issubclass(l2[0].excinfo[0], path.Invalid) def test_disabled_class(): @@ -45,12 +48,12 @@ class y: disabled = True def test_method(self): pass - l = list(test.collect.Class(path.py('', ns=x))) + l = list(collect.Class(path.py('', ns=x))) assert not l class TestCustomCollector: def test_custom_collect(self): - l = list(test.collect.Execfile(datadir.join('Collector.py'))) + l = list(collect.Execfile(datadir.join('Collector.py'))) assert len(l) == 3 for unit in l: assert isinstance(unit, test.Unit) From hpk at codespeak.net Fri Oct 15 00:48:34 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 15 Oct 2004 00:48:34 +0200 (MEST) Subject: [py-svn] r6943 - in py/dist/py: . execnet/bin magic test Message-ID: <20041014224834.57DCF5A165@thoth.codespeak.net> Author: hpk Date: Fri Oct 15 00:48:33 2004 New Revision: 6943 Added: py/dist/py/pytest.conf Removed: py/dist/py/utest.conf Modified: py/dist/py/execnet/bin/startserver.py py/dist/py/magic/dyncode.py py/dist/py/magic/test_invoke.py py/dist/py/test/config.py Log: - renamed utest.conf to pytest.conf - fixes for python2.2 - reordered py.test options and removed some more rarely needed short options Modified: py/dist/py/execnet/bin/startserver.py ============================================================================== --- py/dist/py/execnet/bin/startserver.py (original) +++ py/dist/py/execnet/bin/startserver.py Fri Oct 15 00:48:33 2004 @@ -30,7 +30,7 @@ source = eval(source) if not source: break - co = compile(source, source, 'exec') + co = compile(source+'\n', source, 'exec') print progname, 'compiled source, executing' try: exec co in g Modified: py/dist/py/magic/dyncode.py ============================================================================== --- py/dist/py/magic/dyncode.py (original) +++ py/dist/py/magic/dyncode.py Fri Oct 15 00:48:33 2004 @@ -11,6 +11,7 @@ import re from linecache import getline as linecache_getline from linecache import getlines as linecache_getlines +from inspect import findsource as inspect_findsource import linecache import __builtin__ from __builtin__ import compile as oldcompile @@ -61,7 +62,7 @@ import parser try: parser.suite(source) - except SyntaxError: + except (parser.ParserError, SyntaxError): return False else: return True @@ -114,7 +115,7 @@ #print "making dynfilename", filename try: #print "compiling source:", repr(source) - co = oldcompile(source, filename, mode, flag) + co = oldcompile(source+'\n', filename, mode, flag) except SyntaxError, ex: # re-represent syntax errors from parsing python strings newex = SyntaxError('\n'.join([ @@ -137,11 +138,13 @@ magic.patch(linecache, 'getline', getline) magic.patch(linecache, 'getlines', getlines) magic.patch(__builtin__, 'compile', compile) + magic.patch(inspect, 'findsource', findsource) def revoke(): magic.revert(linecache, 'getline') magic.revert(linecache, 'getlines') magic.revert(__builtin__, 'compile') + magic.revert(inspect, 'findsource') def tbinfo(tb, index = -1): """ return an (filename:lineno, linecontent) tuple for the given @@ -190,7 +193,7 @@ def findsource(obj): try: - return inspect.findsource(obj) + return inspect_findsource(obj) except (TypeError, IOError): code = getcode(obj) filename = obj._co_filename @@ -235,7 +238,7 @@ def findsource(obj): try: - return inspect.findsource(obj) + return inspect_findsource(obj) except IOError: obj = getcode(obj) filename = obj.co_filename Modified: py/dist/py/magic/test_invoke.py ============================================================================== --- py/dist/py/magic/test_invoke.py (original) +++ py/dist/py/magic/test_invoke.py Fri Oct 15 00:48:33 2004 @@ -3,6 +3,7 @@ import inspect def check_dyncode(): + print compile co = compile('x=3', 'bogus', 'exec') s = inspect.getfile(co) assert s Added: py/dist/py/pytest.conf ============================================================================== --- (empty file) +++ py/dist/py/pytest.conf Fri Oct 15 00:48:33 2004 @@ -0,0 +1,10 @@ +# standard options (modified from cmdline) +pythonexecutable = 'python2.3' + +verbose = 0 +nocapture = False +collectonly = False +exitfirstproblem = False +fulltrace = False +showlocals = False +nomagic = False Modified: py/dist/py/test/config.py ============================================================================== --- py/dist/py/test/config.py (original) +++ py/dist/py/test/config.py Fri Oct 15 00:48:33 2004 @@ -8,7 +8,7 @@ # # config file handling (utest.conf) # -configbasename = 'utest.conf' +configbasename = 'pytest.conf' class Config: def __init__(self): @@ -17,31 +17,31 @@ def getoptions(self): Option = test.Option - return ('utest standard options', [ - Option('', '--collectonly', - action="store_true", dest="collectonly", default=False, - help="only collect tests, don't execute them. "), - Option('-S', '--nocapture', - action="store_true", dest="nocapture", default=False, - help="disable catching of sys.pyout/pyerr output."), + return ('py.test standard options', [ Option('-v', '--verbose', action="count", dest="verbose", default=0, help="increase verbosity"), Option('-x', '--exitfirst', action="store_true", dest="exitfirstproblem", default=False, help="exit instantly on first error or failed test."), - Option('-f', '--fulltrace', - action="store_true", dest="fulltrace", default=False, - help="Don't try to cut any tracebacks (default is to cut)"), + Option('-S', '--nocapture', + action="store_true", dest="nocapture", default=False, + help="disable catching of sys.stdout/stderr output."), Option('-l', '--showlocals', action="store_true", dest="showlocals", default=False, help="show locals in tracebacks (disabled by default)"), - Option('-n', '--nomagic', + Option('', '--fulltrace', + action="store_true", dest="fulltrace", default=False, + help="Don't try to cut any tracebacks (default is to cut)"), + Option('', '--nomagic', action="store_true", dest="nomagic", default=False, help="don't invoke magic to e.g. beautify failing/error statements."), Option('', '--pdb', action="store_true", dest="usepdb", default=False, help="Start pdb (the Python debugger) on errors."), + Option('', '--collectonly', + action="store_true", dest="collectonly", default=False, + help="only collect tests, don't execute them. "), ]) def _gettmpdir(self, sessiondir=[]): Deleted: /py/dist/py/utest.conf ============================================================================== --- /py/dist/py/utest.conf Fri Oct 15 00:48:33 2004 +++ (empty file) @@ -1,10 +0,0 @@ -# standard options (modified from cmdline) -#pythonexecutable = 'python2.2' - -verbose = 0 -nocapture = False -collectonly = False -exitfirstproblem = False -fulltrace = False -showlocals = False -nomagic = False From hpk at codespeak.net Fri Oct 15 00:55:33 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 15 Oct 2004 00:55:33 +0200 (MEST) Subject: [py-svn] r6944 - py/dist/py/path/fspy Message-ID: <20041014225533.B1D225A165@thoth.codespeak.net> Author: hpk Date: Fri Oct 15 00:55:33 2004 New Revision: 6944 Added: py/dist/py/path/fspy/ - copied from r6931, py/dist/py/path/pypath/ py/dist/py/path/fspy/test_pypath.py - copied unchanged from r6940, py/dist/py/path/pypath/test_pypath.py Log: smooth transition towards a more useful "fspy" path. It is to be an address of a python object, consisting of a full Path and a python-module path ... From hpk at codespeak.net Fri Oct 15 02:47:30 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 15 Oct 2004 02:47:30 +0200 (MEST) Subject: [py-svn] r6945 - in py/dist/py: . path/fspy Message-ID: <20041015004730.B510F5A165@thoth.codespeak.net> Author: hpk Date: Fri Oct 15 02:47:30 2004 New Revision: 6945 Added: py/dist/py/path/fspy/fspy.py - copied, changed from r6944, py/dist/py/path/fspy/pypath.py py/dist/py/path/fspy/inc_test_fspy.py py/dist/py/path/fspy/test_fspy.py - copied, changed from r6944, py/dist/py/path/fspy/test_pypath.py Removed: py/dist/py/path/fspy/pypath.py py/dist/py/path/fspy/test_pypath.py Modified: py/dist/py/__init__.py Log: the fspy path is moving along nicely ... (much more clean than the previous but still existing py.path.pypath) Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Fri Oct 15 02:47:30 2004 @@ -6,6 +6,7 @@ 'path.svnurl': './path/svn/command.SvnCommandPath', 'path.svnwc': './path/svnwc/command.SvnWCCommandPath', 'path.py': './path/pypath/pypath.PyPath', + 'path.fspy': './path/fspy/fspy.Fspy', 'path.NotFound': './path/error.FileNotFound', 'path.Denied': './path/error.PermissionDenied', 'path.NoDirectory': './path/error.NoDirectory', Copied: py/dist/py/path/fspy/fspy.py (from r6944, py/dist/py/path/fspy/pypath.py) ============================================================================== --- py/dist/py/path/fspy/pypath.py (original) +++ py/dist/py/path/fspy/fspy.py Fri Oct 15 02:47:30 2004 @@ -5,6 +5,8 @@ for the first stable release! """ from __future__ import generators +import py + from py import path from py.__impl__.path import common @@ -32,45 +34,55 @@ rootns = RootNS() -class PyPath(common.PathBase): - """ a path abstraction for pointing to python objects. """ +class Fspy(common.PathBase): + """ a path abstraction addressing python objects in a file system. """ sep = '.' - def __new__(cls, modpath='', ns=rootns): - if isinstance(modpath, PyPath): - assert ns is rootns - return modpath + def __new__(cls, fspath='', modpath=''): assert isinstance(modpath, str) self = object.__new__(cls) self.modpath = modpath - self.ns = ns + if isinstance(fspath, str): + fspath = py.path.local(fspath) + self.fspath = fspath return self - def __str__(self): - return self.modpath - def __hash__(self): - return hash(hash(self.modpath) + hash(self.ns)) + return hash((self.fspath, self.modpath)) def __repr__(self): - if self.ns is rootns: - return 'py(%r)' % (self.modpath, ) - return 'py(%r, %r)' % (self.modpath, self.ns) + #if self.ns is rootns: + # return 'py(%r)' % (self.modpath, ) + return 'fspy(%r, %r)' % (self.fspath, self.modpath) + + def __str__(self): + return str(self.fspath.new(ext=self.modpath)) def join(self, *args): for arg in args: if not isinstance(arg, str): - raise TypeError, "not only strings in %r" % args + raise TypeError, "non-strings not allowed in %r" % args modpath = [x.strip('.') for x in ((self.modpath,)+args) if x] modpath = self.sep.join(modpath) - return self.__class__(modpath, self.ns) + return self.__class__(self.fspath, modpath) def dirpath(self): modpath = self.modpath.split(self.sep) [:-1] modpath = self.sep.join(modpath) - return self.__class__(modpath, self.ns) + return self.__class__(self.fspath, modpath) - def new(self): - return self.__class__(self.modpath, self.ns) + def new(self, **kw): + """ create a modified version of this path. + the following keyword arguments modify various path parts: + modpath substitute module path + """ + cls = self.__class__ + if 'modpath' in kw: + return cls(self.fspath, kw['modpath']) + if 'basename' in kw: + i = self.modpath.rfind('.') + if i != -1: + return cls(self.fspath, self.modpath[i+1:] + kw['basename']) + return cls(self.fspath, self.modpath) def get(self, spec): l = [] @@ -86,59 +98,35 @@ else: return l - def resolve(self): - """return the object underlying the path. - - This works by first trying to import the largest possible - starting part of the path and continuing with AttributeAccesses. - Resolving an non existing path raises a std.path.error.FileNotFound - error. Resolving an unknown method on an existing module raises an - AttributeError. All other Exceptions usually indicate a problem while - executing a module code object. - """ + def getmodule(self): + modname = repr(self.fspath) + # str(self.fspath.new(ext='')).replace(self.fspath.sep, '_') try: - return self._obj - except AttributeError: - pass - if hasattr(self, '_filepath'): - # XXX EVIL NAME HACK - name = self.modpath.replace('.', '_') - mod = imp.load_source("some bogus name", str(self._filepath)) - mod.__name__ = name - self._obj = mod - return mod - - rest = filter(None, self.modpath.split(self.sep)) - target = self.ns - if target is rootns: - i = 0 - while i < len(rest): - modpath = self.sep.join(rest[:i+1]) - try: - target = __import__(modpath, None, None, ['qwe']) - #print "could import %r" % modpath - i += 1 - except (SyntaxError, ImportError): - if modpath in sys.modules: - # file found but import failed - del sys.modules[modpath] - raise - # former replace.py DANGER: - if target is rootns or hasattr(target, '__path__'): - raise path.NotFound, "modpath %r not found" % modpath - break - rest = rest[i:] + return sys.modules[modname] + except KeyError: + #print "importing", modname + try: + mod = imp.load_source(modname, str(self.fspath)) + except: + self.fspath._except(sys.exc_info()) + mod.__name__ = modname + sys.modules[modname] = mod + return mod - for name in rest: + def resolve(self): + """return the python object belonging to path. """ + module = self.getmodule() + rest = filter(None, self.modpath.split('.')) + target = module + for name in rest: try: target = getattr(target, name) except AttributeError: - raise path.NotFound("%r has no subpath %r" %(target, name)) - self._obj = target - return self._obj + raise path.NotFound(str(self)) + return target def relto(self, otherpath): - if self.ns is otherpath.ns: + if self.fspath == otherpath.fspath: if self.modpath.startswith(otherpath.modpath): s = self.modpath[len(otherpath.modpath):] return s.lstrip(self.sep) @@ -157,34 +145,23 @@ else: raise TypeError, "cannot take filter and keyword arguments" obj = self.resolve() - if obj is rootns: - d = {} - for x in sys.modules: - name = x.split('.')[0] - d[name] = 1 - l = [self.join(x) for x in d if not fil or fil(x)] - else: - l = [] - for name in dir(obj): - sub = self.join(name) - sub._obj = getattr(obj, name) # XXX performance hack - if not fil or fil(sub): - l.append(sub) + #if obj is rootns: + # d = {} + # for x in sys.modules: + # name = x.split('.')[0] + # d[name] = 1 + # l = [self.join(x) for x in d if not fil or fil(x)] + #else: + l = [] + for name in dir(obj): + sub = self.join(name) + if not fil or fil(sub): + l.append(sub) #print "listdir(%r) -> %r" %(self, l) #print "listdir on", repr(self) return l - def getmodule(self): - obj = self.resolve() - #file = self.getfile() - #print "getmodule(%r)" % repr(obj)[:100] - #try: - # print inspect.modulesbyfile[file] - #except KeyError: - # print "could not find file:", file - return inspect.getmodule(obj) - def setfile(self, filepath): self._filepath = filepath @@ -231,50 +208,39 @@ def visit(self, fil=None, rec=None, seen=None): # XXX gigantic hack for convenience of utest-collectors if seen is None: - seen = {} + seen = {id(self): True} if isinstance(fil, str): fil = common.fnmatch(fil) if isinstance(rec, str): rec = common.fnmatch(fil) - selffile = self.getfile(scrapinit=1) - def checkrecurse(path): - if not path.check(dir=1): - return False - try: - obj = path.resolve() - except (AttributeError, path.NotFound), e: - print str(e) - return False - - if id(obj) in seen: - return False - seen[id(obj)] = path - - pfile = path.getfile(scrapinit=1) - if pfile is None: - #print "skipping builtin", p - return False - #print "checking external", selffile, pfile - if not pfile.relto(selffile): - #print "skipping external", p - return False - if rec and not rec(path): - return False - return True - reclist = [] for p in self.listdir(): if fil is None or fil(p): yield p - if checkrecurse(p): - reclist.append(p) - + if id(p) not in seen: + try: + obj = p.resolve() + if inspect.isclass(obj) or inspect.ismodule(obj): + reclist.append(p) + finally: + seen[id(p)] = p for p in reclist: for i in p.visit(fil, rec, seen): yield i - + + def samefile(self, other): + otherobj = other.resolve() + try: + x = inspect.getfile(otherobj) + except TypeError: + return False + if x.endswith('pyc'): + x = x[:-1] + if str(self.fspath) == x: + return True + class Checkers(common.Checkers): _existence_deps = 'func', 'class_', 'exists' Added: py/dist/py/path/fspy/inc_test_fspy.py ============================================================================== --- (empty file) +++ py/dist/py/path/fspy/inc_test_fspy.py Fri Oct 15 02:47:30 2004 @@ -0,0 +1,13 @@ + +class A: + x1 = 42 + def func(self): + pass + +class B: + x2 = 23 + +class Nested: + class Class: + def borgfunc(self): pass + Deleted: /py/dist/py/path/fspy/pypath.py ============================================================================== --- /py/dist/py/path/fspy/pypath.py Fri Oct 15 02:47:30 2004 +++ (empty file) @@ -1,300 +0,0 @@ -""" -pypath implementation for navigating through python hierarchies. - -Note: this is still experimental and may be removed - for the first stable release! -""" -from __future__ import generators -from py import path - -from py.__impl__.path import common - -import sys, os -import inspect, imp - -class RootNS(object): - def __init__(self): - self.__dict__ = sys.modules - #def __getattr__(self, name): - # #if name.startswith('__') and name.endswith('__'): - # # raise AttributeError(name) - # try: - # fp, pathname, description = imp.find_module(name) - # except ImportError: - # raise AttributeError("module %r not found from " %(name)) - # else: - # mod = imp.load_module(name, fp, pathname, description) - # return mod - - def __repr__(self): - return '<>' - __str__ = __repr__ - -rootns = RootNS() - -class PyPath(common.PathBase): - """ a path abstraction for pointing to python objects. """ - sep = '.' - def __new__(cls, modpath='', ns=rootns): - if isinstance(modpath, PyPath): - assert ns is rootns - return modpath - assert isinstance(modpath, str) - self = object.__new__(cls) - self.modpath = modpath - self.ns = ns - return self - - def __str__(self): - return self.modpath - - def __hash__(self): - return hash(hash(self.modpath) + hash(self.ns)) - - def __repr__(self): - if self.ns is rootns: - return 'py(%r)' % (self.modpath, ) - return 'py(%r, %r)' % (self.modpath, self.ns) - - def join(self, *args): - for arg in args: - if not isinstance(arg, str): - raise TypeError, "not only strings in %r" % args - modpath = [x.strip('.') for x in ((self.modpath,)+args) if x] - modpath = self.sep.join(modpath) - return self.__class__(modpath, self.ns) - - def dirpath(self): - modpath = self.modpath.split(self.sep) [:-1] - modpath = self.sep.join(modpath) - return self.__class__(modpath, self.ns) - - def new(self): - return self.__class__(self.modpath, self.ns) - - def get(self, spec): - l = [] - modparts = self.modpath.split(self.sep) - for name in spec.split(','): - if name == 'basename': - l.append(modparts[-1]) - - if len(l) == 1: - return l[0] - elif len(l) == 0: - return None - else: - return l - - def resolve(self): - """return the object underlying the path. - - This works by first trying to import the largest possible - starting part of the path and continuing with AttributeAccesses. - Resolving an non existing path raises a std.path.error.FileNotFound - error. Resolving an unknown method on an existing module raises an - AttributeError. All other Exceptions usually indicate a problem while - executing a module code object. - """ - try: - return self._obj - except AttributeError: - pass - if hasattr(self, '_filepath'): - # XXX EVIL NAME HACK - name = self.modpath.replace('.', '_') - mod = imp.load_source("some bogus name", str(self._filepath)) - mod.__name__ = name - self._obj = mod - return mod - - rest = filter(None, self.modpath.split(self.sep)) - target = self.ns - if target is rootns: - i = 0 - while i < len(rest): - modpath = self.sep.join(rest[:i+1]) - try: - target = __import__(modpath, None, None, ['qwe']) - #print "could import %r" % modpath - i += 1 - except (SyntaxError, ImportError): - if modpath in sys.modules: - # file found but import failed - del sys.modules[modpath] - raise - # former replace.py DANGER: - if target is rootns or hasattr(target, '__path__'): - raise path.NotFound, "modpath %r not found" % modpath - break - rest = rest[i:] - - for name in rest: - try: - target = getattr(target, name) - except AttributeError: - raise path.NotFound("%r has no subpath %r" %(target, name)) - self._obj = target - return self._obj - - def relto(self, otherpath): - if self.ns is otherpath.ns: - if self.modpath.startswith(otherpath.modpath): - s = self.modpath[len(otherpath.modpath):] - return s.lstrip(self.sep) - return '' - - def listobj(self, fil=None, **kw): - l = [] - for x in self.listdir(fil, **kw): - l.append(x.resolve()) - return l - - def listdir(self, fil=None, **kw): - if kw: - if fil is None: - fil = path.checker(**kw) - else: - raise TypeError, "cannot take filter and keyword arguments" - obj = self.resolve() - if obj is rootns: - d = {} - for x in sys.modules: - name = x.split('.')[0] - d[name] = 1 - l = [self.join(x) for x in d if not fil or fil(x)] - else: - l = [] - for name in dir(obj): - sub = self.join(name) - sub._obj = getattr(obj, name) # XXX performance hack - if not fil or fil(sub): - l.append(sub) - - #print "listdir(%r) -> %r" %(self, l) - #print "listdir on", repr(self) - return l - - def getmodule(self): - obj = self.resolve() - #file = self.getfile() - #print "getmodule(%r)" % repr(obj)[:100] - #try: - # print inspect.modulesbyfile[file] - #except KeyError: - # print "could not find file:", file - return inspect.getmodule(obj) - - def setfile(self, filepath): - self._filepath = filepath - - def getfilelineno(self, scrapinit=0): - x = obj = self.resolve() - if inspect.ismodule(obj): - return obj.__file__, 0 - if inspect.ismethod(obj): - obj = obj.im_func - if inspect.isfunction(obj): - obj = obj.func_code - if inspect.iscode(obj): - return path.local(obj.co_filename), obj.co_firstlineno - else: - source, lineno = inspect.findsource(obj) - return x.getfile(), lineno - - def getfile(self, scrapinit=0): - # XXX use imp.find_module to find a fspath without resolving the - try: - return self._filepath - except AttributeError: - pass - if self.modpath == '__main__': - return path.local(sys.argv[0]) - obj = self.resolve() - if obj is rootns: - return None - #raise ValueError, "Root Namespace has no file" - if hasattr(obj, '__file__'): - p = path.local(obj.__file__) - else: - try: - fn = inspect.getfile(obj) - except TypeError: - return None - # XXX? - #raise ValueError("could not find file for %r" % self) - p = path.local(fn) - if scrapinit and p.get('purebasename') == '__init__': - p = p.dirpath() - return p - - def visit(self, fil=None, rec=None, seen=None): - # XXX gigantic hack for convenience of utest-collectors - if seen is None: - seen = {} - - if isinstance(fil, str): - fil = common.fnmatch(fil) - if isinstance(rec, str): - rec = common.fnmatch(fil) - - selffile = self.getfile(scrapinit=1) - def checkrecurse(path): - if not path.check(dir=1): - return False - try: - obj = path.resolve() - except (AttributeError, path.NotFound), e: - print str(e) - return False - - if id(obj) in seen: - return False - seen[id(obj)] = path - - pfile = path.getfile(scrapinit=1) - if pfile is None: - #print "skipping builtin", p - return False - #print "checking external", selffile, pfile - if not pfile.relto(selffile): - #print "skipping external", p - return False - if rec and not rec(path): - return False - return True - - reclist = [] - for p in self.listdir(): - if fil is None or fil(p): - yield p - if checkrecurse(p): - reclist.append(p) - - for p in reclist: - for i in p.visit(fil, rec, seen): - yield i - - class Checkers(common.Checkers): - _existence_deps = 'func', 'class_', 'exists' - - def _obj(self): - return self.path.resolve() - - def exists(self): - obj = self._obj() - return True - - def func(self): - ob = self._obj() - return inspect.isfunction(ob) or inspect.ismethod(ob) - - def class_(self): - ob = self._obj() - return inspect.isclass(ob) - - def instanceof(self, args): - return insinstance(self._obj(), args) - - def dir(self): - return hasattr(self._obj(), '__dict__') Copied: py/dist/py/path/fspy/test_fspy.py (from r6944, py/dist/py/path/fspy/test_pypath.py) ============================================================================== --- py/dist/py/path/fspy/test_pypath.py (original) +++ py/dist/py/path/fspy/test_fspy.py Fri Oct 15 02:47:30 2004 @@ -1,28 +1,32 @@ import sys, os -from py import path, test -from py.__impl__.path.pypath import pypath +import py + +mypath = py.magic.autopath().dirpath('inc_test_fspy.py') class TestPyPath: - root = path.py(pypath.__name__) + + def setup_class(cls): + cls.root = py.path.fspy(mypath) def test_join(self): - p = self.root.join('PyPath') + p = self.root.join('A') obj = p.resolve() - assert obj is path.py + assert hasattr(obj, '__bases__') + assert obj.x1 == 42 def test_listdir_module(self): l = self.root.listdir() basenames = [x.basename for x in l] - dlist = dir(pypath) + dlist = dir(self.root.getmodule()) for name in dlist: assert name in basenames for name in basenames: assert name in dlist def test_listdir_class(self): - l = self.root.join('PyPath').listdir() + l = self.root.join('A').listdir() basenames = [x.basename for x in l] - dlist = dir(pypath.PyPath) + dlist = dir(self.root.getmodule().A) for name in dlist: assert name in basenames for name in basenames: @@ -34,64 +38,56 @@ assert l[0] == path def test_visit(self): - PyPath = pypath.PyPath - l = list(self.root.visit(path.checker(basename='PyPath'))) + l = list(self.root.visit(py.path.checker(basename='borgfunc'))) assert len(l) == 1 obj = l[0] - assert str(obj).endswith('path.pypath.pypath.PyPath') - assert obj.resolve() is PyPath + assert str(obj).endswith('Nested.Class.borgfunc') + assert obj.resolve() == self.root.getmodule().Nested.Class.borgfunc def test_visit_fnmatch(self): - PyPath = pypath.PyPath - l = list(self.root.visit('PyPath')) + l = list(self.root.visit('borg*')) assert len(l) == 1 obj = l[0] - assert str(obj).endswith('path.pypath.pypath.PyPath') - assert obj.resolve() is PyPath - - def test_specified_base_empty_path(self): - p = path.py('', ns=ExampleClass) - l = p.listdir() - basenames = [x.basename for x in l] - assert 'testattr' in basenames + assert str(obj).endswith('Nested.Class.borgfunc') + assert obj.resolve() == self.root.getmodule().Nested.Class.borgfunc - def test_join_from_empty(self): - p = path.py('') - n = p.join('tokenize') - assert str(n) == 'tokenize' - - p = path.py('', ns=os) - n = p.join('getlogin') - assert str(n) == 'getlogin' - - def test_unspecifiedpypath_lists_modules(self): - p = path.py('') - l = p.listdir() - for i in l: - assert '.' not in str(i) - - for j in sys.modules: - for i in l: - if j.startswith(str(i)): - break - else: - self.fail("%s is not in sys.modules") - - def test_main_works(self): - m = path.py('__main__') - import __main__ - assert m.resolve() is __main__ + #def test_join_from_empty(self): + # p = path.py('') + # n = p.join('tokenize') + # assert str(n) == 'tokenize' + # + # p = path.py('', ns=os) + # n = p.join('getlogin') + # assert str(n) == 'getlogin' + + #def test_unspecifiedpypath_lists_modules(self): + # p = path.py('') + # l = p.listdir() + # for i in l: + # assert '.' not in str(i) + # + # for j in sys.modules: + # for i in l: + # if j.startswith(str(i)): + # break + # else: + # self.fail("%s is not in sys.modules") + + #def test_main_works(self): + # m = path.py('__main__') + # import __main__ + # assert m.resolve() is __main__ def test_relto(self): - m1 = path.py('a.b.c.d') - m2 = path.py('a.b') - m3 = path.py('') + m1 = self.root.new(modpath='a.b.c.d') + m2 = self.root.new(modpath='a.b') + m3 = self.root.new(modpath='') res = m1.relto(m2) assert str(res) == 'c.d' - assert m2.relto(m3) == str(m2) + assert m2.relto(m3) == 'a.b' def test_basename(self): - m1 = path.py('a.b.hello') + m1 = self.root.new(modpath='a.b.hello') assert m1.basename == 'hello' assert m1.check(basename='hello') assert not m1.check(basename='nono') @@ -99,10 +95,10 @@ assert not m1.check(basestarts='42') def test_dirpath(self): - m1 = path.py('a.b.hello') - m2 = path.py('a.b') - m3 = path.py('a') - m4 = path.py() + m1 = self.root.new(modpath='a.b.hello') + m2 = self.root.new(modpath='a.b') + m3 = self.root.new(modpath='a') + m4 = self.root.new(modpath='') assert m1.dirpath() == m2 assert m2.dirpath() == m3 assert m3.dirpath() == m4 @@ -112,25 +108,25 @@ i = 3 def func(self): pass - p = path.py('func', ns=A) + p = self.root.join('A.func') assert p.check(func=1) - p = path.py('i', ns=A) + p = self.root.join('A.x1') assert p.check(func=0) def test_hashing_equality(self): - x = path.py('os.path') - y = path.py('os.path') + x = self.root + y = self.root.new() assert x == y assert hash(x) == hash(y) def test_parents(self): - x = path.py('os.path.abspath') + x = self.root.new(modpath='os.path.abspath') l = x.parts() assert len(l) == 4 - assert path.py('') == l[0] - assert path.py('os') == l[1] - assert path.py('os.path') == l[2] - assert path.py('os.path.abspath') == l[3] + assert self.root.join('') == l[0] + assert self.root.join('os') == l[1] + assert self.root.join('os.path') == l[2] + assert self.root.join('os.path.abspath') == l[3] class TestEval: disabled = True @@ -149,14 +145,12 @@ class TestErrors: def test_FileNotFound(self): - p = path.py('somesuch') - test.raises(path.NotFound, p.resolve) - p = path.py('email.whatever') - test.raises(path.NotFound, p.resolve) - - def test_attributeerror(self): - p = path.py('os.path.qabspath') - test.raises(path.NotFound, p.resolve) + p = py.path.fspy(mypath, 'somesuch') + py.test.raises(py.path.NotFound, p.resolve) + + def test_FileNotFound_really(self): + p = py.path.fspy(mypath.new(basename='notexist'), 'somesuch') + py.test.raises(py.path.NotFound, p.resolve) #def test_ImportError(): # p = path.py('__std.utest.test.data.failingimport.someattr') @@ -164,5 +158,3 @@ class ExampleClass: testattr = 1 - -test.main() Deleted: /py/dist/py/path/fspy/test_pypath.py ============================================================================== --- /py/dist/py/path/fspy/test_pypath.py Fri Oct 15 02:47:30 2004 +++ (empty file) @@ -1,168 +0,0 @@ -import sys, os -from py import path, test -from py.__impl__.path.pypath import pypath - -class TestPyPath: - root = path.py(pypath.__name__) - - def test_join(self): - p = self.root.join('PyPath') - obj = p.resolve() - assert obj is path.py - - def test_listdir_module(self): - l = self.root.listdir() - basenames = [x.basename for x in l] - dlist = dir(pypath) - for name in dlist: - assert name in basenames - for name in basenames: - assert name in dlist - - def test_listdir_class(self): - l = self.root.join('PyPath').listdir() - basenames = [x.basename for x in l] - dlist = dir(pypath.PyPath) - for name in dlist: - assert name in basenames - for name in basenames: - assert name in dlist - - def listobj(self): - l = self.root.listobj(basestarts='path') - assert len(l) == 1 - assert l[0] == path - - def test_visit(self): - PyPath = pypath.PyPath - l = list(self.root.visit(path.checker(basename='PyPath'))) - assert len(l) == 1 - obj = l[0] - assert str(obj).endswith('path.pypath.pypath.PyPath') - assert obj.resolve() is PyPath - - def test_visit_fnmatch(self): - PyPath = pypath.PyPath - l = list(self.root.visit('PyPath')) - assert len(l) == 1 - obj = l[0] - assert str(obj).endswith('path.pypath.pypath.PyPath') - assert obj.resolve() is PyPath - - def test_specified_base_empty_path(self): - p = path.py('', ns=ExampleClass) - l = p.listdir() - basenames = [x.basename for x in l] - assert 'testattr' in basenames - - def test_join_from_empty(self): - p = path.py('') - n = p.join('tokenize') - assert str(n) == 'tokenize' - - p = path.py('', ns=os) - n = p.join('getlogin') - assert str(n) == 'getlogin' - - def test_unspecifiedpypath_lists_modules(self): - p = path.py('') - l = p.listdir() - for i in l: - assert '.' not in str(i) - - for j in sys.modules: - for i in l: - if j.startswith(str(i)): - break - else: - self.fail("%s is not in sys.modules") - - def test_main_works(self): - m = path.py('__main__') - import __main__ - assert m.resolve() is __main__ - - def test_relto(self): - m1 = path.py('a.b.c.d') - m2 = path.py('a.b') - m3 = path.py('') - res = m1.relto(m2) - assert str(res) == 'c.d' - assert m2.relto(m3) == str(m2) - - def test_basename(self): - m1 = path.py('a.b.hello') - assert m1.basename == 'hello' - assert m1.check(basename='hello') - assert not m1.check(basename='nono') - assert m1.check(basestarts='he') - assert not m1.check(basestarts='42') - - def test_dirpath(self): - m1 = path.py('a.b.hello') - m2 = path.py('a.b') - m3 = path.py('a') - m4 = path.py() - assert m1.dirpath() == m2 - assert m2.dirpath() == m3 - assert m3.dirpath() == m4 - - def test_function(self): - class A: - i = 3 - def func(self): - pass - p = path.py('func', ns=A) - assert p.check(func=1) - p = path.py('i', ns=A) - assert p.check(func=0) - - def test_hashing_equality(self): - x = path.py('os.path') - y = path.py('os.path') - assert x == y - assert hash(x) == hash(y) - - def test_parents(self): - x = path.py('os.path.abspath') - l = x.parts() - assert len(l) == 4 - assert path.py('') == l[0] - assert path.py('os') == l[1] - assert path.py('os.path') == l[2] - assert path.py('os.path.abspath') == l[3] - -class TestEval: - disabled = True - def test_funccall(self): - p = path.py('os.path.join("a", "b")') - s = p.resolve() - assert s == os.path.join("a", "b") - - def test_invalid1(self): - p = path.py('os.path.qwe("a", "b")') - s = test.raises(path.NotFound, "p.resolve()") - - def test_syntaxerror(self): - p = path.py('os.path.qwe("a", ') - s = test.raises(ValueError, "p.resolve()") - -class TestErrors: - def test_FileNotFound(self): - p = path.py('somesuch') - test.raises(path.NotFound, p.resolve) - p = path.py('email.whatever') - test.raises(path.NotFound, p.resolve) - - def test_attributeerror(self): - p = path.py('os.path.qabspath') - test.raises(path.NotFound, p.resolve) - - #def test_ImportError(): - # p = path.py('__std.utest.test.data.failingimport.someattr') - # utest.raises(ImportError, p.resolve) - -class ExampleClass: - testattr = 1 - -test.main() From hpk at codespeak.net Fri Oct 15 02:56:22 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 15 Oct 2004 02:56:22 +0200 (MEST) Subject: [py-svn] r6946 - py/dist/py/path/fspy Message-ID: <20041015005622.6E4D05A165@thoth.codespeak.net> Author: hpk Date: Fri Oct 15 02:56:21 2004 New Revision: 6946 Modified: py/dist/py/path/fspy/fspy.py Log: removed obsolete code Modified: py/dist/py/path/fspy/fspy.py ============================================================================== --- py/dist/py/path/fspy/fspy.py (original) +++ py/dist/py/path/fspy/fspy.py Fri Oct 15 02:56:21 2004 @@ -179,32 +179,6 @@ source, lineno = inspect.findsource(obj) return x.getfile(), lineno - def getfile(self, scrapinit=0): - # XXX use imp.find_module to find a fspath without resolving the - try: - return self._filepath - except AttributeError: - pass - if self.modpath == '__main__': - return path.local(sys.argv[0]) - obj = self.resolve() - if obj is rootns: - return None - #raise ValueError, "Root Namespace has no file" - if hasattr(obj, '__file__'): - p = path.local(obj.__file__) - else: - try: - fn = inspect.getfile(obj) - except TypeError: - return None - # XXX? - #raise ValueError("could not find file for %r" % self) - p = path.local(fn) - if scrapinit and p.get('purebasename') == '__init__': - p = p.dirpath() - return p - def visit(self, fil=None, rec=None, seen=None): # XXX gigantic hack for convenience of utest-collectors if seen is None: From hpk at codespeak.net Fri Oct 15 05:15:22 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 15 Oct 2004 05:15:22 +0200 (MEST) Subject: [py-svn] r6947 - in py/dist/py/path: . fspy local svn svnwc test Message-ID: <20041015031522.481DA5A0E7@thoth.codespeak.net> Author: hpk Date: Fri Oct 15 05:15:19 2004 New Revision: 6947 Added: py/dist/py/path/fspy/inc_pseudofs.py py/dist/py/path/test/common.py - copied, changed from r6937, py/dist/py/path/test/commonfs.py py/dist/py/path/test/fscommon.py - copied, changed from r6937, py/dist/py/path/test/commonfs.py py/dist/py/path/test/test_api.py - copied unchanged from r6941, py/dist/py/path/test_api.py Removed: py/dist/py/path/test/commonfs.py py/dist/py/path/test_api.py Modified: py/dist/py/path/common.py py/dist/py/path/fspy/fspy.py py/dist/py/path/fspy/test_fspy.py py/dist/py/path/local/local.py py/dist/py/path/local/test_local.py py/dist/py/path/svn/common.py py/dist/py/path/svnwc/command.py py/dist/py/path/svnwc/test_command.py py/dist/py/path/test/svncommonfs.py Log: - py.path.fspy is starting to look good and shares code and tests with the filesystem implementations - emphasize the distinction of filesystem and other path objects - small fixes everywhere - fspy has a lot of tests Modified: py/dist/py/path/common.py ============================================================================== --- py/dist/py/path/common.py (original) +++ py/dist/py/path/common.py Fri Oct 15 05:15:19 2004 @@ -4,13 +4,13 @@ """ from __future__ import generators import sys -from py import path +import py def checktype(pathinstance, kw): - names = ('local', 'svnwc', 'svnurl', 'py') + names = ('local', 'svnwc', 'svnurl', 'py', 'fspy') for name,value in kw.items(): if name in names: - cls = getattr(path, name) + cls = getattr(py.path, name) if bool(isinstance(pathinstance, cls)) ^ bool(value): return False del kw[name] @@ -19,25 +19,19 @@ class checker: def __init__(self, **kwargs): self.kwargs = kwargs - def __call__(self, path): - return path.check(**self.kwargs) + def __call__(self, p): + return p.check(**self.kwargs) class invchecker(checker): - def __call__(self, path): - return not path.check(**self.kwargs) + def __call__(self, p): + return not p.check(**self.kwargs) class Checkers: - _existence_deps = 'exists', 'dir', 'file' + _depend_on_existence = 'exists', def __init__(self, path): self.path = path - def dir(self): - raise NotImplementedError - - def file(self): - raise NotImplementedError - def exists(self): raise NotImplementedError @@ -53,20 +47,13 @@ def fnmatch(self, arg): return fnmatch(arg)(self.path) - def dotfile(self): - return self.path.basename.startswith('.') - def endswith(self, arg): return str(self.path).endswith(arg) - def ext(self, arg): - if not arg.startswith('.'): - arg = '.' + arg - return self.path.get('ext') == arg - def _evaluate(self, kw): for name, value in kw.items(): invert = False + meth = None try: meth = getattr(self, name) except AttributeError: @@ -75,7 +62,9 @@ try: meth = getattr(self, name[3:]) except AttributeError: - raise TypeError, "no %r checker available" % name + pass + if meth is None: + raise TypeError, "no %r checker available for %r" % (name, self.path) try: if meth.im_func.func_code.co_argcount > 1: if (not meth(value)) ^ invert: @@ -83,8 +72,8 @@ else: if bool(value) ^ bool(meth()) ^ invert: return False - except (path.NotFound, path.NoDirectory): - for name in self._existence_deps: + except (py.path.NotFound, py.path.NoDirectory): + for name in self._depend_on_existence: if name in kw: if kw.get(name): return False @@ -93,7 +82,6 @@ if not kw.get(name): return False return True - class PathBase(object): """ shared implementation for filesystem path objects.""" @@ -113,9 +101,6 @@ for i in self.listdir(): yield i - def __div__(self, other): - return self.join(str(other)) - def __contains__(self, other): if type(other) is str: for x in self: @@ -130,72 +115,6 @@ return self.get('basename') basename = property(basename, None, None, 'basename part of path') - def ext(self): - return self.get('ext') - ext = property(ext, None, None, 'extension part of path') - - def purebasename(self): - return self.get('purebasename') - purebasename = property(purebasename, None, None, 'basename without extension') - - def read(self, num=-1): - #""" read up to 'num' bytes. (if num==-1 then read all)""" - try: - f = self.open() - try: - return f.read(num) - finally: - f.close() - except: - self._except(sys.exc_info()) - - def readlines(self, cr=1): - if not cr: - content = self.read() - return content.split('\n') - else: - f = self.open('r') - try: - return f.readlines() - finally: - f.close() - - def loadobj(self): - """ return object unpickled from self.read() """ - try: - f = self.open('rb') - try: - from cPickle import load - return load(f) - finally: - f.close() - except: - self._except(sys.exc_info()) - - def visit(self, fil=None, rec=None, ignore=None): - if isinstance(fil, str): - fil = fnmatch(fil) - if isinstance(rec, str): - rec = fnmatch(fil) - if ignore: - try: - dirlist = self.listdir() - except ignore: - return - else: - dirlist = self.listdir() - checkdir = path.checker(dir=1) - reclist = [] - for p in dirlist: - if fil is None or fil(p): - yield p - if checkdir(p) and (rec is None or rec(p)): - reclist.append(p) - - for p in reclist: - for i in p.visit(fil, rec, ignore=ignore): - yield i - def parts(self): """ return a root-first list of all ancestor directories plus the path itself. @@ -227,6 +146,30 @@ """ default exception handling is to reraise it. """ raise excinfo[0], excinfo[1], excinfo[2] + def visit(self, fil=None, rec=None, ignore=None): + if isinstance(fil, str): + fil = fnmatch(fil) + if isinstance(rec, str): + rec = fnmatch(fil) + if ignore: + try: + dirlist = self.listdir() + except ignore: + return + else: + dirlist = self.listdir() + checkdir = py.path.checker(dir=1) + reclist = [] + for p in dirlist: + if fil is None or fil(p): + yield p + if checkdir(p) and (rec is None or rec(p)): + reclist.append(p) + + for p in reclist: + for i in p.visit(fil, rec, ignore=ignore): + yield i + class fnmatch: def __init__(self, pattern): self.pattern = pattern @@ -253,5 +196,72 @@ pattern = '*' + path.sep + pattern from fnmatch import fnmatch return fnmatch(name, pattern) -""" -""" + + +class FSCheckers(Checkers): + _depend_on_existence = Checkers._depend_on_existence+('dir', 'file') + + def dir(self): + raise NotImplementedError + + def file(self): + raise NotImplementedError + + def dotfile(self): + return self.path.basename.startswith('.') + + def ext(self, arg): + if not arg.startswith('.'): + arg = '.' + arg + return self.path.get('ext') == arg + +class FSPathBase(PathBase): + """ shared implementation for filesystem path objects.""" + Checkers = FSCheckers + from py.path import NotFound + + def __div__(self, other): + return self.join(str(other)) + + def ext(self): + return self.get('ext') + ext = property(ext, None, None, 'extension part of path') + + def purebasename(self): + return self.get('purebasename') + purebasename = property(purebasename, None, None, 'basename without extension') + + def read(self, num=-1): + #""" read up to 'num' bytes. (if num==-1 then read all)""" + try: + f = self.open() + try: + return f.read(num) + finally: + f.close() + except: + self._except(sys.exc_info()) + + def readlines(self, cr=1): + if not cr: + content = self.read() + return content.split('\n') + else: + f = self.open('r') + try: + return f.readlines() + finally: + f.close() + + def loadobj(self): + """ return object unpickled from self.read() """ + try: + f = self.open('rb') + try: + from cPickle import load + return load(f) + finally: + f.close() + except: + self._except(sys.exc_info()) + Modified: py/dist/py/path/fspy/fspy.py ============================================================================== --- py/dist/py/path/fspy/fspy.py (original) +++ py/dist/py/path/fspy/fspy.py Fri Oct 15 05:15:19 2004 @@ -1,39 +1,15 @@ """ -pypath implementation for navigating through python hierarchies. +A path to python objects located in filesystems. Note: this is still experimental and may be removed for the first stable release! """ from __future__ import generators import py - -from py import path - from py.__impl__.path import common - -import sys, os +import sys import inspect, imp -class RootNS(object): - def __init__(self): - self.__dict__ = sys.modules - #def __getattr__(self, name): - # #if name.startswith('__') and name.endswith('__'): - # # raise AttributeError(name) - # try: - # fp, pathname, description = imp.find_module(name) - # except ImportError: - # raise AttributeError("module %r not found from " %(name)) - # else: - # mod = imp.load_module(name, fp, pathname, description) - # return mod - - def __repr__(self): - return '<>' - __str__ = __repr__ - -rootns = RootNS() - class Fspy(common.PathBase): """ a path abstraction addressing python objects in a file system. """ sep = '.' @@ -65,9 +41,9 @@ modpath = self.sep.join(modpath) return self.__class__(self.fspath, modpath) - def dirpath(self): + def dirpath(self, *args): modpath = self.modpath.split(self.sep) [:-1] - modpath = self.sep.join(modpath) + modpath = self.sep.join(modpath+list(args)) return self.__class__(self.fspath, modpath) def new(self, **kw): @@ -82,6 +58,8 @@ i = self.modpath.rfind('.') if i != -1: return cls(self.fspath, self.modpath[i+1:] + kw['basename']) + else: + return cls(self.fspath, kw['basename']) return cls(self.fspath, self.modpath) def get(self, spec): @@ -99,12 +77,12 @@ return l def getmodule(self): - modname = repr(self.fspath) - # str(self.fspath.new(ext='')).replace(self.fspath.sep, '_') + #modname = str(self.fspath) + modname = str(self.fspath.new(ext='')).replace(self.fspath.sep, '_') try: return sys.modules[modname] except KeyError: - #print "importing", modname + #print "trying importing", modname try: mod = imp.load_source(modname, str(self.fspath)) except: @@ -122,7 +100,7 @@ try: target = getattr(target, name) except AttributeError: - raise path.NotFound(str(self)) + raise py.path.NotFound(str(self)) return target def relto(self, otherpath): @@ -138,12 +116,14 @@ l.append(x.resolve()) return l - def listdir(self, fil=None, **kw): + def listdir(self, fil=None, sort=True, **kw): if kw: if fil is None: - fil = path.checker(**kw) + fil = py.path.checker(**kw) else: raise TypeError, "cannot take filter and keyword arguments" + elif isinstance(fil, str): + fil = common.fnmatch(fil) obj = self.resolve() #if obj is rootns: # d = {} @@ -174,13 +154,23 @@ if inspect.isfunction(obj): obj = obj.func_code if inspect.iscode(obj): - return path.local(obj.co_filename), obj.co_firstlineno + return py.path.local(obj.co_filename), obj.co_firstlineno else: source, lineno = inspect.findsource(obj) return x.getfile(), lineno - def visit(self, fil=None, rec=None, seen=None): - # XXX gigantic hack for convenience of utest-collectors + def visit(self, fil=None, rec=None, ignore=None, seen=None): + def myrec(p, seen={id(self): True}): + if id(p) in seen: + return False + seen[id(p)] = True + if self.samefile(p): + return True + + for x in super(Fspy, self).visit(fil=fil, rec=rec, ignore=ignore): + yield x + return + if seen is None: seen = {id(self): True} @@ -189,14 +179,21 @@ if isinstance(rec, str): rec = common.fnmatch(fil) + if ignore: + try: + l = self.listdir() + except ignore: + return + else: + l = self.listdir() reclist = [] - for p in self.listdir(): + for p in l: if fil is None or fil(p): yield p if id(p) not in seen: try: obj = p.resolve() - if inspect.isclass(obj) or inspect.ismodule(obj): + if inspect.isclass(obj) or inspect.ismodule(obj): reclist.append(p) finally: seen[id(p)] = p @@ -216,10 +213,12 @@ return True class Checkers(common.Checkers): - _existence_deps = 'func', 'class_', 'exists' + _depend_on_existence = (common.Checkers._depend_on_existence + + ('func', 'class_', 'exists', 'dir')) def _obj(self): - return self.path.resolve() + self._obj = self.path.resolve() + return self._obj def exists(self): obj = self._obj() @@ -233,8 +232,9 @@ ob = self._obj() return inspect.isclass(ob) - def instanceof(self, args): - return insinstance(self._obj(), args) + def isinstance(self, args): + return isinstance(self._obj(), args) def dir(self): - return hasattr(self._obj(), '__dict__') + obj = self._obj() + return inspect.isclass(obj) or inspect.ismodule(obj) Added: py/dist/py/path/fspy/inc_pseudofs.py ============================================================================== --- (empty file) +++ py/dist/py/path/fspy/inc_pseudofs.py Fri Oct 15 05:15:19 2004 @@ -0,0 +1,10 @@ + +samplefile = 'samplefile' +samplepickle = {} + +class sampledir: + otherfile = 'otherfile' + +class otherdir: + init = 42 + Modified: py/dist/py/path/fspy/test_fspy.py ============================================================================== --- py/dist/py/path/fspy/test_fspy.py (original) +++ py/dist/py/path/fspy/test_fspy.py Fri Oct 15 05:15:19 2004 @@ -1,10 +1,16 @@ import sys, os import py -mypath = py.magic.autopath().dirpath('inc_test_fspy.py') +from py.__impl__.path.test import common -class TestPyPath: +mypath = py.magic.autopath().dirpath('inc_test_fspy.py') +class TestFsPyCommonTests(common.CommonPathTests): + def setup_class(cls): + cls.root = py.path.fspy( + py.magic.autopath().dirpath('inc_pseudofs.py')) + +class TestFsPy: def setup_class(cls): cls.root = py.path.fspy(mypath) @@ -104,10 +110,6 @@ assert m3.dirpath() == m4 def test_function(self): - class A: - i = 3 - def func(self): - pass p = self.root.join('A.func') assert p.check(func=1) p = self.root.join('A.x1') Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Fri Oct 15 05:15:19 2004 @@ -15,12 +15,12 @@ else: from py.__impl__.path.local._posix import PosixMixin as PlatformMixin -class LocalPath(common.PathBase, PlatformMixin): +class LocalPath(common.FSPathBase, PlatformMixin): """ the fully specialized local path implementation. (including symbolic links and all kinds of stuff.) """ sep = os.sep - class Checkers(common.Checkers): + class Checkers(common.FSCheckers): def _stat(self): try: return self._statcache Modified: py/dist/py/path/local/test_local.py ============================================================================== --- py/dist/py/path/local/test_local.py (original) +++ py/dist/py/path/local/test_local.py Fri Oct 15 05:15:19 2004 @@ -1,7 +1,7 @@ import sys, os from py.test import main, raises, config from py.path import local, checker -from py.__impl__.path.test.commonfs import CommonFSTests, setuptestfs +from py.__impl__.path.test.fscommon import CommonFSTests, setuptestfs class TestLocalPath(CommonFSTests): def __init__(self): Modified: py/dist/py/path/svn/common.py ============================================================================== --- py/dist/py/path/svn/common.py (original) +++ py/dist/py/path/svn/common.py Fri Oct 15 05:15:19 2004 @@ -8,7 +8,7 @@ #_______________________________________________________________ -class SvnPathBase(common.PathBase): +class SvnPathBase(common.FSPathBase): """ Base implementation for SvnPath implementations. """ sep = '/' @@ -216,7 +216,7 @@ # raise IOError, "could not determine newest repo revision for %s" % self # return rev - class Checkers(common.Checkers): + class Checkers(common.FSCheckers): def _info(self): try: return self._infocache Modified: py/dist/py/path/svnwc/command.py ============================================================================== --- py/dist/py/path/svnwc/command.py (original) +++ py/dist/py/path/svnwc/command.py Fri Oct 15 05:15:19 2004 @@ -17,7 +17,7 @@ DEBUG = 0 -class SvnWCCommandPath(common.PathBase): +class SvnWCCommandPath(common.FSPathBase): sep = os.sep def __new__(cls, wcpath=None, url=None): Modified: py/dist/py/path/svnwc/test_command.py ============================================================================== --- py/dist/py/path/svnwc/test_command.py (original) +++ py/dist/py/path/svnwc/test_command.py Fri Oct 15 05:15:19 2004 @@ -1,6 +1,6 @@ import py from py.__impl__.path.test.svncommonfs import CommonSvnTests -from py.__impl__.path.test.commonfs import setuptestfs +from py.__impl__.path.test.fscommon import setuptestfs # make a wc directory out of a given root url # cache previously obtained wcs! Copied: py/dist/py/path/test/common.py (from r6937, py/dist/py/path/test/commonfs.py) ============================================================================== --- py/dist/py/path/test/commonfs.py (original) +++ py/dist/py/path/test/common.py Fri Oct 15 05:15:19 2004 @@ -1,28 +1,8 @@ from py.path import checker, NotFound import py -def setuptestfs(path): - if path.join('samplefile').check(): - return - - #print "setting up test fs for", repr(path) - - samplefile = path.ensure('samplefile') - samplefile.write('samplefile\n') - - path.ensure('samplefile.py').write('import os') - - d = {1:2, 'hello': 'world', 'answer': 42} - path.ensure('sampledict.pickle').dumpobj(d) - - sampledir = path.ensure('sampledir', dir=1) - sampledir.ensure('otherfile') - - otherdir = path.ensure('otherdir', dir=1) - otherdir.ensure('__init__.py') - -class CommonFSTests: - root = None # subclasses have to provide a current 'root' attribute +class CommonPathTests: + root = None # subclasses have to setup a 'root' attribute def test_constructor_equality(self): p = self.root.__class__(self.root) @@ -50,39 +30,22 @@ newpath = self.root.join() assert self.root == newpath - def test_join_div_operator(self): - newpath = self.root / '/sampledir' / '/test//' - newpath2 = self.root.join('sampledir', 'test') - assert newpath == newpath2 - def test_parts(self): - newpath = self.root / 'sampledir' / 'otherfile' + newpath = self.root.join('sampledir', 'otherfile') par = newpath.parts()[-3:] - assert par == [self.root, self.root/'sampledir', newpath] + assert par == [self.root, self.root.join('sampledir'), newpath] #def test_parents_nonexisting_file(self): # newpath = self.root / 'dirnoexist' / 'nonexisting file' # par = list(newpath.parents()) # assert par[:2] == [self.root / 'dirnoexist', self.root] - # following feature is disabled - #def test_getitem(self): - # newpath = self.root['sampledir']['test'] - # newpath2 = self.root.join('sampledir', 'test') - # assert newpath == newpath2 - def test_basename_checks(self): newpath = self.root.join('sampledir') assert newpath.check(basename='sampledir') assert newpath.check(notbasename='xyz') assert newpath.basename == 'sampledir' - def test_ext(self): - newpath = self.root.join('sampledir.ext') - assert newpath.ext == '.ext' - newpath = self.root.join('sampledir') - assert not newpath.ext - def test_basename(self): newpath = self.root.join('sampledir') assert newpath.check(basename='sampledir') @@ -96,58 +59,12 @@ newpath = self.root.join('sampledir') assert newpath.dirpath('x') == self.root.join('x') - def test_purebasename(self): - newpath = self.root.join('samplefile.py') - assert newpath.purebasename == 'samplefile' - - def test_multiple_parts(self): - newpath = self.root.join('samplefile.py') - dirname, purebasename, basename, ext = newpath.get( - 'dirname,purebasename,basename,ext') - assert dirname == str(self.root) - assert purebasename == 'samplefile' - assert basename == 'samplefile.py' - assert ext == '.py' - - def test_dotted_name_ext(self): - newpath = self.root.join('a.b.c') - ext = newpath.get('ext') - assert ext == '.c' - assert newpath.ext == '.c' - - def test_newext(self): - newpath = self.root.join('samplefile.py') - newext = newpath.new(ext='.txt') - assert newext.basename == "samplefile.txt" - assert newext.purebasename == "samplefile" - def test_newbasename(self): - newpath = self.root.join('samplefile.py') - newbase = newpath.new(basename="sampledict.pickle") - assert newbase.basename == "sampledict.pickle" + newpath = self.root.join('samplefile') + newbase = newpath.new(basename="samplefile2") + assert newbase.basename == "samplefile2" assert newbase.dirpath() == newpath.dirpath() - def test_iteration(self): - l = [] - for i in self.root: - l.append(i.basename) - assert 'sampledir' in l - - def test_read(self): - url = self.root.join("samplefile") - contents = url.read() - assert contents.find("samplefile") != -1 - - def test_readlines(self): - fn = self.root.join('samplefile') - contents = fn.readlines() - assert contents == ['samplefile\n'] - - def test_readlines_nocr(self): - fn = self.root.join('samplefile') - contents = fn.readlines(cr=0) - assert contents == ['samplefile', ''] - def test_not_exists(self): assert not self.root.join('does_not_exist').check() assert self.root.join('does_not_exist').check(exists=0) @@ -165,16 +82,6 @@ def test_filter_dir(self): assert checker(dir=1)(self.root.join("sampledir")) - def test_file(self): - assert self.root.join('samplefile').check(file=1) - - def test_not_file(self): - assert not self.root.join("sampledir").check(file=1) - assert self.root.join("sampledir").check(file=0) - - def test_filter_file(self): - assert checker(file=1)(self.root.join("samplefile")) - def test_fnmatch_file(self): assert self.root.join("samplefile").check(fnmatch='s*e') assert self.root.join("samplefile").check(notfnmatch='s*x') @@ -182,26 +89,10 @@ #def test_fnmatch_dir(self): - def test_non_existent(self): - assert self.root.join("sampledir.nothere").check(dir=0) - assert self.root.join("sampledir.nothere").check(file=0) - assert self.root.join("sampledir.nothere").check(notfile=1) - assert self.root.join("sampledir.nothere").check(notdir=1) - assert self.root.join("sampledir.nothere").check(notexists=1) - assert not self.root.join("sampledir.nothere").check(notfile=0) - # pattern = self.root.sep.join(['s*file']) # sfile = self.root.join("samplefile") # assert sfile.check(fnmatch=pattern) - def test_size(self): - url = self.root.join("samplefile") - assert url.size() > len("samplefile") - - def test_mtime(self): - url = self.root.join("samplefile") - assert url.mtime() > 0 - def test_relto(self): l=self.root.join("sampledir", "otherfile") assert l.relto(self.root) == l.sep.join(["sampledir", "otherfile"]) @@ -230,9 +121,10 @@ assert not self.root.join('samplefile') in l def test_listdir_sorted(self): - l = self.root.listdir(checker(basestarts="samplefile"), sort=True) - assert self.root.join('samplefile') == l[0] - assert self.root.join('samplefile.py') == l[1] + l = self.root.listdir(checker(basestarts="sample"), sort=True) + assert self.root.join('sampledir') == l[0] + assert self.root.join('samplefile') == l[1] + assert self.root.join('samplepickle') == l[2] def test_visit_nofilter(self): l = [] @@ -241,16 +133,9 @@ assert "sampledir" in l assert self.root.sep.join(["sampledir", "otherfile"]) in l - def test_visit_filesonly(self): - l = [] - for i in self.root.visit(checker(file=1)): - l.append(i.relto(self.root)) - assert not "sampledir" in l - assert self.root.sep.join(["sampledir", "otherfile"]) in l - def test_visit_norecurse(self): l = [] - for i in self.root.visit(None, lambda x: not x.basename == "sampledir"): + for i in self.root.visit(None, lambda x: x.basename != "sampledir"): l.append(i.relto(self.root)) assert "sampledir" in l assert not self.root.sep.join(["sampledir", "otherfile"]) in l @@ -264,49 +149,35 @@ assert "otherdir" in l def test_visit_ignore(self): - p = self.root / 'nonexisting' + p = self.root.join('nonexisting') assert list(p.visit(ignore=NotFound)) == [] - def test_load(self): - p = self.root.join('sampledict.pickle') - obj = p.loadobj() - assert type(obj) is dict - assert obj.get('answer',None) == 42 - - def test_visit_nodotfiles(self): - l = [] - for i in self.root.visit(checker(dotfile=0)): - l.append(i.relto(self.root)) - assert "sampledir" in l - assert self.root.sep.join(["sampledir", "otherfile"]) in l - assert not ".dotfile" in l - def test_visit_endswith(self): l = [] - for i in self.root.visit(checker(endswith=".py")): + for i in self.root.visit(checker(endswith="file")): l.append(i.relto(self.root)) - assert not self.root.sep.join(["sampledir", "otherfile"]) in l - assert "samplefile.py" in l + assert self.root.sep.join(["sampledir", "otherfile"]) in l + assert "samplefile" in l def test_endswith(self): assert self.root.check(notendswith='.py') - x = self.root.join('samplefile.py') - assert x.check(endswith='.py') + x = self.root.join('samplefile') + assert x.check(endswith='file') def test_cmp(self): path1 = self.root.join('samplefile') - path2 = self.root.join('samplefile.py') - assert cmp(path1, path2) == cmp('samplefile', 'samplefile.py') + path2 = self.root.join('samplefile2') + assert cmp(path1, path2) == cmp('samplefile', 'samplefile2') assert cmp(path1, path1) == 0 def test_contains_path(self): - path1 = self.root .join('samplefile') + path1 = self.root.join('samplefile') assert path1 in self.root assert not self.root.join('not existing') in self.root def test_contains_path_with_basename(self): assert 'samplefile' in self.root - assert 'not existing' not in self.root + assert 'not_existing' not in self.root def featuretest_check_docstring(self): here = self.root.__class__ @@ -316,59 +187,3 @@ if name[0] != '_': assert name in doc - def test_copy_file(self): - otherdir = self.root.join('otherdir') - initpy = otherdir.join('__init__.py') - copied = otherdir.join('copied') - initpy.copy(copied) - try: - assert copied.check() - s1 = initpy.read() - s2 = copied.read() - assert s1 == s2 - finally: - if copied.check(): - copied.remove() - - def test_copy_dir(self): - otherdir = self.root.join('otherdir') - copied = self.root.join('newdir') - try: - otherdir.copy(copied) - assert copied.check(dir=1) - assert copied.join('__init__.py').check(file=1) - s1 = otherdir.join('__init__.py').read() - s2 = copied.join('__init__.py').read() - assert s1 == s2 - finally: - if copied.check(dir=1): - copied.remove(rec=1) - - def test_remove_file(self): - d = self.root.ensure('todeleted') - assert d.check() - d.remove() - assert not d.check() - - def test_remove_dir_recursive_by_default(self): - d = self.root.ensure('to', 'be', 'deleted') - assert d.check() - p = self.root.join('to') - p.remove() - assert not p.check() - - def test_move_file(self): - p = self.root.ensure('tomove') - newp = p.new(basename='moved') - p.move(newp) - assert newp.check(file=1) - assert not p.check() - - def test_move_directory(self): - source = self.root.join('to') - source.ensure('moved', 'somefile').write("42") - dest = source.new(basename='moveddir') - source.move(dest) - assert dest.check(dir=1) - assert dest.join('moved', 'somefile').read() == '42' - assert not source.check() Deleted: /py/dist/py/path/test/commonfs.py ============================================================================== --- /py/dist/py/path/test/commonfs.py Fri Oct 15 05:15:19 2004 +++ (empty file) @@ -1,374 +0,0 @@ -from py.path import checker, NotFound -import py - -def setuptestfs(path): - if path.join('samplefile').check(): - return - - #print "setting up test fs for", repr(path) - - samplefile = path.ensure('samplefile') - samplefile.write('samplefile\n') - - path.ensure('samplefile.py').write('import os') - - d = {1:2, 'hello': 'world', 'answer': 42} - path.ensure('sampledict.pickle').dumpobj(d) - - sampledir = path.ensure('sampledir', dir=1) - sampledir.ensure('otherfile') - - otherdir = path.ensure('otherdir', dir=1) - otherdir.ensure('__init__.py') - -class CommonFSTests: - root = None # subclasses have to provide a current 'root' attribute - - def test_constructor_equality(self): - p = self.root.__class__(self.root) - assert p == self.root - - def test_eq_nonstring(self): - path1 = self.root.join('sampledir') - path2 = self.root.join('sampledir') - assert path1 == path2 - - def test_new_identical(self): - assert self.root == self.root.new() - - def test_join(self): - p = self.root.join('sampledir') - assert str(p) == self.root.sep.join([str(self.root), 'sampledir']) - - def test_join_normalized(self): - newpath = self.root.join(self.root.sep+'sampledir') - assert str(newpath) == self.root.sep.join([str(self.root), 'sampledir']) - newpath = self.root.join((self.root.sep*2) + 'sampledir') - assert str(newpath) == self.root.sep.join([str(self.root), 'sampledir']) - - def test_join_noargs(self): - newpath = self.root.join() - assert self.root == newpath - - def test_join_div_operator(self): - newpath = self.root / '/sampledir' / '/test//' - newpath2 = self.root.join('sampledir', 'test') - assert newpath == newpath2 - - def test_parts(self): - newpath = self.root / 'sampledir' / 'otherfile' - par = newpath.parts()[-3:] - assert par == [self.root, self.root/'sampledir', newpath] - - #def test_parents_nonexisting_file(self): - # newpath = self.root / 'dirnoexist' / 'nonexisting file' - # par = list(newpath.parents()) - # assert par[:2] == [self.root / 'dirnoexist', self.root] - - # following feature is disabled - #def test_getitem(self): - # newpath = self.root['sampledir']['test'] - # newpath2 = self.root.join('sampledir', 'test') - # assert newpath == newpath2 - - def test_basename_checks(self): - newpath = self.root.join('sampledir') - assert newpath.check(basename='sampledir') - assert newpath.check(notbasename='xyz') - assert newpath.basename == 'sampledir' - - def test_ext(self): - newpath = self.root.join('sampledir.ext') - assert newpath.ext == '.ext' - newpath = self.root.join('sampledir') - assert not newpath.ext - - def test_basename(self): - newpath = self.root.join('sampledir') - assert newpath.check(basename='sampledir') - assert newpath.basename, 'sampledir' - - def test_dirpath(self): - newpath = self.root.join('sampledir') - assert newpath.dirpath() == self.root - - def test_dirpath_with_args(self): - newpath = self.root.join('sampledir') - assert newpath.dirpath('x') == self.root.join('x') - - def test_purebasename(self): - newpath = self.root.join('samplefile.py') - assert newpath.purebasename == 'samplefile' - - def test_multiple_parts(self): - newpath = self.root.join('samplefile.py') - dirname, purebasename, basename, ext = newpath.get( - 'dirname,purebasename,basename,ext') - assert dirname == str(self.root) - assert purebasename == 'samplefile' - assert basename == 'samplefile.py' - assert ext == '.py' - - def test_dotted_name_ext(self): - newpath = self.root.join('a.b.c') - ext = newpath.get('ext') - assert ext == '.c' - assert newpath.ext == '.c' - - def test_newext(self): - newpath = self.root.join('samplefile.py') - newext = newpath.new(ext='.txt') - assert newext.basename == "samplefile.txt" - assert newext.purebasename == "samplefile" - - def test_newbasename(self): - newpath = self.root.join('samplefile.py') - newbase = newpath.new(basename="sampledict.pickle") - assert newbase.basename == "sampledict.pickle" - assert newbase.dirpath() == newpath.dirpath() - - def test_iteration(self): - l = [] - for i in self.root: - l.append(i.basename) - assert 'sampledir' in l - - def test_read(self): - url = self.root.join("samplefile") - contents = url.read() - assert contents.find("samplefile") != -1 - - def test_readlines(self): - fn = self.root.join('samplefile') - contents = fn.readlines() - assert contents == ['samplefile\n'] - - def test_readlines_nocr(self): - fn = self.root.join('samplefile') - contents = fn.readlines(cr=0) - assert contents == ['samplefile', ''] - - def test_not_exists(self): - assert not self.root.join('does_not_exist').check() - assert self.root.join('does_not_exist').check(exists=0) - - def test_exists(self): - assert self.root.join("samplefile").check() - assert self.root.join("samplefile").check(exists=1) - - def test_dir(self): - #print repr(self.root.join("sampledir")) - assert self.root.join("sampledir").check(dir=1) - assert self.root.join('samplefile').check(notdir=1) - assert not self.root.join("samplefile").check(dir=1) - - def test_filter_dir(self): - assert checker(dir=1)(self.root.join("sampledir")) - - def test_file(self): - assert self.root.join('samplefile').check(file=1) - - def test_not_file(self): - assert not self.root.join("sampledir").check(file=1) - assert self.root.join("sampledir").check(file=0) - - def test_filter_file(self): - assert checker(file=1)(self.root.join("samplefile")) - - def test_fnmatch_file(self): - assert self.root.join("samplefile").check(fnmatch='s*e') - assert self.root.join("samplefile").check(notfnmatch='s*x') - assert not self.root.join("samplefile").check(fnmatch='s*x') - - #def test_fnmatch_dir(self): - - def test_non_existent(self): - assert self.root.join("sampledir.nothere").check(dir=0) - assert self.root.join("sampledir.nothere").check(file=0) - assert self.root.join("sampledir.nothere").check(notfile=1) - assert self.root.join("sampledir.nothere").check(notdir=1) - assert self.root.join("sampledir.nothere").check(notexists=1) - assert not self.root.join("sampledir.nothere").check(notfile=0) - - # pattern = self.root.sep.join(['s*file']) - # sfile = self.root.join("samplefile") - # assert sfile.check(fnmatch=pattern) - - def test_size(self): - url = self.root.join("samplefile") - assert url.size() > len("samplefile") - - def test_mtime(self): - url = self.root.join("samplefile") - assert url.mtime() > 0 - - def test_relto(self): - l=self.root.join("sampledir", "otherfile") - assert l.relto(self.root) == l.sep.join(["sampledir", "otherfile"]) - assert l.check(relto=self.root) - assert self.root.check(notrelto=l) - assert not self.root.check(relto=l) - - def test_relto_not_relative(self): - l1=self.root.join("sampledir") - l2=self.root.join("samplefile") - assert not l1.relto(l2) - - def test_listdir(self): - l = self.root.listdir() - assert self.root.join('sampledir') in l - assert self.root.join('samplefile') in l - - def test_listdir_fnmatchstring(self): - l = self.root.listdir('s*dir') - assert len(l), 1 - assert l[0], self.root.join('sampledir') - - def test_listdir_filter(self): - l = self.root.listdir(checker(dir=1)) - assert self.root.join('sampledir') in l - assert not self.root.join('samplefile') in l - - def test_listdir_sorted(self): - l = self.root.listdir(checker(basestarts="samplefile"), sort=True) - assert self.root.join('samplefile') == l[0] - assert self.root.join('samplefile.py') == l[1] - - def test_visit_nofilter(self): - l = [] - for i in self.root.visit(): - l.append(i.relto(self.root)) - assert "sampledir" in l - assert self.root.sep.join(["sampledir", "otherfile"]) in l - - def test_visit_filesonly(self): - l = [] - for i in self.root.visit(checker(file=1)): - l.append(i.relto(self.root)) - assert not "sampledir" in l - assert self.root.sep.join(["sampledir", "otherfile"]) in l - - def test_visit_norecurse(self): - l = [] - for i in self.root.visit(None, lambda x: not x.basename == "sampledir"): - l.append(i.relto(self.root)) - assert "sampledir" in l - assert not self.root.sep.join(["sampledir", "otherfile"]) in l - - def test_visit_filterfunc_is_string(self): - l = [] - for i in self.root.visit('*dir'): - l.append(i.relto(self.root)) - assert len(l), 2 - assert "sampledir" in l - assert "otherdir" in l - - def test_visit_ignore(self): - p = self.root / 'nonexisting' - assert list(p.visit(ignore=NotFound)) == [] - - def test_load(self): - p = self.root.join('sampledict.pickle') - obj = p.loadobj() - assert type(obj) is dict - assert obj.get('answer',None) == 42 - - def test_visit_nodotfiles(self): - l = [] - for i in self.root.visit(checker(dotfile=0)): - l.append(i.relto(self.root)) - assert "sampledir" in l - assert self.root.sep.join(["sampledir", "otherfile"]) in l - assert not ".dotfile" in l - - def test_visit_endswith(self): - l = [] - for i in self.root.visit(checker(endswith=".py")): - l.append(i.relto(self.root)) - assert not self.root.sep.join(["sampledir", "otherfile"]) in l - assert "samplefile.py" in l - - def test_endswith(self): - assert self.root.check(notendswith='.py') - x = self.root.join('samplefile.py') - assert x.check(endswith='.py') - - def test_cmp(self): - path1 = self.root.join('samplefile') - path2 = self.root.join('samplefile.py') - assert cmp(path1, path2) == cmp('samplefile', 'samplefile.py') - assert cmp(path1, path1) == 0 - - def test_contains_path(self): - path1 = self.root .join('samplefile') - assert path1 in self.root - assert not self.root.join('not existing') in self.root - - def test_contains_path_with_basename(self): - assert 'samplefile' in self.root - assert 'not existing' not in self.root - - def featuretest_check_docstring(self): - here = self.root.__class__ - assert here.check.__doc__ - doc = here.check.__doc__ - for name in dir(local.Checkers): - if name[0] != '_': - assert name in doc - - def test_copy_file(self): - otherdir = self.root.join('otherdir') - initpy = otherdir.join('__init__.py') - copied = otherdir.join('copied') - initpy.copy(copied) - try: - assert copied.check() - s1 = initpy.read() - s2 = copied.read() - assert s1 == s2 - finally: - if copied.check(): - copied.remove() - - def test_copy_dir(self): - otherdir = self.root.join('otherdir') - copied = self.root.join('newdir') - try: - otherdir.copy(copied) - assert copied.check(dir=1) - assert copied.join('__init__.py').check(file=1) - s1 = otherdir.join('__init__.py').read() - s2 = copied.join('__init__.py').read() - assert s1 == s2 - finally: - if copied.check(dir=1): - copied.remove(rec=1) - - def test_remove_file(self): - d = self.root.ensure('todeleted') - assert d.check() - d.remove() - assert not d.check() - - def test_remove_dir_recursive_by_default(self): - d = self.root.ensure('to', 'be', 'deleted') - assert d.check() - p = self.root.join('to') - p.remove() - assert not p.check() - - def test_move_file(self): - p = self.root.ensure('tomove') - newp = p.new(basename='moved') - p.move(newp) - assert newp.check(file=1) - assert not p.check() - - def test_move_directory(self): - source = self.root.join('to') - source.ensure('moved', 'somefile').write("42") - dest = source.new(basename='moveddir') - source.move(dest) - assert dest.check(dir=1) - assert dest.join('moved', 'somefile').read() == '42' - assert not source.check() Copied: py/dist/py/path/test/fscommon.py (from r6937, py/dist/py/path/test/commonfs.py) ============================================================================== --- py/dist/py/path/test/commonfs.py (original) +++ py/dist/py/path/test/fscommon.py Fri Oct 15 05:15:19 2004 @@ -1,19 +1,17 @@ -from py.path import checker, NotFound import py +from py.__impl__.path.test import common + +checker = py.path.checker def setuptestfs(path): if path.join('samplefile').check(): return - #print "setting up test fs for", repr(path) - samplefile = path.ensure('samplefile') samplefile.write('samplefile\n') - path.ensure('samplefile.py').write('import os') - d = {1:2, 'hello': 'world', 'answer': 42} - path.ensure('sampledict.pickle').dumpobj(d) + path.ensure('samplepickle').dumpobj(d) sampledir = path.ensure('sampledir', dir=1) sampledir.ensure('otherfile') @@ -21,81 +19,20 @@ otherdir = path.ensure('otherdir', dir=1) otherdir.ensure('__init__.py') -class CommonFSTests: +class CommonFSTests(common.CommonPathTests): root = None # subclasses have to provide a current 'root' attribute - def test_constructor_equality(self): - p = self.root.__class__(self.root) - assert p == self.root - - def test_eq_nonstring(self): - path1 = self.root.join('sampledir') - path2 = self.root.join('sampledir') - assert path1 == path2 - - def test_new_identical(self): - assert self.root == self.root.new() - - def test_join(self): - p = self.root.join('sampledir') - assert str(p) == self.root.sep.join([str(self.root), 'sampledir']) - - def test_join_normalized(self): - newpath = self.root.join(self.root.sep+'sampledir') - assert str(newpath) == self.root.sep.join([str(self.root), 'sampledir']) - newpath = self.root.join((self.root.sep*2) + 'sampledir') - assert str(newpath) == self.root.sep.join([str(self.root), 'sampledir']) - - def test_join_noargs(self): - newpath = self.root.join() - assert self.root == newpath - def test_join_div_operator(self): newpath = self.root / '/sampledir' / '/test//' newpath2 = self.root.join('sampledir', 'test') assert newpath == newpath2 - def test_parts(self): - newpath = self.root / 'sampledir' / 'otherfile' - par = newpath.parts()[-3:] - assert par == [self.root, self.root/'sampledir', newpath] - - #def test_parents_nonexisting_file(self): - # newpath = self.root / 'dirnoexist' / 'nonexisting file' - # par = list(newpath.parents()) - # assert par[:2] == [self.root / 'dirnoexist', self.root] - - # following feature is disabled - #def test_getitem(self): - # newpath = self.root['sampledir']['test'] - # newpath2 = self.root.join('sampledir', 'test') - # assert newpath == newpath2 - - def test_basename_checks(self): - newpath = self.root.join('sampledir') - assert newpath.check(basename='sampledir') - assert newpath.check(notbasename='xyz') - assert newpath.basename == 'sampledir' - def test_ext(self): newpath = self.root.join('sampledir.ext') assert newpath.ext == '.ext' newpath = self.root.join('sampledir') assert not newpath.ext - def test_basename(self): - newpath = self.root.join('sampledir') - assert newpath.check(basename='sampledir') - assert newpath.basename, 'sampledir' - - def test_dirpath(self): - newpath = self.root.join('sampledir') - assert newpath.dirpath() == self.root - - def test_dirpath_with_args(self): - newpath = self.root.join('sampledir') - assert newpath.dirpath('x') == self.root.join('x') - def test_purebasename(self): newpath = self.root.join('samplefile.py') assert newpath.purebasename == 'samplefile' @@ -121,18 +58,6 @@ assert newext.basename == "samplefile.txt" assert newext.purebasename == "samplefile" - def test_newbasename(self): - newpath = self.root.join('samplefile.py') - newbase = newpath.new(basename="sampledict.pickle") - assert newbase.basename == "sampledict.pickle" - assert newbase.dirpath() == newpath.dirpath() - - def test_iteration(self): - l = [] - for i in self.root: - l.append(i.basename) - assert 'sampledir' in l - def test_read(self): url = self.root.join("samplefile") contents = url.read() @@ -148,23 +73,6 @@ contents = fn.readlines(cr=0) assert contents == ['samplefile', ''] - def test_not_exists(self): - assert not self.root.join('does_not_exist').check() - assert self.root.join('does_not_exist').check(exists=0) - - def test_exists(self): - assert self.root.join("samplefile").check() - assert self.root.join("samplefile").check(exists=1) - - def test_dir(self): - #print repr(self.root.join("sampledir")) - assert self.root.join("sampledir").check(dir=1) - assert self.root.join('samplefile').check(notdir=1) - assert not self.root.join("samplefile").check(dir=1) - - def test_filter_dir(self): - assert checker(dir=1)(self.root.join("sampledir")) - def test_file(self): assert self.root.join('samplefile').check(file=1) @@ -172,14 +80,6 @@ assert not self.root.join("sampledir").check(file=1) assert self.root.join("sampledir").check(file=0) - def test_filter_file(self): - assert checker(file=1)(self.root.join("samplefile")) - - def test_fnmatch_file(self): - assert self.root.join("samplefile").check(fnmatch='s*e') - assert self.root.join("samplefile").check(notfnmatch='s*x') - assert not self.root.join("samplefile").check(fnmatch='s*x') - #def test_fnmatch_dir(self): def test_non_existent(self): @@ -202,45 +102,6 @@ url = self.root.join("samplefile") assert url.mtime() > 0 - def test_relto(self): - l=self.root.join("sampledir", "otherfile") - assert l.relto(self.root) == l.sep.join(["sampledir", "otherfile"]) - assert l.check(relto=self.root) - assert self.root.check(notrelto=l) - assert not self.root.check(relto=l) - - def test_relto_not_relative(self): - l1=self.root.join("sampledir") - l2=self.root.join("samplefile") - assert not l1.relto(l2) - - def test_listdir(self): - l = self.root.listdir() - assert self.root.join('sampledir') in l - assert self.root.join('samplefile') in l - - def test_listdir_fnmatchstring(self): - l = self.root.listdir('s*dir') - assert len(l), 1 - assert l[0], self.root.join('sampledir') - - def test_listdir_filter(self): - l = self.root.listdir(checker(dir=1)) - assert self.root.join('sampledir') in l - assert not self.root.join('samplefile') in l - - def test_listdir_sorted(self): - l = self.root.listdir(checker(basestarts="samplefile"), sort=True) - assert self.root.join('samplefile') == l[0] - assert self.root.join('samplefile.py') == l[1] - - def test_visit_nofilter(self): - l = [] - for i in self.root.visit(): - l.append(i.relto(self.root)) - assert "sampledir" in l - assert self.root.sep.join(["sampledir", "otherfile"]) in l - def test_visit_filesonly(self): l = [] for i in self.root.visit(checker(file=1)): @@ -248,27 +109,8 @@ assert not "sampledir" in l assert self.root.sep.join(["sampledir", "otherfile"]) in l - def test_visit_norecurse(self): - l = [] - for i in self.root.visit(None, lambda x: not x.basename == "sampledir"): - l.append(i.relto(self.root)) - assert "sampledir" in l - assert not self.root.sep.join(["sampledir", "otherfile"]) in l - - def test_visit_filterfunc_is_string(self): - l = [] - for i in self.root.visit('*dir'): - l.append(i.relto(self.root)) - assert len(l), 2 - assert "sampledir" in l - assert "otherdir" in l - - def test_visit_ignore(self): - p = self.root / 'nonexisting' - assert list(p.visit(ignore=NotFound)) == [] - def test_load(self): - p = self.root.join('sampledict.pickle') + p = self.root.join('samplepickle') obj = p.loadobj() assert type(obj) is dict assert obj.get('answer',None) == 42 @@ -281,40 +123,11 @@ assert self.root.sep.join(["sampledir", "otherfile"]) in l assert not ".dotfile" in l - def test_visit_endswith(self): - l = [] - for i in self.root.visit(checker(endswith=".py")): - l.append(i.relto(self.root)) - assert not self.root.sep.join(["sampledir", "otherfile"]) in l - assert "samplefile.py" in l - def test_endswith(self): - assert self.root.check(notendswith='.py') - x = self.root.join('samplefile.py') - assert x.check(endswith='.py') - - def test_cmp(self): - path1 = self.root.join('samplefile') - path2 = self.root.join('samplefile.py') - assert cmp(path1, path2) == cmp('samplefile', 'samplefile.py') - assert cmp(path1, path1) == 0 - - def test_contains_path(self): - path1 = self.root .join('samplefile') - assert path1 in self.root - assert not self.root.join('not existing') in self.root - - def test_contains_path_with_basename(self): - assert 'samplefile' in self.root - assert 'not existing' not in self.root - - def featuretest_check_docstring(self): - here = self.root.__class__ - assert here.check.__doc__ - doc = here.check.__doc__ - for name in dir(local.Checkers): - if name[0] != '_': - assert name in doc + chk = checker(endswith='pickle') + assert not chk(self.root) + assert not chk(self.root.join('samplefile')) + assert chk(self.root.join('somepickle')) def test_copy_file(self): otherdir = self.root.join('otherdir') Modified: py/dist/py/path/test/svncommonfs.py ============================================================================== --- py/dist/py/path/test/svncommonfs.py (original) +++ py/dist/py/path/test/svncommonfs.py Fri Oct 15 05:15:19 2004 @@ -1,6 +1,6 @@ import py from py import path, test, process -from py.__impl__.path.test.commonfs import CommonFSTests +from py.__impl__.path.test.fscommon import CommonFSTests from py.__impl__.path.svn import cache class CommonSvnTests(CommonFSTests): Deleted: /py/dist/py/path/test_api.py ============================================================================== --- /py/dist/py/path/test_api.py Fri Oct 15 05:15:19 2004 +++ (empty file) @@ -1,59 +0,0 @@ -from py import path, test -from py.__impl__.path.svnwc.test_command import getrepowc - -class TestAPI: - def __init__(self): - self.root = test.config.tmpdir.ensure('local', dir=1) - - def repr_eval_test(self, p): - r = repr(p) - from py.path import local,svnurl, svnwc, py - y = eval(r) - assert y == p - - def test_defaultlocal(self): - p = path.local() - assert hasattr(p, 'atime') - assert hasattr(p, 'group') - assert hasattr(p, 'setmtime') - assert p.check() - assert p.check(local=1) - assert p.check(svnwc=0) - assert not p.check(svnwc=1) - self.repr_eval_test(p) - - #assert p.std.path() - - def test_local(self): - p = path.local() - assert hasattr(p, 'atime') - assert hasattr(p, 'setmtime') - assert p.check() - assert p.check(local=1) - self.repr_eval_test(p) - - def test_svnurl(self): - p = path.svnurl('http://codespeak.net/svn/py.path') - assert p.check(svnurl=1) - self.repr_eval_test(p) - - def test_svnwc(self): - p = path.svnwc(self.root) - assert p.check(svnwc=1) - self.repr_eval_test(p) - - def test_passing_svnurl(self): - repourl, rootwc = getrepowc() - p = path.svnurl(repourl) - assert p.check() - w = path.svnwc(None, p) - self.repr_eval_test(p) - - def test_pypath(self): - p = path.py('smtplib.SMTP') - self.repr_eval_test(p) - - -if __name__ == '__main__': - test.main() - From hpk at codespeak.net Fri Oct 15 05:57:10 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 15 Oct 2004 05:57:10 +0200 (MEST) Subject: [py-svn] r6948 - in py/dist/py: . path/fspy path/pypath path/test test test/report test/report/text Message-ID: <20041015035710.956575A0E7@thoth.codespeak.net> Author: hpk Date: Fri Oct 15 05:57:09 2004 New Revision: 6948 Removed: py/dist/py/path/pypath/ Modified: py/dist/py/__init__.py py/dist/py/path/fspy/fspy.py py/dist/py/path/test/test_api.py py/dist/py/pytest.conf py/dist/py/test/collect.py py/dist/py/test/report/test_memo.py py/dist/py/test/report/text/summary.py py/dist/py/test/test_collect.py Log: ok, the old and broken py.path.pypath implementation is now completly gone. instead we have the nice py.path.fspy path which offers a reliable way to address python objects within a filesystem. With py.path.fspy(filepath, modulepath) you initialize a path with which you can subsequently inspect python objects on. For starters, this simplified the test collector implementation quite a bit. I guess that naming it 'py.path.fspython' instead of the short ..fspy may look nicer ?! Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Fri Oct 15 05:57:09 2004 @@ -5,18 +5,17 @@ 'path.invchecker': './path/common.invchecker', 'path.svnurl': './path/svn/command.SvnCommandPath', 'path.svnwc': './path/svnwc/command.SvnWCCommandPath', - 'path.py': './path/pypath/pypath.PyPath', 'path.fspy': './path/fspy/fspy.Fspy', 'path.NotFound': './path/error.FileNotFound', 'path.Denied': './path/error.PermissionDenied', 'path.NoDirectory': './path/error.NoDirectory', 'path.Invalid': './path/error.Invalid', - 'test.collect.Collector': './test/collect.Collector', - 'test.collect.Directory': './test/collect.Directory', - 'test.collect.Execfile': './test/collect.Execfile', - 'test.collect.PyCollector': './test/collect.PyCollector', - 'test.collect.Error': './test/collect.Error', + 'test.collect.Collector': './test/collect.Collector', + 'test.collect.Directory': './test/collect.Directory', + 'test.collect.Module': './test/collect.Module', + 'test.collect.PyCollector':'./test/collect.PyCollector', + 'test.collect.Error': './test/collect.Error', 'test.run': './test/run', 'test.main': './test/cmdline.main', 'test.raises': './test/raises.raises', Modified: py/dist/py/path/fspy/fspy.py ============================================================================== --- py/dist/py/path/fspy/fspy.py (original) +++ py/dist/py/path/fspy/fspy.py Fri Oct 15 05:57:09 2004 @@ -14,6 +14,9 @@ """ a path abstraction addressing python objects in a file system. """ sep = '.' def __new__(cls, fspath='', modpath=''): + if isinstance(fspath, cls) and not modpath: + return fspath + assert isinstance(modpath, str) self = object.__new__(cls) self.modpath = modpath @@ -83,10 +86,9 @@ return sys.modules[modname] except KeyError: #print "trying importing", modname - try: - mod = imp.load_source(modname, str(self.fspath)) - except: - self.fspath._except(sys.exc_info()) + if not self.fspath.check(file=1): + raise py.path.NotFound(self.fspath) + mod = imp.load_source(modname, str(self.fspath)) mod.__name__ = modname sys.modules[modname] = mod return mod Modified: py/dist/py/path/test/test_api.py ============================================================================== --- py/dist/py/path/test/test_api.py (original) +++ py/dist/py/path/test/test_api.py Fri Oct 15 05:57:09 2004 @@ -7,7 +7,7 @@ def repr_eval_test(self, p): r = repr(p) - from py.path import local,svnurl, svnwc, py + from py.path import local,svnurl, svnwc, fspy y = eval(r) assert y == p @@ -33,7 +33,7 @@ self.repr_eval_test(p) def test_svnurl(self): - p = path.svnurl('http://codespeak.net/svn/py.path') + p = path.svnurl('http://codespeak.net/svn/py') assert p.check(svnurl=1) self.repr_eval_test(p) @@ -49,9 +49,9 @@ w = path.svnwc(None, p) self.repr_eval_test(p) - def test_pypath(self): - p = path.py('smtplib.SMTP') - self.repr_eval_test(p) + #def test_fspy(self): + # p = path.py('smtplib.SMTP') + # self.repr_eval_test(p) if __name__ == '__main__': Modified: py/dist/py/pytest.conf ============================================================================== --- py/dist/py/pytest.conf (original) +++ py/dist/py/pytest.conf Fri Oct 15 05:57:09 2004 @@ -1,5 +1,5 @@ # standard options (modified from cmdline) -pythonexecutable = 'python2.3' +pythonexecutable = 'python2.2' verbose = 0 nocapture = False Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Fri Oct 15 05:57:09 2004 @@ -2,6 +2,7 @@ import sys, inspect, types, imp from py import path, test +import py class Error(object): """ represents a non-fatal exception while collecting. """ @@ -44,36 +45,23 @@ class FSCollector(Collector): def __init__(self, fspath): - self.fspath = path.local(fspath) + self.fspath = py.path.local(fspath) def __repr__(self): - return '%s(%r)' %(self.__class__.__name__, self.fspath.relto(path.local())) + return '%s(%r)' %(self.__class__.__name__, + self.fspath.relto(py.path.local())) class Directory(FSCollector): def fil(self, fspath): return (fspath.check(file=1, fnmatch='test_*.py') or fspath.check(file=1, fnmatch='*_test.py')) - rec = path.checker(dir=1, dotfile=0, link=0) + rec = py.path.checker(dir=1, dotfile=0, link=0) def __iter__(self): try: for fspath in self.fspath.listdir(sort=True): if self.rec(fspath): yield self.__class__(fspath) elif self.fil(fspath): - yield Execfile(fspath) - except: - yield self._except() - -class Execfile(FSCollector): - def __iter__(self): - try: - self.fspath.stat() # to throw correct filenotfound errors - name = "%s_%s" % (self.fspath.dirpath().basename, self.fspath.purebasename ) - mod = imp.load_source(name, str(self.fspath)) - mod.__name__ = name - pypath = path.py('', ns=mod) - #print type(Module(pypath)) - for x in Module(pypath): - yield x + yield Module(py.path.fspy(fspath)) except: yield self._except() @@ -82,22 +70,24 @@ # ---------------------------------------------- class PyCollector(Collector): - def __init__(self, pypath): - self.pypath = path.py(pypath) - self.yielders = path.py('', ns=self).listobj(basestarts='collect_') + def __init__(self, fspy): + self.fspy = py.path.fspy(fspy) + self.yielders = [getattr(self, x) + for x in dir(self.__class__) + if x.startswith('collect_')] def __repr__(self): - return '%s(%r)' %(self.__class__.__name__, str(self.pypath)) + return '%s(%r)' %(self.__class__.__name__, str(self.fspy)) def __iter__(self): try: # we want to sort according to lineno, so here goes # the generator lazyness l = [] - for pypath in self.pypath.listdir(): + for pypath in self.fspy.listdir(): for meth in self.yielders: for x in meth(pypath): - x.fspath = self.pypath.getfile() + x.fspath = self.fspy.fspath sortvalue = self.getsortvalue(x) l.append((sortvalue, x)) l.sort() @@ -123,29 +113,27 @@ return (getattr(obj, 'co_filename', None), getattr(obj, 'co_firstlineno', sys.maxint)) - def samemodule(self, pypath): - mod1 = pypath.getmodule() - mod2 = self.pypath.getmodule() - return not mod1 or mod1 is mod2 - class Module(PyCollector): def __iter__(self): - iter = self.pypath.join('Collector') - if iter.check(exists=True): - iter = iter.resolve()(self.pypath) - else: - iter = super(Module, self).__iter__() - for x in iter: - yield x + try: + iter = self.fspy.join('Collector') + if iter.check(exists=True): + iter = iter.resolve()(self.fspy) + else: + iter = super(Module, self).__iter__() + for x in iter: + yield x + except: + yield self._except() def collect_function(self, pypath): if pypath.check(func=1, basestarts='test_'): - if self.samemodule(pypath): + if self.fspy.samefile(pypath): yield self.Unit(pypath) def collect_class(self, pypath): #print "checking %r (pypath: %r)" % (pypath.resolve(), pypath) - if pypath.check(basestarts='Test') and self.samemodule(pypath): + if pypath.check(basestarts='Test') and self.fspy.samefile(pypath): obj = pypath.resolve() if inspect.isclass(obj) and not getattr(obj, 'disabled', 0): yield Class(pypath) Modified: py/dist/py/test/report/test_memo.py ============================================================================== --- py/dist/py/test/report/test_memo.py (original) +++ py/dist/py/test/report/test_memo.py Fri Oct 15 05:57:09 2004 @@ -1,6 +1,6 @@ -from py import test, path +import py -datadir = test.config.tmpdir +datadir = py.test.config.tmpdir #def test_equal_should_raise(): # check.equal(1,2) @@ -10,10 +10,10 @@ # try: # def test_memoreporter(): - reporter = test.MemoReporter() + reporter = py.test.MemoReporter() p = datadir.join('memoimport.py') p.write('raise IOError') - collector = test.collect.Execfile(p) + collector = py.test.collect.Module(p) #main(collector=collector, reporter=reporter) #collect_errors = reporter.getlist(collect.Error) #assert len(collect_errors) == 1 Modified: py/dist/py/test/report/text/summary.py ============================================================================== --- py/dist/py/test/report/text/summary.py (original) +++ py/dist/py/test/report/text/summary.py Fri Oct 15 05:57:09 2004 @@ -187,7 +187,7 @@ def repr_traceback(self, unit, tb, tbindex=-1): self.out.line(">>> %s" % unit.pypath) - self.repr_traceback_raw(unit.pypath.getfile(), tb, tbindex) + self.repr_traceback_raw(unit.pypath.fspath, tb, tbindex) def repr_traceback_raw(self, fspath, tb, tbindex=-1): if fspath and not self.option.fulltrace: Modified: py/dist/py/test/test_collect.py ============================================================================== --- py/dist/py/test/test_collect.py (original) +++ py/dist/py/test/test_collect.py Fri Oct 15 05:57:09 2004 @@ -10,7 +10,7 @@ def test_failing_import_execfile(): fn = datadir / 'failingimport.py' - l = list(collect.Execfile(fn)) + l = list(collect.Module(path.fspy(fn))) assert l ex, = l assert issubclass(ex.excinfo[0], ImportError) @@ -20,40 +20,37 @@ fil = path.checker(basestarts="testspecial_", ext='.py') l = list(MyDirectory(datadir)) assert len(l) == 1 - assert isinstance(l[0], collect.Execfile) + assert isinstance(l[0], collect.Module) l2 = list(l[0]) assert l2 exc = l2[0] assert isinstance(exc, collect.Error) assert issubclass(exc.excinfo[0], ImportError) -def test_execfile_file_not_found(): +def test_module_file_not_found(): fn = testdir.join('nada','no') - l = list(collect.Execfile(fn)) + l = list(collect.Module(fn)) assert len(l) == 1 assert isinstance(l[0], collect.Error) import traceback print traceback.print_exception(*l[0].excinfo) - assert isinstance(l[0].excinfo[1], (IOError, OSError)) + assert isinstance(l[0].excinfo[1], path.NotFound) def test_syntax_error_in_module(): - modpath = 'py.__impl__.utest.test.data.syntax_error.whatever' + modpath = 'py.__impl__.test.data.syntax_error.whatever' l2 = list(collect.Module(modpath)) assert len(l2) == 1 assert isinstance(l2[0], collect.Error) assert issubclass(l2[0].excinfo[0], path.Invalid) def test_disabled_class(): - class x: - class y: - disabled = True - def test_method(self): pass - l = list(collect.Class(path.py('', ns=x))) - assert not l + fspy = path.fspy(datadir.join('disabled.py')) + l = list(collect.Class(fspy)) + assert len(l) == 0 class TestCustomCollector: def test_custom_collect(self): - l = list(collect.Execfile(datadir.join('Collector.py'))) + l = list(collect.Module(datadir.join('Collector.py'))) assert len(l) == 3 for unit in l: assert isinstance(unit, test.Unit) @@ -83,5 +80,3 @@ self.reslist.append(3) def test_4(self): assert self.reslist == [1,2,3] - -test.main() From hpk at codespeak.net Fri Oct 15 05:57:30 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 15 Oct 2004 05:57:30 +0200 (MEST) Subject: [py-svn] r6949 - py/dist/py/test/test/data Message-ID: <20041015035730.DF4D55A0E7@thoth.codespeak.net> Author: hpk Date: Fri Oct 15 05:57:30 2004 New Revision: 6949 Added: py/dist/py/test/test/data/disabled.py Log: added forgotten file Added: py/dist/py/test/test/data/disabled.py ============================================================================== --- (empty file) +++ py/dist/py/test/test/data/disabled.py Fri Oct 15 05:57:30 2004 @@ -0,0 +1,4 @@ +class x: + class y: + disabled = True + def test_method(self): pass From hpk at codespeak.net Fri Oct 15 13:29:06 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 15 Oct 2004 13:29:06 +0200 (MEST) Subject: [py-svn] r6951 - in py/dist/py: . execnet path/fspy path/svn path/svnwc path/test test Message-ID: <20041015112906.E3A265A0E7@thoth.codespeak.net> Author: hpk Date: Fri Oct 15 13:29:06 2004 New Revision: 6951 Added: py/dist/py/path/svn/svncommon.py - copied unchanged from r6950, py/dist/py/path/svn/common.py py/dist/py/path/svn/svntestbase.py - copied, changed from r6950, py/dist/py/path/test/svncommonfs.py py/dist/py/path/svn/test_urlcommand.py - copied, changed from r6950, py/dist/py/path/svn/test_command.py py/dist/py/path/svn/test_wccommand.py - copied, changed from r6950, py/dist/py/path/svnwc/test_command.py py/dist/py/path/svn/urlcommand.py - copied, changed from r6950, py/dist/py/path/svn/command.py py/dist/py/path/svn/wccommand.py - copied, changed from r6950, py/dist/py/path/svnwc/command.py Removed: py/dist/py/path/fspy/pypath_test.py py/dist/py/path/svn/command.py py/dist/py/path/svn/common.py py/dist/py/path/svn/test_command.py py/dist/py/path/svnwc/ py/dist/py/path/test/svncommonfs.py Modified: py/dist/py/__init__.py py/dist/py/execnet/register.py py/dist/py/path/test/test_api.py py/dist/py/pytest.conf py/dist/py/test/cmdline.py Log: - hah! the power of export name control has let me do a large implementation level refactoring for the svn-paths "py.path.svnurl" and "py.path.svnwc" without affecting any outside callers/users of the API. IMHO this shows that "export name control" is the way to go ... because one can reorganize the implementation a) without affecting the caller side b) without requiring the caller side to follow funny IInterface schemes but simply see "normal" python object from the outside Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Fri Oct 15 13:29:06 2004 @@ -3,8 +3,8 @@ 'path.local': './path/local/local.LocalPath', 'path.checker': './path/common.checker', 'path.invchecker': './path/common.invchecker', - 'path.svnurl': './path/svn/command.SvnCommandPath', - 'path.svnwc': './path/svnwc/command.SvnWCCommandPath', + 'path.svnurl': './path/svn/urlcommand.SvnCommandPath', + 'path.svnwc': './path/svn/wccommand.SvnWCCommandPath', 'path.fspy': './path/fspy/fspy.Fspy', 'path.NotFound': './path/error.FileNotFound', 'path.Denied': './path/error.PermissionDenied', Modified: py/dist/py/execnet/register.py ============================================================================== --- py/dist/py/execnet/register.py (original) +++ py/dist/py/execnet/register.py Fri Oct 15 13:29:06 2004 @@ -47,7 +47,10 @@ self.trace("could not receive child PID") else: self.trace("waiting for pid %s" % pid) - os.waitpid(pid, 0) + try: + os.waitpid(pid, 0) + except OSError: + self.trace("child process %s already dead?" %pid) class SocketGateway(InstallableGateway): def __init__(self, host, port): Deleted: /py/dist/py/path/fspy/pypath_test.py ============================================================================== --- /py/dist/py/path/fspy/pypath_test.py Fri Oct 15 13:29:06 2004 +++ (empty file) @@ -1,168 +0,0 @@ -import sys, os -from py import path, test -from py.__impl__.path.pypath import pypath - -class TestPyPath: - root = path.py(pypath.__name__) - - def test_join(self): - p = self.root.join('PyPath') - obj = p.resolve() - assert obj is path.py - - def test_listdir_module(self): - l = self.root.listdir() - basenames = [x.basename for x in l] - dlist = dir(pypath) - for name in dlist: - assert name in basenames - for name in basenames: - assert name in dlist - - def test_listdir_class(self): - l = self.root.join('PyPath').listdir() - basenames = [x.basename for x in l] - dlist = dir(pypath.PyPath) - for name in dlist: - assert name in basenames - for name in basenames: - assert name in dlist - - def listobj(self): - l = self.root.listobj(basestarts='path') - assert len(l) == 1 - assert l[0] == path - - def test_visit(self): - PyPath = pypath.PyPath - l = list(self.root.visit(path.checker(basename='PyPath'))) - assert len(l) == 1 - obj = l[0] - assert str(obj).endswith('path.pypath.pypath.PyPath') - assert obj.resolve() is PyPath - - def test_visit_fnmatch(self): - PyPath = pypath.PyPath - l = list(self.root.visit('PyPath')) - assert len(l) == 1 - obj = l[0] - assert str(obj).endswith('path.pypath.pypath.PyPath') - assert obj.resolve() is PyPath - - def test_specified_base_empty_path(self): - p = path.py('', ns=ExampleClass) - l = p.listdir() - basenames = [x.basename for x in l] - assert 'testattr' in basenames - - def test_join_from_empty(self): - p = path.py('') - n = p.join('tokenize') - assert str(n) == 'tokenize' - - p = path.py('', ns=os) - n = p.join('getlogin') - assert str(n) == 'getlogin' - - def test_unspecifiedpypath_lists_modules(self): - p = path.py('') - l = p.listdir() - for i in l: - assert '.' not in str(i) - - for j in sys.modules: - for i in l: - if j.startswith(str(i)): - break - else: - self.fail("%s is not in sys.modules") - - def test_main_works(self): - m = path.py('__main__') - import __main__ - assert m.resolve() is __main__ - - def test_relto(self): - m1 = path.py('a.b.c.d') - m2 = path.py('a.b') - m3 = path.py('') - res = m1.relto(m2) - assert str(res) == 'c.d' - assert m2.relto(m3) == str(m2) - - def test_basename(self): - m1 = path.py('a.b.hello') - assert m1.basename == 'hello' - assert m1.check(basename='hello') - assert not m1.check(basename='nono') - assert m1.check(basestarts='he') - assert not m1.check(basestarts='42') - - def test_dirpath(self): - m1 = path.py('a.b.hello') - m2 = path.py('a.b') - m3 = path.py('a') - m4 = path.py() - assert m1.dirpath() == m2 - assert m2.dirpath() == m3 - assert m3.dirpath() == m4 - - def test_function(self): - class A: - i = 3 - def func(self): - pass - p = path.py('func', ns=A) - assert p.check(func=1) - p = path.py('i', ns=A) - assert p.check(func=0) - - def test_hashing_equality(self): - x = path.py('os.path') - y = path.py('os.path') - assert x == y - assert hash(x) == hash(y) - - def test_parents(self): - x = path.py('os.path.abspath') - l = x.parts() - assert len(l) == 4 - assert path.py('') == l[0] - assert path.py('os') == l[1] - assert path.py('os.path') == l[2] - assert path.py('os.path.abspath') == l[3] - -class TestEval: - disabled = True - def test_funccall(self): - p = path.py('os.path.join("a", "b")') - s = p.resolve() - assert s == os.path.join("a", "b") - - def test_invalid1(self): - p = path.py('os.path.qwe("a", "b")') - s = test.raises(path.NotFound, "p.resolve()") - - def test_syntaxerror(self): - p = path.py('os.path.qwe("a", ') - s = test.raises(ValueError, "p.resolve()") - -class TestErrors: - def test_FileNotFound(self): - p = path.py('somesuch') - test.raises(path.NotFound, p.resolve) - p = path.py('email.whatever') - test.raises(path.NotFound, p.resolve) - - def test_attributeerror(self): - p = path.py('os.path.qabspath') - test.raises(path.NotFound, p.resolve) - - #def test_ImportError(): - # p = path.py('__std.utest.test.data.failingimport.someattr') - # utest.raises(ImportError, p.resolve) - -class ExampleClass: - testattr = 1 - -test.main() Deleted: /py/dist/py/path/svn/command.py ============================================================================== --- /py/dist/py/path/svn/command.py Fri Oct 15 13:29:06 2004 +++ (empty file) @@ -1,231 +0,0 @@ -""" - -module defining a subversion path object based on the external -command 'svn'. - -""" - -import os, sys, time, re -from py import path, process -from py.__impl__.path import common -from py.__impl__.path import error -from py.__impl__.path.svn import common as svncommon - -class SvnCommandPath(svncommon.SvnPathBase): - def __new__(cls, path, rev=None): - if isinstance(path, cls): - if path.rev == rev: - return path - self = object.__new__(cls) - self.strpath = path.rstrip('/') - self.rev = rev - return self - - def __repr__(self): - if self.rev == -1: - return 'svnurl(%r)' % self.strpath - else: - return 'svnurl(%r, %r)' % (self.strpath, self.rev) - - def _svn(self, cmd, *args): - if self.rev is None: - l = ['svn %s' % cmd] - else: - l = ['svn %s -r %d' % (cmd, self.rev)] - args = map(lambda x: '"%s"' % str(x), args) - l.extend(args) - l.append(str(self.strpath)) - # fixing the locale because we can't otherwise parse - string = svncommon.fixlocale() + " ".join(l) - #print "execing", string - out = process.cmdexec(string) - return out - - def _svnwrite(self, cmd, *args): - l = ['svn %s' % cmd] - args = map(lambda x: '"%s"' % str(x), args) - l.extend(args) - l.append(str(self.strpath)) - # fixing the locale because we can't otherwise parse - string = svncommon.fixlocale() + " ".join(l) - #print "execing", string - out = process.cmdexec(string) - return out - - def open(self, mode='r'): - 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.strpath, )) - else: - return popen(svncommon.fixlocale() + - 'svn cat -r %s "%s"' % (self.rev, self.strpath)) - - def mkdir(self, commit_msg=None): - if commit_msg: - self._svnwrite('mkdir', '-m', commit_msg) - else: - self._svnwrite('mkdir') - - def copy(self, target, msg='auto'): - if getattr(target, 'rev', None) is not None: - raise path.Invalid("target can't have a revision: %r" % target) - process.cmdexec("svn copy -m %r %s %s" %(msg, str(self), str(target))) - - def remove(self, rec=1, msg='auto'): - if self.rev is not None: - raise path.Invalid("cannot remove revisioned object: %r" % self) - process.cmdexec("svn rm -m %r %s" %(msg, str(self))) - - def _propget(self, name): - res = self._svn('propget', name) - return res[:-1] # strip trailing newline - - def _proplist(self): - res = self._svn('proplist') - lines = res.split('\n') - lines = map(str.strip, lines[1:]) - return svncommon.PropListDict(self, lines) - - def _listdir_nameinfo(self): - """ return sequence of name-info directory entries of self """ - try: - res = self._svn('ls', '-v') - except process.cmdexec.Error, e: - if e.err.find('non-existent in that revision') != -1: - raise error.FileNotFound(self, e.err) - elif e.err.find('not part of a repository')!=-1: - raise IOError, e.err - elif e.err.find('Unable to open')!=-1: - raise IOError, e.err - elif e.err.lower().find('method not allowed')!=-1: - raise IOError, e.err - raise - lines = res.split('\n') - nameinfo_seq = [] - for lsline in lines: - if lsline: - info = InfoSvnCommand(lsline) - nameinfo_seq.append((info._name, info)) - return nameinfo_seq - - def log(self, rev_start=None, rev_end=1, verbose=False): - assert self.check() #make it simpler for the pipe - rev_start = rev_start is None and _Head or rev_start - rev_end = rev_end is None and _Head or rev_end - - if rev_start is _Head and rev_end == 1: - rev_opt = "" - 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)) - from xml.dom import minidom - tree = minidom.parse(xmlpipe) - result = [] - for logentry in filter(None, tree.firstChild.childNodes): - if logentry.nodeType == logentry.ELEMENT_NODE: - result.append(LogEntry(logentry)) - return result - -#01234567890123456789012345678901234567890123467 -# 2256 hpk 165 Nov 24 17:55 __init__.py -# -class InfoSvnCommand: - #lspattern = re.compile(r'(\D*)(\d*)\s*(\w*)\s*( - def __init__(self, line): - # this is a typical line from 'svn ls http://...' - #_ 1127 jum 0 Jul 13 15:28 branch/ - l = [line[:7], line[7:16], line[16:27], line[27:40], line[41:]] - l = map(str.lstrip, l) - - self._name = l.pop() - if self._name[-1] == '/': - self._name = self._name[:-1] - self.kind = 'dir' - else: - self.kind = 'file' - #self.has_props = l.pop(0) == 'P' - self.created_rev = int(l[0]) - self.last_author = l[1] - self.size = l[2] and int(l[2]) or 0 - datestr = l[3] - self.mtime = parse_time_with_missing_year(datestr) - self.time = self.mtime * 1000000 - - def __eq__(self, other): - return self.__dict__ == other.__dict__ - - -#____________________________________________________ -# -# helper functions -#____________________________________________________ -def parse_time_with_missing_year(timestr): - """ analyze the time part from a single line of "svn ls -v" - the svn output doesn't show the year makes the 'timestr' - ambigous. - """ - t_now = time.gmtime() - - tparts = timestr.split() - month = time.strptime(tparts.pop(0), '%b')[1] - day = time.strptime(tparts.pop(0), '%d')[2] - last = tparts.pop(0) # year or hour:minute - try: - year = time.strptime(last, '%Y')[0] - hour = minute = 0 - except ValueError: - hour, minute = time.strptime(last, '%H:%M')[3:5] - year = t_now[0] - - t_result = (year, month, day, hour, minute, 0,0,0,0) - if t_result > t_now: - year -= 1 - t_result = (year, month, day, hour, minute, 0,0,0,0) - return time.mktime(t_result) - -class PathEntry: - def __init__(self, ppart): - self.strpath = ppart.firstChild.nodeValue.encode('UTF-8') - self.action = ppart.getAttribute('action').encode('UTF-8') - if self.action == 'A': - self.copyfrom_path = ppart.getAttribute('copyfrom-path').encode('UTF-8') - if self.copyfrom_path: - self.copyfrom_rev = int(ppart.getAttribute('copyfrom-rev')) - -class LogEntry: - def __init__(self, logentry): - self.rev = int(logentry.getAttribute('revision')) - for lpart in filter(None, logentry.childNodes): - if lpart.nodeType == lpart.ELEMENT_NODE: - if lpart.nodeName == u'author': - self.author = lpart.firstChild.nodeValue.encode('UTF-8') - elif lpart.nodeName == u'msg': - if lpart.firstChild: - self.msg = lpart.firstChild.nodeValue.encode('UTF-8') - else: - self.msg = '' - elif lpart.nodeName == u'date': - #2003-07-29T20:05:11.598637Z - timestr = lpart.firstChild.nodeValue.encode('UTF-8') - self.date = svncommon.parse_apr_time(timestr) - elif lpart.nodeName == u'paths': - self.strpaths = [] - for ppart in filter(None, lpart.childNodes): - if ppart.nodeType == ppart.ELEMENT_NODE: - self.strpaths.append(PathEntry(ppart)) - def __repr__(self): - return '' % ( - self.rev, self.author, self.date) - - -class _Head: - def __str__(self): - return "HEAD" - Deleted: /py/dist/py/path/svn/common.py ============================================================================== --- /py/dist/py/path/svn/common.py Fri Oct 15 13:29:06 2004 +++ (empty file) @@ -1,277 +0,0 @@ -""" -module with a base subversion path object. -""" -import os, sys, time, re -from py import path, process -from py.__impl__.path import error -from py.__impl__.path import common - -#_______________________________________________________________ - -class SvnPathBase(common.FSPathBase): - """ Base implementation for SvnPath implementations. """ - sep = '/' - - def _geturl(self): - return self.strpath - url = property(_geturl, None, None, "url of this svn-path.") - - def __str__(self): - """ return a string representation (including rev-number) """ - return self.strpath - - def __hash__(self): - return hash(self.strpath) - - def new(self, **kw): - """ create a modified version of this path. A 'rev' argument - indicates a new revision. - the following keyword arguments modify various path parts: - - http://host.com/repo/path/file.ext - |-----------------------| dirname - |------| basename - |--| purebasename - |--| ext - """ - obj = object.__new__(self.__class__) - obj.rev = kw.get('rev', self.rev) - - if 'basename' in kw: - if 'purebasename' in kw or 'ext' in kw: - raise ValueError("invalid specification") - else: - pb = kw.setdefault('purebasename', self.get('purebasename')) - ext = kw.setdefault('ext', self.get('ext')) - if ext and not ext.startswith('.'): - ext = '.' + ext - kw['basename'] = pb + ext - - kw.setdefault('dirname', self.get('dirname')) - kw.setdefault('sep', self.sep) - if kw['basename']: - obj.strpath = "%(dirname)s%(sep)s%(basename)s" % kw - else: - obj.strpath = "%(dirname)s" % kw - return obj - - def get(self, spec): - """ get specified parts of the path. 'arg' is a string - with comma separated path parts. The parts are returned - in exactly the order of the specification. - - you may specify the following parts: - - http://host.com/repo/path/file.ext - |-----------------------| dirname - |------| basename - |--| purebasename - |--| ext - """ - res = [] - parts = self.strpath.split(self.sep) - for name in spec.split(','): - name = name.strip() - if name == 'dirname': - res.append(self.sep.join(parts[:-1])) - elif name == 'basename': - res.append(parts[-1]) - else: - basename = parts[-1] - i = basename.rfind('.') - if i == -1: - purebasename, ext = basename, '' - else: - purebasename, ext = basename[:i], basename[i:] - if name == 'purebasename': - res.append(purebasename) - elif name == 'ext': - res.append(ext) - else: - raise NameError, "Don't know part %r" % name - if len(res) == 1: - return res[0] - elif len(res) == 0: - return None - return res - - def __eq__(self, other): - """ return true if path and rev attributes each match """ - return (str(self) == str(other) and - (self.rev == other.rev or self.rev == other.rev)) - - def __ne__(self, other): - return not self == other - - def join(self, *args): - """ return a new Path (with the same revision) which is composed - of the self Path followed by 'args' path components. - """ - if not args: - return self - - args = tuple([arg.strip(self.sep) for arg in args]) - parts = (self.strpath, ) + args - newpath = self.__class__(self.sep.join(parts), self.rev) - return newpath - - def dirpath(self, *args): - """ return the directory Path of the current Path. """ - return self.new(basename='').join(*args) - - def propget(self, name): - """ return the content of the given property. """ - value = self._propget(name) - return value - - def proplist(self): - """ list all property names. """ - content = self._proplist() - return content - - # XXX unify argument naming with LocalPath.listdir - def listdir(self, fil=None, sort=None): - """ return a sequence of Paths. - - listdir will return either a tuple or a list of paths - depending on implementation choices. - """ - if isinstance(fil, str): - fil = common.fnmatch(fil) - nameinfo_seq = self._listdir_nameinfo() - paths = self._make_path_tuple(nameinfo_seq) - - if fil or sort: - paths = filter(fil, paths) - paths = isinstance(paths, list) and paths or list(paths) - if callable(sort): - paths.sort(sort) - elif sort: - paths.sort() - return paths - - def info(self): - """ return an Info structure with svn-provided information. """ - parent = self.dirpath() - nameinfo_seq = parent._listdir_nameinfo() - bn = self.basename - for name, info in nameinfo_seq: - if name == bn: - return info - raise error.FileNotFound(self) - - def size(self): - """ Return the size of the file content of the Path. """ - return self.info().size - - def mtime(self): - """ Return the last modification time of the file. """ - return self.info().mtime - - def relto(self, rel): - """ Return a string which is the relative part of the Path to 'rel'. - - If the Path is not relative to the given base, return an empty string. - """ - relpath = rel.strpath - if self.strpath.startswith(relpath): - return self.strpath[len(relpath)+1:] - return "" - - - # shared help methods - - def _make_path_tuple(self, nameinfo_seq): - """ return a tuple of paths from a nameinfo-tuple sequence. - """ - #assert self.rev is not None, "revision of %s should not be None here" % self - res = [] - for name, info in nameinfo_seq: - child = self.join(name) - res.append(child) - return tuple(res) - - - #def _childmaxrev(self): - # """ return maximum revision number of childs (or self.rev if no childs) """ - # rev = self.rev - # for name, info in self._listdir_nameinfo(): - # rev = max(rev, info.created_rev) - # return rev - - #def _getlatestrevision(self): - # """ return latest repo-revision for this path. """ - # url = self.strpath - # path = self.__class__(url, None) - # - # # we need a long walk to find the root-repo and revision - # while 1: - # try: - # rev = max(rev, path._childmaxrev()) - # previous = path - # path = path.dirpath() - # except (IOError, process.cmdexec.Error): - # break - # if rev is None: - # raise IOError, "could not determine newest repo revision for %s" % self - # return rev - - class Checkers(common.FSCheckers): - def _info(self): - try: - return self._infocache - except AttributeError: - self._infocache = self.path.info() - return self._infocache - - def dir(self): - try: - return self._info().kind == 'dir' - except IOError: - return self._listdirworks() - - def _listdirworks(self): - try: - self.path.listdir() - except error.FileNotFound: - return False - else: - return True - - def file(self): - try: - return self._info().kind == 'file' - except (IOError, error.FileNotFound): - return False - - def exists(self): - try: - return self._info() - except IOError: - return self._listdirworks() - -def parse_apr_time(timestr): - i = timestr.rfind('.') - if i == -1: - raise ValueError, "could not parse %s" % timestr - timestr = timestr[:i] - parsedtime = time.strptime(timestr, "%Y-%m-%dT%H:%M:%S") - return time.mktime(parsedtime) - -class PropListDict(dict): - """ a Dictionary which fetches values (InfoSvnCommand instances) lazily""" - def __init__(self, path, keynames): - dict.__init__(self, [(x, None) for x in keynames]) - self.path = path - - def __getitem__(self, key): - value = dict.__getitem__(self, key) - if value is None: - value = self.path.propget(key) - dict.__setitem__(self, key, value) - return value - -def fixlocale(): - if sys.platform != 'win32': - return 'LC_ALL=C ' - return '' Copied: py/dist/py/path/svn/svntestbase.py (from r6950, py/dist/py/path/test/svncommonfs.py) ============================================================================== --- py/dist/py/path/test/svncommonfs.py (original) +++ py/dist/py/path/svn/svntestbase.py Fri Oct 15 13:29:06 2004 @@ -1,6 +1,6 @@ import py from py import path, test, process -from py.__impl__.path.test.fscommon import CommonFSTests +from py.__impl__.path.test.fscommon import CommonFSTests from py.__impl__.path.svn import cache class CommonSvnTests(CommonFSTests): Deleted: /py/dist/py/path/svn/test_command.py ============================================================================== --- /py/dist/py/path/svn/test_command.py Fri Oct 15 13:29:06 2004 +++ (empty file) @@ -1,23 +0,0 @@ -import sys, os -import py -from py.__impl__.path.test import svncommonfs -from py.__impl__.path.svnwc.test_command import getrepowc - -class TestSvnCommandPath(svncommonfs.CommonCommandAndBindingTests): - def __init__(self): - repo, wc = getrepowc() - self.root = py.path.svnurl(repo) - - def xtest_copy_file(self): - raise py.test.run.Skipped(msg="XXX fix svnurl first") - - def xtest_copy_dir(self): - raise py.test.run.Skipped(msg="XXX fix svnurl first") - - def XXXtest_info_log(self): - url = self.root.join("samplefile") - res = url.log(rev_start=1155, rev_end=1155, verbose=True) - assert res[0].revision == 1155 and res[0].author == "jum" - from time import gmtime - t = gmtime(res[0].date) - assert t.tm_year == 2003 and t.tm_mon == 7 and t.tm_mday == 17 Copied: py/dist/py/path/svn/test_urlcommand.py (from r6950, py/dist/py/path/svn/test_command.py) ============================================================================== --- py/dist/py/path/svn/test_command.py (original) +++ py/dist/py/path/svn/test_urlcommand.py Fri Oct 15 13:29:06 2004 @@ -1,9 +1,9 @@ import sys, os import py -from py.__impl__.path.test import svncommonfs -from py.__impl__.path.svnwc.test_command import getrepowc +from py.__impl__.path.svn.svntestbase import CommonCommandAndBindingTests +from py.__impl__.path.svn.test_wccommand import getrepowc -class TestSvnCommandPath(svncommonfs.CommonCommandAndBindingTests): +class TestSvnCommandPath(CommonCommandAndBindingTests): def __init__(self): repo, wc = getrepowc() self.root = py.path.svnurl(repo) Copied: py/dist/py/path/svn/test_wccommand.py (from r6950, py/dist/py/path/svnwc/test_command.py) ============================================================================== --- py/dist/py/path/svnwc/test_command.py (original) +++ py/dist/py/path/svn/test_wccommand.py Fri Oct 15 13:29:06 2004 @@ -1,5 +1,5 @@ import py -from py.__impl__.path.test.svncommonfs import CommonSvnTests +from py.__impl__.path.svn.svntestbase import CommonSvnTests from py.__impl__.path.test.fscommon import setuptestfs # make a wc directory out of a given root url Copied: py/dist/py/path/svn/urlcommand.py (from r6950, py/dist/py/path/svn/command.py) ============================================================================== --- py/dist/py/path/svn/command.py (original) +++ py/dist/py/path/svn/urlcommand.py Fri Oct 15 13:29:06 2004 @@ -9,7 +9,7 @@ from py import path, process from py.__impl__.path import common from py.__impl__.path import error -from py.__impl__.path.svn import common as svncommon +from py.__impl__.path.svn import svncommon class SvnCommandPath(svncommon.SvnPathBase): def __new__(cls, path, rev=None): Copied: py/dist/py/path/svn/wccommand.py (from r6950, py/dist/py/path/svnwc/command.py) ============================================================================== --- py/dist/py/path/svnwc/command.py (original) +++ py/dist/py/path/svn/wccommand.py Fri Oct 15 13:29:06 2004 @@ -13,7 +13,7 @@ import py from py.__impl__.path import common from py.__impl__.path.svn import cache -from py.__impl__.path.svn import common as svncommon +from py.__impl__.path.svn import svncommon DEBUG = 0 Deleted: /py/dist/py/path/test/svncommonfs.py ============================================================================== --- /py/dist/py/path/test/svncommonfs.py Fri Oct 15 13:29:06 2004 +++ (empty file) @@ -1,106 +0,0 @@ -import py -from py import path, test, process -from py.__impl__.path.test.fscommon import CommonFSTests -from py.__impl__.path.svn import cache - -class CommonSvnTests(CommonFSTests): - - def setup_method(self, meth): - bn = meth.func_name - if bn.startswith('test_remove') or bn.startswith('test_move'): - raise py.test.run.Skipped(msg= - "tests for (re)move require better svn state management") - - def test_propget(self): - url = self.root.join("samplefile") - value = url.propget('svn:eol-style') - assert value == 'native' - - def test_proplist(self): - url = self.root.join("samplefile") - res = url.proplist() - assert res['svn:eol-style'] == 'native' - - def test_info(self): - url = self.root.join("samplefile") - res = url.info() - assert res.size > len("samplefile") and res.created_rev >= 0 - - def xxxtest_info_log(self): - url = self.root.join("samplefile") - res = url.log(rev_start=1155, rev_end=1155, verbose=True) - assert res[0].revision == 1155 and res[0].author == "jum" - from time import gmtime - t = gmtime(res[0].date) - assert t.tm_year == 2003 and t.tm_mon == 7 and t.tm_mday == 17 - -class CommonCommandAndBindingTests(CommonSvnTests): - def test_trailing_slash_is_stripped(self): - # XXX we need to test more normalizing properties - url = self.root.join("/") - assert self.root == url - - #def test_different_revs_compare_unequal(self): - # newpath = self.root.new(rev=1199) - # assert newpath != self.root - - def test_exists_svn_root(self): - assert self.root.check() - - #def test_not_exists_rev(self): - # url = self.root.__class__(self.rooturl, rev=500) - # assert url.check(exists=0) - - #def test_nonexisting_listdir_rev(self): - # url = self.root.__class__(self.rooturl, rev=500) - # raises(error.FileNotFound, url.listdir) - - #def test_newrev(self): - # url = self.root.new(rev=None) - # assert url.rev == None - # assert url.strpath == self.root.strpath - # url = self.root.new(rev=10) - # assert url.rev == 10 - - #def test_info_rev(self): - # url = self.root.__class__(self.rooturl, rev=1155) - # url = url.join("samplefile") - # res = url.info() - # assert res.size > len("samplefile") and res.created_rev == 1155 - - # the following tests are easier if we have a path class - def test_repocache_simple(self): - repocache = cache.RepoCache() - repocache.put(self.root.strpath, 42) - url, rev = repocache.get(self.root.join('test').strpath) - assert rev == 42 - assert url == self.root.strpath - - def test_repocache_notimeout(self): - repocache = cache.RepoCache() - repocache.timeout = 0 - repocache.put(self.root.strpath, self.root.rev) - url, rev = repocache.get(self.root.strpath) - assert rev == -1 - assert url == self.root.strpath - - def test_repocache_outdated(self): - repocache = cache.RepoCache() - repocache.put(self.root.strpath, 42, timestamp=0) - url, rev = repocache.get(self.root.join('test').strpath) - assert rev == -1 - assert url == self.root.strpath - - def _test_getreporev(self): - """ this test runs so slow it's usually disabled """ - old = cache.repositories.repos - try: - _repocache.clear() - root = self.root.new(rev=-1) - url, rev = cache.repocache.get(root.strpath) - assert rev>=0 - assert url == svnrepourl - finally: - repositories.repos = old - -#cache.repositories.put(svnrepourl, 1200, 0) Modified: py/dist/py/path/test/test_api.py ============================================================================== --- py/dist/py/path/test/test_api.py (original) +++ py/dist/py/path/test/test_api.py Fri Oct 15 13:29:06 2004 @@ -1,5 +1,5 @@ from py import path, test -from py.__impl__.path.svnwc.test_command import getrepowc +from py.__impl__.path.svn.test_wccommand import getrepowc class TestAPI: def __init__(self): Modified: py/dist/py/pytest.conf ============================================================================== --- py/dist/py/pytest.conf (original) +++ py/dist/py/pytest.conf Fri Oct 15 13:29:06 2004 @@ -1,5 +1,5 @@ # standard options (modified from cmdline) -pythonexecutable = 'python2.2' +#pythonexecutable = 'python2.2' verbose = 0 nocapture = False Modified: py/dist/py/test/cmdline.py ============================================================================== --- py/dist/py/test/cmdline.py (original) +++ py/dist/py/test/cmdline.py Fri Oct 15 13:29:06 2004 @@ -57,7 +57,7 @@ for fn in filenames: fullfn = current.join(fn, abs=1) if fullfn.check(file=1): - yield test.collect.Execfile(fullfn) + yield test.collect.Module(fullfn) elif fullfn.check(dir=1): yield test.collect.Directory(fullfn) else: From hpk at codespeak.net Fri Oct 15 20:55:43 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 15 Oct 2004 20:55:43 +0200 (MEST) Subject: [py-svn] r6953 - py/dist/doc Message-ID: <20041015185543.9C23F5A0E7@thoth.codespeak.net> Author: hpk Date: Fri Oct 15 20:55:43 2004 New Revision: 6953 Modified: py/dist/doc/why_py.txt Log: small fix Modified: py/dist/doc/why_py.txt ============================================================================== --- py/dist/doc/why_py.txt (original) +++ py/dist/doc/why_py.txt Fri Oct 15 20:55:43 2004 @@ -22,7 +22,7 @@ module in older python versions which you might have to use for whatever reason. -- non-interactive environments. +- interactive environments - often modules/packages are implemented in java-style From hpk at codespeak.net Fri Oct 15 23:14:00 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 15 Oct 2004 23:14:00 +0200 (MEST) Subject: [py-svn] r6954 - in py/dist: example/test py/magic py/test py/test/report/text Message-ID: <20041015211400.DD8E25A0E7@thoth.codespeak.net> Author: hpk Date: Fri Oct 15 23:14:00 2004 New Revision: 6954 Added: py/dist/example/test/failure_demo.py - copied, changed from r6953, py/dist/py/test/test/demo.py Modified: py/dist/py/magic/assertion.py py/dist/py/magic/dyncode.py py/dist/py/magic/exprinfo.py py/dist/py/magic/test_assertion.py py/dist/py/magic/test_dyncode.py py/dist/py/magic/test_exprinfo.py py/dist/py/magic/test_invoke.py py/dist/py/test/report/text/reporter.py py/dist/py/test/test_collect.py Log: - fixed a fundamental flaw in assertion/error handling in try/finally statements. Now i begin to understand the traceback/frame structure better ... which is somewhat involved because py.test provides proper traceback information even if you are running dynamically generated code ... - created/moved an example/test/failure_demo.py which you can execute with "py.test failure_demo.py | less" to see all the nice output you get when you have tests failing. the demo contains a set of like 40 differently failing tests. - fixed a couple of minor bugs - added some tests while hunting for the real problem (see first item) Copied: py/dist/example/test/failure_demo.py (from r6953, py/dist/py/test/test/demo.py) ============================================================================== --- py/dist/py/test/test/demo.py (original) +++ py/dist/example/test/failure_demo.py Fri Oct 15 23:14:00 2004 @@ -6,7 +6,7 @@ def somefunc(x,y): otherfunc(x,y) -class FailingTests(object): +class TestFailing(object): def test_simple(self): def f(): return 42 Modified: py/dist/py/magic/assertion.py ============================================================================== --- py/dist/py/magic/assertion.py (original) +++ py/dist/py/magic/assertion.py Fri Oct 15 23:14:00 2004 @@ -5,14 +5,11 @@ BuiltinAssertionError = __builtin__.AssertionError class AssertionError(BuiltinAssertionError): - __module__ = 'py.magic' - def __init__(self, *args): BuiltinAssertionError.__init__(self, *args) f = sys._getframe(1) source = dyncode.getparseablestartingblock(f) #print "f.f_lineno", f.f_lineno - #print "source = %r" % source if source: self.msg = exprinfo.interpret(source, f) if self.msg is None: Modified: py/dist/py/magic/dyncode.py ============================================================================== --- py/dist/py/magic/dyncode.py (original) +++ py/dist/py/magic/dyncode.py Fri Oct 15 23:14:00 2004 @@ -28,11 +28,19 @@ tb = tb.tb_next return tblist -def getparseablestartingblock(frame): - lineno_hint = frame.f_lineno - 1 +def getparseablestartingblock(obj): + if hasattr(obj, 'tb_lineno'): + lineno = obj.tb_lineno-1 + frame = obj.tb_frame + elif hasattr(obj, 'f_lineno'): + lineno = obj.f_lineno-1 + frame = obj + else: + raise ValueError, "can only grok frame and traceback objects" + #lineno_hint = frame.f_lineno - 1 #print "getstartingblock: lineno_hint is %d" % lineno_hint lines = magic.dyncode.getlines(frame.f_code.co_filename) - source = getsource_tryparsing(lines, lineno_hint) + source = getsource_tryparsing(lines, lineno) #print "getstartingblock: returning %r" % source return source Modified: py/dist/py/magic/exprinfo.py ============================================================================== --- py/dist/py/magic/exprinfo.py (original) +++ py/dist/py/magic/exprinfo.py Fri Oct 15 23:14:00 2004 @@ -443,8 +443,13 @@ return f, getframeline(f, tb.tb_lineno) def getmsg((typ, val, tb)): - frame = magic.dyncode.listtb(tb)[-1].tb_frame - source = dyncode.getparseablestartingblock(frame) + #frame, line = gettbline(tb) + #frame = RunnerFrame(frame.f_globals, frame.f_locals) + #return interpret(line, frame) + + tb = magic.dyncode.listtb(tb)[-1] + source = dyncode.getparseablestartingblock(tb) + frame = tb.tb_frame x = interpret(source, frame) if not isinstance(x, str): raise TypeError, "interpret returned non-string %r" % (x,) Modified: py/dist/py/magic/test_assertion.py ============================================================================== --- py/dist/py/magic/test_assertion.py (original) +++ py/dist/py/magic/test_assertion.py Fri Oct 15 23:14:00 2004 @@ -1,43 +1,53 @@ +import py -from py.test import main -from py.__impl__.magic import assertion +def setup_module(mod): + py.magic.invoke(assertion=1) + +def teardown_module(mod): + py.magic.revoke(assertion=1) def f(): return 2 def test_assert(): - assertion.invoke() try: + assert f() == 3 + except AssertionError, e: + s = str(e) + assert s.startswith('assert 2 == 3\n') + +def test_assert_within_finally(): + class A: + def f(): + pass + excinfo = py.test.raises(TypeError, """ try: - assert f() == 3 - except AssertionError, e: - s = str(e) - assert s.startswith('assert 2 == 3\n') - finally: - assertion.revoke() + A().f() + finally: + i = 42 + """) + s = str(excinfo[1]) + assert s.find("takes no argument") != -1 + + #def g(): + # A.f() + #excinfo = getexcinfo(TypeError, g) + #msg = getmsg(excinfo) + #assert msg.find("must be called with A") != -1 + def test_assert_multiline_1(): - assertion.invoke() try: - try: - assert (f() == - 3) - except AssertionError, e: - s = str(e) - assert s.startswith('assert 2 == 3\n') - finally: - assertion.revoke() + assert (f() == + 3) + except AssertionError, e: + s = str(e) + assert s.startswith('assert 2 == 3\n') def test_assert_multiline_2(): - assertion.invoke() try: - try: - assert (f() == (4, - 3)[-1]) - except AssertionError, e: - s = str(e) - assert s.startswith('assert 2 ==') - finally: - assertion.revoke() - -main() + assert (f() == (4, + 3)[-1]) + except AssertionError, e: + s = str(e) + assert s.startswith('assert 2 ==') Modified: py/dist/py/magic/test_dyncode.py ============================================================================== --- py/dist/py/magic/test_dyncode.py (original) +++ py/dist/py/magic/test_dyncode.py Fri Oct 15 23:14:00 2004 @@ -1,8 +1,13 @@ import sys import os #print "dyncode_test: __name__ ==", __name__ -from py import test -from py.__impl__.magic import dyncode +from py.__impl__.magic import dyncode, exprinfo +import py + +def setup_module(mod): + py.magic.invoke(dyncode=1) +def teardown_module(mod): + py.magic.revoke(dyncode=1) def test_dyncode_trace(): source = """ @@ -11,7 +16,7 @@ """ co = dyncode.compile2(source) exec co - excinfo = test.raises(ValueError, f) + excinfo = py.test.raises(ValueError, f) filename, lineno = dyncode.tbinfo(excinfo[2]) line = dyncode.getline(filename, lineno) assert line.strip() == 'raise ValueError' @@ -26,7 +31,7 @@ assert fn1 != fn2 def test_syntaxerror_rerepresentation(): - ex = test.raises(SyntaxError, dyncode.compile2, 'x x')[1] + ex = py.test.raises(SyntaxError, dyncode.compile2, 'x x')[1] assert ex.lineno == 1 assert ex.offset == 3 assert ex.text.strip(), 'x x' @@ -63,7 +68,7 @@ class A: def __init__(self, *args): frame = sys._getframe(1) - self.source = dyncode.getparseablestartingblock(frame) + self.source = dyncode.getparseablestartingblock(frame) x = A('x', 'y' \ @@ -72,7 +77,17 @@ l = [i for i in x.source.split('\n') if i.strip()] assert len(l) == 4 - -if __name__ == '__main__': - test.main() +def test_getline_finally(): + def c(): pass + excinfo = py.test.raises(TypeError, """ + teardown = None + try: + c(1) + finally: + if teardown: + teardown() + """) + tb = dyncode.gettb(excinfo[2], -1) + source = dyncode.getparseablestartingblock(tb) + assert source.strip() == 'c(1)' Modified: py/dist/py/magic/test_exprinfo.py ============================================================================== --- py/dist/py/magic/test_exprinfo.py (original) +++ py/dist/py/magic/test_exprinfo.py Fri Oct 15 23:14:00 2004 @@ -23,6 +23,30 @@ msg = getmsg(excinfo) assert msg == 'AssertionError: assert 1 == 2' +def test_assert_func_argument_type_error(): + def f (): + pass + def g(): + f(1) + excinfo = getexcinfo(TypeError, g) + msg = getmsg(excinfo) + assert msg.find("takes no argument") != -1 + + class A: + def f(): + pass + def g(): + A().f() + excinfo = getexcinfo(TypeError, g) + msg = getmsg(excinfo) + assert msg.find("takes no argument") != -1 + + def g(): + A.f() + excinfo = getexcinfo(TypeError, g) + msg = getmsg(excinfo) + assert msg.find("must be called with A") != -1 + def global_f(): return 42 Modified: py/dist/py/magic/test_invoke.py ============================================================================== --- py/dist/py/magic/test_invoke.py (original) +++ py/dist/py/magic/test_invoke.py Fri Oct 15 23:14:00 2004 @@ -3,7 +3,6 @@ import inspect def check_dyncode(): - print compile co = compile('x=3', 'bogus', 'exec') s = inspect.getfile(co) assert s @@ -13,7 +12,6 @@ def check_assertion(): excinfo = py.test.raises(AssertionError, "assert 1 == 2") value = excinfo[1] - print value assert str(value) == "assert 1 == 2" def test_invoke_dyncode(): Modified: py/dist/py/test/report/text/reporter.py ============================================================================== --- py/dist/py/test/report/text/reporter.py (original) +++ py/dist/py/test/report/text/reporter.py Fri Oct 15 23:14:00 2004 @@ -75,7 +75,7 @@ # self.out.write('%s[%d]' % (collector.pypath, # numunits)) #else: - self.out.line(collector.pypath.getfile()) + #self.out.line("collector.fspy.modpath) return self.out.line def open_Directory(self, collector): @@ -99,7 +99,7 @@ unit.iocapture = SimpleOutErrCapture() if self.out.tty: realpath, lineno = unit.pypath.getfilelineno() - location = "running %s:%d %s" % (realpath.basename, lineno, str(unit.pypath)) + location = "running %s:%d %s" % (realpath.basename, lineno, str(unit.pypath.modpath)) self.out.rewrite(location) self._started[unit] = now() @@ -131,7 +131,7 @@ location = "%s:%d" % (realpath.basename, lineno) resultstring = self.namemap.get(restype, result.__class__.__name__) self.out.rewrite("%.3f %-2s %-20s %s%s" % ( - elapsed, resultstring, location, str(unit.pypath), writeinfo + elapsed, resultstring, location, str(unit.pypath.modpath), writeinfo )) if self.option.usepdb: if (issubclass(restype, collect.Error) or Modified: py/dist/py/test/test_collect.py ============================================================================== --- py/dist/py/test/test_collect.py (original) +++ py/dist/py/test/test_collect.py Fri Oct 15 23:14:00 2004 @@ -32,8 +32,6 @@ l = list(collect.Module(fn)) assert len(l) == 1 assert isinstance(l[0], collect.Error) - import traceback - print traceback.print_exception(*l[0].excinfo) assert isinstance(l[0].excinfo[1], path.NotFound) def test_syntax_error_in_module(): From hpk at codespeak.net Sat Oct 16 00:18:20 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 16 Oct 2004 00:18:20 +0200 (MEST) Subject: [py-svn] r6955 - py/dist/doc Message-ID: <20041015221820.E3D335A0E7@thoth.codespeak.net> Author: hpk Date: Sat Oct 16 00:18:20 2004 New Revision: 6955 Added: py/dist/doc/future.txt Log: added a new future document which currently discusses the future direction and ideas regarding the path object. This evolved from some comments and discussion with Laura Creighton on the mailing list. Added: py/dist/doc/future.txt ============================================================================== --- (empty file) +++ py/dist/doc/future.txt Sat Oct 16 00:18:20 2004 @@ -0,0 +1,136 @@ +======================================================= +Visions and ideas for further development of the py lib +======================================================= + +.. contents:: +.. sectnum:: + +This document tries to make transparent ideas regarding future +development of the py lib. *Note that all statements within this +document - even if they sound factual - just express +thoughts and not always real code so read with some caution. +This is not a reference guide (tm).* + +Direction: A more generalizing view on ``py.path`` objects +========================================================== + +Seen from a more general persective, the current ``fspy`` path +offers a way to go from a file to the structured content of +a file, namely a python object. This path retains some +common "path" operations and semantics but also offers additional +methods, e.g. ``resolve()`` gets you a true python object. + +But apart from python files there are many other examples +of structured content like xml documents or Ini-style +config files. While some tasks will only be possible +to perform in a domain specific manner (e.g. applying xslt +etc.pp) ``py.path`` offers a common behaviour for +structured content paths. So far only ``py.path.fspy`` +is implemented and used by py.test to address tests +and traverse into test files. + +One ring to rule them all +------------------------- + +Now, for the sake of finding out a good direction, +let's consider some code that wants to find all +*sections* which have a certain *option* value +within some given ``startpath``:: + + def find_option(startpath, optionname): + for section in startpath.listdir(dir=1): + opt = section.join(optionname) + if opt.check(): # does the option exist here? + print section.basename, "found:", opt.read() + +Obviously, this would print all string values. Now the +point is that ``find_option()`` would obviously work when +``startpath`` is a filesystem-like path like a +local filesystem path or a subversion URL path. It would +then see directories as sections and files as option-names +and the content of the file as values. + +But it also works for ``fspy`` paths if you put the following +python code in a file:: + + class Section1: + someoption = "i am an option value" + + class Section2: + someoption = "i am another option value" + +An ``fspy()`` paths maps classes and modules to directories and +name-value bindings to file/read() operations. + +And it could also work for 'xml' paths if you put +the following xml string in a file:: + + + + + value + + value + +where tags containing non-text tags map to directories +and tags with just text-children map to files (which +upon read() return the joined content of the text +tags possibly as unicode. + +Now, to complete the picture, we can make Config-Parser +*ini-style* config files also available:: + + [section1] + name = value + + [section2] + othername = value + +where sections map to directories and name=value mappings +to file/contents. + +So it seems that our above ``find_option()`` function would +work nicely on all these *mappings*. + +Of course, the somewhat open question is how to make the +transition from a filesystem path to structured content +useful and unified (as much as possible without overdoing it). + +Again, there are tasks that will need fully domain specific +solutions (DOM/XSLT/...) but i think the above view warrants +some experiments and refactoring. The degree of uniformity +still needs to be determined and thought about. + +path objects should be stackable +-------------------------------- + +Oh, and btw, a ``py.path.fspy`` file could live on top of a +'py.path.xml' path as well, i.e. take:: + + + + + + + import py + ... + + def getmsg(x): pass + +and use it to have a ``fspy`` path living on it:: + + p = py.path.local(xmlfilename) + xmlp = py.path.xml(p, 'py/magic/exprinfo') + p = py.path.fspy(xmlp, 'getmsg') + + assert p.check(func=1, basename='getmsg') + getmsg = p.resolve() + # we now a living getmsg() function compiled from the above xml + +Of course, there could be generic converters which convert between +different content formats ... allowing configuration files to e.g. +be in XML/Ini/python or filesystem-format with some common way +to check/iterate for values. + +After all the unix filesystem and the python namespaces are +two great honking ideas, why not do more of them? From hpk at codespeak.net Sat Oct 16 02:25:18 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 16 Oct 2004 02:25:18 +0200 (MEST) Subject: [py-svn] r6956 - py/dist/doc Message-ID: <20041016002518.D57F35A0E7@thoth.codespeak.net> Author: hpk Date: Sat Oct 16 02:25:18 2004 New Revision: 6956 Modified: py/dist/doc/future.txt Log: small "fix" Modified: py/dist/doc/future.txt ============================================================================== --- py/dist/doc/future.txt (original) +++ py/dist/doc/future.txt Sat Oct 16 02:25:18 2004 @@ -14,6 +14,7 @@ Direction: A more generalizing view on ``py.path`` objects ========================================================== + Seen from a more general persective, the current ``fspy`` path offers a way to go from a file to the structured content of a file, namely a python object. This path retains some @@ -29,8 +30,8 @@ is implemented and used by py.test to address tests and traverse into test files. -One ring to rule them all -------------------------- +*You are in a maze of twisty passages, all alike* +------------------------------------------------- Now, for the sake of finding out a good direction, let's consider some code that wants to find all From hpk at codespeak.net Sat Oct 16 02:46:47 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 16 Oct 2004 02:46:47 +0200 (MEST) Subject: [py-svn] r6957 - in py/dist/py: . bin test Message-ID: <20041016004647.1AA6B5A0E7@thoth.codespeak.net> Author: hpk Date: Sat Oct 16 02:46:46 2004 New Revision: 6957 Added: py/dist/py/bin/_findpy.py - copied, changed from r6954, py/dist/py/bin/_findstd.py py/dist/py/pytest.py - copied, changed from r6954, py/dist/py/pytest.conf Removed: py/dist/py/bin/_findstd.py py/dist/py/pytest.conf Modified: py/dist/py/bin/py.test py/dist/py/test/cmdline.py py/dist/py/test/config.py Log: - cleaned up the startup of py.test - slowly approaching a refactoring of the configuration mess^h^h^hmechanism of py.test Copied: py/dist/py/bin/_findpy.py (from r6954, py/dist/py/bin/_findstd.py) ============================================================================== --- py/dist/py/bin/_findstd.py (original) +++ py/dist/py/bin/_findpy.py Sat Oct 16 02:46:46 2004 @@ -1,25 +1,31 @@ -# Detect nearestby version of 'std' +# +# find and import a version of 'py' # -import sys, os +import sys +import os from os.path import dirname as opd, exists, join, basename, abspath -def searchstd(current): +def searchpy(current): while 1: last = current initpy = join(current, '__init__.py') if not exists(initpy): - stddir = join(current, 'py') - # recognize std-package and make it importable first thing - if exists(stddir) and exists(join(stddir, '__init__.py')): - sys.path[:] = [p for p in sys.path if p != current] - print "inserting", current + pydir = join(current, 'py') + # recognize py-package and ensure it is importable + if exists(pydir) and exists(join(pydir, '__init__.py')): + for p in sys.path: + if p == current: + return True + print "inserting into sys.path:", current sys.path.insert(0, current) return True current = opd(current) if last == current: return False -if not searchstd(abspath(os.curdir)): - if not searchstd(opd(abspath(sys.argv[0]))): - if not searchstd(opd(__file__)): +if not searchpy(abspath(os.curdir)): + if not searchpy(opd(abspath(sys.argv[0]))): + if not searchpy(opd(__file__)): raise SystemExit, "Could not find 'py' package!" + +import py Deleted: /py/dist/py/bin/_findstd.py ============================================================================== --- /py/dist/py/bin/_findstd.py Sat Oct 16 02:46:46 2004 +++ (empty file) @@ -1,25 +0,0 @@ -# Detect nearestby version of 'std' -# -import sys, os -from os.path import dirname as opd, exists, join, basename, abspath - -def searchstd(current): - while 1: - last = current - initpy = join(current, '__init__.py') - if not exists(initpy): - stddir = join(current, 'py') - # recognize std-package and make it importable first thing - if exists(stddir) and exists(join(stddir, '__init__.py')): - sys.path[:] = [p for p in sys.path if p != current] - print "inserting", current - sys.path.insert(0, current) - return True - current = opd(current) - if last == current: - return False - -if not searchstd(abspath(os.curdir)): - if not searchstd(opd(abspath(sys.argv[0]))): - if not searchstd(opd(__file__)): - raise SystemExit, "Could not find 'py' package!" Modified: py/dist/py/bin/py.test ============================================================================== --- py/dist/py/bin/py.test (original) +++ py/dist/py/bin/py.test Sat Oct 16 02:46:46 2004 @@ -1,8 +1,5 @@ #!/usr/bin/env python -import _findstd +from _findpy import py import sys -# we now can import (a version of) std - -from py.test import main -main(sys.argv) +py.test.main(sys.argv) Deleted: /py/dist/py/pytest.conf ============================================================================== --- /py/dist/py/pytest.conf Sat Oct 16 02:46:46 2004 +++ (empty file) @@ -1,10 +0,0 @@ -# standard options (modified from cmdline) -#pythonexecutable = 'python2.2' - -verbose = 0 -nocapture = False -collectonly = False -exitfirstproblem = False -fulltrace = False -showlocals = False -nomagic = False Copied: py/dist/py/pytest.py (from r6954, py/dist/py/pytest.conf) ============================================================================== --- py/dist/py/pytest.conf (original) +++ py/dist/py/pytest.py Sat Oct 16 02:46:46 2004 @@ -1,6 +1,15 @@ -# standard options (modified from cmdline) -#pythonexecutable = 'python2.2' +#pythonexecutables = ('python2.2', 'python2.3',) +pythonexecutable = 'python2.2' +def setup_module(extpy): + mod = extpy.resolve() + mod.module = 23 + directory = pypath.root.dirpath() + + + + +# standard options (modified from cmdline) verbose = 0 nocapture = False collectonly = False Modified: py/dist/py/test/cmdline.py ============================================================================== --- py/dist/py/test/cmdline.py (original) +++ py/dist/py/test/cmdline.py Sat Oct 16 02:46:46 2004 @@ -6,7 +6,7 @@ # # main entry point # -configbasename = 'utest.conf' +configbasename = 'pytest.py' def main(argv=None): # the collectors we will be running @@ -17,8 +17,9 @@ name = frame.f_locals.get('__name__') if name != '__main__': return # called from an imported test file - #raise RuntimeError, "Must provide 'argv' parameter" - collectors.append(test.collect.Module('__main__')) + import __main__ + collectors.append(test.collect.Module(__main__)) + args = argv[1:] for x in getanchors(args): config.readconfiguration(x) Modified: py/dist/py/test/config.py ============================================================================== --- py/dist/py/test/config.py (original) +++ py/dist/py/test/config.py Sat Oct 16 02:46:46 2004 @@ -8,7 +8,7 @@ # # config file handling (utest.conf) # -configbasename = 'pytest.conf' +configbasename = 'pytest.py' class Config: def __init__(self): From hpk at codespeak.net Sun Oct 17 04:38:04 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Oct 2004 04:38:04 +0200 (MEST) Subject: [py-svn] r6960 - py/dist/doc Message-ID: <20041017023804.8C76B5A914@thoth.codespeak.net> Author: hpk Date: Sun Oct 17 04:38:02 2004 New Revision: 6960 Modified: py/dist/doc/future.txt Log: added a large chapter about import/export and a possible future of this mechanism. Modified: py/dist/doc/future.txt ============================================================================== --- py/dist/doc/future.txt (original) +++ py/dist/doc/future.txt Sun Oct 17 04:38:02 2004 @@ -14,7 +14,6 @@ Direction: A more generalizing view on ``py.path`` objects ========================================================== - Seen from a more general persective, the current ``fspy`` path offers a way to go from a file to the structured content of a file, namely a python object. This path retains some @@ -135,3 +134,151 @@ After all the unix filesystem and the python namespaces are two great honking ideas, why not do more of them? + + +Revising and improving the import/export system +=============================================== + + or let's wrap the world all around + +the export/import interface +--------------------------- + +The py lib already incorporates a mechanism to select which +namespaces and names get exposed to a user of the library. +Apart from reducing the outside visible namespaces complexity +this allows to quickly rename and refactor stuff in the +implementation without affecting the caller side. This export +control can be used by other python packages as well. + +However, all is not fine as the import/export has a +few major deficiencies and shortcomings: + +- it doesn't allow to specify doc-strings + +- it is a bit hackish (see py/initpkg.py) + +- while better than normal it still doesn't really + present a consistent view of classes. + +- ``help(constructed_namespace)`` doesn't fully work + +- when the py lib implementation accesses parts of itself + it uses the native python import mechanism which is + limiting in some respects. Especially for distributed + programs as encouraged by `py.execnet`_ it is not clear + how the mechanism can nicely integrate to support remote + lazy importing. + +Discussions have been going on between for a while but it is +still not clear how to best tackle the problem. Personally, i +believe the main missing thing for the first major release +is the docstring one. The current specification +of exported names is dictionary based. It would be +better to declare it in terms of Objects. + + +Example sketch for a new export specification +--------------------------------------------- + +Here is a sketch of how the py libs ``__init__.py`` file +might or should look like:: + + """ + the py lib version 0.8 + http://codespeak.net/py/0.8 + """ + + from py import pkg + pkg.export(__name__, + pkg.Module('path', + '''provides path objects for local filesystem, + subversion url and working copy, and extension paths. + ''', + pkg.Class('local', ''' + the local filesystem path offering a single + point of interaction for many purposes. + ''', extpy='./path/local.LocalPath'), + + pkg.Class('svnurl', ''' + the subversion url path. + ''', extpy='./path/local/svn/urlcommand.SvnUrlPath'), + ), + # it goes on ... + ) + +The current ``initpkg.py`` code can be cleaned up to support +this new more explicit style of stating things. Note that +in principle there is nothing that stops us from retrieving +implementations over the network, e.g. a subversion repository. + + +Let there be alternatives +------------------------- + +We could also specify alternative implementations easily:: + + pkg.Class('svnwc', ''' + the subversion working copy. + ''', extpy=('./path/local/svn/urlbinding.SvnUrlPath', + './path/local/svn/urlcommand.SvnUrlPath',) + ) + +This would prefer the python binding based implementation over +the one working through he 'svn' command line utility. And +of course, it could uniformly signal if no implementation is +available at all. + + +Problems problems +----------------- + +Now there are reasons there isn't a clear conclusion so far. +For example, the above approach has some implications, the +main one being that implementation classes like +``py/path/local.LocalPath`` are visible to the caller side but +present an inconsistency because the user started out with +``py.path.local`` and expects that the two are really much +the same. We have the same problem today, of course. + +The naive solution strategy of wrapping the "implementation +level" objects into their exported representations may remind +of the `wrapping techniques PyPy uses`_. But it +*may* result in a slightly heavyweight mechanism that affects +runtime speed. However, I guess that this standard strategy +is probably the cleanest. + + +Every problem can be solved with ... +------------------------------------ + +The wrapping of implementation level classes in their export +representations objects adds another level of indirection. +But this indirection would have interesting advantages: + +- we could easily present a consistent view of the library + +- it could take care of exceptions as well + +- it provides natural interception points for logging + +- it enables remote lazy loading of implementations + or certain versions of interfaces + +And quite likely the extra indirection wouldn't hurt so much +as it is not much more than a function call and we cared +we could even generate some c-code (with PyPy :-) to speed +it up. + +But it can lead to new problems ... +----------------------------------- + +However, it is critical to avoid to burden the implementation +code of being aware of its wrapping. This is what we have +to do in PyPy but the import/export mechanism works at +a higher level of the language, i think. + +Oh, and we didn't talk about bootrapping :-) + +.. _`py.execnet`: execnet.html +.. _`wrapping techniques PyPy uses`: http://codespeak.net/pypy/index.cgi?doc/wrapping.html From hpk at codespeak.net Sun Oct 17 04:41:11 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Oct 2004 04:41:11 +0200 (MEST) Subject: [py-svn] r6961 - in py/dist: py py/path py/path/local tool Message-ID: <20041017024111.3DE145A914@thoth.codespeak.net> Author: hpk Date: Sun Oct 17 04:41:10 2004 New Revision: 6961 Added: py/dist/tool/ py/dist/tool/rest.py - copied, changed from r6879, pypy/trunk/www/bin/rest.py Modified: py/dist/py/env.py py/dist/py/path/common.py py/dist/py/path/local/local.py Log: - added new directory 'tool' which is to contain some helpful tools that also serve as examples of how you can work with the py lib. - added tool/rest.py which makes it easy to just give a couple of filenames and directories and they will be converted. Detects any 'style.css' files and makes the rest-html document use it. - added a css-file with the codespeak background-image (lacking a logo for 'the py lib') Modified: py/dist/py/env.py ============================================================================== --- py/dist/py/env.py (original) +++ py/dist/py/env.py Sun Oct 17 04:41:10 2004 @@ -5,8 +5,9 @@ progpath = sys.argv[0] packagedir = os.path.abspath(os.path.dirname(progpath)) packagename = os.path.basename(packagedir) -rootdir = os.path.dirname(packagedir) bindir = os.path.join(packagedir, 'bin') +rootdir = os.path.dirname(packagedir) +tooldir = os.path.join(rootdir, 'tool') def prepend_unixpath(VAR, strpath): value = "%r:$%s" % (strpath, VAR) @@ -18,4 +19,5 @@ if sys.platform != 'win32': print prepend_unixpath('PATH', bindir) + print prepend_unixpath('PATH', tooldir) print prepend_unixpath('PYTHONPATH', rootdir) Modified: py/dist/py/path/common.py ============================================================================== --- py/dist/py/path/common.py (original) +++ py/dist/py/path/common.py Sun Oct 17 04:41:10 2004 @@ -234,7 +234,7 @@ def read(self, num=-1): #""" read up to 'num' bytes. (if num==-1 then read all)""" try: - f = self.open() + f = self.open('rb') try: return f.read(num) finally: Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Sun Oct 17 04:41:10 2004 @@ -315,7 +315,7 @@ def write(self, content): """ write string content into path. """ - f = self.open('w') + f = self.open('wb') try: f.write(content) finally: Copied: py/dist/tool/rest.py (from r6879, pypy/trunk/www/bin/rest.py) ============================================================================== --- pypy/trunk/www/bin/rest.py (original) +++ py/dist/tool/rest.py Sun Oct 17 04:41:10 2004 @@ -1,6 +1,17 @@ -#!/usr/bin/env xpython +#!/usr/bin/python + +""" +invoke + + rest.py filename1.txt directory + +to generate html files from restructered text. + +http://docutils.sourceforge.net/docs/user/rst/quickref.html + +""" + import sys, os, traceback -os.environ['PATH'] = os.environ['PATH'] + ':/work/svn-head/bin' if os.isatty(sys.stdout.fileno()): def log(msg): @@ -9,57 +20,21 @@ def log(msg): pass -def convert_rest_html_string(source, source_path, stylesheet=None, **kwargs): - """ convert from rest format to html using the specified stylesheet. - - source a ReST-string - sourcepath where to look for includes (basically) - stylesheet path (to be used if any) +def convert_rest_html(source, source_path, stylesheet=None): + """ return html latin1-encoded document for the given input. + source a ReST-string + sourcepath where to look for includes (basically) + stylesheet path (to be used if any) """ from docutils.core import publish_string - kwargs['stylesheet'] = stylesheet - kwargs['traceback'] = 1 + kwargs = { + 'stylesheet' : stylesheet, + 'traceback' : 1, + 'output_encoding' : 'latin1', + } return publish_string(source, str(source_path), writer_name='html', settings_overrides=kwargs) - -def strip_html_header(string): - """ return a unicode string with just the body-tag and its contents """ - - import re - rex = re.compile(ur'', re.MULTILINE | re.DOTALL) - uni = unicode(string, 'utf8') - return rex.search(uni).group() - -def correct_relative_href(txtpath, string): - """ return a unicode string with just the body-tag and its contents """ - - dirname = txtpath.dirpath() - - import re - rex = re.compile(ur' 1: - #print "reverting to currentdir as localdocdir" - onefile = path.svnwc(sys.argv[1]) - process(onefile) - raise SystemExit, 1 - - if 1: - log("svn-updating %s" % docpath) - #env = os.environ['PATH'].split(':') - #env.insert(0, '/work/svn-head/bin') - #os.environ['PATH'] = ":".join(env) - #docpath.localpath.chdir() - wcdir = path.svnwc(docpath) - wcdir.update() - - txtfiles = path.checker(file=1, versioned=1, ext='.txt') - htmlfiles = path.checker(file=1, ext='.html') - rec = path.checker(dir=1, dotfile=0) + import py + from py import path - for txtpath in docpath.visit(txtfiles, rec): - if txtpath.dirpath().basename() == 'fundingattic': + if len(sys.argv) == 1: + filenames = [py.path.svnwc()] + else: + filenames = [py.path.svnwc(x) for x in sys.argv[1:]] + + for p in filenames: + if not p.check(): + log("path %s not found, ignoring" % p) continue - process(txtpath) - - for htmlpath in docpath.visit(htmlfiles, rec): - if not htmlpath.new(ext='.txt').check(): - if htmlpath.check(versioned=0): - log("pruning stale %s" % htmlpath) - htmlpath.localpath.remove() - p = htmlpath.localpath.new(ext='.svninfo') - if p.check(): - p.remove() + if p.check(dir=1): + for x in p.visit(fil=py.path.checker(fnmatch='*.txt', versioned=True), + rec=py.path.checker(dotfile=0)): + process(x) + elif p.check(file=1): + process(p) From hpk at codespeak.net Sun Oct 17 05:01:55 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Oct 2004 05:01:55 +0200 (MEST) Subject: [py-svn] r6962 - py/dist/py/path/svn Message-ID: <20041017030155.5ABF85A914@thoth.codespeak.net> Author: hpk Date: Sun Oct 17 05:01:54 2004 New Revision: 6962 Modified: py/dist/py/path/svn/test_wccommand.py py/dist/py/path/svn/wccommand.py Log: streamlined the subversion working copy api, you don't provide an initial URL anymore. Modified: py/dist/py/path/svn/test_wccommand.py ============================================================================== --- py/dist/py/path/svn/test_wccommand.py (original) +++ py/dist/py/path/svn/test_wccommand.py Sun Oct 17 05:01:54 2004 @@ -18,8 +18,8 @@ raise py.test.run.Skipped(msg='could not create temporary svn test repository') wcdir.ensure(dir=1) print "created svn repository", repo - wc = py.path.svnwc(wcdir, url='file://%s' % repo) - wc.checkout() + wc = py.path.svnwc(wcdir) + wc.checkout(url='file://%s' % repo) print "checked out new repo into", wc setuptestfs(wc) wc.join('samplefile').propset('svn:eol-style', 'native') Modified: py/dist/py/path/svn/wccommand.py ============================================================================== --- py/dist/py/path/svn/wccommand.py (original) +++ py/dist/py/path/svn/wccommand.py Sun Oct 17 05:01:54 2004 @@ -20,10 +20,9 @@ class SvnWCCommandPath(common.FSPathBase): sep = os.sep - def __new__(cls, wcpath=None, url=None): + def __new__(cls, wcpath=None): self = object.__new__(cls) self.localpath = path.local(wcpath) - self._url = url return self strpath = property(lambda x: str(x.localpath), None, None, "string path") @@ -32,7 +31,7 @@ return self.localpath == getattr(other, 'localpath', None) def _geturl(self): - if self._url is None: + if getattr(self, '_url', None) is None: info = self.info() self._url = info.url #SvnPath(info.url, info.rev) assert isinstance(self._url, str) @@ -66,7 +65,7 @@ out = py.process.cmdexec(string) return out - def checkout(self, rev = None, url=None): + def checkout(self, url=None, rev = None): """ checkout from url to local wcpath. """ if url is None: url = self.url @@ -283,11 +282,9 @@ def new(self, **kw): if kw: localpath = self.localpath.new(**kw) - url = None else: localpath = self.localpath - url = self._url - return self.__class__(localpath, url) + return self.__class__(localpath) def join(self, *args, **kwargs): """ return a new Path (with the same revision) which is composed @@ -329,8 +326,7 @@ paths = [] for localpath in self.localpath.listdir(notsvn): - name = localpath.get('basename') - p = self.__class__(localpath, '%s/%s' % (self.url, name)) # XXX + p = self.__class__(localpath) paths.append(p) if fil or sort: From hpk at codespeak.net Sun Oct 17 05:12:14 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Oct 2004 05:12:14 +0200 (MEST) Subject: [py-svn] r6963 - py/dist/doc Message-ID: <20041017031214.451595A914@thoth.codespeak.net> Author: hpk Date: Sun Oct 17 05:12:13 2004 New Revision: 6963 Added: py/dist/doc/style.css Modified: py/dist/doc/test.txt Log: small bit about test configuration and a css file Added: py/dist/doc/style.css ============================================================================== --- (empty file) +++ py/dist/doc/style.css Sun Oct 17 05:12:13 2004 @@ -0,0 +1,76 @@ +body { + background: url(http://codespeak.net/img/codespeak1b.png) no-repeat fixed; + font: 120% Arial, Verdana, Helvetica, sans-serif; + border: 0; + margin: 0.5em 0em 0.5em 1.5em; + padding: 0 0 0 127px; +} + +a { + text-decoration: underline; + background-color: transparent; +} + +p { + /*margin: 0.5em 0em 1em 0em;*/ + text-align: left; + line-height: 1.5em; + margin: 0.5em 0em 0em 0em; +} + +p a { + text-decoration: underline; +} + + +p a:active { + color: Red; + background-color: transparent; +} + +hr { + clear: both; + height: 1px; + color: #8CACBB; + background-color: transparent; +} + + +ul { + line-height: 1.5em; + /*list-style-image: url("bullet.gif"); */ + margin-left: 1em; + padding:0; +} + +ol { + line-height: 1.5em; + margin-left: 0em; + padding:0; +} + +ul a, ol a { + text-decoration: underline; +} + +blockquote { + font-family: Times, "Times New Roman", serif; + font-style: italic; + font-size: 120%; +} + +code { + font-size: 120%; + color: Black; + /*background-color: #dee7ec;*/ + background-color: #cccccc; +} + +pre { + font-size: 120%; + padding: 1em; + border: 1px solid #8cacbb; + color: Black; + background-color: #dee7ec; + background-color: #cccccc; +} Modified: py/dist/doc/test.txt ============================================================================== --- py/dist/doc/test.txt (original) +++ py/dist/doc/test.txt Sun Oct 17 05:12:13 2004 @@ -209,7 +209,7 @@ |___________________| |________________| ....................... - . "utest.conf" . + . configuration . . cmdline options . ....................... @@ -220,6 +220,19 @@ *reporter* instance. +Configuration +------------- + +``py.test`` detects and honours ``pytest.py`` configuration files +that can stack in your filesystem. The following configuration +values are currently recognized: + + pythonexecutables a sequence of strings which represent + a python executable, e.g. ('python2.2',) + +For configuration values the upmost ``pytest.py`` has the top +priority. For example, you can specify + Collectors and the test collection process ------------------------------------------ From hpk at codespeak.net Sun Oct 17 05:13:32 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Oct 2004 05:13:32 +0200 (MEST) Subject: [py-svn] r6964 - py/dist/doc Message-ID: <20041017031332.D75EF5A914@thoth.codespeak.net> Author: hpk Date: Sun Oct 17 05:13:32 2004 New Revision: 6964 Added: py/dist/doc/index.txt Log: added an index document referencing all the other documentation. Added: py/dist/doc/index.txt ============================================================================== --- (empty file) +++ py/dist/doc/index.txt Sun Oct 17 05:13:32 2004 @@ -0,0 +1,20 @@ +*the py lib* +============ + +Here is some documentation about main areas within +*the py lib*: + + `py.test`_ describes features of the py.test utility + + `py.execnet`_ offers an innovative way to distribute programs across the net + + `why_py`_ describes a bit of the motivation and background + + `future`_ handles development visions and plans for the near future. + + +.. _`py.execnet`: execnet.html +.. _`py.test`: test.html +.. _`why_py`: why_py.html +.. _`future`: future.html + From hpk at codespeak.net Sun Oct 17 05:20:42 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Oct 2004 05:20:42 +0200 (MEST) Subject: [py-svn] r6965 - py/dist/doc Message-ID: <20041017032042.64DE65A914@thoth.codespeak.net> Author: hpk Date: Sun Oct 17 05:20:41 2004 New Revision: 6965 Modified: py/dist/doc/execnet.txt Log: added content-macro Modified: py/dist/doc/execnet.txt ============================================================================== --- py/dist/doc/execnet.txt (original) +++ py/dist/doc/execnet.txt Sun Oct 17 05:20:41 2004 @@ -1,7 +1,10 @@ The py.execnet library ====================== -this is very much a draft version. +.. contents:: +.. sectnum:: + +*this is a draft version* Execnet deals with letting your python programs execute and communicate across process and computer barriers. At the From hpk at codespeak.net Sun Oct 17 05:33:55 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Oct 2004 05:33:55 +0200 (MEST) Subject: [py-svn] r6966 - py/dist/doc Message-ID: <20041017033355.9EBAA5A914@thoth.codespeak.net> Author: hpk Date: Sun Oct 17 05:33:54 2004 New Revision: 6966 Modified: py/dist/doc/execnet.txt py/dist/doc/style.css Log: - took out the named signals interface which was not thought out, anyway from execnet, reordered examples - slightly improved css Modified: py/dist/doc/execnet.txt ============================================================================== --- py/dist/doc/execnet.txt (original) +++ py/dist/doc/execnet.txt Sun Oct 17 05:33:54 2004 @@ -30,8 +30,6 @@ communication protocols from the client side, making "server" upgrades superflous. -See file:py/gateway/gateway_test.py for how it works currently. - High Level Interface for remote execution ----------------------------------------- @@ -56,8 +54,8 @@ ... -(Proposed "Channel" VHL interface for exchanging data) ------------------------------------------------------- +The "Channel" interface for exchanging data +------------------------------------------- While executing custom strings on "the other side" is simple enough it is often tricky to deal with. Therefore we want a way @@ -65,8 +63,7 @@ program. The idea is to inject a Channel object for each execution of source code. This Channel object allows two program parts to send data to each other. - -Here is the rough idea for the interface:: +Here is the current interface:: # # attributes API @@ -76,7 +73,8 @@ channel.timeout # the default timeout is None, i.e. all operations block # setting the timeout to a number value indicates the - # timeout in seconds for all operations. + # timeout in seconds for all operations. + # (not implemented) # # API for sending and receiving anonymous values # @@ -101,28 +99,32 @@ reraised as gateway.RemoteError exceptions containing a textual representation of the remote traceback. - # - # API for setting and getting named values ("named signals") - # - channel.set(**namevaluepairs): - set the given namevaluepairs on the channel object. - These binding will only appear on the remote side of the channel. - - channel.get(name): - return the value from a name binding, possibly blocking - until it appears. Note that the name is immediately - deleted from the channels namespace. - Obviously, set/get can be used for sending named signals - to each other. - - # use with caution: - channel.peek(name=None): - return a tuple (isvalue, value) where isvalue indicates - if the named value was available. Callers may only assume - a meaningful value if isvalue is True. Otherwise the value - is undefined. Please note that the peek function is - a so called "polling" interface and thus can cause scheduling - penalties that may considerably slow down your process. +A simple and useful Example for Channels +---------------------------------------- + +problem: retrieving contents of remote files:: + + import py + contentserverbootstrap = py.code.Source( + """ + for fn in channel.receive(): + f = open(f, 'rb') + try: + channel.send(f.read()) + finally: + f.close() + """ + # open a gateway to a fresh child process + contentgateway = py.execnet.SSHGateway('codespeak.net', identity) + channel = contentgateway.remote_exec_async(contentserverbootstrap) + + for fn in somefilelist: + channel.send(fn) + content = channel.receive() + # process content + + # later you can exit / close down the gateway + contentgateway.exit() An Example for Channels ----------------------- @@ -139,19 +141,19 @@ py.script.getpath('py.execnet.socketserver').read(), """ import socket - portrange = channel.get("portrange") + portrange = channel.receive() for i in portrange: try: sock = bind_and_listen(("localhost", i)) except socket.error: continue else: - channel.set(listenport=i) + channel.send(i) startserver(sock) print "started server with socket" break else: - channel.set(listenport=None) + channel.send(None) """) # open a gateway to a fresh child process @@ -161,12 +163,12 @@ channel = proxygw.remote_exec_async(socketserverbootstrap) # send parameters for the for-loop - channel.set(portrange=(7770, 7800)) + channel.send((7770, 7800)) # # the other side should start the for loop now, we # wait for the result # - listenport = channel.get('listenport') + listenport = channel.receive() if listenport is None: raise IOError, "could not setup remote SocketServer" @@ -178,33 +180,7 @@ socketgw.exit() proxygw.exit() -the example is slightly futuristic but apart from the -channel interface the code for the above functionality -is there albeit in slightly different namespaces. +The example runs as is without the use of "named signals" +above. Only sending and receiving of anonymous values +is currently supported. -Another Example for Channels ----------------------------- - -problem: retrieving contents of remote files:: - - import py - contentserverbootstrap = py.code.Source( - """ - for fn in channel.receive(): - f = open(f, 'rb') - try: - channel.send(f.read()) - finally: - f.close() - """ - # open a gateway to a fresh child process - contentgateway = py.execnet.SSHGateway('codespeak.net', identity) - channel = contentgateway.remote_exec_async(contentserverbootstrap) - - for fn in filelist: - channel.send(fn) - content = channel.receive() - # process content - - # later you can exit / close down the gateway - contentgateway.exit() Modified: py/dist/doc/style.css ============================================================================== --- py/dist/doc/style.css (original) +++ py/dist/doc/style.css Sun Oct 17 05:33:54 2004 @@ -1,9 +1,9 @@ body { - background: url(http://codespeak.net/img/codespeak1b.png) no-repeat fixed; + background: url(http://codespeak.net/img/codespeak1b.png) no-repeat; font: 120% Arial, Verdana, Helvetica, sans-serif; border: 0; - margin: 0.5em 0em 0.5em 1.5em; - padding: 0 0 0 127px; + margin: 0.5em 0em 0.5em 0.5em; + padding: 0 0 0 145px; } a { @@ -40,13 +40,11 @@ line-height: 1.5em; /*list-style-image: url("bullet.gif"); */ margin-left: 1em; - padding:0; } ol { line-height: 1.5em; margin-left: 0em; - padding:0; } ul a, ol a { From hpk at codespeak.net Sun Oct 17 05:50:50 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Oct 2004 05:50:50 +0200 (MEST) Subject: [py-svn] r6967 - py/dist/doc Message-ID: <20041017035050.697555A914@thoth.codespeak.net> Author: hpk Date: Sun Oct 17 05:50:49 2004 New Revision: 6967 Modified: py/dist/doc/ (props changed) Log: set svn:ignore to ignore generated files From hpk at codespeak.net Sun Oct 17 05:54:21 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Oct 2004 05:54:21 +0200 (MEST) Subject: [py-svn] r6968 - py/dist/doc Message-ID: <20041017035421.97AC75A914@thoth.codespeak.net> Author: hpk Date: Sun Oct 17 05:54:20 2004 New Revision: 6968 Modified: py/dist/doc/future.txt Log: minor fixes, among them bootrapping -> bootstrapping as mentioned by laura :-) Modified: py/dist/doc/future.txt ============================================================================== --- py/dist/doc/future.txt (original) +++ py/dist/doc/future.txt Sun Oct 17 05:54:20 2004 @@ -158,8 +158,7 @@ - it is a bit hackish (see py/initpkg.py) -- while better than normal it still doesn't really - present a consistent view of classes. +- it doesn't present a complete and consistent view of the API. - ``help(constructed_namespace)`` doesn't fully work @@ -278,7 +277,7 @@ to do in PyPy but the import/export mechanism works at a higher level of the language, i think. -Oh, and we didn't talk about bootrapping :-) +Oh, and we didn't talk about bootstrapping :-) .. _`py.execnet`: execnet.html .. _`wrapping techniques PyPy uses`: http://codespeak.net/pypy/index.cgi?doc/wrapping.html From hpk at codespeak.net Sun Oct 17 06:23:51 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Oct 2004 06:23:51 +0200 (MEST) Subject: [py-svn] r6969 - in py/dist/py/path: . local test Message-ID: <20041017042351.05A685A914@thoth.codespeak.net> Author: hpk Date: Sun Oct 17 06:23:51 2004 New Revision: 6969 Modified: py/dist/py/path/common.py py/dist/py/path/local/_posix.py py/dist/py/path/local/local.py py/dist/py/path/test/common.py py/dist/py/path/test/test_api.py Log: - the local path 'commondir' method is renamed to 'common' and is now a general path method which works with on all implementations. - some more svnwc cleanup Modified: py/dist/py/path/common.py ============================================================================== --- py/dist/py/path/common.py (original) +++ py/dist/py/path/common.py Sun Oct 17 06:23:51 2004 @@ -130,6 +130,19 @@ l.reverse() return l + def common(self, other): + """ return the common part shared with the other path + or None if there is no common part. + """ + last = None + for x, y in zip(self.parts(), other.parts()): + print "x", x + print "y", y + if x != y: + return last + last = x + return last + def __cmp__(self, other): try: try: Modified: py/dist/py/path/local/_posix.py ============================================================================== --- py/dist/py/path/local/_posix.py (original) +++ py/dist/py/path/local/_posix.py Sun Oct 17 06:23:51 2004 @@ -85,10 +85,8 @@ if absolute: os.symlink(str(value), self.strpath) else: - base = self.commondir(value) - if base is None: - raise ValueError, \ - "path %r and %r have no common base" % (self, value) + base = self.common(value) + # with posix local paths '/' is always a common base relsource = self.__class__(value).relto(base) reldest = self.relto(base) n = reldest.count(self.sep) Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Sun Oct 17 06:23:51 2004 @@ -154,16 +154,6 @@ else: return res - def commondir(self, otherpath): - """ return the common directory of path and 'otherpath'. """ - if self == otherpath: - return self - common = os.path.commonprefix([str(self), str(otherpath)]) - i = common.rfind(self.sep) - if i <= 0: - return None - return self.__class__(common[:i]) - def join(self, *args, **kwargs): """ return a new path by appending all 'args' as path components. if abs=1 is used start from root if any if the args Modified: py/dist/py/path/test/common.py ============================================================================== --- py/dist/py/path/test/common.py (original) +++ py/dist/py/path/test/common.py Sun Oct 17 06:23:51 2004 @@ -35,6 +35,16 @@ par = newpath.parts()[-3:] assert par == [self.root, self.root.join('sampledir'), newpath] + def test_common(self): + other = self.root.join('sampledir') + x = other.common(self.root) + assert x == self.root + + third = py.path.fspy('x', 'whatever') + x = other.common(third) + assert x is None + + #def test_parents_nonexisting_file(self): # newpath = self.root / 'dirnoexist' / 'nonexisting file' # par = list(newpath.parents()) Modified: py/dist/py/path/test/test_api.py ============================================================================== --- py/dist/py/path/test/test_api.py (original) +++ py/dist/py/path/test/test_api.py Sun Oct 17 06:23:51 2004 @@ -42,13 +42,6 @@ assert p.check(svnwc=1) self.repr_eval_test(p) - def test_passing_svnurl(self): - repourl, rootwc = getrepowc() - p = path.svnurl(repourl) - assert p.check() - w = path.svnwc(None, p) - self.repr_eval_test(p) - #def test_fspy(self): # p = path.py('smtplib.SMTP') # self.repr_eval_test(p) From hpk at codespeak.net Sun Oct 17 06:26:19 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Oct 2004 06:26:19 +0200 (MEST) Subject: [py-svn] r6970 - in py/dist/py: execnet path/doc Message-ID: <20041017042619.AABC25A914@thoth.codespeak.net> Author: hpk Date: Sun Oct 17 06:26:19 2004 New Revision: 6970 Removed: py/dist/py/execnet/README.txt py/dist/py/path/doc/ Log: erased old and wrong doc files Deleted: /py/dist/py/execnet/README.txt ============================================================================== --- /py/dist/py/execnet/README.txt Sun Oct 17 06:26:19 2004 +++ (empty file) @@ -1,48 +0,0 @@ -how remote execution is supposed to be used -------------------------------------------- - -- bootstrap: client initializes server loop - on the server. Note that both client and server - run the same gateway.SocketGateway thread, - accepting incoming source code and offering - a remote invocation API. Thus the two hosts - are symetrically gatewaying calls to each other. - - a client registers with a server by invoking - shpy.net.register.register('server:port'). The server - is expected to be a 'startserver.py' i.e. a - server which unpickles strings and executes them. - -- in order to remotely execute source code you call - - exec_remote(source) - - on your gateway. this will asynchronously execute - the given source code on the other side. Note that there - there is no return value, just as with the usual - python 'exec' statement. exec_remote returns - immediately and does not actually guarantee that - your source code is executed on the other side. - - Note that all other API calls use this - low level mechanism in order to implement - control/synchronous mechanisms. - -- in order to get a result a more higher level - function can be invoked: - - call_asynchronous(callback, func, *args, **kwargs) - - will send the source code of 'func' and invoke - it on the other side with the given arguments. - this will not return any value directly but instead - invoke the given callback with the result when and if - it arrives from the remote execution of 'func'. - - res = call_synchronous(func, *args, **kwargs) - - this will only returns after it received a result. - -# XXX concept for object references (the above API - does not differentiate between pickleable/copyable values - and remote objects. From hpk at codespeak.net Sun Oct 17 06:32:59 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Oct 2004 06:32:59 +0200 (MEST) Subject: [py-svn] r6971 - in py/dist/py/path: . test Message-ID: <20041017043259.BEF385A914@thoth.codespeak.net> Author: hpk Date: Sun Oct 17 06:32:59 2004 New Revision: 6971 Modified: py/dist/py/path/common.py py/dist/py/path/test/common.py Log: improved 'parts()' method of path objects. Modified: py/dist/py/path/common.py ============================================================================== --- py/dist/py/path/common.py (original) +++ py/dist/py/path/common.py Sun Oct 17 06:32:59 2004 @@ -21,10 +21,6 @@ self.kwargs = kwargs def __call__(self, p): return p.check(**self.kwargs) - -class invchecker(checker): - def __call__(self, p): - return not p.check(**self.kwargs) class Checkers: _depend_on_existence = 'exists', @@ -115,7 +111,7 @@ return self.get('basename') basename = property(basename, None, None, 'basename part of path') - def parts(self): + def parts(self, reverse=False): """ return a root-first list of all ancestor directories plus the path itself. """ @@ -126,8 +122,9 @@ current = current.dirpath() if last == current: break - l.append(current) - l.reverse() + l.insert(0, current) + if reverse: + l.reverse() return l def common(self, other): Modified: py/dist/py/path/test/common.py ============================================================================== --- py/dist/py/path/test/common.py (original) +++ py/dist/py/path/test/common.py Sun Oct 17 06:32:59 2004 @@ -35,6 +35,9 @@ par = newpath.parts()[-3:] assert par == [self.root, self.root.join('sampledir'), newpath] + revpar = newpath.parts(reverse=True)[:3] + assert revpar == [newpath, self.root.join('sampledir'), self.root] + def test_common(self): other = self.root.join('sampledir') x = other.common(self.root) From hpk at codespeak.net Sun Oct 17 06:35:28 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Oct 2004 06:35:28 +0200 (MEST) Subject: [py-svn] r6972 - py/dist/py/path Message-ID: <20041017043528.670E85A914@thoth.codespeak.net> Author: hpk Date: Sun Oct 17 06:35:27 2004 New Revision: 6972 Removed: py/dist/py/path/ISSUES.txt Log: removed another obsolete file Deleted: /py/dist/py/path/ISSUES.txt ============================================================================== --- /py/dist/py/path/ISSUES.txt Sun Oct 17 06:35:27 2004 +++ (empty file) @@ -1,63 +0,0 @@ -idea: a "collection path" -------------------------- - -colpath = newpath(colpath=(p1, p2, p3,...)) - -for i in p.visit(...): - # i will iterate over all of p1, p2, p3, ... - -p.check(dir=1) # will check that every pN is a directory - -p.basename() # will raise exception? - -p.join('something') # will perform the join operation on every pN - -for x in p.read(): # will concat contents of pN - -p.write() # will write the same thinkg - -and so on ... - -missing: posix ownership/permission change functions ----------------------------------------------------- - -currently, the local implementation does not offer -methods to change ownership or permissions, but it should. - - -consolidation of error handling -------------------------------- - -local path implementations throw Errors quite ok, but all -svn-command based ones often throw low-level errors instead -of parsing the error output and return local-like high-level exceptions. - -Also in light of the recent 'pypath' experiment it might -be useful if the error message were not too filesystem -specific, especially 'NotFound' instead of 'FileNotFound' - -get() is to be deprecated -------------------------- - -somehow all the syntaxes seem nice except "get"ting stuff like -the basename: - - p.get('basename') - -instead of the much simpler - - p.basename or p.basename - -(DONE, at least basename, purebasename and ext are now computed attributes) -the idea is that everything which is computable without touching -the filesystem should be stored on such a readonly-attribute. - -dirpath() is different because on some implementations (svn) -it needs to interact with the filesystem to determine -out-of-bound and versioning conditions. - -note also that there is no *.dirname because this can -be retrieved by str(x.dirpath()) and most often you -want dirpath() i.e. a full blown path object back -and not some string representation. - From hpk at codespeak.net Sun Oct 17 06:38:00 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Oct 2004 06:38:00 +0200 (MEST) Subject: [py-svn] r6973 - py/dist/py Message-ID: <20041017043800.8ABDB5A914@thoth.codespeak.net> Author: hpk Date: Sun Oct 17 06:38:00 2004 New Revision: 6973 Modified: py/dist/py/__init__.py Log: erased unused 'invchecker'. Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Sun Oct 17 06:38:00 2004 @@ -2,7 +2,6 @@ initpkg(__name__, exportdefs = { 'path.local': './path/local/local.LocalPath', 'path.checker': './path/common.checker', - 'path.invchecker': './path/common.invchecker', 'path.svnurl': './path/svn/urlcommand.SvnCommandPath', 'path.svnwc': './path/svn/wccommand.SvnWCCommandPath', 'path.fspy': './path/fspy/fspy.Fspy', From hpk at codespeak.net Sun Oct 17 06:58:37 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Oct 2004 06:58:37 +0200 (MEST) Subject: [py-svn] r6974 - in py/dist/py/path: . local svn Message-ID: <20041017045837.4E9365A914@thoth.codespeak.net> Author: hpk Date: Sun Oct 17 06:58:36 2004 New Revision: 6974 Added: py/dist/py/path/local/posix.py - copied unchanged from r6969, py/dist/py/path/local/_posix.py py/dist/py/path/local/win.py - copied unchanged from r6954, py/dist/py/path/local/_win.py Removed: py/dist/py/path/local/_posix.py py/dist/py/path/local/_win.py Modified: py/dist/py/path/common.py py/dist/py/path/local/local.py py/dist/py/path/local/test_local.py py/dist/py/path/svn/wccommand.py Log: - a couple of renames and fixes - moved the py.path.local move() impl to the FSPathBase because it is kind of generic - fs-paths have to offer a "rename" operation actually which the move() implementation refers to. Modified: py/dist/py/path/common.py ============================================================================== --- py/dist/py/path/common.py (original) +++ py/dist/py/path/common.py Sun Oct 17 06:58:36 2004 @@ -275,3 +275,12 @@ except: self._except(sys.exc_info()) + def move(self, target): + if target.relto(self): + raise py.path.Invalid("cannot move path into a subdirectory of itself") + try: + self.rename(target) + except py.path.Invalid: + self.copy(target) + self.remove() + Deleted: /py/dist/py/path/local/_posix.py ============================================================================== --- /py/dist/py/path/local/_posix.py Sun Oct 17 06:58:36 2004 +++ (empty file) @@ -1,111 +0,0 @@ -""" -module to access local filesystem pathes -(mostly filename manipulations but also file operations) -""" -import os, sys, stat - -import py -#__________________________________________________________ -# -# Local Path Posix Mixin -#__________________________________________________________ - -class PosixMixin: - # an instance needs to be a local path instance - def owner(self): - """ return owner name of file. """ - try: - from pwd import getpwuid - return getpwuid(self.stat().st_uid)[0] - except: - self._except(sys.exc_info()) - - def group(self): - """ return group name of file. """ - try: - from grp import getgrgid - return getgrgid(self.stat().st_gid)[0] - except: - self._except(sys.exc_info()) - - def mode(self): - """ return permission mode of the path object """ - return self.stat().st_mode - - def chmod(self, mode, rec=0): - """ change permissions to the given mode. If mode is an - integer it directly encodes the os-specific modes. - (xxx if mode is a string then it specifies access rights - in '/bin/chmod' style, e.g. a+r). - if rec is True perform recursively. - """ - try: - if not isinstance(mode, int): - raise NotImplementedError - if rec: - for x in self.visit(): - os.chmod(str(x), mode) - os.chmod(str(self), mode) - except: - self._except(sys.exc_info()) - - def chown(self, user, group, rec=0): - """ change ownership to the given user and group. - user and group may be specified by a number or - by a name. if rec is True change ownership - recursively. - """ - uid = getuserid(user) - gid = getgroupid(group) - try: - if rec: - for x in self.visit(rec=py.path.checker(link=0)): - os.chown(str(x), uid, gid) - os.chown(str(self), uid, gid) - except: - self._except(sys.exc_info()) - - def readlink(self): - """ return value of a symbolic link. """ - try: - return os.readlink(self.strpath) - except: - self._except(sys.exc_info()) - - def mklinkto(self, oldname): - """ hard link to an old name. """ - try: - os.link(str(oldname), str(self)) - except: - self._except(sys.exc_info()) - - def mksymlinkto(self, value, absolute=1): - """ create a symbolic link with the given value (pointing to another name). """ - try: - if absolute: - os.symlink(str(value), self.strpath) - else: - base = self.common(value) - # with posix local paths '/' is always a common base - relsource = self.__class__(value).relto(base) - reldest = self.relto(base) - n = reldest.count(self.sep) - target = self.sep.join(('..', )*n + (relsource, )) - os.symlink(target, self.strpath) - except: - self._except(sys.exc_info()) - - -def getuserid(user): - import pwd - if isinstance(user, int): - return user - entry = pwd.getpwnam(user) - return entry[2] - -def getgroupid(group): - import grp - if isinstance(group, int): - return group - entry = grp.getgrnam(group) - return entry[2] Deleted: /py/dist/py/path/local/_win.py ============================================================================== --- /py/dist/py/path/local/_win.py Sun Oct 17 06:58:36 2004 +++ (empty file) @@ -1,8 +0,0 @@ -""" -module for win-specific local path stuff - -(implementor needed :-) -""" - -class WinMixin: - pass Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Sun Oct 17 06:58:36 2004 @@ -11,9 +11,9 @@ from py.__impl__.path import common if sys.platform == 'win32': - from py.__impl__.path.local._win import WinMixin as PlatformMixin + from py.__impl__.path.local.win import WinMixin as PlatformMixin else: - from py.__impl__.path.local._posix import PosixMixin as PlatformMixin + from py.__impl__.path.local.posix import PosixMixin as PlatformMixin class LocalPath(common.FSPathBase, PlatformMixin): """ the fully specialized local path implementation. @@ -267,15 +267,6 @@ except: self._except(sys.exc_info()) - def move(self, target): - if target.relto(self): - raise py.path.Invalid("cannot move path into a subdirectory of itself") - try: - self.rename(target) - except py.path.Invalid: - self.copy(target) - self.remove() - def rename(self, target): try: os.rename(str(self), str(target)) Modified: py/dist/py/path/local/test_local.py ============================================================================== --- py/dist/py/path/local/test_local.py (original) +++ py/dist/py/path/local/test_local.py Sun Oct 17 06:58:36 2004 @@ -4,11 +4,10 @@ from py.__impl__.path.test.fscommon import CommonFSTests, setuptestfs class TestLocalPath(CommonFSTests): - def __init__(self): - print "tmpdir is", config.tmpdir - self.root = config.tmpdir / 'local' - self.root.ensure(dir=1) - setuptestfs(self.root) + def setup_class(cls): + cls.root = config.tmpdir / 'TestLocalPath' + cls.root.ensure(dir=1) + setuptestfs(cls.root) def test_initialize_curdir(self): assert str(local()) == os.getcwd() @@ -146,177 +145,6 @@ tmpdir.remove(rec=1) -class TestPOSIXLocalPath: - #root = local(TestLocalPath.root) - disabled = sys.platform == 'win32' - - def __init__(self): - print "tmpdir is", config.tmpdir - self.root = config.tmpdir / 'local' - self.root.ensure(dir=1) - setuptestfs(self.root) - - def test_hardlink(self): - tmpdir = local(local.mkdtemp()) - try: - linkpath = tmpdir.join('test') - filepath = tmpdir.join('file') - filepath.write("Hello") - linkpath.mklinkto(filepath) - assert filepath.read() == linkpath.read() - finally: - tmpdir.remove(rec=1) - - def test_symlink_are_identical(self): - tmpdir = local(local.mkdtemp()) - try: - filepath = tmpdir.join('file') - filepath.write("Hello") - linkpath = tmpdir.join('test') - linkpath.mksymlinkto(filepath) - assert filepath.read() == linkpath.read() - finally: - tmpdir.remove(rec=1) - - def test_symlink_isfile(self): - tmpdir = local(local.mkdtemp()) - try: - linkpath = tmpdir.join('test') - filepath = tmpdir.join('file') - filepath.write("") - linkpath.mksymlinkto(filepath) - assert linkpath.check(file=1) - assert not linkpath.check(link=0, file=1) - finally: - tmpdir.remove(rec=1) - - def test_symlink_relative(self): - tmpdir = local(local.mkdtemp()) - try: - linkpath = tmpdir.join('test') - filepath = tmpdir.join('file') - filepath.write("Hello") - linkpath.mksymlinkto(filepath, absolute=False) - assert linkpath.readlink() == "file" - assert filepath.read() == linkpath.read() - finally: - tmpdir.remove(rec=1) - - def test_visit_recursive_symlink(self): - tmpdir = local.mkdtemp() - try: - linkpath = tmpdir.join('test') - linkpath.mksymlinkto(tmpdir) - visitor = tmpdir.visit(None, checker(link=0)) - assert list(visitor) == [linkpath] - #check.equal(list(tmpdir.visit()), [linkpath]) - finally: - tmpdir.remove(rec=1) - - def test_symlink_isdir(self): - tmpdir = local.mkdtemp() - try: - linkpath = tmpdir.join('test') - linkpath.mksymlinkto(tmpdir) - assert linkpath.check(dir=1) - assert not linkpath.check(link=0, dir=1) - finally: - tmpdir.remove(rec=1) - - def test_symlink_remove(self): - tmpdir = local.mkdtemp() - try: - linkpath = tmpdir.join('test') - linkpath.mksymlinkto(linkpath) # point to itself - assert linkpath.check(dir=0) - assert linkpath.check(link=1) - linkpath.remove() - assert not linkpath.check() - finally: - tmpdir.remove(rec=1) - - def test_realpath_file(self): - tmpdir = local.mkdtemp() - try: - linkpath = tmpdir.join('test') - filepath = tmpdir.join('file') - filepath.write("") - linkpath.mksymlinkto(filepath) - realpath = linkpath.realpath() - assert realpath.get('basename') == 'file' - finally: - tmpdir.remove(rec=1) - - def test_owner(self): - from pwd import getpwuid - assert getpwuid(self.root.stat().st_uid)[0] == self.root.owner() - - def test_group(self): - from grp import getgrgid - assert getgrgid(self.root.stat().st_gid)[0] == self.root.group() - - def XXXtest_atime(self): - # XXX disabled. this test is just not platform independent enough - # because acesstime resolution is very different through - # filesystems even on one platform. - import time - path = self.root.join('samplefile') - atime = path.atime() - time.sleep(1) - path.read(1) - assert path.atime() != atime - - def testcommondir(self): - # XXX This is here in local until we find a way to implement this - # using the subversion command line api. - p1 = self.root.join('something') - p2 = self.root.join('otherthing') - assert p1.commondir(p2) == self.root - assert p2.commondir(p1) == self.root - - def testcommondir_nocommon(self): - # XXX This is here in local until we find a way to implement this - # using the subversion command line api. - p1 = self.root.join('something') - p2 = local(os.sep+'blabla') - assert p1.commondir(p2) is None - - - def test_chmod_simple_int(self): - print "self.root is", self.root - mode = self.root.mode() - self.root.chmod(mode/2) - try: - assert self.root.mode() != mode - finally: - self.root.chmod(mode) - assert self.root.mode() == mode - - def test_chmod_rec_int(self): - # XXX fragile test - print "self.root is", self.root - recfilter = checker(dotfile=0) - oldmodes = {} - for x in self.root.visit(rec=recfilter): - oldmodes[x] = x.mode() - self.root.chmod(0772, rec=1) - try: - for x in self.root.visit(rec=recfilter): - assert x.mode() & 0777 == 0772 - finally: - for x,y in oldmodes.items(): - x.chmod(y) - - def test_chown_identity(self): - owner = self.root.owner() - group = self.root.group() - self.root.chown(owner, group) - - def test_chown_identity_rec_mayfail(self): - owner = self.root.owner() - group = self.root.group() - self.root.chown(owner, group) - class TestMisc: root = local(TestLocalPath.root) Modified: py/dist/py/path/svn/wccommand.py ============================================================================== --- py/dist/py/path/svn/wccommand.py (original) +++ py/dist/py/path/svn/wccommand.py Sun Oct 17 06:58:36 2004 @@ -134,7 +134,7 @@ def copy(self, target): py.process.cmdexec("svn copy %s %s" %(str(self), str(target))) - def move(self, target): + def rename(self, target): py.process.cmdexec("svn move --force %s %s" %(str(self), str(target))) def status(self, updates=0, rec=0): From hpk at codespeak.net Sun Oct 17 06:59:48 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Oct 2004 06:59:48 +0200 (MEST) Subject: [py-svn] r6975 - py/dist/py/path/local Message-ID: <20041017045948.4904D5A914@thoth.codespeak.net> Author: hpk Date: Sun Oct 17 06:59:47 2004 New Revision: 6975 Added: py/dist/py/path/local/test_posix.py Log: ups, forget to add the posix tests i separated out before ... Added: py/dist/py/path/local/test_posix.py ============================================================================== --- (empty file) +++ py/dist/py/path/local/test_posix.py Sun Oct 17 06:59:47 2004 @@ -0,0 +1,175 @@ +import py +import sys +from py.__impl__.path.test.fscommon import setuptestfs +checker = py.path.checker +local = py.path.local + +class TestPOSIXLocalPath: + #root = local(TestLocalPath.root) + disabled = sys.platform == 'win32' + + def setup_class(cls): + cls.root = py.test.config.tmpdir / 'TestPosixLocalPath' + cls.root.ensure(dir=1) + setuptestfs(cls.root) + + def test_hardlink(self): + tmpdir = local(local.mkdtemp()) + try: + linkpath = tmpdir.join('test') + filepath = tmpdir.join('file') + filepath.write("Hello") + linkpath.mklinkto(filepath) + assert filepath.read() == linkpath.read() + finally: + tmpdir.remove(rec=1) + + def test_symlink_are_identical(self): + tmpdir = local(local.mkdtemp()) + try: + filepath = tmpdir.join('file') + filepath.write("Hello") + linkpath = tmpdir.join('test') + linkpath.mksymlinkto(filepath) + assert filepath.read() == linkpath.read() + finally: + tmpdir.remove(rec=1) + + def test_symlink_isfile(self): + tmpdir = local(local.mkdtemp()) + try: + linkpath = tmpdir.join('test') + filepath = tmpdir.join('file') + filepath.write("") + linkpath.mksymlinkto(filepath) + assert linkpath.check(file=1) + assert not linkpath.check(link=0, file=1) + finally: + tmpdir.remove(rec=1) + + def test_symlink_relative(self): + tmpdir = local(local.mkdtemp()) + try: + linkpath = tmpdir.join('test') + filepath = tmpdir.join('file') + filepath.write("Hello") + linkpath.mksymlinkto(filepath, absolute=False) + assert linkpath.readlink() == "file" + assert filepath.read() == linkpath.read() + finally: + tmpdir.remove(rec=1) + + def test_visit_recursive_symlink(self): + tmpdir = local.mkdtemp() + try: + linkpath = tmpdir.join('test') + linkpath.mksymlinkto(tmpdir) + visitor = tmpdir.visit(None, checker(link=0)) + assert list(visitor) == [linkpath] + #check.equal(list(tmpdir.visit()), [linkpath]) + finally: + tmpdir.remove(rec=1) + + def test_symlink_isdir(self): + tmpdir = local.mkdtemp() + try: + linkpath = tmpdir.join('test') + linkpath.mksymlinkto(tmpdir) + assert linkpath.check(dir=1) + assert not linkpath.check(link=0, dir=1) + finally: + tmpdir.remove(rec=1) + + def test_symlink_remove(self): + tmpdir = local.mkdtemp() + try: + linkpath = tmpdir.join('test') + linkpath.mksymlinkto(linkpath) # point to itself + assert linkpath.check(dir=0) + assert linkpath.check(link=1) + linkpath.remove() + assert not linkpath.check() + finally: + tmpdir.remove(rec=1) + + def test_realpath_file(self): + tmpdir = local.mkdtemp() + try: + linkpath = tmpdir.join('test') + filepath = tmpdir.join('file') + filepath.write("") + linkpath.mksymlinkto(filepath) + realpath = linkpath.realpath() + assert realpath.get('basename') == 'file' + finally: + tmpdir.remove(rec=1) + + def test_owner(self): + from pwd import getpwuid + assert getpwuid(self.root.stat().st_uid)[0] == self.root.owner() + + def test_group(self): + from grp import getgrgid + assert getgrgid(self.root.stat().st_gid)[0] == self.root.group() + + def XXXtest_atime(self): + # XXX disabled. this test is just not platform independent enough + # because acesstime resolution is very different through + # filesystems even on one platform. + import time + path = self.root.join('samplefile') + atime = path.atime() + time.sleep(1) + path.read(1) + assert path.atime() != atime + + def testcommondir(self): + # XXX This is here in local until we find a way to implement this + # using the subversion command line api. + p1 = self.root.join('something') + p2 = self.root.join('otherthing') + assert p1.commondir(p2) == self.root + assert p2.commondir(p1) == self.root + + def testcommondir_nocommon(self): + # XXX This is here in local until we find a way to implement this + # using the subversion command line api. + p1 = self.root.join('something') + p2 = local(os.sep+'blabla') + assert p1.commondir(p2) is None + + + def test_chmod_simple_int(self): + print "self.root is", self.root + mode = self.root.mode() + self.root.chmod(mode/2) + try: + assert self.root.mode() != mode + finally: + self.root.chmod(mode) + assert self.root.mode() == mode + + def test_chmod_rec_int(self): + # XXX fragile test + print "self.root is", self.root + recfilter = checker(dotfile=0) + oldmodes = {} + for x in self.root.visit(rec=recfilter): + oldmodes[x] = x.mode() + self.root.chmod(0772, rec=1) + try: + for x in self.root.visit(rec=recfilter): + assert x.mode() & 0777 == 0772 + finally: + for x,y in oldmodes.items(): + x.chmod(y) + + def test_chown_identity(self): + owner = self.root.owner() + group = self.root.group() + self.root.chown(owner, group) + + def test_chown_identity_rec_mayfail(self): + owner = self.root.owner() + group = self.root.group() + self.root.chown(owner, group) From hpk at codespeak.net Sun Oct 17 16:38:00 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Oct 2004 16:38:00 +0200 (MEST) Subject: [py-svn] r6977 - py/dist/doc Message-ID: <20041017143800.2A9C95A914@thoth.codespeak.net> Author: hpk Date: Sun Oct 17 16:37:59 2004 New Revision: 6977 Modified: py/dist/doc/future.txt Log: added a new future chapter on system/executable interaction ... Modified: py/dist/doc/future.txt ============================================================================== --- py/dist/doc/future.txt (original) +++ py/dist/doc/future.txt Sun Oct 17 16:37:59 2004 @@ -11,8 +11,9 @@ thoughts and not always real code so read with some caution. This is not a reference guide (tm).* -Direction: A more generalizing view on ``py.path`` objects -========================================================== +Direction: A more general view on ``py.path`` objects +===================================================== +.. _`a more general view on path objects`: Seen from a more general persective, the current ``fspy`` path offers a way to go from a file to the structured content of @@ -281,3 +282,101 @@ .. _`py.execnet`: execnet.html .. _`wrapping techniques PyPy uses`: http://codespeak.net/pypy/index.cgi?doc/wrapping.html + + +Supporting interaction with system utilities/binaries +===================================================== + +Currently, the py lib offers a simple way to interact with +executables in shell style:: + + out = py.process.cmdexec('ls -v') + +which will raise an exception in case of a return-code other than 0 +and otherwise return the output of the child process. + +shortcomings of the current approach +------------------------------------ + +However, the ``cmdexec`` approach has a few shortcomings:: + +- it relies on the underlying system shell + +- it neccessitates shell-escaping for expressing arguments + +- it does not easily allow to "fix" the binary you want to run. + +- it only allows to execute executables from the local + filesystem + +path objects come to rescue +--------------------------- + +We probably want to offer a stripped down functionality of what +the new `PEP-324 subprocess module`_ offers. The main functionality +of synchronously should have a straightforward api, like:: + + binsvn.execute('ls', 'http://codespeak.net/svn') + +where ``binsvn`` is a path that points to the ``svn`` commandline +binary. Note that this function would not offer any shell-escaping +so you really have to pass in separated arguments. This idea +fits nicely into `a more general view on path objects`_. + +For a first go, we could simply reuse the existing `subprocess +implementation`_ but would not expose any of its API apart +from the above "execute()" method. + +finding an executable +--------------------- + +Finding an executable is quite different on multiple platforms. +At least, the ``PATH`` environment variable based search on +unix platforms should be supported with something like:: + + py.path.local.sysfind('svn') + +which would return the first path found to the ``svn`` exectuable. +In principle, 'sysfind' deploys platform specific algorithms +to perform the search. On Windows, for example, it may look +at the registry. + +To make the story complete, we can allow to pass in a "checker" +that would be called for each found executable. For example, +if you have multiple binaries available you may want to select +the right version:: + + def mysvn(p): + """ check that the given svn binary has version 1.1. """ + line = p.execute('--version'').readlines()[0] + if line.find('version 1.1'): + return p + + py.path.local.sysfind('svn', checker=mysvn) + +world wide subversive interaction? +---------------------------------- + +While we are at it, we may want to run python scripts from +the net:: + + vadm = py.path.svnurl('http://codespeak.net/svn/vadm/dist/vadm/cmdline.py') + stdoutput = vadm.execute('diff') + +To be able to execute this code fragement, we need either or all of + +- an improved import system that allows remote imports + +- a way to specify what the "neccessary" python import + directories are. for example, the above scriptlet will + require a certain root included in the python search for module + in order to execute something like "import vadm". + +- a way to specify dependencies ... which opens up another + interesting can of worms, suitable for another chapter + in the neverending `future book`_. + + +.. _`future book`: future.html +.. _`PEP-324 subprocess module`: http://www.python.org/peps/pep-0324.html +.. _`subprocess implementation`: http://www.lysator.liu.se/~astrand/popen5/ From hpk at codespeak.net Sun Oct 17 16:53:48 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Oct 2004 16:53:48 +0200 (MEST) Subject: [py-svn] r6978 - py/dist/doc Message-ID: <20041017145348.73C2D5A914@thoth.codespeak.net> Author: hpk Date: Sun Oct 17 16:53:47 2004 New Revision: 6978 Modified: py/dist/doc/execnet.txt py/dist/doc/future.txt Log: insert a though reference to the channel api (and learned some more ReST :-) Modified: py/dist/doc/execnet.txt ============================================================================== --- py/dist/doc/execnet.txt (original) +++ py/dist/doc/execnet.txt Sun Oct 17 16:53:47 2004 @@ -57,6 +57,8 @@ The "Channel" interface for exchanging data ------------------------------------------- +.. _`channel-api`: + While executing custom strings on "the other side" is simple enough it is often tricky to deal with. Therefore we want a way to send data items to and fro between the distributedly running Modified: py/dist/doc/future.txt ============================================================================== --- py/dist/doc/future.txt (original) +++ py/dist/doc/future.txt Sun Oct 17 16:53:47 2004 @@ -314,7 +314,8 @@ We probably want to offer a stripped down functionality of what the new `PEP-324 subprocess module`_ offers. The main functionality -of synchronously should have a straightforward api, like:: +of synchronously executing [#]_ a system executable should have a +straightforward api, maybe:: binsvn.execute('ls', 'http://codespeak.net/svn') @@ -327,6 +328,13 @@ implementation`_ but would not expose any of its API apart from the above "execute()" method. +.. [#] of course we could also think about replicating the `channel api`_ + of gateways and return a special ``Channel`` object where you + could call ``receive()`` and ``send()`` on. But these methods + probably don't really fit here. Hum, food for thought. + +.. _`channel api`: execnet.html#channel-api + finding an executable --------------------- From hpk at codespeak.net Sun Oct 17 16:55:57 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Oct 2004 16:55:57 +0200 (MEST) Subject: [py-svn] r6979 - py/dist/doc Message-ID: <20041017145557.DE73E5A914@thoth.codespeak.net> Author: hpk Date: Sun Oct 17 16:55:57 2004 New Revision: 6979 Modified: py/dist/doc/future.txt Log: small rest fix Modified: py/dist/doc/future.txt ============================================================================== --- py/dist/doc/future.txt (original) +++ py/dist/doc/future.txt Sun Oct 17 16:55:57 2004 @@ -153,7 +153,7 @@ control can be used by other python packages as well. However, all is not fine as the import/export has a -few major deficiencies and shortcomings: +few major deficiencies and shortcomings:: - it doesn't allow to specify doc-strings From hpk at codespeak.net Sun Oct 17 17:12:44 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Oct 2004 17:12:44 +0200 (MEST) Subject: [py-svn] r6980 - py/dist/doc Message-ID: <20041017151244.921BF5A914@thoth.codespeak.net> Author: hpk Date: Sun Oct 17 17:12:44 2004 New Revision: 6980 Modified: py/dist/doc/future.txt Log: more fixes and cleanups Modified: py/dist/doc/future.txt ============================================================================== --- py/dist/doc/future.txt (original) +++ py/dist/doc/future.txt Sun Oct 17 17:12:44 2004 @@ -44,15 +44,14 @@ if opt.check(): # does the option exist here? print section.basename, "found:", opt.read() -Obviously, this would print all string values. Now the -point is that ``find_option()`` would obviously work when -``startpath`` is a filesystem-like path like a -local filesystem path or a subversion URL path. It would -then see directories as sections and files as option-names -and the content of the file as values. +Now the point is that ``find_option()`` would obviously work +when ``startpath`` is a filesystem-like path like a local +filesystem path or a subversion URL path. It would then see +directories as sections and files as option-names and the +content of the file as values. -But it also works for ``fspy`` paths if you put the following -python code in a file:: +But it also works (today!) for ``fspy`` paths if you put the following +python code in the file, referenced as ``startpath`` above:: class Section1: someoption = "i am an option value" @@ -60,7 +59,7 @@ class Section2: someoption = "i am another option value" -An ``fspy()`` paths maps classes and modules to directories and +An ``fspy()`` path maps classes and modules to directories and name-value bindings to file/read() operations. And it could also work for 'xml' paths if you put @@ -95,7 +94,8 @@ Of course, the somewhat open question is how to make the transition from a filesystem path to structured content -useful and unified (as much as possible without overdoing it). +useful and unified, as much as possible without overdoing it, +of course. Again, there are tasks that will need fully domain specific solutions (DOM/XSLT/...) but i think the above view warrants @@ -126,15 +126,16 @@ assert p.check(func=1, basename='getmsg') getmsg = p.resolve() - # we now a living getmsg() function compiled from the above xml + # we now have a *live* getmsg() function taken and compiled from + # the above xml fragment Of course, there could be generic converters which convert between different content formats ... allowing configuration files to e.g. be in XML/Ini/python or filesystem-format with some common way to check/iterate for values. -After all the unix filesystem and the python namespaces are -two great honking ideas, why not do more of them? +*After all the unix filesystem and the python namespaces are +two honking great ideas, why not do more of them?* Revising and improving the import/export system @@ -153,16 +154,12 @@ control can be used by other python packages as well. However, all is not fine as the import/export has a -few major deficiencies and shortcomings:: +few major deficiencies and shortcomings: - it doesn't allow to specify doc-strings - - it is a bit hackish (see py/initpkg.py) - - it doesn't present a complete and consistent view of the API. - - ``help(constructed_namespace)`` doesn't fully work - - when the py lib implementation accesses parts of itself it uses the native python import mechanism which is limiting in some respects. Especially for distributed @@ -237,8 +234,8 @@ For example, the above approach has some implications, the main one being that implementation classes like ``py/path/local.LocalPath`` are visible to the caller side but -present an inconsistency because the user started out with -``py.path.local`` and expects that the two are really much +this presents an inconsistency because the user started out with +``py.path.local`` and expects that the two classes are really much the same. We have the same problem today, of course. The naive solution strategy of wrapping the "implementation @@ -257,11 +254,8 @@ But this indirection would have interesting advantages: - we could easily present a consistent view of the library - - it could take care of exceptions as well - - it provides natural interception points for logging - - it enables remote lazy loading of implementations or certain versions of interfaces @@ -298,14 +292,11 @@ shortcomings of the current approach ------------------------------------ -However, the ``cmdexec`` approach has a few shortcomings:: +However, the ``cmdexec`` approach has a few shortcomings: - it relies on the underlying system shell - - it neccessitates shell-escaping for expressing arguments - - it does not easily allow to "fix" the binary you want to run. - - it only allows to execute executables from the local filesystem From hpk at codespeak.net Sun Oct 17 17:17:21 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Oct 2004 17:17:21 +0200 (MEST) Subject: [py-svn] r6981 - py/dist/doc Message-ID: <20041017151721.F3EF55A914@thoth.codespeak.net> Author: hpk Date: Sun Oct 17 17:17:21 2004 New Revision: 6981 Modified: py/dist/doc/execnet.txt py/dist/doc/future.txt Log: lalala, still fixing and learning more/better Rest. i begin to like it, especially with the new nice'n'easy 'rest.py' script. Modified: py/dist/doc/execnet.txt ============================================================================== --- py/dist/doc/execnet.txt (original) +++ py/dist/doc/execnet.txt Sun Oct 17 17:17:21 2004 @@ -54,11 +54,12 @@ ... -The "Channel" interface for exchanging data -------------------------------------------- - .. _`channel-api`: +The "Channel" interface for exchanging data between gateways +------------------------------------------------------------ + + While executing custom strings on "the other side" is simple enough it is often tricky to deal with. Therefore we want a way to send data items to and fro between the distributedly running Modified: py/dist/doc/future.txt ============================================================================== --- py/dist/doc/future.txt (original) +++ py/dist/doc/future.txt Sun Oct 17 17:17:21 2004 @@ -11,9 +11,10 @@ thoughts and not always real code so read with some caution. This is not a reference guide (tm).* +.. _`a more general view on path objects`: + Direction: A more general view on ``py.path`` objects ===================================================== -.. _`a more general view on path objects`: Seen from a more general persective, the current ``fspy`` path offers a way to go from a file to the structured content of @@ -246,8 +247,8 @@ is probably the cleanest. -Every problem can be solved with ... ------------------------------------- +Every problem can be solved with another level ... +-------------------------------------------------- The wrapping of implementation level classes in their export representations objects adds another level of indirection. From hpk at codespeak.net Sun Oct 17 17:28:34 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Oct 2004 17:28:34 +0200 (MEST) Subject: [py-svn] r6982 - py/dist/doc Message-ID: <20041017152834.340E85A914@thoth.codespeak.net> Author: hpk Date: Sun Oct 17 17:28:33 2004 New Revision: 6982 Modified: py/dist/doc/test.txt Log: inserted a warning notice on the py.test doc. Modified: py/dist/doc/test.txt ============================================================================== --- py/dist/doc/test.txt (original) +++ py/dist/doc/test.txt Sun Oct 17 17:28:33 2004 @@ -5,6 +5,15 @@ .. contents:: .. sectnum:: +*PLEASE NOTICE*: + Warning: a few names like ``Driver`` and ``Item`` used in this document + mostly represent a future naming, they are currently called differently, + ``Runner`` and ``Unit`` respectively. The latter names are causing + confusion with the ``unittest.py`` nomen clatura. ``py.test`` + takes a much more general view on testing and thus it should start + out with a slightly different view. However, as a user you will + not get in touch too quickly with these names, anyway. So calm down :-) + starting point: ``py.test`` command line tool ============================================= From hpk at codespeak.net Sun Oct 17 17:37:22 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Oct 2004 17:37:22 +0200 (MEST) Subject: [py-svn] r6983 - py/dist/doc Message-ID: <20041017153722.380C35A914@thoth.codespeak.net> Author: hpk Date: Sun Oct 17 17:37:21 2004 New Revision: 6983 Modified: py/dist/doc/test.txt Log: fixes and some more words on how Drivers work. Modified: py/dist/doc/test.txt ============================================================================== --- py/dist/doc/test.txt (original) +++ py/dist/doc/test.txt Sun Oct 17 17:37:21 2004 @@ -234,7 +234,7 @@ ``py.test`` detects and honours ``pytest.py`` configuration files that can stack in your filesystem. The following configuration -values are currently recognized: +values are currently recognized:: pythonexecutables a sequence of strings which represent a python executable, e.g. ('python2.2',) @@ -275,15 +275,24 @@ Drivers bind it all together ---------------------------- -Drivers serve as the glue code which call Collectors -in order to receive test Items, call their ``Item.execute()`` -method and send events/results to the ``Reporter``. +Drivers serve as the glue code between the various parts of +the interacting ``py.test`` objects, more specifically +they: + +- iterate over Collectors, which yield more Collectors or Items. + +- call the ``Item.execute()`` method in order to execute + a test + +- send "result" and other events to the reporter so that + the users gets informed about progress and problems + in possibly custom ways. Reporters process events ------------------------ A driver typically invokes certain methods of a Reporter -for various points in the execution process:: +for various points in the testing process:: start(), end() are invoked only from the outmost driver to allow a reporter to write headers and From hpk at codespeak.net Sun Oct 17 18:05:47 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Oct 2004 18:05:47 +0200 (MEST) Subject: [py-svn] r6984 - py/dist/doc Message-ID: <20041017160547.9EBE05A914@thoth.codespeak.net> Author: hpk Date: Sun Oct 17 18:05:46 2004 New Revision: 6984 Modified: py/dist/doc/future.txt py/dist/doc/why_py.txt Log: some cross-connections and some why_py structure Modified: py/dist/doc/future.txt ============================================================================== --- py/dist/doc/future.txt (original) +++ py/dist/doc/future.txt Sun Oct 17 18:05:46 2004 @@ -139,6 +139,8 @@ two honking great ideas, why not do more of them?* +.. _importexport: + Revising and improving the import/export system =============================================== Modified: py/dist/doc/why_py.txt ============================================================================== --- py/dist/doc/why_py.txt (original) +++ py/dist/doc/why_py.txt Sun Oct 17 18:05:46 2004 @@ -2,8 +2,11 @@ Why, who, what and how do you do *the py lib*? ============================================== +.. contents:: +.. sectnum:: + Why do we do the py lib? ------------------------- +======================== Among the main motivations for writing the py lib is frustration at existing python modules and packages, @@ -13,8 +16,8 @@ scripts or modules. everybody does its own hack around the unittest.py if testing happens at all. -- due to python's "export every name" policy for modules - and packages it is hard to refactor them across +- due to python's default *export all names* policy for modules + and packages it is hard to refactor code across releases becaue you might break uses of your code - the python "batteries included" are tied to the python @@ -22,7 +25,8 @@ module in older python versions which you might have to use for whatever reason. -- interactive environments +- support for interaction with command line utilities + or e.g. pygame-based shells is missing. - often modules/packages are implemented in java-style @@ -34,23 +38,26 @@ - there is no _automated_ way of installing, maintaining and upgrading applications -The py lib tries to address these problems step by step. -Of course, we can't solve them all at once but you will -find that the above points currently drive the development -of the py lib. +The py lib tries to address these problems. Of course, we +can't solve them all at once but you will find that the above +points currently drive the development of the py lib. The py +lib wants to support a *decent and well supported development +process* addressing important deployment, versioning, testing and +documentation issues - seen from the perspective of a +FOSS developer. What is your current focus? ---------------------------- +=========================== Currently, the main focus of the py lib is to get a decent `test environment`_. Automated tests fit very well to the dynamism of Python. Automated tests ease development and allow fast refactoring cycles. So we are trying to develop -the best python `test environment`_ to makes writing +the best python `test environment`_ to make writing tests as easy as possible. And fun. Most importantly, we try to allow test scripts with minimal -boilerplate code or - mostly - no boilerplate at all. With +boilerplate code or actually no boilerplate at all! With the py lib you can simply use ``assert`` statements in order to - well - assert something about objects in your code. No ``assertEqual(s)`` and all the other kinds of funny names @@ -76,8 +83,10 @@ Moreover, it provides an experimental fspython path to address a Python object on the filesystem. +You may read some more regarding the future_ of the py lib. + How does py development work? ------------------------------ +============================= We are discussing things on our `py-dev mailing list`_ and collaborate via the codespeak subversion repository. @@ -101,8 +110,22 @@ should have a problem to use the py lib under any OSI approved license and also for commercial purposes. +Are there connections with PyPy_? +------------------------------------- + +Some of the motivation for writing the py lib stems from needs +during PyPy_ development, most notably testing and +file system access issues. + +More importantly, the development perspective taken from the +PyPy developers has some influence. For example, the +`envisioned import/export system`_ clearly has some thought +references to the way wrapping of implementation level objects +is done in PyPy_. + + Who is "we"? Some history ... ------------------------------ +============================= Some of the initial code was written from Jens-Uwe Mager and Holger Krekel, after which Holger continued on an @@ -120,7 +143,9 @@ there is no real core development team as such. Also we are somewhat lacking in the win32 area. Every now and then the py lib is tested on windows but it's currently -not a continous concern of one of the current developers. +not a continous concern of one of the current developers or +contributors. + However, one of the improvements to the testing code is to allow running tests across multiple python versions and computers. Then we can run tests without having to walk @@ -131,3 +156,6 @@ .. _`PEP 8`: http://www.python.org/peps/pep-0008.html .. _`py-dev mailing list`: http://codespeak.net/mailman/listinfo/py-dev .. _`test environment`: test.html +.. _`PyPy`: http://codespeak.net/pypy +.. _`envisioned import/export system`: future.html#importexport +.. _future: future.html From hpk at codespeak.net Sun Oct 17 18:10:47 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Oct 2004 18:10:47 +0200 (MEST) Subject: [py-svn] r6985 - py/dist/doc Message-ID: <20041017161047.603CA5A914@thoth.codespeak.net> Author: hpk Date: Sun Oct 17 18:10:46 2004 New Revision: 6985 Modified: py/dist/doc/execnet.txt Log: fixes/improvements to the execnet.txt document. Modified: py/dist/doc/execnet.txt ============================================================================== --- py/dist/doc/execnet.txt (original) +++ py/dist/doc/execnet.txt Sun Oct 17 18:10:46 2004 @@ -33,33 +33,29 @@ High Level Interface for remote execution ----------------------------------------- -These gateways offer one main high level interface: +These gateways offer one main high level interface:: def remote_exec(source): """return channel object for communicating with the asynchronously executing 'source' code which will have a corresponding 'channel' - object in its executing namespace. + object in its executing namespace.""" -The most important property of execnet gateways is that -the protocol they speak with each other ``is determined by -the client side``. If you open a gateway on some server -in order to coordinate with some other programs you -determine your own protocol, not affecting all the others. -Multiple clients can run their own gateway versions in -the same remote process (e.g. connecting through their -version of a SocketGateway). +The most important property of execnet gateways is that the +protocol they speak with each other ``is determined by the +client side``. If you open a gateway on some server in order +to coordinate with some other programs you determine your +communication protocol. Multiple clients can run their own +gateway versions in the same remote process (e.g. connecting +through their version of a SocketGateway). You should not need to maintain software on the other sides you are running your code at. -... - .. _`channel-api`: The "Channel" interface for exchanging data between gateways ------------------------------------------------------------ - While executing custom strings on "the other side" is simple enough it is often tricky to deal with. Therefore we want a way to send data items to and fro between the distributedly running From hpk at codespeak.net Sun Oct 17 18:46:25 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Oct 2004 18:46:25 +0200 (MEST) Subject: [py-svn] r6986 - py/dist/doc Message-ID: <20041017164625.B34AA5A914@thoth.codespeak.net> Author: hpk Date: Sun Oct 17 18:46:25 2004 New Revision: 6986 Added: py/dist/doc/getting_started.txt Modified: py/dist/doc/index.txt py/dist/doc/why_py.txt Log: added a first "getting started" document. Added: py/dist/doc/getting_started.txt ============================================================================== --- (empty file) +++ py/dist/doc/getting_started.txt Sun Oct 17 18:46:25 2004 @@ -0,0 +1,76 @@ +Getting started with the py lib +=============================== + +.. contents:: +.. sectnum:: + +getting the current py lib +========================== + +Due to the nature of its goals the py lib can't be easily +released without a certain API consistency. Nevertheless, +the API is pretty stable in many respects and very +well tested. So we invite you to participate and +use it - especially if you share `the frustrations with +current python package development`_. + +getting it via subversion +------------------------- + +Checkout the py lib distribution tree with subversion, e.g. use:: + + svn co http://codespeak.net/svn/py/dist dist-py + +to checkout the code, documentation, tool and example tree +into a ``dist-py`` checkout directory. Your naming desire may vary, +of course. + +setting it up +------------- + +You need to put the checkout-directory into your PYTHONPATH +and you want to have the ``dist-py/py/bin/py.test`` script in +your system path, which lets you execute test files and directories. + +There already is a convenient way for Bash/Shell based systems +to setup the PYTHONPATH as well as the shell PATH, insert:: + + eval `python ~/path/to/dist-py/dist/py/env.py` + +into your ``.bash_profile``. Of course, you need to +specify your own checkout-directory. + +If you know of a good developer-style way of doing the +equivalent on win32 (non-cygwin) environments, tell us_. + +And no, we don't provide a distutils-install currently, +because we really want have the py lib installable +on multiple python versions, want to have automated +upgrades ... + +upgrading it +------------ + +Well, simple. Go to your checkout and issue:: + + svn up + +:-) + + +Participating in development +============================ + +If you feel the desire to help tackle bugs and fixes, +or support resolution of some `frustrations`_ then +please subscribe to our mailinglist: + + http://codespeak.net/mailman/listinfo/py-dev + +or start communicating by submitting improved code +if you already have an account on codespeak_. + +.. _`frustrations`: +.. _`the frustrations with current python package development`: why_py.html#frustrations +.. _us: http://codespeak.net/mailman/listinfo/py-dev +.. _codespeak: http://codespeak.net/ Modified: py/dist/doc/index.txt ============================================================================== --- py/dist/doc/index.txt (original) +++ py/dist/doc/index.txt Sun Oct 17 18:46:25 2004 @@ -13,8 +13,11 @@ `future`_ handles development visions and plans for the near future. +There also is some preliminary documentation on `getting started`_ + .. _`py.execnet`: execnet.html .. _`py.test`: test.html .. _`why_py`: why_py.html .. _`future`: future.html +.. _`getting started`: getting_started.html Modified: py/dist/doc/why_py.txt ============================================================================== --- py/dist/doc/why_py.txt (original) +++ py/dist/doc/why_py.txt Sun Oct 17 18:46:25 2004 @@ -5,6 +5,8 @@ .. contents:: .. sectnum:: +.. _frustrations: + Why do we do the py lib? ======================== @@ -102,7 +104,7 @@ across releases. If you are itching to actually fix or refactor any implementation code you can likely get commit rights to do it. However, it is then a good idea to follow -py-dev at codespeak.net and grasp some of the things that are +`py-dev mailing list`_ and grasp some of the things that are going on. Oh right, and you should also agree to release your code under an ``MIT license``. Maybe we can compute the individual copyrights from the subversion blame From hpk at codespeak.net Sun Oct 17 18:50:21 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Oct 2004 18:50:21 +0200 (MEST) Subject: [py-svn] r6987 - py/dist/doc Message-ID: <20041017165021.F05805A914@thoth.codespeak.net> Author: hpk Date: Sun Oct 17 18:50:21 2004 New Revision: 6987 Modified: py/dist/doc/index.txt Log: added a warning. Modified: py/dist/doc/index.txt ============================================================================== --- py/dist/doc/index.txt (original) +++ py/dist/doc/index.txt Sun Oct 17 18:50:21 2004 @@ -11,9 +11,12 @@ `why_py`_ describes a bit of the motivation and background `future`_ handles development visions and plans for the near future. - + +Note that some of these texts refer to future development and +do not exactly reflect the current state. Welcome to +documentation & test driven development :-) -There also is some preliminary documentation on `getting started`_ +There is some preliminary documentation on `getting started`_ .. _`py.execnet`: execnet.html .. _`py.test`: test.html From hpk at codespeak.net Sun Oct 17 23:50:47 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Oct 2004 23:50:47 +0200 (MEST) Subject: [py-svn] r6988 - py/dist/doc Message-ID: <20041017215047.A66585A914@thoth.codespeak.net> Author: hpk Date: Sun Oct 17 23:50:47 2004 New Revision: 6988 Modified: py/dist/doc/ (props changed) py/dist/doc/future.txt py/dist/doc/test.txt py/dist/doc/why_py.txt Log: little fixes here and there. Modified: py/dist/doc/future.txt ============================================================================== --- py/dist/doc/future.txt (original) +++ py/dist/doc/future.txt Sun Oct 17 23:50:47 2004 @@ -5,25 +5,26 @@ .. contents:: .. sectnum:: -This document tries to make transparent ideas regarding future -development of the py lib. *Note that all statements within this -document - even if they sound factual - just express -thoughts and not always real code so read with some caution. -This is not a reference guide (tm).* +This document tries to describe directions and guiding ideas +for the near-future development of the py lib. *Note that all +statements within this document - even if they sound factual - +may just express thoughts. They not always refer to real code +so read with some caution. This is not a reference guide +(tm).* .. _`a more general view on path objects`: -Direction: A more general view on ``py.path`` objects -===================================================== +A more general view on ``py.path`` objects +========================================== -Seen from a more general persective, the current ``fspy`` path +Seen from a more general persective, the current ``py.path.fspy`` path offers a way to go from a file to the structured content of -a file, namely a python object. This path retains some -common "path" operations and semantics but also offers additional +a file, namely a python object. The ``fspy`` path retains some +common ``path`` operations and semantics but offers additional methods, e.g. ``resolve()`` gets you a true python object. But apart from python files there are many other examples -of structured content like xml documents or Ini-style +of structured content like xml documents or INI-style config files. While some tasks will only be possible to perform in a domain specific manner (e.g. applying xslt etc.pp) ``py.path`` offers a common behaviour for @@ -52,7 +53,7 @@ content of the file as values. But it also works (today!) for ``fspy`` paths if you put the following -python code in the file, referenced as ``startpath`` above:: +python code in a file:: class Section1: someoption = "i am an option value" Modified: py/dist/doc/test.txt ============================================================================== --- py/dist/doc/test.txt (original) +++ py/dist/doc/test.txt Sun Oct 17 23:50:47 2004 @@ -6,7 +6,7 @@ .. sectnum:: *PLEASE NOTICE*: - Warning: a few names like ``Driver`` and ``Item`` used in this document + a few names like ``Driver`` and ``Item`` used in this document mostly represent a future naming, they are currently called differently, ``Runner`` and ``Unit`` respectively. The latter names are causing confusion with the ``unittest.py`` nomen clatura. ``py.test`` @@ -24,21 +24,18 @@ statement. The existence of a standalone ``py.test`` tool allows -to write tests without any boilerplate: +to write tests without any boilerplate:: def test_answer(): assert 42 == 43 this would be the complete content of a ``test_sample.py`` -test script and you can simply execute the tests by invoking: +test script and you can execute the tests by invoking:: py.test test_sample.py -For seeing various options you can use: - - py.test -h - -to see the list of options. +See `getting started`_ for how to install the 'py.test' script +on your system. Basic features for writing tests @@ -349,3 +346,5 @@ - lift some of the arbitrary restrictions of unittest.py: allow plain test functions (without being in a class) and allow classes to simply mean "grouping" of tests. + +.. _`getting started`: getting_started Modified: py/dist/doc/why_py.txt ============================================================================== --- py/dist/doc/why_py.txt (original) +++ py/dist/doc/why_py.txt Sun Oct 17 23:50:47 2004 @@ -20,17 +20,17 @@ - due to python's default *export all names* policy for modules and packages it is hard to refactor code across - releases becaue you might break uses of your code + releases because you might break usages of your code -- the python "batteries included" are tied to the python - release process. You can't easily benefit from new python - module in older python versions which you might have - to use for whatever reason. +- the python "batteries included" standard modules are tied to + the python release process. You can't easily benefit from + new python module in older python versions which you might + have to use for whatever reason. -- support for interaction with command line utilities - or e.g. pygame-based shells is missing. +- support for richer interactive interaction with command line + utilities or e.g. pygame-based shells is missing. -- often modules/packages are implemented in java-style +- modules/packages are often implemented in javaesque -style - distributed applications are implemented using a variant of Remote Method Invocation (RMI) which carries @@ -40,16 +40,17 @@ - there is no _automated_ way of installing, maintaining and upgrading applications -The py lib tries to address these problems. Of course, we +The py lib tries to address these problems. It is thus in +risk of trying to do too many things at once. Of course, we can't solve them all at once but you will find that the above points currently drive the development of the py lib. The py lib wants to support a *decent and well supported development process* addressing important deployment, versioning, testing and -documentation issues - seen from the perspective of a +documentation issues - seen primarily from the perspective of a FOSS developer. -What is your current focus? -=========================== +What is the py libs current focus? +================================== Currently, the main focus of the py lib is to get a decent `test environment`_. Automated tests fit very well to the @@ -133,23 +134,23 @@ and Holger Krekel, after which Holger continued on an iteration of the py.test tool (known first as 'utest', then as 'std.utest', now 'py.test'). Helpful discussions took place -with Martijn Faassen, and then Armin Rigo who contributed -important parts. He and Holger came up with a couple of -iterations of the testing-code that reduced the API to almost -nothing: just the assert statement and an assert_raises -method. +with Martijn Faassen, Stephan Schwarzer and then Armin Rigo who +contributed important parts. He and Holger came up with a +couple of iterations of the testing-code that reduced the API +to almost nothing: just the assert statement and an +assert_raises method. Now recently, after Holgers `talk at EP2004`_ more people -were interested and there were some discussions with Jim Fulton -and Marius Gedminas and more recently, Ian Bicking. However, -there is no real core development team as such. Also we -are somewhat lacking in the win32 area. Every now and -then the py lib is tested on windows but it's currently -not a continous concern of one of the current developers or +were interested and there were some discussions with Jim Fulton, +Marius Gedminas, Laura Creighton and more recently, Ian Bicking. +However, there is no real core development team as such. Also +we are somewhat lacking in the win32 area. Every now and then +the py lib is tested on windows but it's currently not a +continous concern of one of the current developers or contributors. -However, one of the improvements to the testing code is to -allow running tests across multiple python versions and +However, one of the future directions of the `py.test tool and library`_ +is to allow running tests across multiple python versions and computers. Then we can run tests without having to walk up or boot up a windows machine :-) @@ -161,3 +162,4 @@ .. _`PyPy`: http://codespeak.net/pypy .. _`envisioned import/export system`: future.html#importexport .. _future: future.html +.. _`py.test tool and library`: test From hpk at codespeak.net Mon Oct 18 00:57:50 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 18 Oct 2004 00:57:50 +0200 (MEST) Subject: [py-svn] r6991 - in py/dist: doc example/test py/test/report/text Message-ID: <20041017225750.AE44F5A914@thoth.codespeak.net> Author: hpk Date: Mon Oct 18 00:57:49 2004 New Revision: 6991 Modified: py/dist/doc/test.txt py/dist/example/test/failure_demo.py py/dist/py/test/report/text/summary.py Log: made the reporter display traceback information more nicely (i think) some fixes/extensions to the test document. Modified: py/dist/doc/test.txt ============================================================================== --- py/dist/doc/test.txt (original) +++ py/dist/doc/test.txt Mon Oct 18 00:57:49 2004 @@ -26,11 +26,13 @@ The existence of a standalone ``py.test`` tool allows to write tests without any boilerplate:: + # content of test_sample.py def test_answer(): assert 42 == 43 -this would be the complete content of a ``test_sample.py`` -test script and you can execute the tests by invoking:: +In contrast to ``unitest.py`` limitations you +can have test functions as well as test methods. +You can execute the test by invoking:: py.test test_sample.py @@ -38,30 +40,30 @@ on your system. -Basic features for writing tests -================================ +Features of ``py.test`` +======================= assert with the ``assert`` statement ------------------------------------ -writing assertions is very simple and this -is one of py.tests most noticeable features: - - You can simply use the ``assert`` statement. - -For example you can write: - - assert hasattr(x, 'something') - -and in case this fails the ``test reporter`` will provide -you with a very helpful analysis and a clean traceback. - -Please note that in order to display helpful analysis -of a failing ``assert`` expression some magic takes -place behind the scenes. For now, you only need to -know that if something looks strange or you suspect -a bug in that behind-the-scenes-magic you may turn -of the magic by providing the ``--nomagic`` option. +Writing assertions is very simple and this is one of py.tests +most noticeable features, as you can use the ``assert`` +statement with arbitrary expressions. For example you can +write the following in your tests:: + + assert hasattr(x, 'attribute') + +to state that your object has a certain ``attribute`` +and in case this assertion fails the test ``reporter`` +will provide you with a very helpful analysis and a +clean traceback. + +Note that in order to display helpful analysis of a failing +``assert`` statement some magic takes place behind the +scenes. For now, you only need to know that if something +looks strange or you suspect a bug in that +*behind-the-scenes-magic* you may turn of the magic by +providing the ``--nomagic`` option. how to write assertions about execptions ---------------------------------------- @@ -92,6 +94,15 @@ you will find that they execute in exactly the same order within each file. +useful tracebacks, recursion detection +-------------------------------------- + +You will at some point notice that a lot of care is taken +to present nice tracebacks. Tracebacks usually start +with your failing test function and they will abort early +in case of recursion with a statement stating the +recursion. + Managing test state across test modules, classes and methods ------------------------------------------------------------ Modified: py/dist/example/test/failure_demo.py ============================================================================== --- py/dist/example/test/failure_demo.py (original) +++ py/dist/example/test/failure_demo.py Mon Oct 18 00:57:49 2004 @@ -27,27 +27,27 @@ return 43 somefunc(f(), g()) - def test_z1_unpack_error(): + def test_z1_unpack_error(self): l = [] a,b = l - def test_z2_type_error(): + def test_z2_type_error(self): l = 3 a,b = l - def test_startswith(): + def test_startswith(self): s = "123" g = "456" assert s.startswith(g) - def test_startswith_nested(): + def test_startswith_nested(self): def f(): return "123" def g(): return "456" assert f().startswith(g()) - def test_global_func(): + def test_global_func(self): assert isinstance(globf(42), float) def test_instance(self): Modified: py/dist/py/test/report/text/summary.py ============================================================================== --- py/dist/py/test/report/text/summary.py (original) +++ py/dist/py/test/report/text/summary.py Mon Oct 18 00:57:49 2004 @@ -98,10 +98,10 @@ pass else: if out.strip(): - self.out.sep("- ", "recorded pyout") + self.out.sep("- ", "recorded stdout") self.out.line(out.strip()) if err.strip(): - self.out.sep("- ", "recorded pyerr") + self.out.sep("- ", "recorded stderr") self.out.line(err.strip()) #self.out.line() @@ -109,7 +109,7 @@ cls = res.excinfo[0] if issubclass(cls, run.ExceptionFailure): if not res.innerexcinfo: - self.out.line(">>> %s <<< DID NOT RAISE" % (res.expr,)) + self.out.line("%s <<< DID NOT RAISE" % (res.expr,)) self.out.line("expected: %r" % (res.expected,)) else: self.out.write("%r raised wrong Exception, " % (res.expr,)) @@ -186,8 +186,14 @@ return old def repr_traceback(self, unit, tb, tbindex=-1): - self.out.line(">>> %s" % unit.pypath) - self.repr_traceback_raw(unit.pypath.fspath, tb, tbindex) + t_file, t_lineno = unit.pypath.getfilelineno() + self.out.line("%s, line %d" % (unit.pypath, t_lineno) ) + fspath = unit.pypath.fspath + self.repr_traceback_raw(fspath, tb, tbindex) + #t_file, t_lineno = unit.pypath.getfilelineno() + #if t_file != filename or lineno != t_lineno: + #self.out.line("%s, line %d" % (unit.pypath, t_lineno) ) + self.out.sep('-') def repr_traceback_raw(self, fspath, tb, tbindex=-1): if fspath and not self.option.fulltrace: @@ -200,6 +206,7 @@ recursioncache = {} for tb in tbentries: + self.out.sep('-') filename = tb.tb_frame.f_code.co_filename lineno = tb.tb_lineno name = tb.tb_frame.f_code.co_name @@ -207,8 +214,8 @@ #showfn = filename.split(os.sep) #if len(showfn) > 5: # showfn = os.sep.join(['...'] + showfn[-5:]) - self.out.line(">>> %r, line %d" %(showfn, lineno)) # , name)) self.repr_source(tb.tb_frame, lineno) + self.out.line("> %s, line %d" %(showfn, lineno)) # , name)) if self.option.showlocals: self.out.sep('- ', 'locals') for name, value in tb.tb_frame.f_locals.items(): @@ -216,7 +223,6 @@ key = (filename, lineno) if key not in recursioncache: recursioncache.setdefault(key, []).append(tb.tb_frame.f_locals) - self.out.sep('-') else: loc = tb.tb_frame.f_locals for x in recursioncache[key]: @@ -224,7 +230,7 @@ self.out.line("Recursion detected (same locals & position)") break else: - self.out.sep('-') + #self.out.sep('-') continue break From hpk at codespeak.net Mon Oct 18 02:10:17 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 18 Oct 2004 02:10:17 +0200 (MEST) Subject: [py-svn] r6993 - py/dist/py/test/report/text Message-ID: <20041018001017.C848F5A914@thoth.codespeak.net> Author: hpk Date: Mon Oct 18 02:10:17 2004 New Revision: 6993 Modified: py/dist/py/test/report/text/summary.py Log: reducing the linenoise in the reporter Modified: py/dist/py/test/report/text/summary.py ============================================================================== --- py/dist/py/test/report/text/summary.py (original) +++ py/dist/py/test/report/text/summary.py Mon Oct 18 02:10:17 2004 @@ -86,8 +86,10 @@ exc, frame, filename,lineno = self.getexinfo(res) self.out.line() self.out.sep("_") - self.out.sep("_", "Test Failure") # %s" % res.unit.pypath) self.out.line() + #self.out.sep("_", "Test Failure") # %s" % res.unit.pypath) + #self.out.sep("_") + #self.out.line() self.repr_traceback(res.unit, res.excinfo[2], getattr(res, 'tbindex', -1)) #self.out.line() @@ -189,6 +191,7 @@ t_file, t_lineno = unit.pypath.getfilelineno() self.out.line("%s, line %d" % (unit.pypath, t_lineno) ) fspath = unit.pypath.fspath + self.out.sep('_') self.repr_traceback_raw(fspath, tb, tbindex) #t_file, t_lineno = unit.pypath.getfilelineno() #if t_file != filename or lineno != t_lineno: @@ -205,8 +208,12 @@ tbentries = tbentries[:tbindex+1] recursioncache = {} + first = True for tb in tbentries: - self.out.sep('-') + if first: + first = False + else: + self.out.sep('-') filename = tb.tb_frame.f_code.co_filename lineno = tb.tb_lineno name = tb.tb_frame.f_code.co_name From hpk at codespeak.net Mon Oct 18 02:15:26 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 18 Oct 2004 02:15:26 +0200 (MEST) Subject: [py-svn] r6995 - in py/dist: doc py py/path/fspy py/path/test py/test py/test/report/text py/test/test/data Message-ID: <20041018001526.149A15A914@thoth.codespeak.net> Author: hpk Date: Mon Oct 18 02:15:25 2004 New Revision: 6995 Modified: py/dist/doc/future.txt py/dist/doc/test.txt py/dist/doc/why_py.txt py/dist/py/__init__.py py/dist/py/initpkg.py py/dist/py/path/fspy/test_fspy.py py/dist/py/path/test/common.py py/dist/py/path/test/test_api.py py/dist/py/test/cmdline.py py/dist/py/test/collect.py py/dist/py/test/compat.py py/dist/py/test/report/text/reporter.py py/dist/py/test/report/text/summary.py py/dist/py/test/run.py py/dist/py/test/test/data/Collector.py py/dist/py/test/test_collect.py Log: some refactoring / renaming to make the documentation right :-) - the test code now really deals with a Driver and test Items and not a Runner and Units anymore. The caution notice from the future text is removed etc.pp. - the filesystem/python/root path formerly known as 'fspy' is now called 'extpy'. - fixed some documentation Modified: py/dist/doc/future.txt ============================================================================== --- py/dist/doc/future.txt (original) +++ py/dist/doc/future.txt Mon Oct 18 02:15:25 2004 @@ -17,9 +17,9 @@ A more general view on ``py.path`` objects ========================================== -Seen from a more general persective, the current ``py.path.fspy`` path +Seen from a more general persective, the current ``py.path.extpy`` path offers a way to go from a file to the structured content of -a file, namely a python object. The ``fspy`` path retains some +a file, namely a python object. The ``extpy`` path retains some common ``path`` operations and semantics but offers additional methods, e.g. ``resolve()`` gets you a true python object. @@ -28,7 +28,7 @@ config files. While some tasks will only be possible to perform in a domain specific manner (e.g. applying xslt etc.pp) ``py.path`` offers a common behaviour for -structured content paths. So far only ``py.path.fspy`` +structured content paths. So far only ``py.path.extpy`` is implemented and used by py.test to address tests and traverse into test files. @@ -52,7 +52,7 @@ directories as sections and files as option-names and the content of the file as values. -But it also works (today!) for ``fspy`` paths if you put the following +But it also works (today!) for ``extpy`` paths if you put the following python code in a file:: class Section1: @@ -61,7 +61,7 @@ class Section2: someoption = "i am another option value" -An ``fspy()`` path maps classes and modules to directories and +An ``extpy()`` path maps classes and modules to directories and name-value bindings to file/read() operations. And it could also work for 'xml' paths if you put @@ -107,7 +107,7 @@ path objects should be stackable -------------------------------- -Oh, and btw, a ``py.path.fspy`` file could live on top of a +Oh, and btw, a ``py.path.extpy`` file could live on top of a 'py.path.xml' path as well, i.e. take:: @@ -120,11 +120,11 @@ def getmsg(x): pass -and use it to have a ``fspy`` path living on it:: +and use it to have a ``extpy`` path living on it:: p = py.path.local(xmlfilename) xmlp = py.path.xml(p, 'py/magic/exprinfo') - p = py.path.fspy(xmlp, 'getmsg') + p = py.path.extpy(xmlp, 'getmsg') assert p.check(func=1, basename='getmsg') getmsg = p.resolve() Modified: py/dist/doc/test.txt ============================================================================== --- py/dist/doc/test.txt (original) +++ py/dist/doc/test.txt Mon Oct 18 02:15:25 2004 @@ -5,15 +5,6 @@ .. contents:: .. sectnum:: -*PLEASE NOTICE*: - a few names like ``Driver`` and ``Item`` used in this document - mostly represent a future naming, they are currently called differently, - ``Runner`` and ``Unit`` respectively. The latter names are causing - confusion with the ``unittest.py`` nomen clatura. ``py.test`` - takes a much more general view on testing and thus it should start - out with a slightly different view. However, as a user you will - not get in touch too quickly with these names, anyway. So calm down :-) - starting point: ``py.test`` command line tool ============================================= @@ -30,7 +21,7 @@ def test_answer(): assert 42 == 43 -In contrast to ``unitest.py`` limitations you +In contrast to ``unittest.py`` limitations you can have test functions as well as test methods. You can execute the test by invoking:: @@ -261,7 +252,7 @@ The ``driver`` invokes the iteration protocol, i.e. the ``__iter__`` method of Collectors. These methods yield more (sub) -*collectors* or Test *Items*. They should usually not raise +*Collectors* or test *Items*. They should usually not raise exceptions but yield back a specific CollectError. This is to avoid that a collecting error breaks the whole collection chain. It is at the drivers discretion to react to errors Modified: py/dist/doc/why_py.txt ============================================================================== --- py/dist/doc/why_py.txt (original) +++ py/dist/doc/why_py.txt Mon Oct 18 02:15:25 2004 @@ -83,7 +83,7 @@ Another focus is a well tested Path implementation that supports different backends, currently a local filesystem and subversion working copies and subversion remote URLs. -Moreover, it provides an experimental fspython path to address +Moreover, it provides an experimental extpython path to address a Python object on the filesystem. You may read some more regarding the future_ of the py lib. Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Mon Oct 18 02:15:25 2004 @@ -4,7 +4,7 @@ 'path.checker': './path/common.checker', 'path.svnurl': './path/svn/urlcommand.SvnCommandPath', 'path.svnwc': './path/svn/wccommand.SvnWCCommandPath', - 'path.fspy': './path/fspy/fspy.Fspy', + 'path.extpy': './path/fspy/fspy.Fspy', 'path.NotFound': './path/error.FileNotFound', 'path.Denied': './path/error.PermissionDenied', 'path.NoDirectory': './path/error.NoDirectory', @@ -20,7 +20,7 @@ 'test.raises': './test/raises.raises', 'test.config': './test/config.config', 'test.compat.TestCase': './test/compat.TestCase', - 'test.Unit': './test/run.Unit', + 'test.Item': './test/run.Item', 'test.Option': './test/tool/optparse.Option', 'test.TextReporter': './test/report/text/reporter.TextReporter', 'test.MemoReporter': './test/report/memo.MemoReporter', Modified: py/dist/py/initpkg.py ============================================================================== --- py/dist/py/initpkg.py (original) +++ py/dist/py/initpkg.py Mon Oct 18 02:15:25 2004 @@ -32,18 +32,18 @@ # inhibit further direct filesystem imports through the package module del pkgmodule.__path__ - def _resolve(self, fspy): + def _resolve(self, extpy): """ resolve a combined filesystem/python "fspy" path. """ - assert fspy.startswith('./'), \ - "%r is not an implementation path (XXX)" % fspy + assert extpy.startswith('./'), \ + "%r is not an implementation path (XXX)" % extpy - slash = fspy.rfind('/') - dot = fspy.find('.', slash) + slash = extpy.rfind('/') + dot = extpy.find('.', slash) if dot == -1: - return self._loadimpl(fspy) + return self._loadimpl(extpy) - implmodule = self._loadimpl(fspy[:dot]) - attrname = fspy[dot+1:] + implmodule = self._loadimpl(extpy[:dot]) + attrname = extpy[dot+1:] return getattr(implmodule, attrname) def _loadimpl(self, relfile): @@ -111,11 +111,11 @@ def __getattr__(self, name): try: - fspy = self.__map__[name] + extpy = self.__map__[name] except KeyError: raise AttributeError(name) #print "getattr(%r, %r)" %(self, name) - result = self.__package__._resolve(fspy) + result = self.__package__._resolve(extpy) setattr(self, name, result) del self.__map__[name] # XXX modify some attrs to make a class appear at virtual module level @@ -153,7 +153,7 @@ pkg = Package(pkgname, exportdefs) seen = { pkgname : pkg.module } - for pypath, fspy in pkg.exportitems(): + for pypath, extpy in pkg.exportitems(): pyparts = pypath.split('.') modparts = pyparts[:-1] current = pkgname @@ -170,6 +170,6 @@ if not hasattr(mod, '__map__'): assert mod is pkg.module, \ "only root modules are allowed to be non-lazy. " - setattr(mod, pyparts[-1], pkg._resolve(fspy)) + setattr(mod, pyparts[-1], pkg._resolve(extpy)) else: - mod.__map__[pyparts[-1]] = fspy + mod.__map__[pyparts[-1]] = extpy Modified: py/dist/py/path/fspy/test_fspy.py ============================================================================== --- py/dist/py/path/fspy/test_fspy.py (original) +++ py/dist/py/path/fspy/test_fspy.py Mon Oct 18 02:15:25 2004 @@ -7,12 +7,12 @@ class TestFsPyCommonTests(common.CommonPathTests): def setup_class(cls): - cls.root = py.path.fspy( + cls.root = py.path.extpy( py.magic.autopath().dirpath('inc_pseudofs.py')) class TestFsPy: def setup_class(cls): - cls.root = py.path.fspy(mypath) + cls.root = py.path.extpy(mypath) def test_join(self): p = self.root.join('A') @@ -147,11 +147,11 @@ class TestErrors: def test_FileNotFound(self): - p = py.path.fspy(mypath, 'somesuch') + p = py.path.extpy(mypath, 'somesuch') py.test.raises(py.path.NotFound, p.resolve) def test_FileNotFound_really(self): - p = py.path.fspy(mypath.new(basename='notexist'), 'somesuch') + p = py.path.extpy(mypath.new(basename='notexist'), 'somesuch') py.test.raises(py.path.NotFound, p.resolve) #def test_ImportError(): Modified: py/dist/py/path/test/common.py ============================================================================== --- py/dist/py/path/test/common.py (original) +++ py/dist/py/path/test/common.py Mon Oct 18 02:15:25 2004 @@ -43,7 +43,7 @@ x = other.common(self.root) assert x == self.root - third = py.path.fspy('x', 'whatever') + third = py.path.extpy('x', 'whatever') x = other.common(third) assert x is None Modified: py/dist/py/path/test/test_api.py ============================================================================== --- py/dist/py/path/test/test_api.py (original) +++ py/dist/py/path/test/test_api.py Mon Oct 18 02:15:25 2004 @@ -7,7 +7,7 @@ def repr_eval_test(self, p): r = repr(p) - from py.path import local,svnurl, svnwc, fspy + from py.path import local,svnurl, svnwc, extpy y = eval(r) assert y == p Modified: py/dist/py/test/cmdline.py ============================================================================== --- py/dist/py/test/cmdline.py (original) +++ py/dist/py/test/cmdline.py Mon Oct 18 02:15:25 2004 @@ -32,7 +32,7 @@ collectors.append(test.collect.Directory(path.local())) reporter = config.reporter - runner = test.run.Runner(reporter) + runner = test.run.Driver(reporter) runner.setup() try: try: Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Mon Oct 18 02:15:25 2004 @@ -22,7 +22,7 @@ """ instances are *restartable iterators*. They will yield Units or Collector instances during iteration. """ - Unit = test.run.Unit + Item = test.run.Item def iterunits(self): """ yield all units of the Collector instance. """ @@ -61,7 +61,7 @@ if self.rec(fspath): yield self.__class__(fspath) elif self.fil(fspath): - yield Module(py.path.fspy(fspath)) + yield Module(py.path.extpy(fspath)) except: yield self._except() @@ -70,24 +70,24 @@ # ---------------------------------------------- class PyCollector(Collector): - def __init__(self, fspy): - self.fspy = py.path.fspy(fspy) + def __init__(self, extpy): + self.extpy = py.path.extpy(extpy) self.yielders = [getattr(self, x) for x in dir(self.__class__) if x.startswith('collect_')] def __repr__(self): - return '%s(%r)' %(self.__class__.__name__, str(self.fspy)) + return '%s(%r)' %(self.__class__.__name__, str(self.extpy)) def __iter__(self): try: # we want to sort according to lineno, so here goes # the generator lazyness l = [] - for pypath in self.fspy.listdir(): + for pypath in self.extpy.listdir(): for meth in self.yielders: for x in meth(pypath): - x.fspath = self.fspy.fspath + x.fspath = self.extpy.fspath sortvalue = self.getsortvalue(x) l.append((sortvalue, x)) l.sort() @@ -100,7 +100,7 @@ """ sorting function to bring test methods in the same order as int he file. """ - if isinstance(obj, self.Unit): + if isinstance(obj, self.Item): obj = obj.pypath.resolve() elif isinstance(obj, PyCollector): for x in obj: @@ -116,9 +116,9 @@ class Module(PyCollector): def __iter__(self): try: - iter = self.fspy.join('Collector') + iter = self.extpy.join('Collector') if iter.check(exists=True): - iter = iter.resolve()(self.fspy) + iter = iter.resolve()(self.extpy) else: iter = super(Module, self).__iter__() for x in iter: @@ -128,12 +128,12 @@ def collect_function(self, pypath): if pypath.check(func=1, basestarts='test_'): - if self.fspy.samefile(pypath): - yield self.Unit(pypath) + if self.extpy.samefile(pypath): + yield self.Item(pypath) def collect_class(self, pypath): #print "checking %r (pypath: %r)" % (pypath.resolve(), pypath) - if pypath.check(basestarts='Test') and self.fspy.samefile(pypath): + if pypath.check(basestarts='Test') and self.extpy.samefile(pypath): obj = pypath.resolve() if inspect.isclass(obj) and not getattr(obj, 'disabled', 0): yield Class(pypath) @@ -145,5 +145,5 @@ # methods like in the Module Collector if pypath.check(basestarts='test_', func=1): func = pypath.resolve() - yield getattr(func.im_class, 'Unit', self.Unit)(pypath) + yield getattr(func.im_class, 'Item', self.Item)(pypath) Modified: py/dist/py/test/compat.py ============================================================================== --- py/dist/py/test/compat.py (original) +++ py/dist/py/test/compat.py Mon Oct 18 02:15:25 2004 @@ -1,7 +1,7 @@ from __future__ import generators from py import test, magic -class TestCaseUnit(test.run.Unit): +class TestCaseUnit(test.run.Item): """ compatibility Unit executor for TestCase methods honouring setUp and tearDown semantics. """ @@ -18,7 +18,7 @@ class TestCase: """compatibility class of unittest's TestCase. """ - Unit = TestCaseUnit + Item = TestCaseUnit def setUp(self): pass Modified: py/dist/py/test/report/text/reporter.py ============================================================================== --- py/dist/py/test/report/text/reporter.py (original) +++ py/dist/py/test/report/text/reporter.py Mon Oct 18 02:15:25 2004 @@ -93,26 +93,26 @@ self.out.line() return close_directory - def startunit(self, unit): + def startitem(self, item): if not self.option.nocapture: from py.__impl__.test.tool.outerrcapture import SimpleOutErrCapture - unit.iocapture = SimpleOutErrCapture() + item.iocapture = SimpleOutErrCapture() if self.out.tty: - realpath, lineno = unit.pypath.getfilelineno() - location = "running %s:%d %s" % (realpath.basename, lineno, str(unit.pypath.modpath)) + realpath, lineno = item.pypath.getfilelineno() + location = "running %s:%d %s" % (realpath.basename, lineno, str(item.pypath.modpath)) self.out.rewrite(location) - self._started[unit] = now() + self._started[item] = now() - def endunit(self, result): + def enditem(self, result): endtime = now() - unit = result.unit - starttime = self._started[unit] - del self._started[unit] + item = result.item + starttime = self._started[item] + del self._started[item] elapsed = endtime - starttime - unit.elapsed = elapsed + item.elapsed = elapsed if not self.option.nocapture: - result.out, result.err = unit.iocapture.reset() + result.out, result.err = item.iocapture.reset() restype, c = self.processresult(result) writeinfo = None @@ -127,11 +127,11 @@ self.out.write(c) if writeinfo is not None: - realpath, lineno = unit.pypath.getfilelineno() + realpath, lineno = item.pypath.getfilelineno() location = "%s:%d" % (realpath.basename, lineno) resultstring = self.namemap.get(restype, result.__class__.__name__) self.out.rewrite("%.3f %-2s %-20s %s%s" % ( - elapsed, resultstring, location, str(unit.pypath.modpath), writeinfo + elapsed, resultstring, location, str(item.pypath.modpath), writeinfo )) if self.option.usepdb: if (issubclass(restype, collect.Error) or Modified: py/dist/py/test/report/text/summary.py ============================================================================== --- py/dist/py/test/report/text/summary.py (original) +++ py/dist/py/test/report/text/summary.py Mon Oct 18 02:15:25 2004 @@ -90,7 +90,7 @@ #self.out.sep("_", "Test Failure") # %s" % res.unit.pypath) #self.out.sep("_") #self.out.line() - self.repr_traceback(res.unit, res.excinfo[2], + self.repr_traceback(res.item, res.excinfo[2], getattr(res, 'tbindex', -1)) #self.out.line() self.repr_failure_result(res) @@ -120,7 +120,7 @@ #self.out.line() self.out.sep("- ", "traceback of unexpected exception") #self.out.line("reality:") - self.repr_traceback(res.unit, res.innerexcinfo[2]) + self.repr_traceback(res.item, res.innerexcinfo[2]) for line in traceback.format_exception_only(*res.innerexcinfo[:2]): self.out.line(line) elif issubclass(cls, assertion.AssertionError): @@ -187,10 +187,10 @@ tb = tb.tb_next return old - def repr_traceback(self, unit, tb, tbindex=-1): - t_file, t_lineno = unit.pypath.getfilelineno() - self.out.line("%s, line %d" % (unit.pypath, t_lineno) ) - fspath = unit.pypath.fspath + def repr_traceback(self, item, tb, tbindex=-1): + t_file, t_lineno = item.pypath.getfilelineno() + self.out.line("%s, line %d" % (item.pypath, t_lineno) ) + fspath = item.pypath.fspath self.out.sep('_') self.repr_traceback_raw(fspath, tb, tbindex) #t_file, t_lineno = unit.pypath.getfilelineno() Modified: py/dist/py/test/run.py ============================================================================== --- py/dist/py/test/run.py (original) +++ py/dist/py/test/run.py Mon Oct 18 02:15:25 2004 @@ -3,7 +3,7 @@ import sys, inspect from py import test -class Runner: +class Driver: option = test.config.option def __init__(self, reporter): @@ -14,12 +14,12 @@ def run(self, obj): """ run (possibly many) testitems and/or collectors. """ collect = test.collect - if isinstance(obj, Unit): + if isinstance(obj, Item): if not self.option.collectonly: #if self.option.args: # if str(obj.path).find(self.option.args[0]) == -1: # return - self.rununit(obj) + self.runitem(obj) elif isinstance(obj, collect.Collector): self.runcollector(obj) elif isinstance(obj, collect.Error): @@ -37,7 +37,7 @@ if self.option.exitfirstproblem: raise SystemExit, 2 else: - raise TypeError("%r is not a Unit or Info instance" % obj) + raise TypeError("%r is not a Item or Info instance" % obj) def runcollector(self, collector): close = self.reporter.open(collector) @@ -48,18 +48,18 @@ if close: close() - def rununit(self, unit): - self.reporter.startunit(unit) + def runitem(self, item): + self.reporter.startitem(item) try: - res = unit.execute(self) or Passed() + res = item.execute(self) or Passed() except Outcome, res: res.excinfo = sys.exc_info() except (KeyboardInterrupt, SystemExit): raise except: res = Failed(excinfo=sys.exc_info()) - res.unit = unit - self.reporter.endunit(res) + res.item = item + self.reporter.enditem(res) def setup_path(self, pypath): """ setup objects along the path to the test-method @@ -117,7 +117,7 @@ # ---------------------------------------------- # Basic Test Unit Executor # ---------------------------------------------- -class Unit(object): +class Item(object): _setupcache = [] _lastinstance = None Modified: py/dist/py/test/test/data/Collector.py ============================================================================== --- py/dist/py/test/test/data/Collector.py (original) +++ py/dist/py/test/test/data/Collector.py Mon Oct 18 02:15:25 2004 @@ -4,7 +4,7 @@ class Collector(collect.PyCollector): def collect_function(self, pypath): if pypath.check(func=1, basestarts='myprefix_'): - yield self.Unit(pypath, pypath.basename) + yield self.Item(pypath, pypath.basename) def myprefix_1(arg): assert arg == 'myprefix_1' Modified: py/dist/py/test/test_collect.py ============================================================================== --- py/dist/py/test/test_collect.py (original) +++ py/dist/py/test/test_collect.py Mon Oct 18 02:15:25 2004 @@ -10,7 +10,7 @@ def test_failing_import_execfile(): fn = datadir / 'failingimport.py' - l = list(collect.Module(path.fspy(fn))) + l = list(collect.Module(path.extpy(fn))) assert l ex, = l assert issubclass(ex.excinfo[0], ImportError) @@ -42,16 +42,16 @@ assert issubclass(l2[0].excinfo[0], path.Invalid) def test_disabled_class(): - fspy = path.fspy(datadir.join('disabled.py')) - l = list(collect.Class(fspy)) + extpy = path.extpy(datadir.join('disabled.py')) + l = list(collect.Class(extpy)) assert len(l) == 0 class TestCustomCollector: def test_custom_collect(self): l = list(collect.Module(datadir.join('Collector.py'))) assert len(l) == 3 - for unit in l: - assert isinstance(unit, test.Unit) + for item in l: + assert isinstance(item, test.Item) #for x in l2: # assert isinstance(x, Unit) # x.execute() From hpk at codespeak.net Mon Oct 18 02:45:04 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 18 Oct 2004 02:45:04 +0200 (MEST) Subject: [py-svn] r6996 - in py/dist: doc py/test Message-ID: <20041018004504.ECC005A914@thoth.codespeak.net> Author: hpk Date: Mon Oct 18 02:45:04 2004 New Revision: 6996 Added: py/dist/doc/rest_test.py Modified: py/dist/doc/test.txt py/dist/py/test/run.py Log: fixes and a test for the rest :-) Added: py/dist/doc/rest_test.py ============================================================================== --- (empty file) +++ py/dist/doc/rest_test.py Mon Oct 18 02:45:04 2004 @@ -0,0 +1,30 @@ +from __future__ import generators + +import py + +mypath = py.magic.autopath() +mydir = mypath.dirpath() +rest = mydir.dirpath('tool', 'rest.py') + +class RestItem(py.test.run.Item): + def __init__(self, path): + self.path = path + self.pypath = py.path.extpy(mypath, 'RestItem.execute') + + def execute(self, *args): + out = py.process.cmdexec("%s %s 2>&1" %(rest, self.path)) + assert not out + +class Collector(py.test.collect.Module): + def __init__(self, extpy): + self.extpy = extpy + + def __iter__(self): + for x in mydir.listdir('*.txt'): + yield RestItem(x) + +#def test_rest(p): +# out = py.process.cmdexec("%s %s" %(rest, mypath)) +# print out +# assert not out + Modified: py/dist/doc/test.txt ============================================================================== --- py/dist/doc/test.txt (original) +++ py/dist/doc/test.txt Mon Oct 18 02:45:04 2004 @@ -92,10 +92,12 @@ to present nice tracebacks. Tracebacks usually start with your failing test function and they will abort early in case of recursion with a statement stating the -recursion. +recursion. If you run ``example/test/failure_demo.py`` you +will see a variety of 40 tracebacks shown for different +failure situations. Managing test state across test modules, classes and methods ------------------------------------------------------------- +------------------------------------------------------------ Often you want to create some test files, db-connections or other state in order to run tests in a certain environment. Modified: py/dist/py/test/run.py ============================================================================== --- py/dist/py/test/run.py (original) +++ py/dist/py/test/run.py Mon Oct 18 02:45:04 2004 @@ -37,7 +37,7 @@ if self.option.exitfirstproblem: raise SystemExit, 2 else: - raise TypeError("%r is not a Item or Info instance" % obj) + raise TypeError("%r is not a Item or Collector instance" % obj) def runcollector(self, collector): close = self.reporter.open(collector) From hpk at codespeak.net Mon Oct 18 03:50:49 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 18 Oct 2004 03:50:49 +0200 (MEST) Subject: [py-svn] r6997 - in py/dist/py: . path/fspy Message-ID: <20041018015049.68DF25A18F@thoth.codespeak.net> Author: hpk Date: Mon Oct 18 03:50:47 2004 New Revision: 6997 Added: py/dist/py/path/fspy/extpy.py - copied, changed from r6996, py/dist/py/path/fspy/fspy.py Removed: py/dist/py/path/fspy/fspy.py Modified: py/dist/py/__init__.py py/dist/py/pytest.py Log: some remaining moves that can - alas - only be performed subsequently. renaming a child while renaming its parent is not supported by subversion ... Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Mon Oct 18 03:50:47 2004 @@ -4,7 +4,7 @@ 'path.checker': './path/common.checker', 'path.svnurl': './path/svn/urlcommand.SvnCommandPath', 'path.svnwc': './path/svn/wccommand.SvnWCCommandPath', - 'path.extpy': './path/fspy/fspy.Fspy', + 'path.extpy': './path/fspy/extpy.Extpy', 'path.NotFound': './path/error.FileNotFound', 'path.Denied': './path/error.PermissionDenied', 'path.NoDirectory': './path/error.NoDirectory', Copied: py/dist/py/path/fspy/extpy.py (from r6996, py/dist/py/path/fspy/fspy.py) ============================================================================== --- py/dist/py/path/fspy/fspy.py (original) +++ py/dist/py/path/fspy/extpy.py Mon Oct 18 03:50:47 2004 @@ -10,7 +10,7 @@ import sys import inspect, imp -class Fspy(common.PathBase): +class Extpy(common.PathBase): """ a path abstraction addressing python objects in a file system. """ sep = '.' def __new__(cls, fspath='', modpath=''): @@ -31,7 +31,7 @@ def __repr__(self): #if self.ns is rootns: # return 'py(%r)' % (self.modpath, ) - return 'fspy(%r, %r)' % (self.fspath, self.modpath) + return 'extpy(%r, %r)' % (self.fspath, self.modpath) def __str__(self): return str(self.fspath.new(ext=self.modpath)) @@ -169,7 +169,7 @@ if self.samefile(p): return True - for x in super(Fspy, self).visit(fil=fil, rec=rec, ignore=ignore): + for x in super(Extpy, self).visit(fil=fil, rec=rec, ignore=ignore): yield x return Deleted: /py/dist/py/path/fspy/fspy.py ============================================================================== --- /py/dist/py/path/fspy/fspy.py Mon Oct 18 03:50:47 2004 +++ (empty file) @@ -1,242 +0,0 @@ -""" -A path to python objects located in filesystems. - -Note: this is still experimental and may be removed - for the first stable release! -""" -from __future__ import generators -import py -from py.__impl__.path import common -import sys -import inspect, imp - -class Fspy(common.PathBase): - """ a path abstraction addressing python objects in a file system. """ - sep = '.' - def __new__(cls, fspath='', modpath=''): - if isinstance(fspath, cls) and not modpath: - return fspath - - assert isinstance(modpath, str) - self = object.__new__(cls) - self.modpath = modpath - if isinstance(fspath, str): - fspath = py.path.local(fspath) - self.fspath = fspath - return self - - def __hash__(self): - return hash((self.fspath, self.modpath)) - - def __repr__(self): - #if self.ns is rootns: - # return 'py(%r)' % (self.modpath, ) - return 'fspy(%r, %r)' % (self.fspath, self.modpath) - - def __str__(self): - return str(self.fspath.new(ext=self.modpath)) - - def join(self, *args): - for arg in args: - if not isinstance(arg, str): - raise TypeError, "non-strings not allowed in %r" % args - modpath = [x.strip('.') for x in ((self.modpath,)+args) if x] - modpath = self.sep.join(modpath) - return self.__class__(self.fspath, modpath) - - def dirpath(self, *args): - modpath = self.modpath.split(self.sep) [:-1] - modpath = self.sep.join(modpath+list(args)) - return self.__class__(self.fspath, modpath) - - def new(self, **kw): - """ create a modified version of this path. - the following keyword arguments modify various path parts: - modpath substitute module path - """ - cls = self.__class__ - if 'modpath' in kw: - return cls(self.fspath, kw['modpath']) - if 'basename' in kw: - i = self.modpath.rfind('.') - if i != -1: - return cls(self.fspath, self.modpath[i+1:] + kw['basename']) - else: - return cls(self.fspath, kw['basename']) - return cls(self.fspath, self.modpath) - - def get(self, spec): - l = [] - modparts = self.modpath.split(self.sep) - for name in spec.split(','): - if name == 'basename': - l.append(modparts[-1]) - - if len(l) == 1: - return l[0] - elif len(l) == 0: - return None - else: - return l - - def getmodule(self): - #modname = str(self.fspath) - modname = str(self.fspath.new(ext='')).replace(self.fspath.sep, '_') - try: - return sys.modules[modname] - except KeyError: - #print "trying importing", modname - if not self.fspath.check(file=1): - raise py.path.NotFound(self.fspath) - mod = imp.load_source(modname, str(self.fspath)) - mod.__name__ = modname - sys.modules[modname] = mod - return mod - - def resolve(self): - """return the python object belonging to path. """ - module = self.getmodule() - rest = filter(None, self.modpath.split('.')) - target = module - for name in rest: - try: - target = getattr(target, name) - except AttributeError: - raise py.path.NotFound(str(self)) - return target - - def relto(self, otherpath): - if self.fspath == otherpath.fspath: - if self.modpath.startswith(otherpath.modpath): - s = self.modpath[len(otherpath.modpath):] - return s.lstrip(self.sep) - return '' - - def listobj(self, fil=None, **kw): - l = [] - for x in self.listdir(fil, **kw): - l.append(x.resolve()) - return l - - def listdir(self, fil=None, sort=True, **kw): - if kw: - if fil is None: - fil = py.path.checker(**kw) - else: - raise TypeError, "cannot take filter and keyword arguments" - elif isinstance(fil, str): - fil = common.fnmatch(fil) - obj = self.resolve() - #if obj is rootns: - # d = {} - # for x in sys.modules: - # name = x.split('.')[0] - # d[name] = 1 - # l = [self.join(x) for x in d if not fil or fil(x)] - #else: - l = [] - for name in dir(obj): - sub = self.join(name) - if not fil or fil(sub): - l.append(sub) - - #print "listdir(%r) -> %r" %(self, l) - #print "listdir on", repr(self) - return l - - def setfile(self, filepath): - self._filepath = filepath - - def getfilelineno(self, scrapinit=0): - x = obj = self.resolve() - if inspect.ismodule(obj): - return obj.__file__, 0 - if inspect.ismethod(obj): - obj = obj.im_func - if inspect.isfunction(obj): - obj = obj.func_code - if inspect.iscode(obj): - return py.path.local(obj.co_filename), obj.co_firstlineno - else: - source, lineno = inspect.findsource(obj) - return x.getfile(), lineno - - def visit(self, fil=None, rec=None, ignore=None, seen=None): - def myrec(p, seen={id(self): True}): - if id(p) in seen: - return False - seen[id(p)] = True - if self.samefile(p): - return True - - for x in super(Fspy, self).visit(fil=fil, rec=rec, ignore=ignore): - yield x - return - - if seen is None: - seen = {id(self): True} - - if isinstance(fil, str): - fil = common.fnmatch(fil) - if isinstance(rec, str): - rec = common.fnmatch(fil) - - if ignore: - try: - l = self.listdir() - except ignore: - return - else: - l = self.listdir() - reclist = [] - for p in l: - if fil is None or fil(p): - yield p - if id(p) not in seen: - try: - obj = p.resolve() - if inspect.isclass(obj) or inspect.ismodule(obj): - reclist.append(p) - finally: - seen[id(p)] = p - for p in reclist: - for i in p.visit(fil, rec, seen): - yield i - - def samefile(self, other): - otherobj = other.resolve() - try: - x = inspect.getfile(otherobj) - except TypeError: - return False - if x.endswith('pyc'): - x = x[:-1] - if str(self.fspath) == x: - return True - - class Checkers(common.Checkers): - _depend_on_existence = (common.Checkers._depend_on_existence + - ('func', 'class_', 'exists', 'dir')) - - def _obj(self): - self._obj = self.path.resolve() - return self._obj - - def exists(self): - obj = self._obj() - return True - - def func(self): - ob = self._obj() - return inspect.isfunction(ob) or inspect.ismethod(ob) - - def class_(self): - ob = self._obj() - return inspect.isclass(ob) - - def isinstance(self, args): - return isinstance(self._obj(), args) - - def dir(self): - obj = self._obj() - return inspect.isclass(obj) or inspect.ismodule(obj) Modified: py/dist/py/pytest.py ============================================================================== --- py/dist/py/pytest.py (original) +++ py/dist/py/pytest.py Mon Oct 18 03:50:47 2004 @@ -1,5 +1,5 @@ #pythonexecutables = ('python2.2', 'python2.3',) -pythonexecutable = 'python2.2' +#pythonexecutable = 'python2.2' def setup_module(extpy): mod = extpy.resolve() From hpk at codespeak.net Mon Oct 18 03:59:37 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 18 Oct 2004 03:59:37 +0200 (MEST) Subject: [py-svn] r6998 - in py/dist/py: . path/extpy path/fspy Message-ID: <20041018015937.231595A18F@thoth.codespeak.net> Author: hpk Date: Mon Oct 18 03:59:36 2004 New Revision: 6998 Added: py/dist/py/path/extpy/ - copied from r6996, py/dist/py/path/fspy/ py/dist/py/path/extpy/__init__.py - copied unchanged from r6997, py/dist/py/path/fspy/__init__.py py/dist/py/path/extpy/extpy.py - copied unchanged from r6997, py/dist/py/path/fspy/extpy.py py/dist/py/path/extpy/inc_pseudofs.py - copied unchanged from r6997, py/dist/py/path/fspy/inc_pseudofs.py py/dist/py/path/extpy/inc_test_fspy.py - copied unchanged from r6997, py/dist/py/path/fspy/inc_test_fspy.py py/dist/py/path/extpy/test_fspy.py - copied unchanged from r6997, py/dist/py/path/fspy/test_fspy.py Removed: py/dist/py/path/fspy/ Modified: py/dist/py/__init__.py Log: next step to get rid of 'fspy' ... Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Mon Oct 18 03:59:36 2004 @@ -4,7 +4,7 @@ 'path.checker': './path/common.checker', 'path.svnurl': './path/svn/urlcommand.SvnCommandPath', 'path.svnwc': './path/svn/wccommand.SvnWCCommandPath', - 'path.extpy': './path/fspy/extpy.Extpy', + 'path.extpy': './path/extpy/extpy.Extpy', 'path.NotFound': './path/error.FileNotFound', 'path.Denied': './path/error.PermissionDenied', 'path.NoDirectory': './path/error.NoDirectory', From hpk at codespeak.net Mon Oct 18 04:06:01 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 18 Oct 2004 04:06:01 +0200 (MEST) Subject: [py-svn] r6999 - py/dist/py/path/extpy Message-ID: <20041018020601.D38B35A18F@thoth.codespeak.net> Author: hpk Date: Mon Oct 18 04:06:01 2004 New Revision: 6999 Added: py/dist/py/path/extpy/inc_test_extpy.py - copied unchanged from r6998, py/dist/py/path/extpy/inc_test_fspy.py py/dist/py/path/extpy/test_extpy.py - copied, changed from r6998, py/dist/py/path/extpy/test_fspy.py Removed: py/dist/py/path/extpy/inc_test_fspy.py py/dist/py/path/extpy/test_fspy.py Log: and some more fspy renames Deleted: /py/dist/py/path/extpy/inc_test_fspy.py ============================================================================== --- /py/dist/py/path/extpy/inc_test_fspy.py Mon Oct 18 04:06:01 2004 +++ (empty file) @@ -1,13 +0,0 @@ - -class A: - x1 = 42 - def func(self): - pass - -class B: - x2 = 23 - -class Nested: - class Class: - def borgfunc(self): pass - Copied: py/dist/py/path/extpy/test_extpy.py (from r6998, py/dist/py/path/extpy/test_fspy.py) ============================================================================== --- py/dist/py/path/extpy/test_fspy.py (original) +++ py/dist/py/path/extpy/test_extpy.py Mon Oct 18 04:06:01 2004 @@ -3,14 +3,14 @@ from py.__impl__.path.test import common -mypath = py.magic.autopath().dirpath('inc_test_fspy.py') +mypath = py.magic.autopath().dirpath('inc_test_extpy.py') -class TestFsPyCommonTests(common.CommonPathTests): +class TestExtPyCommonTests(common.CommonPathTests): def setup_class(cls): cls.root = py.path.extpy( py.magic.autopath().dirpath('inc_pseudofs.py')) -class TestFsPy: +class TestExtPy: def setup_class(cls): cls.root = py.path.extpy(mypath) Deleted: /py/dist/py/path/extpy/test_fspy.py ============================================================================== --- /py/dist/py/path/extpy/test_fspy.py Mon Oct 18 04:06:01 2004 +++ (empty file) @@ -1,162 +0,0 @@ -import sys, os -import py - -from py.__impl__.path.test import common - -mypath = py.magic.autopath().dirpath('inc_test_fspy.py') - -class TestFsPyCommonTests(common.CommonPathTests): - def setup_class(cls): - cls.root = py.path.extpy( - py.magic.autopath().dirpath('inc_pseudofs.py')) - -class TestFsPy: - def setup_class(cls): - cls.root = py.path.extpy(mypath) - - def test_join(self): - p = self.root.join('A') - obj = p.resolve() - assert hasattr(obj, '__bases__') - assert obj.x1 == 42 - - def test_listdir_module(self): - l = self.root.listdir() - basenames = [x.basename for x in l] - dlist = dir(self.root.getmodule()) - for name in dlist: - assert name in basenames - for name in basenames: - assert name in dlist - - def test_listdir_class(self): - l = self.root.join('A').listdir() - basenames = [x.basename for x in l] - dlist = dir(self.root.getmodule().A) - for name in dlist: - assert name in basenames - for name in basenames: - assert name in dlist - - def listobj(self): - l = self.root.listobj(basestarts='path') - assert len(l) == 1 - assert l[0] == path - - def test_visit(self): - l = list(self.root.visit(py.path.checker(basename='borgfunc'))) - assert len(l) == 1 - obj = l[0] - assert str(obj).endswith('Nested.Class.borgfunc') - assert obj.resolve() == self.root.getmodule().Nested.Class.borgfunc - - def test_visit_fnmatch(self): - l = list(self.root.visit('borg*')) - assert len(l) == 1 - obj = l[0] - assert str(obj).endswith('Nested.Class.borgfunc') - assert obj.resolve() == self.root.getmodule().Nested.Class.borgfunc - - #def test_join_from_empty(self): - # p = path.py('') - # n = p.join('tokenize') - # assert str(n) == 'tokenize' - # - # p = path.py('', ns=os) - # n = p.join('getlogin') - # assert str(n) == 'getlogin' - - #def test_unspecifiedpypath_lists_modules(self): - # p = path.py('') - # l = p.listdir() - # for i in l: - # assert '.' not in str(i) - # - # for j in sys.modules: - # for i in l: - # if j.startswith(str(i)): - # break - # else: - # self.fail("%s is not in sys.modules") - - #def test_main_works(self): - # m = path.py('__main__') - # import __main__ - # assert m.resolve() is __main__ - - def test_relto(self): - m1 = self.root.new(modpath='a.b.c.d') - m2 = self.root.new(modpath='a.b') - m3 = self.root.new(modpath='') - res = m1.relto(m2) - assert str(res) == 'c.d' - assert m2.relto(m3) == 'a.b' - - def test_basename(self): - m1 = self.root.new(modpath='a.b.hello') - assert m1.basename == 'hello' - assert m1.check(basename='hello') - assert not m1.check(basename='nono') - assert m1.check(basestarts='he') - assert not m1.check(basestarts='42') - - def test_dirpath(self): - m1 = self.root.new(modpath='a.b.hello') - m2 = self.root.new(modpath='a.b') - m3 = self.root.new(modpath='a') - m4 = self.root.new(modpath='') - assert m1.dirpath() == m2 - assert m2.dirpath() == m3 - assert m3.dirpath() == m4 - - def test_function(self): - p = self.root.join('A.func') - assert p.check(func=1) - p = self.root.join('A.x1') - assert p.check(func=0) - - def test_hashing_equality(self): - x = self.root - y = self.root.new() - assert x == y - assert hash(x) == hash(y) - - def test_parents(self): - x = self.root.new(modpath='os.path.abspath') - l = x.parts() - assert len(l) == 4 - assert self.root.join('') == l[0] - assert self.root.join('os') == l[1] - assert self.root.join('os.path') == l[2] - assert self.root.join('os.path.abspath') == l[3] - -class TestEval: - disabled = True - def test_funccall(self): - p = path.py('os.path.join("a", "b")') - s = p.resolve() - assert s == os.path.join("a", "b") - - def test_invalid1(self): - p = path.py('os.path.qwe("a", "b")') - s = test.raises(path.NotFound, "p.resolve()") - - def test_syntaxerror(self): - p = path.py('os.path.qwe("a", ') - s = test.raises(ValueError, "p.resolve()") - -class TestErrors: - def test_FileNotFound(self): - p = py.path.extpy(mypath, 'somesuch') - py.test.raises(py.path.NotFound, p.resolve) - - def test_FileNotFound_really(self): - p = py.path.extpy(mypath.new(basename='notexist'), 'somesuch') - py.test.raises(py.path.NotFound, p.resolve) - - #def test_ImportError(): - # p = path.py('__std.utest.test.data.failingimport.someattr') - # utest.raises(ImportError, p.resolve) - -class ExampleClass: - testattr = 1 From hpk at codespeak.net Mon Oct 18 13:08:01 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 18 Oct 2004 13:08:01 +0200 (MEST) Subject: [py-svn] r7000 - py/dist/doc Message-ID: <20041018110801.4DE865A92B@thoth.codespeak.net> Author: hpk Date: Mon Oct 18 13:08:00 2004 New Revision: 7000 Modified: py/dist/doc/ (props changed) py/dist/doc/future.txt py/dist/doc/index.txt py/dist/doc/test.txt Log: - added a future chapter about lightweightly generating xml - some more fixes and additions Modified: py/dist/doc/future.txt ============================================================================== --- py/dist/doc/future.txt (original) +++ py/dist/doc/future.txt Mon Oct 18 13:08:00 2004 @@ -380,6 +380,68 @@ in the neverending `future book`_. +Generating xml/html +=================== + +The py-doc strives to offer enough functionality to represent +itself and especially its API in html or xml. Moreover, +`py.test`_ wants to run distributedly in order to execute +tests on different processes and platforms. It will need to +exchange data between its running program fragments. + +For example, test ``Results`` could be represented in lightweight +xml or we could export the whole result of the testing process +as xml, anyway, to allow other tools to post-process the output. +The starting point for this, btw, is the `Reporter`_ from the +test module. + +lightweight xml generation +-------------------------- + +Besides DOM implementations there are some interesting +techniques for marrying XML and python, most notable xist_ +which `uses python class objects`_ to build xml trees. + +However, its implementation seems slightly heavy because it +has additional goals like transformations and supporting +many namespaces. + +Luckily the basic idea is pretty easy to implement, especially +if we don't validate. There already is a basic implementation +with a lot of *py.test-style* tests `under the +xpy tree`_ which was mentioned in Holgers `xpython EuroPython 2004 talk`_ +at EuroPython 2004. However, this is still somewhat entangled with +a different project (see the talk) so it would need some refactoring +before integrating it into the py lib. + +basic example for generating html +--------------------------------- + +Consider this example:: + + import py + py.xml.namespace('html') + paras = "First Para", "Second para" + doc = html.html( + html.head( + html.meta(name="Content-Type", value="text/html; charset=utf8)) + html.body( + [html.p(p) for p in paras])) + print unicode(doc).encode('latin1') + +this would give you a latin1 representation of the obvious document. +I think the beauty of this is apparent and beats string-concantentation +or DOM-manipulations big time. For now, i don't think we should +strive to offer much more than the above. Well, of course, we would +like to access a sane object model for the generated html but this +needs some more experimentations and a comparison with xist_ i guess. + +.. _`xpython EuroPython 2004 talk`: http://codespeak.net/svn/user/hpk/talks/xpython-talk.txt +.. _`under the xpy tree`: http://codespeak.net/svn/user/hpk/xpy/xml.py .. _`future book`: future.html .. _`PEP-324 subprocess module`: http://www.python.org/peps/pep-0324.html .. _`subprocess implementation`: http://www.lysator.liu.se/~astrand/popen5/ +.. _`py.test`: test.html +.. _`Reporter`: test.html#reporter +.. _xist: http://www.livinglogic.de/Python/xist/index.html +.. _`uses python class objects`: http://www.livinglogic.de/Python/xist/Howto.html Modified: py/dist/doc/index.txt ============================================================================== --- py/dist/doc/index.txt (original) +++ py/dist/doc/index.txt Mon Oct 18 13:08:00 2004 @@ -16,7 +16,8 @@ do not exactly reflect the current state. Welcome to documentation & test driven development :-) -There is some preliminary documentation on `getting started`_ +There is some preliminary documentation for `getting started`_ + .. _`py.execnet`: execnet.html .. _`py.test`: test.html Modified: py/dist/doc/test.txt ============================================================================== --- py/dist/doc/test.txt (original) +++ py/dist/doc/test.txt Mon Oct 18 13:08:00 2004 @@ -289,11 +289,16 @@ the users gets informed about progress and problems in possibly custom ways. -Reporters process events ------------------------- +.. _reporter: + +Reporters process test events +----------------------------- A driver typically invokes certain methods of a Reporter -for various points in the testing process:: +for various points in the testing process. A reporter +is reponsible for representing the testing process +to the user or other programs. These are the events +(function calls) that the reporter receives:: start(), end() are invoked only from the outmost driver to allow a reporter to write headers and From hpk at codespeak.net Mon Oct 18 13:10:30 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 18 Oct 2004 13:10:30 +0200 (MEST) Subject: [py-svn] r7001 - py/dist/doc Message-ID: <20041018111030.1A90E5A92B@thoth.codespeak.net> Author: hpk Date: Mon Oct 18 13:10:30 2004 New Revision: 7001 Modified: py/dist/doc/future.txt Log: why is it that you only see bugs after you committed them? Modified: py/dist/doc/future.txt ============================================================================== --- py/dist/doc/future.txt (original) +++ py/dist/doc/future.txt Mon Oct 18 13:10:30 2004 @@ -420,16 +420,19 @@ Consider this example:: import py - py.xml.namespace('html') + html = py.xml.namespace('html') + paras = "First Para", "Second para" + doc = html.html( html.head( html.meta(name="Content-Type", value="text/html; charset=utf8)) html.body( [html.p(p) for p in paras])) + print unicode(doc).encode('latin1') -this would give you a latin1 representation of the obvious document. +This would give you a latin1 representation of the obvious document. I think the beauty of this is apparent and beats string-concantentation or DOM-manipulations big time. For now, i don't think we should strive to offer much more than the above. Well, of course, we would From hpk at codespeak.net Mon Oct 18 14:07:49 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 18 Oct 2004 14:07:49 +0200 (MEST) Subject: [py-svn] r7007 - py/dist/doc Message-ID: <20041018120749.D97075AA7F@thoth.codespeak.net> Author: hpk Date: Mon Oct 18 14:07:49 2004 New Revision: 7007 Modified: py/dist/doc/future.txt Log: added a new future chapter about mapping the standard python library into the 'py' namespace ... Modified: py/dist/doc/future.txt ============================================================================== --- py/dist/doc/future.txt (original) +++ py/dist/doc/future.txt Mon Oct 18 14:07:49 2004 @@ -383,13 +383,13 @@ Generating xml/html =================== -The py-doc strives to offer enough functionality to represent +The py lib strives to offer enough functionality to represent itself and especially its API in html or xml. Moreover, `py.test`_ wants to run distributedly in order to execute tests on different processes and platforms. It will need to exchange data between its running program fragments. -For example, test ``Results`` could be represented in lightweight +For example, test ``results`` could be represented in lightweight xml or we could export the whole result of the testing process as xml, anyway, to allow other tools to post-process the output. The starting point for this, btw, is the `Reporter`_ from the @@ -432,13 +432,56 @@ print unicode(doc).encode('latin1') -This would give you a latin1 representation of the obvious document. +This would print a latin1 representation of the obvious document. I think the beauty of this is apparent and beats string-concantentation -or DOM-manipulations big time. For now, i don't think we should -strive to offer much more than the above. Well, of course, we would -like to access a sane object model for the generated html but this -needs some more experimentations and a comparison with xist_ i guess. +or DOM-manipulations big time. +For now, i don't think we should strive to offer much more +than the above. Well, of course, we would like to access a +sane object model for the generated html but this needs some +more experimentations and a comparison with xist_ i guess. + +mapping the standard python library into py? +============================================ + +After you have worked with the py lib a bit, you might enjoy +the lazy importing, i.e. you only have to ``import py`` and +work your way to your desired object. This way of using it +also ensures that we will focus on getting short paths to +objects and (because of the import/export system we can) avoid +uglyness like ''ConfigParser.ConfigParser`` alltogether. + +convenience, lazyness, oh the joy! +---------------------------------- + +Now it would be rather convenient to be able to say:: + + py.std.traceback.print_exc() + +without having to import anything else than 'py' at +the beginning. Not having imports for the `python +standard library` would obviously get rid of the +unused import problem :-) + +Moreover, this would resolve some of the issues +stated in `the relative/absolute import PEP-328`_, +as with the above approach you never have ambiguity +problems. The above is obviously a pretty absolute +path that will not be confused with local names. +(Well, never put a file ``py.py`` in an importable +path, mind you :-) + +Backporting new python modules? +------------------------------- + +We may also think about backporting some 2.3 and 2.4 +modules to have them generally available. Maybe it +is not really worth it. At least we don't need to +think about it before the first step of making +the plain system libraries available. + +.. _`the relative/absolute import PEP-328`: http://www.python.org/peps/pep-0328.html +.. _`python standard library`: http://www.python.org/doc/2.3.4/lib/lib.html .. _`xpython EuroPython 2004 talk`: http://codespeak.net/svn/user/hpk/talks/xpython-talk.txt .. _`under the xpy tree`: http://codespeak.net/svn/user/hpk/xpy/xml.py .. _`future book`: future.html From hpk at codespeak.net Mon Oct 18 21:38:41 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 18 Oct 2004 21:38:41 +0200 (MEST) Subject: [py-svn] r7010 - py/dist/py/test Message-ID: <20041018193841.C6DAA5A18F@thoth.codespeak.net> Author: hpk Date: Mon Oct 18 21:38:39 2004 New Revision: 7010 Modified: py/dist/py/test/config.py Log: fixed a small bug with respect to module imports (accidentally a trailing '.py' in a module's __name__ only works for 'py', oh well, like i said the config stuff needs some cleanup and some more tests) Modified: py/dist/py/test/config.py ============================================================================== --- py/dist/py/test/config.py (original) +++ py/dist/py/test/config.py Mon Oct 18 21:38:39 2004 @@ -89,7 +89,7 @@ try: return self.files[uconf] except KeyError: - par = [x.basename for x in uconf.parts()] + par = [x.basename for x in uconf.new(ext='').parts()] name = "_".join(par) mod = new.module(name) mod.__file__ = uconf From hpk at codespeak.net Mon Oct 18 22:34:11 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 18 Oct 2004 22:34:11 +0200 (MEST) Subject: [py-svn] r7013 - in py/dist/py: . misc Message-ID: <20041018203411.459E65A18F@thoth.codespeak.net> Author: hpk Date: Mon Oct 18 22:34:10 2004 New Revision: 7013 Added: py/dist/py/misc/ (props changed) py/dist/py/misc/__init__.py py/dist/py/misc/test_api.py - copied unchanged from r6999, py/dist/py/test_api.py py/dist/py/misc/test_initpkg.py - copied unchanged from r6999, py/dist/py/test_initpkg.py Removed: py/dist/py/test_api.py py/dist/py/test_initpkg.py Log: moved some stuff into a separate directory to clean up the top level and getter better filename completion on py/test :-) Added: py/dist/py/misc/__init__.py ============================================================================== --- (empty file) +++ py/dist/py/misc/__init__.py Mon Oct 18 22:34:10 2004 @@ -0,0 +1 @@ +# Deleted: /py/dist/py/test_api.py ============================================================================== --- /py/dist/py/test_api.py Mon Oct 18 22:34:10 2004 +++ (empty file) @@ -1,54 +0,0 @@ - -from py.test import raises -import py -import sys -import inspect - -class API_V0_namespace_consistence: - def test_path_entrypoints(self): - assert inspect.ismodule(py.path) - assert_class('py.path', 'local') - assert_class('py.path', 'svnwc') - assert_class('py.path', 'svnurl') - assert_class('py.path', 'py') - assert_class('py.path', 'checker') - assert_class('py.path', 'invchecker') - assert_class('py.path', 'NotFound') - assert_class('py.path', 'Denied') - - def test_magic_entrypoints(self): - assert_class('py.magic', 'View') - assert_function('py.magic', 'invoke') - assert_function('py.magic', 'revoke') - assert_function('py.magic', 'patch') - assert_function('py.magic', 'revoke') - - assert inspect.ismodule(py.magic.dyncode) - assert_function('py.magic.dyncode', 'compile') - assert_function('py.magic.dyncode', 'compile2') - assert_function('py.magic.dyncode', 'findsource') - assert_function('py.magic.dyncode', 'getsource') - assert_function('py.magic.dyncode', 'listtb') - assert_function('py.magic.dyncode', 'findsource') - - def test_process_entrypoints(self): - assert_function('py.process', 'cmdexec') - - def test_utest_entrypoints(self): - # XXX TOBECOMPLETED - assert_function('py.test', 'main') - #assert_module('std.utest', 'collect') - -def assert_class(modpath, name): - mod = __import__(modpath, None, None, [name]) - obj = getattr(mod, name) - fullpath = modpath + '.' + name - assert obj.__module__ == modpath - if sys.version_info >= (2,3): - assert obj.__name__ == name - -def assert_function(modpath, name): - mod = __import__(modpath, None, None, [name]) - obj = getattr(mod, name) - assert hasattr(obj, 'func_doc') - #assert obj.func_name == name Deleted: /py/dist/py/test_initpkg.py ============================================================================== --- /py/dist/py/test_initpkg.py Mon Oct 18 22:34:10 2004 +++ (empty file) @@ -1,60 +0,0 @@ -import py -import types - -def test_dir(): - for name in dir(py): - if not name.startswith('_'): - obj = getattr(py, name) - if isinstance(obj, types.ModuleType): - keys = dir(obj) - assert len(keys) > 0 - assert getattr(obj, '__map__') == {} - -def test_virtual_module_identity(): - from py import path as path1 - from py import path as path2 - assert path1 is path2 - from py.path import local as local1 - from py.path import local as local2 - assert local1 is local2 - -def test_importing_all_implementations(): - base = py.path.local(py.__file__).dirpath() - for p in base.visit('*.py', py.path.checker(dotfile=0)): - relpath = p.new(ext='').relto(base) - if base.sep in relpath: # not std/*.py itself - if relpath.find('test/data') != -1: - continue - if relpath.find('bin/') != -1: - continue - relpath = relpath.replace(base.sep, '.') - modpath = 'py.__impl__.%s' % relpath - assert __import__(modpath) - -def test_shahexdigest(): - hex = py.__package__.shahexdigest() - assert len(hex) == 40 - -def test_getzipdata(): - s = py.__package__.getzipdata() - -# the following test should abasically work in the future -def XXXtest_virtual_on_the_fly(): - py.initpkg('my', { - 'x.abspath' : 'os.path.abspath', - 'x.local' : 'py.path.local', - 'y' : 'smtplib', - 'z.cmdexec' : 'py.process.cmdexec', - }) - from my.x import abspath - from my.x import local - import smtplib - from my import y - assert y is smtplib - from my.z import cmdexec - from py.process import cmdexec as cmdexec2 - assert cmdexec is cmdexec2 - -##def test_help(): -# help(std.path) -# #assert False From hpk at codespeak.net Mon Oct 18 23:52:43 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 18 Oct 2004 23:52:43 +0200 (MEST) Subject: [py-svn] r7014 - in py/dist: doc py py/misc Message-ID: <20041018215243.C48475A18F@thoth.codespeak.net> Author: hpk Date: Mon Oct 18 23:52:42 2004 New Revision: 7014 Added: py/dist/doc/misc.txt py/dist/py/misc/std.py py/dist/py/misc/test_std.py Modified: py/dist/doc/ (props changed) py/dist/doc/future.txt py/dist/doc/index.txt py/dist/py/__init__.py py/dist/py/pytest.py Log: the future is becoming experimentally present :-) may i present the py.std hook? http://codespeak.net/py/current/doc/misc.html Modified: py/dist/doc/future.txt ============================================================================== --- py/dist/doc/future.txt (original) +++ py/dist/doc/future.txt Mon Oct 18 23:52:42 2004 @@ -441,46 +441,7 @@ sane object model for the generated html but this needs some more experimentations and a comparison with xist_ i guess. -mapping the standard python library into py? -============================================ -After you have worked with the py lib a bit, you might enjoy -the lazy importing, i.e. you only have to ``import py`` and -work your way to your desired object. This way of using it -also ensures that we will focus on getting short paths to -objects and (because of the import/export system we can) avoid -uglyness like ''ConfigParser.ConfigParser`` alltogether. - -convenience, lazyness, oh the joy! ----------------------------------- - -Now it would be rather convenient to be able to say:: - - py.std.traceback.print_exc() - -without having to import anything else than 'py' at -the beginning. Not having imports for the `python -standard library` would obviously get rid of the -unused import problem :-) - -Moreover, this would resolve some of the issues -stated in `the relative/absolute import PEP-328`_, -as with the above approach you never have ambiguity -problems. The above is obviously a pretty absolute -path that will not be confused with local names. -(Well, never put a file ``py.py`` in an importable -path, mind you :-) - -Backporting new python modules? -------------------------------- - -We may also think about backporting some 2.3 and 2.4 -modules to have them generally available. Maybe it -is not really worth it. At least we don't need to -think about it before the first step of making -the plain system libraries available. - -.. _`the relative/absolute import PEP-328`: http://www.python.org/peps/pep-0328.html .. _`python standard library`: http://www.python.org/doc/2.3.4/lib/lib.html .. _`xpython EuroPython 2004 talk`: http://codespeak.net/svn/user/hpk/talks/xpython-talk.txt .. _`under the xpy tree`: http://codespeak.net/svn/user/hpk/xpy/xml.py Modified: py/dist/doc/index.txt ============================================================================== --- py/dist/doc/index.txt (original) +++ py/dist/doc/index.txt Mon Oct 18 23:52:42 2004 @@ -4,11 +4,14 @@ Here is some documentation about main areas within *the py lib*: + `py.test`_ describes features of the py.test utility `py.execnet`_ offers an innovative way to distribute programs across the net - - `why_py`_ describes a bit of the motivation and background + + `miscellaneous features`_ describes some more py lib features + + `why py?`_ describes a bit of the motivation and background `future`_ handles development visions and plans for the near future. @@ -21,7 +24,8 @@ .. _`py.execnet`: execnet.html .. _`py.test`: test.html -.. _`why_py`: why_py.html +.. _`why py?`: why_py.html .. _`future`: future.html .. _`getting started`: getting_started.html +.. _`miscellaneous features`: misc.html Added: py/dist/doc/misc.txt ============================================================================== --- (empty file) +++ py/dist/doc/misc.txt Mon Oct 18 23:52:42 2004 @@ -0,0 +1,83 @@ +==================================== +Miscellaneous features of the py lib +==================================== + +.. contents:: +.. sectnum:: + +Mapping the standard python library into py +=========================================== + + Warning: This feature is very young and thus experimental. + Be prepared to adapt your code later if you use it. + +After you have worked with the py lib a bit, you might enjoy +the lazy importing, i.e. you only have to do ``import py`` and +work your way to your desired object. Using the full path +also ensures that there remains a focus on getting short paths +to objects. + +The ``py.std`` hook +------------------- + +Of course, no matter what, everybody will continue to use the +python standard library because it is a very usable code base. +However, to properly support lazyness the py lib offers a way +to get to many standard modules without requiring "import" +statements. For example, to get to the print-exception +functionality of the standard library you can write:: + + py.std.traceback.print_exc() + +without having to do anything else than the usual ``import py`` +at the beginning. Note that not having imports for the +`python standard library` obviously gets rid of the *unused +import* problem. Modules only get imported when you actually +need them. + +Moreover, this approach resolves some of the issues stated in +`the relative/absolute import PEP-328`_, as with the above +approach you never have ambiguity problems. The above +traceback-usage is an absolute path that will not be +accidentally get confused with local names. (Well, never put +a file ``py.py`` in an importable path, btw, mind you :-) + +Automagically accessing sub packages doesn't work (yet?) +-------------------------------------------------------- + +If you use the ``py.std`` hook you currently cannot magically +import nested packages which otherwise need explicit imports of +their sub-packages. For example, the suversion bindings +require you to do something like:: + + import svn.client + +If you just do the naive thing with the py lib, i.e. write +``py.std.svn.client`` it will not work unless you previously +imported it already. The py lib currently doesn't try to +magically make this work. The ``py.std`` hook really is +intended for Python standard modules which very seldomly (if +at all) provide such nested packages. + +**Note that you may never rely** on module identity, i.e. +that ``X is py.std.X`` for any ``X``. This is to allow +us later to lazyly import nested packages. Yes, lazyness +is hard to resist :-) + +Note: you get an AttributeError, not an ImportError +--------------------------------------------------- + +If you say ``py.std.XYZ`` and importing ``XYZ`` produces an +``ImportError`` , it will actually show up as an +``AttributeError``. It is deemed more important to adhere to +the standard ``__getattr__`` protocol than to let the +``ImportError`` pass through. For example, you might want to +do:: + + getattr(py.std.cStringIO, 'StringIO', py.std.StringIO.StringIO) + +and you would expect that it works. It does work although it will +take away some lazyness because ``py.std.StringIO.StringIO`` will +be imported in any case. + +.. _`the relative/absolute import PEP-328`: http://www.python.org/peps/pep-0328.html Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Mon Oct 18 23:52:42 2004 @@ -1,5 +1,6 @@ from initpkg import initpkg initpkg(__name__, exportdefs = { + 'std': './misc/std.std', 'path.local': './path/local/local.LocalPath', 'path.checker': './path/common.checker', 'path.svnurl': './path/svn/urlcommand.SvnCommandPath', Added: py/dist/py/misc/std.py ============================================================================== --- (empty file) +++ py/dist/py/misc/std.py Mon Oct 18 23:52:42 2004 @@ -0,0 +1,15 @@ + +import sys + +class Std(object): + def __init__(self): + self.__dict__ = sys.modules + + def __getattr__(self, name): + try: + m = __import__(name) + except ImportError: + raise AttributeError("py.std: could not import %s" % name) + return m + +std = Std() Added: py/dist/py/misc/test_std.py ============================================================================== --- (empty file) +++ py/dist/py/misc/test_std.py Mon Oct 18 23:52:42 2004 @@ -0,0 +1,13 @@ + +import py + +def test_os(): + import os + assert py.std.os is os + +def test_import_error_converts_to_attributeerror(): + py.test.raises(AttributeError, "py.std.xyzalskdj") + +def test_std_gets_it(): + for x in py.std.sys.modules: + assert x in py.std.__dict__ Modified: py/dist/py/pytest.py ============================================================================== --- py/dist/py/pytest.py (original) +++ py/dist/py/pytest.py Mon Oct 18 23:52:42 2004 @@ -1,13 +1,11 @@ #pythonexecutables = ('python2.2', 'python2.3',) #pythonexecutable = 'python2.2' -def setup_module(extpy): - mod = extpy.resolve() - mod.module = 23 - directory = pypath.root.dirpath() - - - +# in the future we want to be able to say here: +#def setup_module(extpy): +# mod = extpy.resolve() +# mod.module = 23 +# directory = pypath.root.dirpath() # standard options (modified from cmdline) verbose = 0 From hpk at codespeak.net Tue Oct 19 04:19:00 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Oct 2004 04:19:00 +0200 (MEST) Subject: [py-svn] r7015 - in py/dist/py: . bin magic path path/extpy path/local path/svn path/test process test test/report test/report/text test/test test/test/data test/tool Message-ID: <20041019021900.B5E4C5A15E@thoth.codespeak.net> Author: hpk Date: Tue Oct 19 04:18:56 2004 New Revision: 7015 Removed: py/dist/py/path/extpy/fspy.py Modified: py/dist/py/__init__.py py/dist/py/bin/py.test py/dist/py/magic/test_autopath.py py/dist/py/magic/test_exprinfo.py py/dist/py/magic/test_viewtype.py py/dist/py/path/common.py py/dist/py/path/extpy/extpy.py py/dist/py/path/extpy/inc_pseudofs.py py/dist/py/path/extpy/inc_test_extpy.py py/dist/py/path/extpy/test_extpy.py py/dist/py/path/local/local.py py/dist/py/path/local/test_local.py py/dist/py/path/svn/svntestbase.py py/dist/py/path/test/fscommon.py py/dist/py/process/test_cmdexec.py py/dist/py/test/cmdline.py py/dist/py/test/collect.py py/dist/py/test/compat.py py/dist/py/test/report/memo.py py/dist/py/test/report/text/reporter.py py/dist/py/test/report/text/summary.py py/dist/py/test/run.py py/dist/py/test/test/data/Collector.py py/dist/py/test/test/demo.py py/dist/py/test/test_collect.py py/dist/py/test/test_compat.py py/dist/py/test/test_config.py py/dist/py/test/test_raises.py py/dist/py/test/tool/test_outerrcapture.py Log: - some more future ... - made 'extpy' more flexible, it now accepts all filesystem path which grew another method "getmodule()" which will load a given file as a module (and remember it mangled in sys.modules) - cleaned up the py.test namespace so that it doesn't export wholesale modules anymore. - lots of minor refactorings Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Tue Oct 19 04:18:56 2004 @@ -16,11 +16,13 @@ 'test.collect.Module': './test/collect.Module', 'test.collect.PyCollector':'./test/collect.PyCollector', 'test.collect.Error': './test/collect.Error', - 'test.run': './test/run', - 'test.main': './test/cmdline.main', + 'test.Failed': './test/run.Failed', + 'test.Passed': './test/run.Passed', + 'test.Skipped': './test/run.Skipped', 'test.raises': './test/raises.raises', 'test.config': './test/config.config', 'test.compat.TestCase': './test/compat.TestCase', + 'test.Driver': './test/run.Driver', 'test.Item': './test/run.Item', 'test.Option': './test/tool/optparse.Option', 'test.TextReporter': './test/report/text/reporter.TextReporter', Modified: py/dist/py/bin/py.test ============================================================================== --- py/dist/py/bin/py.test (original) +++ py/dist/py/bin/py.test Tue Oct 19 04:18:56 2004 @@ -1,5 +1,5 @@ #!/usr/bin/env python from _findpy import py -import sys -py.test.main(sys.argv) +from py.__impl__.test.cmdline import main +main(py.std.sys.argv) Modified: py/dist/py/magic/test_autopath.py ============================================================================== --- py/dist/py/magic/test_autopath.py (original) +++ py/dist/py/magic/test_autopath.py Tue Oct 19 04:18:56 2004 @@ -87,5 +87,3 @@ finally: sys.path[:] = oldsyspath sys.argv = sys.argv - -py.test.main() Modified: py/dist/py/magic/test_exprinfo.py ============================================================================== --- py/dist/py/magic/test_exprinfo.py (original) +++ py/dist/py/magic/test_exprinfo.py Tue Oct 19 04:18:56 2004 @@ -1,6 +1,5 @@ import sys -from py.test import main from py.__impl__.magic.exprinfo import getmsg, interpret def getexcinfo(exc, obj, *args, **kwargs): @@ -74,5 +73,3 @@ pass else: raise AssertionError, "ex %s didn't pass through" %(exstr, ) - -main() Modified: py/dist/py/magic/test_viewtype.py ============================================================================== --- py/dist/py/magic/test_viewtype.py (original) +++ py/dist/py/magic/test_viewtype.py Tue Oct 19 04:18:56 2004 @@ -1,4 +1,3 @@ -from py import test from py.magic import View def test_class_dispatch(): @@ -54,6 +53,3 @@ codelines = [PyOp(op).generate() for op in existing] assert codelines == ["4 + 5", "getitem('', 'join')", "setattr('x', 'y', 3)", "12 - 1"] - -test.main() - Modified: py/dist/py/path/common.py ============================================================================== --- py/dist/py/path/common.py (original) +++ py/dist/py/path/common.py Tue Oct 19 04:18:56 2004 @@ -284,3 +284,18 @@ self.copy(target) self.remove() + def getmodule(self): + """resolve this path to a module python object. """ + modname = str(self) + for x in self.sep, '.': + modname = modname.replace(x, '_') + try: + return sys.modules[modname] + except KeyError: + s = self.read() + mod = py.std.new.module(modname) + mod.__file__ = str(self) + co = py.magic.dyncode.compile2(s) + sys.modules[modname] = mod + exec co in mod.__dict__ + return mod Modified: py/dist/py/path/extpy/extpy.py ============================================================================== --- py/dist/py/path/extpy/extpy.py (original) +++ py/dist/py/path/extpy/extpy.py Tue Oct 19 04:18:56 2004 @@ -9,32 +9,32 @@ from py.__impl__.path import common import sys import inspect, imp +moduletype = type(py) class Extpy(common.PathBase): - """ a path abstraction addressing python objects in a file system. """ + """ path object for addressing python objects. """ sep = '.' - def __new__(cls, fspath='', modpath=''): - if isinstance(fspath, cls) and not modpath: - return fspath - - assert isinstance(modpath, str) + def __new__(cls, root, modpath=''): + if isinstance(root, str): + root = py.path.local(root) + #root = py.path.local(root) + #raise TypeError("first root argument is not resolvable") + if not isinstance(modpath, str): + raise TypeError("second 'modpath' argument must be a dotted name.") + #assert not isinstance(root, Extpy) self = object.__new__(cls) self.modpath = modpath - if isinstance(fspath, str): - fspath = py.path.local(fspath) - self.fspath = fspath + self.root = root return self def __hash__(self): - return hash((self.fspath, self.modpath)) + return hash((self.root, self.modpath)) def __repr__(self): - #if self.ns is rootns: - # return 'py(%r)' % (self.modpath, ) - return 'extpy(%r, %r)' % (self.fspath, self.modpath) + return 'extpy(%r, %r)' % (self.root, self.modpath) def __str__(self): - return str(self.fspath.new(ext=self.modpath)) + return str(self.root.new(ext=self.modpath)) def join(self, *args): for arg in args: @@ -42,12 +42,12 @@ raise TypeError, "non-strings not allowed in %r" % args modpath = [x.strip('.') for x in ((self.modpath,)+args) if x] modpath = self.sep.join(modpath) - return self.__class__(self.fspath, modpath) + return self.__class__(self.root, modpath) def dirpath(self, *args): modpath = self.modpath.split(self.sep) [:-1] modpath = self.sep.join(modpath+list(args)) - return self.__class__(self.fspath, modpath) + return self.__class__(self.root, modpath) def new(self, **kw): """ create a modified version of this path. @@ -56,14 +56,14 @@ """ cls = self.__class__ if 'modpath' in kw: - return cls(self.fspath, kw['modpath']) + return cls(self.root, kw['modpath']) if 'basename' in kw: i = self.modpath.rfind('.') if i != -1: - return cls(self.fspath, self.modpath[i+1:] + kw['basename']) + return cls(self.root, self.modpath[i+1:] + kw['basename']) else: - return cls(self.fspath, kw['basename']) - return cls(self.fspath, self.modpath) + return cls(self.root, kw['basename']) + return cls(self.root, self.modpath) def get(self, spec): l = [] @@ -79,34 +79,27 @@ else: return l - def getmodule(self): - #modname = str(self.fspath) - modname = str(self.fspath.new(ext='')).replace(self.fspath.sep, '_') - try: - return sys.modules[modname] - except KeyError: - #print "trying importing", modname - if not self.fspath.check(file=1): - raise py.path.NotFound(self.fspath) - mod = imp.load_source(modname, str(self.fspath)) - mod.__name__ = modname - sys.modules[modname] = mod - return mod - def resolve(self): - """return the python object belonging to path. """ - module = self.getmodule() + """return the python object, obtained from traversing from + the root along the modpath. + """ rest = filter(None, self.modpath.split('.')) - target = module - for name in rest: + target = self.getmodule() + for name in rest: try: target = getattr(target, name) except AttributeError: - raise py.path.NotFound(str(self)) + raise py.path.NotFound("%r in %s" %(self.modpath, self)) return target + def getmodule(self): + if hasattr(self.root, 'resolve'): + return self.root.resolve() + else: + return self.root.getmodule() + def relto(self, otherpath): - if self.fspath == otherpath.fspath: + if self.root == otherpath.root: if self.modpath.startswith(otherpath.modpath): s = self.modpath[len(otherpath.modpath):] return s.lstrip(self.sep) @@ -127,14 +120,8 @@ elif isinstance(fil, str): fil = common.fnmatch(fil) obj = self.resolve() - #if obj is rootns: - # d = {} - # for x in sys.modules: - # name = x.split('.')[0] - # d[name] = 1 - # l = [self.join(x) for x in d if not fil or fil(x)] - #else: l = [] + #print "listdir on", self for name in dir(obj): sub = self.join(name) if not fil or fil(sub): @@ -211,7 +198,7 @@ return False if x.endswith('pyc'): x = x[:-1] - if str(self.fspath) == x: + if str(self.root) == x: return True class Checkers(common.Checkers): Deleted: /py/dist/py/path/extpy/fspy.py ============================================================================== --- /py/dist/py/path/extpy/fspy.py Tue Oct 19 04:18:56 2004 +++ (empty file) @@ -1,242 +0,0 @@ -""" -A path to python objects located in filesystems. - -Note: this is still experimental and may be removed - for the first stable release! -""" -from __future__ import generators -import py -from py.__impl__.path import common -import sys -import inspect, imp - -class Fspy(common.PathBase): - """ a path abstraction addressing python objects in a file system. """ - sep = '.' - def __new__(cls, fspath='', modpath=''): - if isinstance(fspath, cls) and not modpath: - return fspath - - assert isinstance(modpath, str) - self = object.__new__(cls) - self.modpath = modpath - if isinstance(fspath, str): - fspath = py.path.local(fspath) - self.fspath = fspath - return self - - def __hash__(self): - return hash((self.fspath, self.modpath)) - - def __repr__(self): - #if self.ns is rootns: - # return 'py(%r)' % (self.modpath, ) - return 'fspy(%r, %r)' % (self.fspath, self.modpath) - - def __str__(self): - return str(self.fspath.new(ext=self.modpath)) - - def join(self, *args): - for arg in args: - if not isinstance(arg, str): - raise TypeError, "non-strings not allowed in %r" % args - modpath = [x.strip('.') for x in ((self.modpath,)+args) if x] - modpath = self.sep.join(modpath) - return self.__class__(self.fspath, modpath) - - def dirpath(self, *args): - modpath = self.modpath.split(self.sep) [:-1] - modpath = self.sep.join(modpath+list(args)) - return self.__class__(self.fspath, modpath) - - def new(self, **kw): - """ create a modified version of this path. - the following keyword arguments modify various path parts: - modpath substitute module path - """ - cls = self.__class__ - if 'modpath' in kw: - return cls(self.fspath, kw['modpath']) - if 'basename' in kw: - i = self.modpath.rfind('.') - if i != -1: - return cls(self.fspath, self.modpath[i+1:] + kw['basename']) - else: - return cls(self.fspath, kw['basename']) - return cls(self.fspath, self.modpath) - - def get(self, spec): - l = [] - modparts = self.modpath.split(self.sep) - for name in spec.split(','): - if name == 'basename': - l.append(modparts[-1]) - - if len(l) == 1: - return l[0] - elif len(l) == 0: - return None - else: - return l - - def getmodule(self): - #modname = str(self.fspath) - modname = str(self.fspath.new(ext='')).replace(self.fspath.sep, '_') - try: - return sys.modules[modname] - except KeyError: - #print "trying importing", modname - if not self.fspath.check(file=1): - raise py.path.NotFound(self.fspath) - mod = imp.load_source(modname, str(self.fspath)) - mod.__name__ = modname - sys.modules[modname] = mod - return mod - - def resolve(self): - """return the python object belonging to path. """ - module = self.getmodule() - rest = filter(None, self.modpath.split('.')) - target = module - for name in rest: - try: - target = getattr(target, name) - except AttributeError: - raise py.path.NotFound(str(self)) - return target - - def relto(self, otherpath): - if self.fspath == otherpath.fspath: - if self.modpath.startswith(otherpath.modpath): - s = self.modpath[len(otherpath.modpath):] - return s.lstrip(self.sep) - return '' - - def listobj(self, fil=None, **kw): - l = [] - for x in self.listdir(fil, **kw): - l.append(x.resolve()) - return l - - def listdir(self, fil=None, sort=True, **kw): - if kw: - if fil is None: - fil = py.path.checker(**kw) - else: - raise TypeError, "cannot take filter and keyword arguments" - elif isinstance(fil, str): - fil = common.fnmatch(fil) - obj = self.resolve() - #if obj is rootns: - # d = {} - # for x in sys.modules: - # name = x.split('.')[0] - # d[name] = 1 - # l = [self.join(x) for x in d if not fil or fil(x)] - #else: - l = [] - for name in dir(obj): - sub = self.join(name) - if not fil or fil(sub): - l.append(sub) - - #print "listdir(%r) -> %r" %(self, l) - #print "listdir on", repr(self) - return l - - def setfile(self, filepath): - self._filepath = filepath - - def getfilelineno(self, scrapinit=0): - x = obj = self.resolve() - if inspect.ismodule(obj): - return obj.__file__, 0 - if inspect.ismethod(obj): - obj = obj.im_func - if inspect.isfunction(obj): - obj = obj.func_code - if inspect.iscode(obj): - return py.path.local(obj.co_filename), obj.co_firstlineno - else: - source, lineno = inspect.findsource(obj) - return x.getfile(), lineno - - def visit(self, fil=None, rec=None, ignore=None, seen=None): - def myrec(p, seen={id(self): True}): - if id(p) in seen: - return False - seen[id(p)] = True - if self.samefile(p): - return True - - for x in super(Fspy, self).visit(fil=fil, rec=rec, ignore=ignore): - yield x - return - - if seen is None: - seen = {id(self): True} - - if isinstance(fil, str): - fil = common.fnmatch(fil) - if isinstance(rec, str): - rec = common.fnmatch(fil) - - if ignore: - try: - l = self.listdir() - except ignore: - return - else: - l = self.listdir() - reclist = [] - for p in l: - if fil is None or fil(p): - yield p - if id(p) not in seen: - try: - obj = p.resolve() - if inspect.isclass(obj) or inspect.ismodule(obj): - reclist.append(p) - finally: - seen[id(p)] = p - for p in reclist: - for i in p.visit(fil, rec, seen): - yield i - - def samefile(self, other): - otherobj = other.resolve() - try: - x = inspect.getfile(otherobj) - except TypeError: - return False - if x.endswith('pyc'): - x = x[:-1] - if str(self.fspath) == x: - return True - - class Checkers(common.Checkers): - _depend_on_existence = (common.Checkers._depend_on_existence + - ('func', 'class_', 'exists', 'dir')) - - def _obj(self): - self._obj = self.path.resolve() - return self._obj - - def exists(self): - obj = self._obj() - return True - - def func(self): - ob = self._obj() - return inspect.isfunction(ob) or inspect.ismethod(ob) - - def class_(self): - ob = self._obj() - return inspect.isclass(ob) - - def isinstance(self, args): - return isinstance(self._obj(), args) - - def dir(self): - obj = self._obj() - return inspect.isclass(obj) or inspect.ismodule(obj) Modified: py/dist/py/path/extpy/inc_pseudofs.py ============================================================================== --- py/dist/py/path/extpy/inc_pseudofs.py (original) +++ py/dist/py/path/extpy/inc_pseudofs.py Tue Oct 19 04:18:56 2004 @@ -8,3 +8,5 @@ class otherdir: init = 42 +class execfile: + x = 42 Modified: py/dist/py/path/extpy/inc_test_extpy.py ============================================================================== --- py/dist/py/path/extpy/inc_test_extpy.py (original) +++ py/dist/py/path/extpy/inc_test_extpy.py Tue Oct 19 04:18:56 2004 @@ -10,4 +10,5 @@ class Nested: class Class: def borgfunc(self): pass + Modified: py/dist/py/path/extpy/test_extpy.py ============================================================================== --- py/dist/py/path/extpy/test_extpy.py (original) +++ py/dist/py/path/extpy/test_extpy.py Tue Oct 19 04:18:56 2004 @@ -23,7 +23,7 @@ def test_listdir_module(self): l = self.root.listdir() basenames = [x.basename for x in l] - dlist = dir(self.root.getmodule()) + dlist = dir(self.root.resolve()) for name in dlist: assert name in basenames for name in basenames: @@ -32,7 +32,7 @@ def test_listdir_class(self): l = self.root.join('A').listdir() basenames = [x.basename for x in l] - dlist = dir(self.root.getmodule().A) + dlist = dir(self.root.resolve().A) for name in dlist: assert name in basenames for name in basenames: @@ -48,14 +48,14 @@ assert len(l) == 1 obj = l[0] assert str(obj).endswith('Nested.Class.borgfunc') - assert obj.resolve() == self.root.getmodule().Nested.Class.borgfunc + assert obj.resolve() == self.root.resolve().Nested.Class.borgfunc def test_visit_fnmatch(self): l = list(self.root.visit('borg*')) assert len(l) == 1 obj = l[0] assert str(obj).endswith('Nested.Class.borgfunc') - assert obj.resolve() == self.root.getmodule().Nested.Class.borgfunc + assert obj.resolve() == self.root.resolve().Nested.Class.borgfunc #def test_join_from_empty(self): # p = path.py('') @@ -121,7 +121,7 @@ assert x == y assert hash(x) == hash(y) - def test_parents(self): + def test_parts2(self): x = self.root.new(modpath='os.path.abspath') l = x.parts() assert len(l) == 4 @@ -129,6 +129,15 @@ assert self.root.join('os') == l[1] assert self.root.join('os.path') == l[2] assert self.root.join('os.path.abspath') == l[3] + +#class TestExtPyWithModule: +# def test_module(self): +# import os +# x = py.path.extpy(os, 'path.abspath') +# assert x.check() +# assert x.resolve() is os.path.abspath +# #def setup_class(cls): + # cls.root = py.path.extpy(mypath) class TestEval: disabled = True Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Tue Oct 19 04:18:56 2004 @@ -51,7 +51,7 @@ If it is None then the current working directory is taken. Note that Path instances always carry an absolute path. Note also that passing in a local path object will simply return - the exact same path object. + the exact same path object. Use new() to get a new copy. """ if isinstance(path, cls): if path.__class__ == cls: @@ -378,6 +378,18 @@ """ return string representation of the Path. """ return self.strpath + def getmodule(self): + modname = str(self).replace(self.sep, '_').replace('.','_') + try: + return sys.modules[modname] + except KeyError: + #print "trying importing", modname + if not self.check(file=1): + raise py.path.NotFound(self) + mod = py.std.imp.load_source(modname, str(self)) + mod.__name__ = modname + sys.modules[modname] = mod + return mod #""" #special class constructors for local filesystem paths Modified: py/dist/py/path/local/test_local.py ============================================================================== --- py/dist/py/path/local/test_local.py (original) +++ py/dist/py/path/local/test_local.py Tue Oct 19 04:18:56 2004 @@ -1,5 +1,5 @@ import sys, os -from py.test import main, raises, config +from py.test import raises, config from py.path import local, checker from py.__impl__.path.test.fscommon import CommonFSTests, setuptestfs @@ -181,7 +181,3 @@ # def __init__(self): # TestLocalPath.__init__(self) # self.root = local(self.root) - -if __name__ == '__main__': - main() - Modified: py/dist/py/path/svn/svntestbase.py ============================================================================== --- py/dist/py/path/svn/svntestbase.py (original) +++ py/dist/py/path/svn/svntestbase.py Tue Oct 19 04:18:56 2004 @@ -8,7 +8,7 @@ def setup_method(self, meth): bn = meth.func_name if bn.startswith('test_remove') or bn.startswith('test_move'): - raise py.test.run.Skipped(msg= + raise py.test.Skipped(msg= "tests for (re)move require better svn state management") def test_propget(self): Modified: py/dist/py/path/test/fscommon.py ============================================================================== --- py/dist/py/path/test/fscommon.py (original) +++ py/dist/py/path/test/fscommon.py Tue Oct 19 04:18:56 2004 @@ -10,6 +10,9 @@ samplefile = path.ensure('samplefile') samplefile.write('samplefile\n') + execfile = path.ensure('execfile') + execfile.write('x=42') + d = {1:2, 'hello': 'world', 'answer': 42} path.ensure('samplepickle').dumpobj(d) @@ -185,3 +188,12 @@ assert dest.check(dir=1) assert dest.join('moved', 'somefile').read() == '42' assert not source.check() + + def test_getmodule(self): + obj = self.root.join('execfile').getmodule() + assert obj.x == 42 + + def test_not_has_resolve(self): + # because this would mean confusion with respect to + # py.path.extpy + assert not hasattr(self.root, 'resolve') Modified: py/dist/py/process/test_cmdexec.py ============================================================================== --- py/dist/py/process/test_cmdexec.py (original) +++ py/dist/py/process/test_cmdexec.py Tue Oct 19 04:18:56 2004 @@ -23,4 +23,3 @@ assert hasattr(e, 'err') assert hasattr(e, 'out') assert e.err or e.out -test.main() Modified: py/dist/py/test/cmdline.py ============================================================================== --- py/dist/py/test/cmdline.py (original) +++ py/dist/py/test/cmdline.py Tue Oct 19 04:18:56 2004 @@ -1,7 +1,5 @@ from __future__ import generators -from py import test, path -from py.test import config -import os, sys +import py # # main entry point @@ -12,27 +10,27 @@ # the collectors we will be running collectors = [] if argv is None: - argv = sys.argv - frame = sys._getframe(1) + argv = py.std.sys.argv + frame = py.std.sys._getframe(1) name = frame.f_locals.get('__name__') if name != '__main__': return # called from an imported test file import __main__ - collectors.append(test.collect.Module(__main__)) + collectors.append(py.test.collect.Module(py.std.sys.argv[0])) args = argv[1:] for x in getanchors(args): - config.readconfiguration(x) + py.test.config.readconfiguration(x) - if config.restartpython(): + if py.test.config.restartpython(): return - filenames = config.parseargs(args) + filenames = py.test.config.parseargs(args) collectors.extend(getcollectors(filenames)) if not collectors: - collectors.append(test.collect.Directory(path.local())) + collectors.append(py.test.collect.Directory(py.path.local())) - reporter = config.reporter - runner = test.run.Driver(reporter) + reporter = py.test.config.reporter + runner = py.test.Driver(reporter) runner.setup() try: try: @@ -40,34 +38,34 @@ try: for collector in collectors: runner.run(collector) - except test.run.Exit: + except runner.Exit: pass finally: runner.teardown() except KeyboardInterrupt: - print >>sys.stderr, "KEYBOARD INTERRUPT" - sys.exit(2) + print >>py.std.sys.stderr, "KEYBOARD INTERRUPT" + py.std.sys.exit(2) except SystemExit: - print >>sys.stderr, "SYSTEM Exit" - sys.exit(-1) + print >>py.std.sys.stderr, "SYSTEM Exit" + py.std.sys.exit(-1) reporter.end() def getcollectors(filenames): - current = path.local() + current = py.path.local() yielded = False for fn in filenames: fullfn = current.join(fn, abs=1) if fullfn.check(file=1): - yield test.collect.Module(fullfn) + yield py.test.collect.Module(fullfn) elif fullfn.check(dir=1): - yield test.collect.Directory(fullfn) + yield py.test.collect.Directory(fullfn) else: raise RuntimeError, "%r does not exist" % fn yielded = True def getanchors(args): """ yield anchors from skimming the args for existing files/dirs. """ - current = path.local() + current = py.path.local() yielded = False for arg in args: anchor = current.join(arg, abs=1) Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Tue Oct 19 04:18:56 2004 @@ -22,7 +22,7 @@ """ instances are *restartable iterators*. They will yield Units or Collector instances during iteration. """ - Item = test.run.Item + Item = test.Item def iterunits(self): """ yield all units of the Collector instance. """ @@ -61,7 +61,7 @@ if self.rec(fspath): yield self.__class__(fspath) elif self.fil(fspath): - yield Module(py.path.extpy(fspath)) + yield Module(fspath) except: yield self._except() @@ -71,7 +71,9 @@ class PyCollector(Collector): def __init__(self, extpy): - self.extpy = py.path.extpy(extpy) + if not hasattr(extpy, 'resolve'): + extpy = py.path.extpy(extpy) + self.extpy = extpy self.yielders = [getattr(self, x) for x in dir(self.__class__) if x.startswith('collect_')] @@ -87,7 +89,7 @@ for pypath in self.extpy.listdir(): for meth in self.yielders: for x in meth(pypath): - x.fspath = self.extpy.fspath + x.fspath = self.extpy.root sortvalue = self.getsortvalue(x) l.append((sortvalue, x)) l.sort() Modified: py/dist/py/test/compat.py ============================================================================== --- py/dist/py/test/compat.py (original) +++ py/dist/py/test/compat.py Tue Oct 19 04:18:56 2004 @@ -1,7 +1,7 @@ from __future__ import generators -from py import test, magic +import py -class TestCaseUnit(test.run.Item): +class TestCaseUnit(py.test.Item): """ compatibility Unit executor for TestCase methods honouring setUp and tearDown semantics. """ @@ -14,7 +14,7 @@ unboundmethod(instance) finally: instance.tearDown() - return test.run.Passed() + return py.test.Passed() class TestCase: """compatibility class of unittest's TestCase. """ @@ -28,10 +28,10 @@ def fail(self, msg=None): """ fail immediate with given message. """ - raise test.run.Failed(msg=msg) + raise py.test.Failed(msg=msg) def assertRaises(self, excclass, func, *args, **kwargs): - test.raises(excclass, func, *args, **kwargs) + py.test.raises(excclass, func, *args, **kwargs) failUnlessRaises = assertRaises # dynamically construct (redundant) methods @@ -49,10 +49,10 @@ items.append(""" def %(name)s(self, %(sig)s): if %(expr)s: - raise test.run.Failed(tbindex=-2, msg=%(sigsubst)r %% (%(sig)s)) + raise py.test.Failed(tbindex=-2, msg=%(sigsubst)r %% (%(sig)s)) """ % locals() ) source = "".join(items) - exec magic.dyncode.compile2(source) + exec py.magic.dyncode.compile2(source) __all__ = ['TestCase'] Modified: py/dist/py/test/report/memo.py ============================================================================== --- py/dist/py/test/report/memo.py (original) +++ py/dist/py/test/report/memo.py Tue Oct 19 04:18:56 2004 @@ -1,9 +1,6 @@ from __future__ import generators -import sys -import os -from py import test - -run = test.run +import py +from py.__impl__.test import run class MemoReporter: typemap = { Modified: py/dist/py/test/report/text/reporter.py ============================================================================== --- py/dist/py/test/report/text/reporter.py (original) +++ py/dist/py/test/report/text/reporter.py Tue Oct 19 04:18:56 2004 @@ -1,14 +1,8 @@ from __future__ import generators -import sys -import os -import inspect -import traceback -from time import time as now - -from py import test, magic -from py.test import collect, run -from py.magic import dyncode +import py +from py.__impl__.test import run +from time import time as now # lazy relative Implementation imports from summary import Summary from out import getout @@ -17,20 +11,20 @@ Summary = Summary typemap = { run.Passed: '.', run.Skipped: 's', run.Failed: 'F', - collect.Error: 'C', + py.test.collect.Error: 'C', } namemap = { run.Passed: 'ok', run.Skipped: 'SKIP', run.Failed: 'FAIL', - collect.Error: 'COLLECT ERROR', + py.test.collect.Error: 'COLLECT ERROR', } def __init__(self, f=None): if f is None: - f = sys.stdout + f = py.std.sys.stdout self.out = getout(f) self._started = {} self.summary = self.Summary() - self.summary.option = self.option = test.config.option + self.summary.option = self.option = py.test.config.option def start(self, conf=None): self.summary.starttime = now() @@ -40,11 +34,11 @@ self.out.sep("_", " TESTS STARTING ") self.out.sep("_") if not self.option.nomagic: - magic.invoke(assertion=1) + py.magic.invoke(assertion=1) def end(self): if not self.option.nomagic: - magic.revoke(assertion=1) + py.magic.revoke(assertion=1) self.summary.endtime = now() self.summary.render(self.out) @@ -60,7 +54,7 @@ cls = getattr(collector, '__class__', None) if cls is None: return - for typ in inspect.getmro(cls): + for typ in py.std.inspect.getmro(cls): meth = getattr(self, 'open_%s' % typ.__name__, None) if meth: return meth(collector) @@ -134,7 +128,7 @@ elapsed, resultstring, location, str(item.pypath.modpath), writeinfo )) if self.option.usepdb: - if (issubclass(restype, collect.Error) or + if (issubclass(restype, py.test.collect.Error) or issubclass(restype, run.Failed)): import pdb self.out.rewrite( @@ -143,7 +137,7 @@ result.excinfo[1])) pdb.post_mortem(result.excinfo[2]) if self.option.exitfirstproblem: - if (issubclass(restype, collect.Error) or + if (issubclass(restype, py.test.collect.Error) or issubclass(restype, run.Failed)): raise run.Exit("first problem, exit configured.") Modified: py/dist/py/test/report/text/summary.py ============================================================================== --- py/dist/py/test/report/text/summary.py (original) +++ py/dist/py/test/report/text/summary.py Tue Oct 19 04:18:56 2004 @@ -1,10 +1,6 @@ from __future__ import generators -import sys -import os -import traceback - -from py.test import collect, run -from py.magic import dyncode +import py +from py.__impl__.test import run from py.__impl__.magic import exprinfo, assertion class Summary(object): @@ -30,7 +26,7 @@ #if isinstance(res.excinfo[1], AssertionError): # from std.utest.tool import hackexpr # res.msg = "failed: " + hackexpr.getmsg(res.excinfo) - for line in traceback.format_exception_only(*error.excinfo[:2]): + for line in py.std.traceback.format_exception_only(*error.excinfo[:2]): self.out.line(line.rstrip()) #if self.option.showlocals: @@ -38,7 +34,7 @@ # for name, value in frame.f_locals.items(): # self.out.line("%-10s = %r" %(name, value)) def summary_collect_errors(self): - for error in self.getlist(collect.Error): + for error in self.getlist(py.test.collect.Error): self.repr_collect_error(error) def render(self, out): @@ -60,14 +56,14 @@ def getexinfo(self, res): _,exc,tb = res.excinfo tbindex = getattr(res, 'tbindex', -1) - filename, lineno = dyncode.tbinfo(tb, tbindex) - frame = dyncode.listtb(tb)[tbindex].tb_frame + filename, lineno = py.magic.dyncode.tbinfo(tb, tbindex) + frame = py.magic.dyncode.listtb(tb)[tbindex].tb_frame return exc, frame, filename, lineno def skippedreasons(self): d = {} for res in self.getlist(run.Skipped): - raisingtb = dyncode.listtb(res.excinfo[2])[-1] + raisingtb = py.magic.dyncode.listtb(res.excinfo[2])[-1] fn = raisingtb.tb_frame.f_code.co_filename lineno = raisingtb.tb_lineno d[(fn,lineno)] = res @@ -121,7 +117,7 @@ self.out.sep("- ", "traceback of unexpected exception") #self.out.line("reality:") self.repr_traceback(res.item, res.innerexcinfo[2]) - for line in traceback.format_exception_only(*res.innerexcinfo[:2]): + for line in py.std.traceback.format_exception_only(*res.innerexcinfo[:2]): self.out.line(line) elif issubclass(cls, assertion.AssertionError): res.msg = str(res.excinfo[1]) @@ -137,7 +133,7 @@ except: if self.option.verbose > 1: self.out.line("reinterpretation traceback") - traceback.print_exc() + py.std.traceback.print_exc() else: self.out.line("(reinterpretation failed, you may increase " "verbosity to see details)") @@ -147,14 +143,14 @@ else: showex = True if showex: - for line in traceback.format_exception_only(*res.excinfo[:2]): + for line in py.std.traceback.format_exception_only(*res.excinfo[:2]): self.out.line(line) def repr_source(self, frame, lineno): # represent the source code self.out.line() try: - lines, firstlineno = dyncode.findsource(frame) # .f_code) + lines, firstlineno = py.magic.dyncode.findsource(frame) # .f_code) except IOError: self.out.line("failure to retrieve sourcelines from %r" % frame) #self.out.line("(co_filename = %r)" % (frame.f_code.co_filename)) @@ -173,9 +169,9 @@ # prelines = 3 # if prelines: # for i in range(lineno-prelines-1, lineno): - # line = dyncode.getline(filename, i) + # line = py.magic.dyncode.getline(filename, i) # self.out.line(" %s" % line.rstrip()) - # line = dyncode.getline(filename, lineno) + # line = py.magic.dyncode.getline(filename, lineno) # self.out.line("> %s" % line.rstrip()) def forward_traceback_to_test(self, tb, filename): @@ -190,7 +186,7 @@ def repr_traceback(self, item, tb, tbindex=-1): t_file, t_lineno = item.pypath.getfilelineno() self.out.line("%s, line %d" % (item.pypath, t_lineno) ) - fspath = item.pypath.fspath + fspath = item.pypath.root self.out.sep('_') self.repr_traceback_raw(fspath, tb, tbindex) #t_file, t_lineno = unit.pypath.getfilelineno() @@ -203,7 +199,7 @@ tb = self.forward_traceback_to_test(tb, str(fspath)) else: tbindex = -1 - tbentries = dyncode.listtb(tb) + tbentries = py.magic.dyncode.listtb(tb) if tbindex < -1 and not self.option.fulltrace: tbentries = tbentries[:tbindex+1] Modified: py/dist/py/test/run.py ============================================================================== --- py/dist/py/test/run.py (original) +++ py/dist/py/test/run.py Tue Oct 19 04:18:56 2004 @@ -1,10 +1,12 @@ from __future__ import generators +import py -import sys, inspect -from py import test +class Exit(Exception): + """ for immediate program exits without tracebacks. """ class Driver: - option = test.config.option + option = py.test.config.option + Exit = Exit def __init__(self, reporter): self.reporter = reporter @@ -13,7 +15,7 @@ def run(self, obj): """ run (possibly many) testitems and/or collectors. """ - collect = test.collect + collect = py.test.collect if isinstance(obj, Item): if not self.option.collectonly: #if self.option.args: @@ -53,11 +55,11 @@ try: res = item.execute(self) or Passed() except Outcome, res: - res.excinfo = sys.exc_info() + res.excinfo = py.std.sys.exc_info() except (KeyboardInterrupt, SystemExit): raise except: - res = Failed(excinfo=sys.exc_info()) + res = Failed(excinfo=py.std.sys.exc_info()) res.item = item self.reporter.enditem(res) @@ -86,19 +88,19 @@ def _setupone(self, pypath): obj = pypath.resolve() - if inspect.ismodule(obj): + if py.std.inspect.ismodule(obj): if hasattr(obj, 'setup_module'): obj.setup_module(obj) - elif inspect.isclass(obj): + elif py.std.inspect.isclass(obj): if hasattr(obj, 'setup_class'): obj.setup_class.im_func(obj) return obj def _teardownone(self, obj): - if inspect.ismodule(obj): + if py.std.inspect.ismodule(obj): if hasattr(obj, 'teardown_module'): obj.teardown_module(obj) - elif inspect.isclass(obj): + elif py.std.inspect.isclass(obj): if hasattr(obj, 'teardown_class'): obj.teardown_class.im_func(obj) @@ -147,6 +149,4 @@ class ExceptionFailure(Failed): pass class Skipped(Outcome): pass -class Exit(Exception): - """ for immediate program exits without tracebacks. """ Modified: py/dist/py/test/test/data/Collector.py ============================================================================== --- py/dist/py/test/test/data/Collector.py (original) +++ py/dist/py/test/test/data/Collector.py Tue Oct 19 04:18:56 2004 @@ -1,7 +1,7 @@ from __future__ import generators -from py.test import collect +import py -class Collector(collect.PyCollector): +class Collector(py.test.collect.PyCollector): def collect_function(self, pypath): if pypath.check(func=1, basestarts='myprefix_'): yield self.Item(pypath, pypath.basename) Modified: py/dist/py/test/test/demo.py ============================================================================== --- py/dist/py/test/test/demo.py (original) +++ py/dist/py/test/test/demo.py Tue Oct 19 04:18:56 2004 @@ -1,4 +1,4 @@ -from py.test import raises, main +from py.test import raises def otherfunc(a,b): assert a==b @@ -87,5 +87,3 @@ def globf(x): return x+1 - -main() Modified: py/dist/py/test/test_collect.py ============================================================================== --- py/dist/py/test/test_collect.py (original) +++ py/dist/py/test/test_collect.py Tue Oct 19 04:18:56 2004 @@ -1,8 +1,7 @@ from __future__ import generators -from py import test, path -from py.magic import autopath ; autopath = autopath() -from py.__impl__.test import collect - +import py +from py.__impl__.test import collect +autopath = py.magic.autopath() testdir = autopath.dirpath('test') assert testdir.check(dir=1) datadir = testdir / 'data' @@ -10,14 +9,14 @@ def test_failing_import_execfile(): fn = datadir / 'failingimport.py' - l = list(collect.Module(path.extpy(fn))) + l = list(collect.Module(py.path.extpy(fn))) assert l ex, = l assert issubclass(ex.excinfo[0], ImportError) def test_failing_import_directory(): class MyDirectory(collect.Directory): - fil = path.checker(basestarts="testspecial_", ext='.py') + fil = py.path.checker(basestarts="testspecial_", ext='.py') l = list(MyDirectory(datadir)) assert len(l) == 1 assert isinstance(l[0], collect.Module) @@ -32,26 +31,26 @@ l = list(collect.Module(fn)) assert len(l) == 1 assert isinstance(l[0], collect.Error) - assert isinstance(l[0].excinfo[1], path.NotFound) + assert isinstance(l[0].excinfo[1], py.path.NotFound) def test_syntax_error_in_module(): - modpath = 'py.__impl__.test.data.syntax_error.whatever' + modpath = py.path.extpy(datadir.join('syntax_error.py')) l2 = list(collect.Module(modpath)) assert len(l2) == 1 assert isinstance(l2[0], collect.Error) - assert issubclass(l2[0].excinfo[0], path.Invalid) + assert issubclass(l2[0].excinfo[0], SyntaxError) def test_disabled_class(): - extpy = path.extpy(datadir.join('disabled.py')) + extpy = py.path.extpy(datadir.join('disabled.py')) l = list(collect.Class(extpy)) assert len(l) == 0 class TestCustomCollector: def test_custom_collect(self): l = list(collect.Module(datadir.join('Collector.py'))) - assert len(l) == 3 for item in l: - assert isinstance(item, test.Item) + assert isinstance(item, py.test.Item) + assert len(l) == 3 #for x in l2: # assert isinstance(x, Unit) # x.execute() Modified: py/dist/py/test/test_compat.py ============================================================================== --- py/dist/py/test/test_compat.py (original) +++ py/dist/py/test/test_compat.py Tue Oct 19 04:18:56 2004 @@ -1,7 +1,7 @@ from __future__ import generators -from py import test, magic +import py -class TestCompatTestCaseSetupSemantics(test.compat.TestCase): +class TestCompatTestCaseSetupSemantics(py.test.compat.TestCase): globlist = [] def setUp(self): @@ -27,7 +27,7 @@ for x,y in zip(self.globlist, self.globlist[1:]): assert x is not y -class TestCompatAssertions(test.compat.TestCase): +class TestCompatAssertions(py.test.compat.TestCase): nameparamdef = { 'failUnlessEqual,assertEqual,assertEquals': ('1, 1', '1, 0'), 'assertNotEquals,failIfEqual': ('0, 1', '0,0'), @@ -44,10 +44,8 @@ #self.%(name)s(%(paramfail)s) def test_%(name)s_failing(self): - self.assertRaises(test.run.Failed, + self.assertRaises(py.test.Failed, self.%(name)s, %(paramfail)s) """ % locals() - co = magic.dyncode.compile2(source) + co = py.magic.dyncode.compile2(source) exec co - -test.main() Modified: py/dist/py/test/test_config.py ============================================================================== --- py/dist/py/test/test_config.py (original) +++ py/dist/py/test/test_config.py Tue Oct 19 04:18:56 2004 @@ -1,5 +1,4 @@ from __future__ import generators -from py import test from py.test import config class MyClass: @@ -26,5 +25,3 @@ d1 = config.tmpdir d2 = config.tmpdir assert d1 == d2 - -test.main() Modified: py/dist/py/test/test_raises.py ============================================================================== --- py/dist/py/test/test_raises.py (original) +++ py/dist/py/test/test_raises.py Tue Oct 19 04:18:56 2004 @@ -12,6 +12,3 @@ def test_raises_function(self): test.raises(ValueError, int, 'hello') - - -test.main() Modified: py/dist/py/test/tool/test_outerrcapture.py ============================================================================== --- py/dist/py/test/tool/test_outerrcapture.py (original) +++ py/dist/py/test/tool/test_outerrcapture.py Tue Oct 19 04:18:56 2004 @@ -22,5 +22,3 @@ print "hello" cap2.reset() test.raises(AttributeError, "cap2.reset()") - -test.main() From hpk at codespeak.net Tue Oct 19 04:21:22 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Oct 2004 04:21:22 +0200 (MEST) Subject: [py-svn] r7016 - py/dist/doc Message-ID: <20041019022122.718475A15E@thoth.codespeak.net> Author: hpk Date: Tue Oct 19 04:21:21 2004 New Revision: 7016 Modified: py/dist/doc/rest_test.py Log: fixed one remaining test (which actually tests that the ResT-Documentation doesn't give a warning) Modified: py/dist/doc/rest_test.py ============================================================================== --- py/dist/doc/rest_test.py (original) +++ py/dist/doc/rest_test.py Tue Oct 19 04:21:21 2004 @@ -6,7 +6,7 @@ mydir = mypath.dirpath() rest = mydir.dirpath('tool', 'rest.py') -class RestItem(py.test.run.Item): +class RestItem(py.test.Item): def __init__(self, path): self.path = path self.pypath = py.path.extpy(mypath, 'RestItem.execute') From hpk at codespeak.net Tue Oct 19 04:32:54 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Oct 2004 04:32:54 +0200 (MEST) Subject: [py-svn] r7017 - py/dist/py/test Message-ID: <20041019023254.1C8485A15E@thoth.codespeak.net> Author: hpk Date: Tue Oct 19 04:32:53 2004 New Revision: 7017 Added: py/dist/py/test/defaultconfig.py Modified: py/dist/py/test/config.py Log: simplified py.test.config ... Modified: py/dist/py/test/config.py ============================================================================== --- py/dist/py/test/config.py (original) +++ py/dist/py/test/config.py Tue Oct 19 04:32:53 2004 @@ -1,8 +1,10 @@ from __future__ import generators -from py import test, path + +import py from py.__impl__.test.tool import optparse -import os, sys, new +defaultconfig = py.magic.autopath().dirpath('defaultconfig.py') +defaultconfig = py.path.extpy(defaultconfig) dummy = object() # @@ -12,124 +14,42 @@ class Config: def __init__(self): - self.files = {} + self.configpaths = [] self.option = optparse.Values() - def getoptions(self): - Option = test.Option - return ('py.test standard options', [ - Option('-v', '--verbose', - action="count", dest="verbose", default=0, - help="increase verbosity"), - Option('-x', '--exitfirst', - action="store_true", dest="exitfirstproblem", default=False, - help="exit instantly on first error or failed test."), - Option('-S', '--nocapture', - action="store_true", dest="nocapture", default=False, - help="disable catching of sys.stdout/stderr output."), - Option('-l', '--showlocals', - action="store_true", dest="showlocals", default=False, - help="show locals in tracebacks (disabled by default)"), - Option('', '--fulltrace', - action="store_true", dest="fulltrace", default=False, - help="Don't try to cut any tracebacks (default is to cut)"), - Option('', '--nomagic', - action="store_true", dest="nomagic", default=False, - help="don't invoke magic to e.g. beautify failing/error statements."), - Option('', '--pdb', - action="store_true", dest="usepdb", default=False, - help="Start pdb (the Python debugger) on errors."), - Option('', '--collectonly', - action="store_true", dest="collectonly", default=False, - help="only collect tests, don't execute them. "), - ]) - def _gettmpdir(self, sessiondir=[]): try: return sessiondir[0] except IndexError: - d = path.local.make_numbered_dir(base='utest-') + d = py.path.local.make_numbered_dir(base='utest-') sessiondir.append(d) return d tmpdir = property(_gettmpdir, None, None, "Temp Directory") def readconfiguration(self, anchor): - """ read all neccessary configuration files for the given anchor file. + """ read configuration files left-to-right for the given anchor file. """ + for p in anchor.parts(): + x = p.join(configbasename) + if x.check(file=1): + extpy = py.path.extpy(x) + extpy.resolve() # trigger loading it + self.configpaths.append(extpy) + self.configpaths.sort(lambda x,y: cmp(len(str(x)), cmp(len(str(y))))) - a) visit downwards for all config files - b) traverse upwards to find parent config files - """ - if anchor.check(file=1): - anchor = anchor.dirpath() - fil = path.checker(file=1, basename=configbasename) - for p in anchor.visit(fil=fil, rec=path.checker(dotfile=0)): - if p not in self.files: - self.importconfig(p) - candidates = [x / configbasename for x in anchor.parts()] - candidates.reverse() - for p in candidates: - if p.check(file=1): - self.importconfig(p) - - #def getconfig(self, directory, upwards=True): - # assert upwards, "for now" - # assert directory.check(dir=1) - # here = directory / configbasename - # if here.check(file=1): - # return self.importconfig(here) - # min = None - # for x in self.files: - # r = directory.relto(x.dirpath()) - # if r and (min is None or len(r) < min): - # min = x - # if min: - # return self.importconfig(min) - - def importconfig(self, uconf): - try: - return self.files[uconf] - except KeyError: - par = [x.basename for x in uconf.new(ext='').parts()] - name = "_".join(par) - mod = new.module(name) - mod.__file__ = uconf - execfile(str(uconf), mod.__dict__) - self.files[uconf] = mod - return mod - - def listconfigs(self, reverse=False): - """ list config modules, (reverse) sorted by length of path, shortest first. """ - keys = self.files.keys() - keys.sort() - configs = [] - for key in keys: - configs.append(self.files[key]) - if reverse: - configs.reverse() - return configs - - def topmost(self): - """ return topmost (shortest path) config module. """ - configs = self.listconfigs() - if not configs: - return None - return configs[0] - def _getreporter(self): try: return self._reporter except AttributeError: - self._reporter = test.TextReporter() + self._reporter = py.test.TextReporter() return self._reporter reporter = property(_getreporter, None, None) def getfirst(self, param, default=dummy): """ return first found parameter. """ - for config in self.listconfigs(): - try: - return getattr(config, param) - except AttributeError: - pass + for config in self.configpaths: + x = config.join(param) + if x.check(): + return x.resolve() if default is dummy: raise AttributeError, param return default @@ -139,11 +59,12 @@ def parseargs(self, args): # first a small fight with optparse to merge the - # utest.conf file options correctly + # pytest.py file options correctly parser = optparse.OptionParser() - for config in self.listconfigs() + [self]: - meth = getattr(config, 'getoptions', None) - if meth is not None: + for config in self.configpaths + [defaultconfig]: + meth = config.join('getoptions') + if meth.check(func=1): + meth = meth.resolve() groupname, groupoptions = meth() optgroup = optparse.OptionGroup(parser, groupname) optgroup.add_options(groupoptions) @@ -170,12 +91,13 @@ # XXX better hack to restart with correct python version? pythonexecutable = self.getfirst('pythonexecutable', None) if pythonexecutable: - bn = path.local(sys.executable).basename + bn = py.path.local(py.std.sys.executable).basename if bn != pythonexecutable: # XXX shell escaping print "restarting with", pythonexecutable - print "%s %s" % (pythonexecutable, " ".join(sys.argv[0:])) - os.system("%s %s" % (pythonexecutable, " ".join(sys.argv[0:]))) + print "%s %s" % (pythonexecutable, " ".join(py.std.sys.argv[0:])) + py.std.os.system("%s %s" % ( + pythonexecutable, " ".join(py.std.sys.argv[0:]))) return True config = Config() Added: py/dist/py/test/defaultconfig.py ============================================================================== --- (empty file) +++ py/dist/py/test/defaultconfig.py Tue Oct 19 04:32:53 2004 @@ -0,0 +1,30 @@ +import py + +def getoptions(): + Option = py.test.Option + return ('py.test standard options', [ + Option('-v', '--verbose', + action="count", dest="verbose", default=0, + help="increase verbosity"), + Option('-x', '--exitfirst', + action="store_true", dest="exitfirstproblem", default=False, + help="exit instantly on first error or failed test."), + Option('-S', '--nocapture', + action="store_true", dest="nocapture", default=False, + help="disable catching of sys.stdout/stderr output."), + Option('-l', '--showlocals', + action="store_true", dest="showlocals", default=False, + help="show locals in tracebacks (disabled by default)"), + Option('', '--fulltrace', + action="store_true", dest="fulltrace", default=False, + help="Don't try to cut any tracebacks (default is to cut)"), + Option('', '--nomagic', + action="store_true", dest="nomagic", default=False, + help="don't invoke magic to e.g. beautify failing/error statements."), + Option('', '--pdb', + action="store_true", dest="usepdb", default=False, + help="Start pdb (the Python debugger) on errors."), + Option('', '--collectonly', + action="store_true", dest="collectonly", default=False, + help="only collect tests, don't execute them. "), + ]) From hpk at codespeak.net Tue Oct 19 05:13:16 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Oct 2004 05:13:16 +0200 (MEST) Subject: [py-svn] r7018 - in py/dist/py/test: . report Message-ID: <20041019031316.C71105A15E@thoth.codespeak.net> Author: hpk Date: Tue Oct 19 05:13:16 2004 New Revision: 7018 Modified: py/dist/py/test/cmdline.py py/dist/py/test/config.py py/dist/py/test/defaultconfig.py py/dist/py/test/report/test_memo.py py/dist/py/test/test_config.py Log: simplified py.test.config further and added some tests Modified: py/dist/py/test/cmdline.py ============================================================================== --- py/dist/py/test/cmdline.py (original) +++ py/dist/py/test/cmdline.py Tue Oct 19 05:13:16 2004 @@ -19,8 +19,7 @@ collectors.append(py.test.collect.Module(py.std.sys.argv[0])) args = argv[1:] - for x in getanchors(args): - py.test.config.readconfiguration(x) + py.test.config.readconfiguration(*getanchors(args)) if py.test.config.restartpython(): return @@ -29,7 +28,7 @@ if not collectors: collectors.append(py.test.collect.Directory(py.path.local())) - reporter = py.test.config.reporter + reporter = py.test.config.getfirst('getreporter') () runner = py.test.Driver(reporter) runner.setup() try: @@ -66,11 +65,11 @@ def getanchors(args): """ yield anchors from skimming the args for existing files/dirs. """ current = py.path.local() - yielded = False + l = [] for arg in args: anchor = current.join(arg, abs=1) if anchor.check(): - yield anchor - yielded = True - if not yielded: - yield current + l.append(anchor) + if not l: + l = [current] + return l Modified: py/dist/py/test/config.py ============================================================================== --- py/dist/py/test/config.py (original) +++ py/dist/py/test/config.py Tue Oct 19 05:13:16 2004 @@ -24,26 +24,22 @@ d = py.path.local.make_numbered_dir(base='utest-') sessiondir.append(d) return d - tmpdir = property(_gettmpdir, None, None, "Temp Directory") + tmpdir = property(_gettmpdir, None, None, "Temporary Directory") - def readconfiguration(self, anchor): + def readconfiguration(self, *anchors): """ read configuration files left-to-right for the given anchor file. """ - for p in anchor.parts(): - x = p.join(configbasename) - if x.check(file=1): - extpy = py.path.extpy(x) - extpy.resolve() # trigger loading it - self.configpaths.append(extpy) - self.configpaths.sort(lambda x,y: cmp(len(str(x)), cmp(len(str(y))))) + self.configpaths = [] + for anchor in anchors: + for p in anchor.parts(): + x = p.join(configbasename) + if x.check(file=1): + extpy = py.path.extpy(x) + extpy.resolve() # trigger loading it + self.configpaths.append(extpy) + print "sorting", self.configpaths + self.configpaths.sort(lambda x,y: cmp(len(str(x)), len(str(y)))) + self.configpaths.append(defaultconfig) - def _getreporter(self): - try: - return self._reporter - except AttributeError: - self._reporter = py.test.TextReporter() - return self._reporter - reporter = property(_getreporter, None, None) - def getfirst(self, param, default=dummy): """ return first found parameter. """ for config in self.configpaths: @@ -61,11 +57,10 @@ # first a small fight with optparse to merge the # pytest.py file options correctly parser = optparse.OptionParser() - for config in self.configpaths + [defaultconfig]: - meth = config.join('getoptions') - if meth.check(func=1): - meth = meth.resolve() - groupname, groupoptions = meth() + for config in self.configpaths: + meth = config.join('options') + if meth.check(): + groupname, groupoptions = meth.resolve() optgroup = optparse.OptionGroup(parser, groupname) optgroup.add_options(groupoptions) parser.add_option_group(optgroup) Modified: py/dist/py/test/defaultconfig.py ============================================================================== --- py/dist/py/test/defaultconfig.py (original) +++ py/dist/py/test/defaultconfig.py Tue Oct 19 05:13:16 2004 @@ -1,8 +1,10 @@ import py +Option = py.test.Option -def getoptions(): - Option = py.test.Option - return ('py.test standard options', [ +def getreporter(): + return py.test.TextReporter() + +options = ('py.test standard options', [ Option('-v', '--verbose', action="count", dest="verbose", default=0, help="increase verbosity"), @@ -27,4 +29,4 @@ Option('', '--collectonly', action="store_true", dest="collectonly", default=False, help="only collect tests, don't execute them. "), - ]) +]) Modified: py/dist/py/test/report/test_memo.py ============================================================================== --- py/dist/py/test/report/test_memo.py (original) +++ py/dist/py/test/report/test_memo.py Tue Oct 19 05:13:16 2004 @@ -1,6 +1,10 @@ import py -datadir = py.test.config.tmpdir +def setup_module(mod): + mod.tmpdir = py.path.local.make_numbered_dir() +def teardown_module(mod): + mod.tmpdir.remove() + del mod.tmpdir #def test_equal_should_raise(): # check.equal(1,2) @@ -11,7 +15,7 @@ # def test_memoreporter(): reporter = py.test.MemoReporter() - p = datadir.join('memoimport.py') + p = tmpdir.join('memoimport.py') p.write('raise IOError') collector = py.test.collect.Module(p) #main(collector=collector, reporter=reporter) Modified: py/dist/py/test/test_config.py ============================================================================== --- py/dist/py/test/test_config.py (original) +++ py/dist/py/test/test_config.py Tue Oct 19 05:13:16 2004 @@ -1,5 +1,6 @@ from __future__ import generators -from py.test import config +import py +config = py.test.config class MyClass: def getoptions(self): @@ -25,3 +26,20 @@ d1 = config.tmpdir d2 = config.tmpdir assert d1 == d2 + +def test_config_order(): + from py.__impl__.test import config + o = py.test.config.tmpdir.ensure('configorder', dir=1) + o.ensure('pytest.py').write('x=1 ; import py ; py._x = [x]') + o.ensure('a/pytest.py').write('x=2 ; import py ; py._x.append(x)') + o.ensure('a/b/c/pytest.py').write('x=3 ; import py ; py._x.append(x)') + cfg = config.Config() + cfg.readconfiguration(o) + assert cfg.getfirst('x') == 1 + assert py._x == [1] + + cfg = config.Config() + cfg.readconfiguration(o.join('a/b/c')) + assert cfg.getfirst('x') == 1 + assert py._x == [1,2,3] + From hpk at codespeak.net Tue Oct 19 05:33:11 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Oct 2004 05:33:11 +0200 (MEST) Subject: [py-svn] r7019 - py/dist/py/test Message-ID: <20041019033311.DC85E5A15E@thoth.codespeak.net> Author: hpk Date: Tue Oct 19 05:33:11 2004 New Revision: 7019 Modified: py/dist/py/test/config.py Log: removed a print Modified: py/dist/py/test/config.py ============================================================================== --- py/dist/py/test/config.py (original) +++ py/dist/py/test/config.py Tue Oct 19 05:33:11 2004 @@ -36,7 +36,6 @@ extpy = py.path.extpy(x) extpy.resolve() # trigger loading it self.configpaths.append(extpy) - print "sorting", self.configpaths self.configpaths.sort(lambda x,y: cmp(len(str(x)), len(str(y)))) self.configpaths.append(defaultconfig) From arigo at codespeak.net Tue Oct 19 15:40:21 2004 From: arigo at codespeak.net (arigo at codespeak.net) Date: Tue, 19 Oct 2004 15:40:21 +0200 (MEST) Subject: [py-svn] r7025 - in py/dist/py: path path/extpy path/local path/svn test Message-ID: <20041019134021.3CA045A15E@thoth.codespeak.net> Author: arigo Date: Tue Oct 19 15:40:20 2004 New Revision: 7025 Modified: py/dist/py/path/common.py py/dist/py/path/extpy/extpy.py py/dist/py/path/local/local.py py/dist/py/path/svn/test_urlcommand.py py/dist/py/path/svn/test_wccommand.py py/dist/py/test/collect.py Log: Get rid of the imp module to import local modules. Instead: FSPathBase.getmodule(): uses a new internal method 'self.getcodeobj()' to get the module's top-level code object, and then (as before) build a module object, put it in sys.modules, and run the code in the module. FSPathBase.getcodeobj(): uses self.read() and dyncode as before. LocalPath.getcodeobj(): uses the regular compile() to make the code object, so that a local module shows up normally in tracebacks and in the co_filename attributes. This is also where we look for and create .pyc files. In this scheme, .pyc files are completely ignored for non-local paths. This is probably not too bad: with svn paths, say, creating the .pyc files would add them to the repository. Plus a few details, including setting up the new module's __path__ so that relative imports work (at least on local paths). Modified: py/dist/py/path/common.py ============================================================================== --- py/dist/py/path/common.py (original) +++ py/dist/py/path/common.py Tue Oct 19 15:40:20 2004 @@ -285,17 +285,21 @@ self.remove() def getmodule(self): - """resolve this path to a module python object. """ + """resolve this path to a module python object. """ modname = str(self) - for x in self.sep, '.': - modname = modname.replace(x, '_') + modname = modname.replace('.', self.sep) try: return sys.modules[modname] except KeyError: - s = self.read() + co = self.getcodeobj() mod = py.std.new.module(modname) mod.__file__ = str(self) - co = py.magic.dyncode.compile2(s) + mod.__path__ = [str(self.get('dirname'))] sys.modules[modname] = mod exec co in mod.__dict__ return mod + + def getcodeobj(self): + s = self.read() + # XXX str(self) should show up somewhere in the code's filename + return py.magic.dyncode.compile2(s) Modified: py/dist/py/path/extpy/extpy.py ============================================================================== --- py/dist/py/path/extpy/extpy.py (original) +++ py/dist/py/path/extpy/extpy.py Tue Oct 19 15:40:20 2004 @@ -8,7 +8,7 @@ import py from py.__impl__.path import common import sys -import inspect, imp +import inspect moduletype = type(py) class Extpy(common.PathBase): @@ -82,7 +82,7 @@ def resolve(self): """return the python object, obtained from traversing from the root along the modpath. - """ + """ rest = filter(None, self.modpath.split('.')) target = self.getmodule() for name in rest: @@ -196,7 +196,7 @@ x = inspect.getfile(otherobj) except TypeError: return False - if x.endswith('pyc'): + if x.endswith('.pyc'): x = x[:-1] if str(self.root) == x: return True Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Tue Oct 19 15:40:20 2004 @@ -378,18 +378,41 @@ """ return string representation of the Path. """ return self.strpath - def getmodule(self): - modname = str(self).replace(self.sep, '_').replace('.','_') - try: - return sys.modules[modname] - except KeyError: - #print "trying importing", modname - if not self.check(file=1): - raise py.path.NotFound(self) - mod = py.std.imp.load_source(modname, str(self)) - mod.__name__ = modname - sys.modules[modname] = mod - return mod + def getcodeobj(self): + dotpy = self.strpath.endswith('.py') + if dotpy: + my_magic = py.std.imp.get_magic() + my_timestamp = int(self.mtime()) + if __debug__: + pycfile = self.strpath + 'c' + else: + pycfile = self.strpath + 'o' + try: + f = open(pycfile, 'rb') + try: + header = f.read(8) + if len(header) == 8: + magic, timestamp = py.std.struct.unpack('<4si', header) + if magic == my_magic and timestamp == my_timestamp: + return py.std.marshal.load(f) + finally: + f.close() + except IOError: + pass + s = self.read() + codeobj = compile(s, self.strpath, 'exec') + if dotpy: + try: + f = open(pycfile, 'wb') + f.write(py.std.struct.pack('<4si', 'TEMP', -1)) # fixed below + py.std.marshal.dump(codeobj, f) + f.flush() + f.seek(0) + f.write(py.std.struct.pack('<4si', my_magic, my_timestamp)) + f.close() + except IOError: + pass + return codeobj #""" #special class constructors for local filesystem paths Modified: py/dist/py/path/svn/test_urlcommand.py ============================================================================== --- py/dist/py/path/svn/test_urlcommand.py (original) +++ py/dist/py/path/svn/test_urlcommand.py Tue Oct 19 15:40:20 2004 @@ -9,10 +9,10 @@ self.root = py.path.svnurl(repo) def xtest_copy_file(self): - raise py.test.run.Skipped(msg="XXX fix svnurl first") + raise py.test.Skipped(msg="XXX fix svnurl first") def xtest_copy_dir(self): - raise py.test.run.Skipped(msg="XXX fix svnurl first") + raise py.test.Skipped(msg="XXX fix svnurl first") def XXXtest_info_log(self): url = self.root.join("samplefile") Modified: py/dist/py/path/svn/test_wccommand.py ============================================================================== --- py/dist/py/path/svn/test_wccommand.py (original) +++ py/dist/py/path/svn/test_wccommand.py Tue Oct 19 15:40:20 2004 @@ -15,7 +15,7 @@ py.process.cmdexec('svnadmin create %s' % repo) except py.process.cmdexec.Error: repo.remove() - raise py.test.run.Skipped(msg='could not create temporary svn test repository') + raise py.test.Skipped(msg='could not create temporary svn test repository') wcdir.ensure(dir=1) print "created svn repository", repo wc = py.path.svnwc(wcdir) Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Tue Oct 19 15:40:20 2004 @@ -1,6 +1,6 @@ from __future__ import generators -import sys, inspect, types, imp +import sys, inspect, types from py import path, test import py From hpk at codespeak.net Tue Oct 19 15:41:24 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Oct 2004 15:41:24 +0200 (MEST) Subject: [py-svn] r7026 - py/dist/doc Message-ID: <20041019134124.E62155A15E@thoth.codespeak.net> Author: hpk Date: Tue Oct 19 15:41:24 2004 New Revision: 7026 Modified: py/dist/doc/index.txt Log: reordered the chapters slightly Modified: py/dist/doc/index.txt ============================================================================== --- py/dist/doc/index.txt (original) +++ py/dist/doc/index.txt Tue Oct 19 15:41:24 2004 @@ -4,6 +4,7 @@ Here is some documentation about main areas within *the py lib*: + `why py?`_ describes a bit of the motivation and background `py.test`_ describes features of the py.test utility @@ -11,8 +12,6 @@ `miscellaneous features`_ describes some more py lib features - `why py?`_ describes a bit of the motivation and background - `future`_ handles development visions and plans for the near future. Note that some of these texts refer to future development and From hpk at codespeak.net Tue Oct 19 16:45:07 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Oct 2004 16:45:07 +0200 (MEST) Subject: [py-svn] r7029 - py/dist/doc Message-ID: <20041019144507.B1DE75A15E@thoth.codespeak.net> Author: hpk Date: Tue Oct 19 16:45:07 2004 New Revision: 7029 Added: py/dist/doc/api.txt Modified: py/dist/doc/execnet.txt py/dist/doc/index.txt py/dist/doc/why_py.txt Log: - improved execnet documentation - improved why_py - added a somewhat empty api.txt Added: py/dist/doc/api.txt ============================================================================== --- (empty file) +++ py/dist/doc/api.txt Tue Oct 19 16:45:07 2004 @@ -0,0 +1,19 @@ +The py lib API +============== + +This is a placeholder for upcoming API documentation. We +intend to mostly generate this API description from the py +lib's entry points, their docstrings and cross-reference it +with its tests and examples that are already available +at our little `getting started`_ chapter. + +Yours tests are your API insurance +================================== + +Tests are the primary assurance of API compatilibity. If you +like to ensure some behaviour gets preserved across major +releases you have to write a test. Note though, that the +documentation may state additional restrictions which +take precedence. + +.. _`getting started`: getting_started.html Modified: py/dist/doc/execnet.txt ============================================================================== --- py/dist/doc/execnet.txt (original) +++ py/dist/doc/execnet.txt Tue Oct 19 16:45:07 2004 @@ -4,19 +4,49 @@ .. contents:: .. sectnum:: -*this is a draft version* - Execnet deals with letting your python programs execute and communicate across process and computer barriers. At the core it is a very simple and powerful mechanism: executing source code at "the other side" and communicating with -the remote part of your program. +remote parts of your program. + +A warning note: We are doing documentation-driven development +in some ways. So some of the described features are not +there yet. You may refer to the `py API`_ reference for +further information. + +The "shpy" way, historical remarks +---------------------------------- + +Some of you may have seen the pygame-based editor **shpy** +that Armin Rigo, Holger Krekel and Michael Hudson demonstrated +at EuroPython 2004. Is is a multiline interactive python +environment which allows multiple remote users to have their +own cursors and shared interaction with the graphical shell +which can execute python code, of course. + +**py.execnet** extracts and refines the basic mechanism that +was originally developed from the one-week hack that is the +current **shpy**. No, its is not released yet but hopefully +Armin, Holger and others get to do a second one-week sprint at +some point to be able to release it. If you are interested +drop them a note. This is real fun. + +A new view on distributed execution +----------------------------------- + +**py.execnet** takes the view of **asynchronously executing +client-provided code fragments** to help address a core +problem of distributed programs. A core feature of +**py.execnet** is that ''the communication protocols can be +defined by the client side'''. Usually, with server/client +apps and especially RMI systems you often have to upgrade your +server if you upgrade your client. + +What about Security? Are you completly nuts? +-------------------------------------------- -The main benefit of taking the view of '''asynchronously executing -code fragments''' for the problem of remote executio is that ''the -communication protocols can be defined by the client side'''. -Usually, with server/client apps and especially RMI systems you -often have to upgrade your server if you upgrade your client. +We'll talk about that later :-) Basic Features ============== @@ -28,10 +58,13 @@ - to distribute your program across a network and define communication protocols from the client side, making - "server" upgrades superflous. + server maintenance superflous. In fact, there is no such + thing as a server. It's just another computer ... if it + doesn't run in a kernel-level jail in which case + even that is virtualized. -High Level Interface for remote execution ------------------------------------------ +High Level Interface: **remote_exec** +------------------------------------- These gateways offer one main high level interface:: @@ -53,8 +86,8 @@ .. _`channel-api`: -The "Channel" interface for exchanging data between gateways ------------------------------------------------------------- +The **Channel** interface for exchanging data across gateways +------------------------------------------------------------- While executing custom strings on "the other side" is simple enough it is often tricky to deal with. Therefore we want a way @@ -98,8 +131,9 @@ reraised as gateway.RemoteError exceptions containing a textual representation of the remote traceback. + A simple and useful Example for Channels ----------------------------------------- +........................................ problem: retrieving contents of remote files:: @@ -125,19 +159,31 @@ # later you can exit / close down the gateway contentgateway.exit() -An Example for Channels ------------------------ +A more complicated "nested" Gateway Example +........................................... -The following proposed example opens a PopenGateway, i.e. a +The following example opens a PopenGateway, i.e. a python child process, starts a socket server within that process and then opens a SocketGateway to the freshly started -socketserver. The "py.execnet.socketserver" is a small script that -basically listens and accepts socket connections, receives one -liners and executes them. Here is the code:: +socketserver. Thus it forms a "triangle":: + + + CLIENT < ... > PopenGateway() + < . + . . + . . + . . + > SocketGateway() + +The below "socketserver" mentioned script is a small script that +basically listens and accepts socket connections, receives one +liners and executes them. + +Here is the code for making the above triangle happen:: import py socketserverbootstrap = py.code.Source( - py.script.getpath('py.execnet.socketserver').read(), + autopath.dirpath('bin/socketserver').read(), """ import socket portrange = channel.receive() @@ -161,8 +207,9 @@ # execute asynchronously the above socketserverbootstrap on the other channel = proxygw.remote_exec_async(socketserverbootstrap) - # send parameters for the for-loop + # send socket-port range to the receiving for-loop channel.send((7770, 7800)) + # # the other side should start the for loop now, we # wait for the result @@ -179,7 +226,4 @@ socketgw.exit() proxygw.exit() -The example runs as is without the use of "named signals" -above. Only sending and receiving of anonymous values -is currently supported. - +.. _`py API`: api.html Modified: py/dist/doc/index.txt ============================================================================== --- py/dist/doc/index.txt (original) +++ py/dist/doc/index.txt Tue Oct 19 16:45:07 2004 @@ -4,7 +4,7 @@ Here is some documentation about main areas within *the py lib*: - `why py?`_ describes a bit of the motivation and background + `why what how py?`_ describes a bit of the motivation and background `py.test`_ describes features of the py.test utility @@ -23,7 +23,7 @@ .. _`py.execnet`: execnet.html .. _`py.test`: test.html -.. _`why py?`: why_py.html +.. _`Why What how py?`: why_py.html .. _`future`: future.html .. _`getting started`: getting_started.html .. _`miscellaneous features`: misc.html Modified: py/dist/doc/why_py.txt ============================================================================== --- py/dist/doc/why_py.txt (original) +++ py/dist/doc/why_py.txt Tue Oct 19 16:45:07 2004 @@ -7,8 +7,19 @@ .. _frustrations: -Why do we do the py lib? -======================== +Mission Statement +================= + +The py lib wants to support a decent and convenient +development process addressing important deployment, +versioning, testing and documentation issues - seen +primarily from the perspective of a FOSS (Free and +Open Source) developer. At the same time, or maybe +just because of that, the py lib is to be very usable +for real life python applications. + +Why did we start the py lib? +============================ Among the main motivations for writing the py lib is frustration at existing python modules and packages, @@ -43,50 +54,53 @@ The py lib tries to address these problems. It is thus in risk of trying to do too many things at once. Of course, we can't solve them all at once but you will find that the above -points currently drive the development of the py lib. The py -lib wants to support a *decent and well supported development -process* addressing important deployment, versioning, testing and -documentation issues - seen primarily from the perspective of a -FOSS developer. +points currently drive the development of the py lib. What is the py libs current focus? ================================== Currently, the main focus of the py lib is to get a decent -`test environment`_. Automated tests fit very well to the -dynamism of Python. Automated tests ease development and -allow fast refactoring cycles. So we are trying to develop -the best python `test environment`_ to make writing -tests as easy as possible. And fun. +`test environment`_, indeed to produce the best one out there. +Writing, distributing and deploying tests should become +a snap ... and fun! + +Automated tests fit very well to the dynamism of Python. +Automated tests ease development and allow fast refactoring +cycles. Automated tests are a means of communication +as well. Most importantly, we try to allow test scripts with minimal -boilerplate code or actually no boilerplate at all! With -the py lib you can simply use ``assert`` statements in order -to - well - assert something about objects in your code. -No ``assertEqual(s)`` and all the other kinds of funny names +boilerplate code or no boilerplate at all. With the py lib +you can simply use ``assert`` statements in order to - well - +assert something about objects in your code. No +``assertEqual(s)`` and all the other kinds of funny names which only cover part of what you want to assert about an expression, anyway. In order, to allow a fast development pace across versions of -the py lib there is '''explicit name export control'''. You +the py lib there is **explicit name export control**. You will only see names which make sense to use from the outside and which the py lib developers want to guarantee across major -versions. No '''tested feature''' of this tightly controled API -will vanish across major releases until it is marked -deprecated. But if deprecated an API could go with every -following major release. Much thought is given to reduce the -exported *name complexity*. This is an area where the python -"batteries" lack a lot. They expose so many names that it -becomes very hard to change APIs across releases. This kills -the fun of refactoring and improving things. - -Another focus is a well tested Path implementation that -supports different backends, currently a local filesystem and -subversion working copies and subversion remote URLs. -Moreover, it provides an experimental extpython path to address -a Python object on the filesystem. +versions. -You may read some more regarding the future_ of the py lib. +No *tested feature* of the exported `py API`_ will +vanish across major releases until it is marked deprecated. +But if deprecated an API could go with every following major +release. Indeed, much thought is given to reduce the exported +*name complexity*. This is an area where the python "batteries" +lack a lot. They expose so many names that it becomes very +hard to change APIs across releases. This kills the fun of +refactoring and improving things. + +Another focus are well tested *Path* implementations that +allow to seemlessly work with different backends, currently +a local filesystem and subversion working copies and subversion +remote URLs. Moreover, it provides an experimental 'extpy' +path to address a Python object on a possibly remote filesystem. + +If you ready to grasp more then you may try reading +about the future_ of the py lib, which reflect the +current developers thoughts and discussions. How does py development work? ============================= @@ -99,22 +113,35 @@ especially if you are an experienced python developer and share some of the frustrations described above. -Moreover, the world has been granted svn commit rights to all +Moreover, the world will be granted svn commit rights to all py test files so that you can easily add bug-tests or tests for behaviour to make sure the tested behaviour persists across releases. If you are itching to actually fix or refactor any implementation code you can likely get commit -rights to do it. However, it is then a good idea to follow +rights to do it. However, it is then a good idea to follow the `py-dev mailing list`_ and grasp some of the things that are -going on. Oh right, and you should also agree to release -your code under an ``MIT license``. Maybe we can compute -the individual copyrights from the subversion blame -command. Would be a fun way to handle it. Basically, nobody -should have a problem to use the py lib under any OSI approved -license and also for commercial purposes. +going on. + + +Licensing, please +----------------- + +Oh right, and you should also agree to release your code under +an ``MIT license`` and consequently any other OSI-approved +license. This is FOSS (an evolving acronym for Free and Open +Source Software), and we want to have the py lib interopable +with whatever license style you prefer. Copyright generally +stays with the contributors. We are following the +linux-kernel dev-model with this one. + +Hum, maybe we can also compute the individual copyrights from +the subversion blame command. Would be a fun way to handle it. +Basically, nobody should ever have a problem to use the py lib +under any OSI approved license and also for commercial +purposes. Are there connections with PyPy_? -------------------------------------- +--------------------------------- Some of the motivation for writing the py lib stems from needs during PyPy_ development, most notably testing and @@ -131,28 +158,30 @@ ============================= Some of the initial code was written from Jens-Uwe Mager -and Holger Krekel, after which Holger continued on an -iteration of the py.test tool (known first as 'utest', then as -'std.utest', now 'py.test'). Helpful discussions took place -with Martijn Faassen, Stephan Schwarzer and then Armin Rigo who -contributed important parts. He and Holger came up with a -couple of iterations of the testing-code that reduced the API -to almost nothing: just the assert statement and an -assert_raises method. +and Holger Krekel, after which Holger continued on a previous +incarnation of the py.test tool (known first as 'utest', then as +'std.utest', now, finally and at last 'py.test'). + +Helpful discussions took place with Martijn Faassen, Stephan +Schwarzer and then Armin Rigo who contributed important parts. +He and Holger came up with a couple of iterations of the +testing-code that reduced the API to almost nothing: just the +assert statement and an assert_raises method. Now recently, after Holgers `talk at EP2004`_ more people -were interested and there were some discussions with Jim Fulton, +were interested and there were discussions with Jim Fulton, Marius Gedminas, Laura Creighton and more recently, Ian Bicking. + However, there is no real core development team as such. Also we are somewhat lacking in the win32 area. Every now and then the py lib is tested on windows but it's currently not a -continous concern of one of the current developers or +practical concern of one of the current developers or contributors. -However, one of the future directions of the `py.test tool and library`_ -is to allow running tests across multiple python versions and -computers. Then we can run tests without having to walk -up or boot up a windows machine :-) +However, one of the future directions of the `py.test tool and +library`_ is to allow running tests across multiple python +versions and computers. Then we can run tests without having +to walk up or boot up a windows machine :-) .. _`talk at EP2004`: http://codespeak.net/svn/user/hpk/talks/std-talk.txt .. _`coding style`: coding-style.html @@ -163,3 +192,4 @@ .. _`envisioned import/export system`: future.html#importexport .. _future: future.html .. _`py.test tool and library`: test +.. _`py API`: api.html From hpk at codespeak.net Tue Oct 19 16:53:09 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Oct 2004 16:53:09 +0200 (MEST) Subject: [py-svn] r7030 - py/dist/doc Message-ID: <20041019145309.4247E5A15E@thoth.codespeak.net> Author: hpk Date: Tue Oct 19 16:53:08 2004 New Revision: 7030 Modified: py/dist/doc/index.txt py/dist/doc/why_py.txt Log: some more start-page changes, it now clearly states at the beginning what the py lib is about. Modified: py/dist/doc/index.txt ============================================================================== --- py/dist/doc/index.txt (original) +++ py/dist/doc/index.txt Tue Oct 19 16:53:08 2004 @@ -1,8 +1,13 @@ *the py lib* ============ -Here is some documentation about main areas within -*the py lib*: +*The py lib wants to support a decent and convenient +development process addressing important deployment, +versioning, testing and documentation issues - seen +primarily from the perspective of a FOSS (Free and +Open Source) developer. At the same time, or maybe +just because of that, the py lib is to be very usable +for real life python applications.* `why what how py?`_ describes a bit of the motivation and background @@ -14,7 +19,7 @@ `future`_ handles development visions and plans for the near future. -Note that some of these texts refer to future development and +Note that some parts of these texts refer to future development and do not exactly reflect the current state. Welcome to documentation & test driven development :-) Modified: py/dist/doc/why_py.txt ============================================================================== --- py/dist/doc/why_py.txt (original) +++ py/dist/doc/why_py.txt Tue Oct 19 16:53:08 2004 @@ -7,17 +7,6 @@ .. _frustrations: -Mission Statement -================= - -The py lib wants to support a decent and convenient -development process addressing important deployment, -versioning, testing and documentation issues - seen -primarily from the perspective of a FOSS (Free and -Open Source) developer. At the same time, or maybe -just because of that, the py lib is to be very usable -for real life python applications. - Why did we start the py lib? ============================ From hpk at codespeak.net Tue Oct 19 17:06:45 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Oct 2004 17:06:45 +0200 (MEST) Subject: [py-svn] r7031 - py/dist/doc Message-ID: <20041019150645.C6A555A15E@thoth.codespeak.net> Author: hpk Date: Tue Oct 19 17:06:45 2004 New Revision: 7031 Modified: py/dist/doc/index.txt py/dist/doc/why_py.txt Log: some more style improvements Modified: py/dist/doc/index.txt ============================================================================== --- py/dist/doc/index.txt (original) +++ py/dist/doc/index.txt Tue Oct 19 17:06:45 2004 @@ -11,7 +11,7 @@ `why what how py?`_ describes a bit of the motivation and background - `py.test`_ describes features of the py.test utility + `py.test`_ introduces to the new'n'easy **py.test** utility `py.execnet`_ offers an innovative way to distribute programs across the net Modified: py/dist/doc/why_py.txt ============================================================================== --- py/dist/doc/why_py.txt (original) +++ py/dist/doc/why_py.txt Tue Oct 19 17:06:45 2004 @@ -146,24 +146,24 @@ Who is "we"? Some history ... ============================= -Some of the initial code was written from Jens-Uwe Mager -and Holger Krekel, after which Holger continued on a previous +Some of the initial code was written from *Jens-Uwe Mager* +and *Holger Krekel*, after which Holger continued on a previous incarnation of the py.test tool (known first as 'utest', then as 'std.utest', now, finally and at last 'py.test'). -Helpful discussions took place with Martijn Faassen, Stephan -Schwarzer and then Armin Rigo who contributed important parts. +Helpful discussions took place with *Martijn Faassen*, *Stephan +Schwarzer* and then *Armin Rigo* who contributed important parts. He and Holger came up with a couple of iterations of the testing-code that reduced the API to almost nothing: just the assert statement and an assert_raises method. Now recently, after Holgers `talk at EP2004`_ more people -were interested and there were discussions with Jim Fulton, -Marius Gedminas, Laura Creighton and more recently, Ian Bicking. +were interested and there were discussions with *Jim Fulton*, +*Marius Gedminas*, *Laura Creighton* and more recently, *Ian Bicking*. -However, there is no real core development team as such. Also -we are somewhat lacking in the win32 area. Every now and then -the py lib is tested on windows but it's currently not a +However, there is no real core development team as such, yet. +Also we are somewhat lacking in the win32 area. Every now and +then the py lib is tested on windows but it's currently not a practical concern of one of the current developers or contributors. @@ -180,5 +180,5 @@ .. _`PyPy`: http://codespeak.net/pypy .. _`envisioned import/export system`: future.html#importexport .. _future: future.html -.. _`py.test tool and library`: test +.. _`py.test tool and library`: test.html .. _`py API`: api.html From hpk at codespeak.net Tue Oct 19 17:31:30 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Oct 2004 17:31:30 +0200 (MEST) Subject: [py-svn] r7032 - py/dist/doc Message-ID: <20041019153130.1B3655A15E@thoth.codespeak.net> Author: hpk Date: Tue Oct 19 17:31:29 2004 New Revision: 7032 Added: py/dist/doc/coding-style.txt Log: added a new coding-style document. Added: py/dist/doc/coding-style.txt ============================================================================== --- (empty file) +++ py/dist/doc/coding-style.txt Tue Oct 19 17:31:29 2004 @@ -0,0 +1,49 @@ +===================================================== +Coding Style for the Py lib and friendly applications +===================================================== + +.. contents:: +.. sectnum:: + +First of all, if you haven't already read it, read +the `PEP 8 Style Guide for Python Code`_ which, if in doubt, +describes the policy for the py lib. + +Naming and environment +---------------------- + +- directories/modules/namespaces are always **lowercase** + +- classes and Exceptions are often **CamelCase** + +- never use plural names in directory and file names + +- functions/methods are lowercase and ``_`` - separated if + you really need to separate at all + +- it's appreciated if you manage to name files in a directory + so that tab-completion on the shell level is as easy as possible. + +Committing +---------- + +- write telling log messages because several people + are reading the diffs, an we will have a search facility + over the py lib's repo. + +- if you add ``.txt`` or ``.py`` files to the repository then + please make sure you have 'svn:eol-style' set to native. + which allows checkin/checkout in native line-ending format. + +Test conventions +---------------- + +- adding features requires adding appropriate tests. + Tests are the insurance that your code will be maintained + further and survives major releases. + +- Try to put the tests close to the tested code, don't clutter + directories. + +.. _test-design: ../devel/testdesign.html +.. _`PEP 8 Style Guide for Python Code`: http://www.python.org/peps/pep-0008.html From hpk at codespeak.net Tue Oct 19 17:33:51 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Oct 2004 17:33:51 +0200 (MEST) Subject: [py-svn] r7033 - py/dist/doc Message-ID: <20041019153351.ECA8A5A15E@thoth.codespeak.net> Author: hpk Date: Tue Oct 19 17:33:51 2004 New Revision: 7033 Modified: py/dist/doc/why_py.txt Log: fixed the connection reference with pypy. Modified: py/dist/doc/why_py.txt ============================================================================== --- py/dist/doc/why_py.txt (original) +++ py/dist/doc/why_py.txt Tue Oct 19 17:33:51 2004 @@ -139,9 +139,9 @@ More importantly, the development perspective taken from the PyPy developers has some influence. For example, the `envisioned import/export system`_ clearly has some thought -references to the way wrapping of implementation level objects -is done in PyPy_. - +references to the way PyPy tries to separate different levels +of objects and execution in order to reach a higher level +view of what is going on. Who is "we"? Some history ... ============================= From hpk at codespeak.net Tue Oct 19 18:29:59 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Oct 2004 18:29:59 +0200 (MEST) Subject: [py-svn] r7035 - py/dist/doc Message-ID: <20041019162959.154BF5A15E@thoth.codespeak.net> Author: hpk Date: Tue Oct 19 18:29:58 2004 New Revision: 7035 Modified: py/dist/doc/test.txt Log: documented two more py.test features Modified: py/dist/doc/test.txt ============================================================================== --- py/dist/doc/test.txt (original) +++ py/dist/doc/test.txt Tue Oct 19 18:29:58 2004 @@ -67,7 +67,7 @@ both of which execute the given function with args and kwargs and asserts that the given ``Exception`` is raised. Again, -in case of the possible failures (*no exception** or *wrong +in case of the possible failures (*no exception* or *wrong exception*) the reporter provides you with helpful output. debug with the ``print`` statement @@ -96,6 +96,26 @@ will see a variety of 40 tracebacks shown for different failure situations. +no inheritance requirement +-------------------------- + +Classes are recognized by their leading ``Test`` name. +You don't need to inherit some base class. This allows +to intermingle tests with an application level object. + +disabling a class of tests +-------------------------- + +If you want to disable a complete class of tests you +can set the class-level attribute ``disabled``. +For example, in order to avoid running some tests on Win32:: + + class TestEgSomePosixStuff: + disabled = sys.platform == 'win32' + + def test_xxx(self): + ... + Managing test state across test modules, classes and methods ------------------------------------------------------------ From hpk at codespeak.net Tue Oct 19 20:24:33 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Oct 2004 20:24:33 +0200 (MEST) Subject: [py-svn] r7040 - py/dist/doc Message-ID: <20041019182433.43B175A15E@thoth.codespeak.net> Author: hpk Date: Tue Oct 19 20:24:31 2004 New Revision: 7040 Modified: py/dist/doc/coding-style.txt py/dist/doc/index.txt py/dist/doc/test.txt py/dist/doc/why_py.txt Log: - added some more documentation about py.test features - improved coding-style - a reference to AMKs blog about some python standard library issues. - lots of little fixes and improvements Modified: py/dist/doc/coding-style.txt ============================================================================== --- py/dist/doc/coding-style.txt (original) +++ py/dist/doc/coding-style.txt Tue Oct 19 20:24:31 2004 @@ -5,16 +5,19 @@ .. contents:: .. sectnum:: -First of all, if you haven't already read it, read -the `PEP 8 Style Guide for Python Code`_ which, if in doubt, -describes the policy for the py lib. +Honour PEP 8: Style Guide for Python Code +----------------------------------------- -Naming and environment ----------------------- +First of all, if you haven't already read it, read the `PEP 8 +Style Guide for Python Code`_ which, if in doubt, serves as +the default coding-style for the py lib. -- directories/modules/namespaces are always **lowercase** +Naming +------ -- classes and Exceptions are often **CamelCase** +- directories, modules and namespaces are always **lowercase** + +- classes and especially Exceptions are most often **CamelCase** - never use plural names in directory and file names @@ -24,26 +27,37 @@ - it's appreciated if you manage to name files in a directory so that tab-completion on the shell level is as easy as possible. + Committing ---------- +- adding features requires adding appropriate tests. + +- bug fixes should be encoded in a test before being fixed. + - write telling log messages because several people - are reading the diffs, an we will have a search facility - over the py lib's repo. + will read your diffs, an we will have a search facility + over the py lib's subversion repository. - if you add ``.txt`` or ``.py`` files to the repository then - please make sure you have 'svn:eol-style' set to native. + please make sure you have ``svn:eol-style`` set to native. which allows checkin/checkout in native line-ending format. -Test conventions ----------------- +Miscellaneous +------------- -- adding features requires adding appropriate tests. - Tests are the insurance that your code will be maintained +- Tests are the insurance that your code will be maintained further and survives major releases. -- Try to put the tests close to the tested code, don't clutter - directories. +- Try to put the tests close to the tested code, don't + overload directories with names. + +- If you think of exporting new py lib APIs, discuss it first on the + `py-dev mailing list`_ and possibly write a chapter in our + `future_` book. Communication is considered a key here to make + sure that the py lib develops in a consistent way. .. _test-design: ../devel/testdesign.html .. _`PEP 8 Style Guide for Python Code`: http://www.python.org/peps/pep-0008.html +.. _`py-dev mailing list`: http://codespeak.net/mailman/listinfo/py-dev +.. _`future`: future.html Modified: py/dist/doc/index.txt ============================================================================== --- py/dist/doc/index.txt (original) +++ py/dist/doc/index.txt Tue Oct 19 20:24:31 2004 @@ -6,7 +6,7 @@ versioning, testing and documentation issues - seen primarily from the perspective of a FOSS (Free and Open Source) developer. At the same time, or maybe -just because of that, the py lib is to be very usable +just because of that, the py lib is very usable for real life python applications.* `why what how py?`_ describes a bit of the motivation and background Modified: py/dist/doc/test.txt ============================================================================== --- py/dist/doc/test.txt (original) +++ py/dist/doc/test.txt Tue Oct 19 20:24:31 2004 @@ -70,6 +70,22 @@ in case of the possible failures (*no exception* or *wrong exception*) the reporter provides you with helpful output. +automatic collection of tests on all levels +------------------------------------------- + +The automated test (see `collection process`_ for +implemenetation details) takes care that all +files with a leading ``test_`` and then every function +with a leading ``test_`` or ``Test`` in the case of classes +are collected. + +no interference with cmdline utilities +-------------------------------------- + +As ``py.test`` mainly operates as a separate cmdline +tool you can easily have a command line utility and +some tests in the same file. + debug with the ``print`` statement ---------------------------------- @@ -77,6 +93,7 @@ tests. This output is only displayed when the test fails, otherwise you will not see it. + order of execution is guaranteed -------------------------------- @@ -215,7 +232,8 @@ Note that ``setup_class(TestStateFullThing)`` is called and not ``TestStateFullThing.setup_class()`` which would require you to insert ``setup_class = classmethod(setup_class)`` to make -your setup function callable. +your setup function callable. Did we mention that lazyness +is a virtue? The three components of ``py.test`` =================================== @@ -249,19 +267,7 @@ outcome (possibly an exception) and sends it over to the *reporter* instance. - -Configuration -------------- - -``py.test`` detects and honours ``pytest.py`` configuration files -that can stack in your filesystem. The following configuration -values are currently recognized:: - - pythonexecutables a sequence of strings which represent - a python executable, e.g. ('python2.2',) - -For configuration values the upmost ``pytest.py`` has the top -priority. For example, you can specify +.. _`collection process`: Collectors and the test collection process ------------------------------------------ @@ -362,8 +368,9 @@ ------------------------------ - Items allow total control of executing their contained test - method. iteminstance.execute() gets called in order to - execute a test. + method. ``iteminstance.execute()`` will get called by the + driver in order to actually execute a test. Thus a custom + ``execute()`` method can pass arguments to test functions. - Item.execute() methods can provide custom paramters (a db-connection, some initialized application entity, whatever) Modified: py/dist/doc/why_py.txt ============================================================================== --- py/dist/doc/why_py.txt (original) +++ py/dist/doc/why_py.txt Tue Oct 19 20:24:31 2004 @@ -45,6 +45,9 @@ can't solve them all at once but you will find that the above points currently drive the development of the py lib. +And it seems there are `related thoughts from AMK`_ about +standard library development. + What is the py libs current focus? ================================== @@ -84,11 +87,11 @@ Another focus are well tested *Path* implementations that allow to seemlessly work with different backends, currently a local filesystem and subversion working copies and subversion -remote URLs. Moreover, it provides an experimental 'extpy' +remote URLs. Moreover, it provides an experimental ``extpy`` path to address a Python object on a possibly remote filesystem. -If you ready to grasp more then you may try reading -about the future_ of the py lib, which reflect the +If you are ready to grasp more then you may try reading +about future_ coding goals of the py lib, which reflect the current developers thoughts and discussions. How does py development work? @@ -117,8 +120,7 @@ Oh right, and you should also agree to release your code under an ``MIT license`` and consequently any other OSI-approved -license. This is FOSS (an evolving acronym for Free and Open -Source Software), and we want to have the py lib interopable +license. This is FOSS [#]_ and we want to have the py lib interopable with whatever license style you prefer. Copyright generally stays with the contributors. We are following the linux-kernel dev-model with this one. @@ -155,7 +157,8 @@ Schwarzer* and then *Armin Rigo* who contributed important parts. He and Holger came up with a couple of iterations of the testing-code that reduced the API to almost nothing: just the -assert statement and an assert_raises method. +plain assert statement and a ``py.test.raises`` method to +check for occuring exceptions. Now recently, after Holgers `talk at EP2004`_ more people were interested and there were discussions with *Jim Fulton*, @@ -182,3 +185,9 @@ .. _future: future.html .. _`py.test tool and library`: test.html .. _`py API`: api.html +.. _`related thoughts from AMK`: http://www.amk.ca/diary/archives/cat_python.html + +-- + +.. [#] FOSS i an evolving acronym for Free and Open Source Software + From hpk at codespeak.net Tue Oct 19 20:31:07 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Oct 2004 20:31:07 +0200 (MEST) Subject: [py-svn] r7041 - py/dist/doc Message-ID: <20041019183107.07C745A98A@thoth.codespeak.net> Author: hpk Date: Tue Oct 19 20:31:07 2004 New Revision: 7041 Modified: py/dist/doc/test.txt Log: fix Modified: py/dist/doc/test.txt ============================================================================== --- py/dist/doc/test.txt (original) +++ py/dist/doc/test.txt Tue Oct 19 20:31:07 2004 @@ -296,9 +296,6 @@ By default, methods are only collected if their class starts with ``Test``. -name, unless you provide a custom collector in your module. - - Drivers bind it all together ---------------------------- From hpk at codespeak.net Tue Oct 19 20:36:39 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Oct 2004 20:36:39 +0200 (MEST) Subject: [py-svn] r7042 - py/dist/py/path Message-ID: <20041019183639.E56065A15E@thoth.codespeak.net> Author: hpk Date: Tue Oct 19 20:36:39 2004 New Revision: 7042 Modified: py/dist/py/path/error.py Log: bug fix Modified: py/dist/py/path/error.py ============================================================================== --- py/dist/py/path/error.py (original) +++ py/dist/py/path/error.py Tue Oct 19 20:36:39 2004 @@ -1,7 +1,9 @@ """ -module with some shared utils +Error module """ +import py + class Error(Exception): def __init__(self, *args): self.args = args @@ -67,7 +69,7 @@ return cache[baseclass, newclass] except KeyError: import new - Mixed = new.classobj( + Mixed = py.std.new.classobj( newclass.__name__, (newclass, baseclass), {}) From hpk at codespeak.net Tue Oct 19 20:40:11 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Oct 2004 20:40:11 +0200 (MEST) Subject: [py-svn] r7043 - py/dist/doc Message-ID: <20041019184011.519715A15E@thoth.codespeak.net> Author: hpk Date: Tue Oct 19 20:40:10 2004 New Revision: 7043 Modified: py/dist/doc/why_py.txt Log: made the "communication" issue more obvious. Modified: py/dist/doc/why_py.txt ============================================================================== --- py/dist/doc/why_py.txt (original) +++ py/dist/doc/why_py.txt Tue Oct 19 20:40:10 2004 @@ -97,6 +97,9 @@ How does py development work? ============================= +Communication and coding style +------------------------------ + We are discussing things on our `py-dev mailing list`_ and collaborate via the codespeak subversion repository. @@ -110,9 +113,9 @@ for behaviour to make sure the tested behaviour persists across releases. If you are itching to actually fix or refactor any implementation code you can likely get commit -rights to do it. However, it is then a good idea to follow the -`py-dev mailing list`_ and grasp some of the things that are -going on. +rights to do it. However, it is then (and anyway) a good idea to +follow the `py-dev mailing list`_ and grasp some of the things +that are going on in the `future`_ book. Licensing, please From hpk at codespeak.net Tue Oct 19 21:17:35 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Oct 2004 21:17:35 +0200 (MEST) Subject: [py-svn] r7044 - py/dist/doc Message-ID: <20041019191735.AA15E5A15E@thoth.codespeak.net> Author: hpk Date: Tue Oct 19 21:17:35 2004 New Revision: 7044 Modified: py/dist/doc/why_py.txt Log: added a joking hint at cvs, sorry couldn't resist :-) Modified: py/dist/doc/why_py.txt ============================================================================== --- py/dist/doc/why_py.txt (original) +++ py/dist/doc/why_py.txt Tue Oct 19 21:17:35 2004 @@ -25,7 +25,8 @@ - the python "batteries included" standard modules are tied to the python release process. You can't easily benefit from new python module in older python versions which you might - have to use for whatever reason. + have to use for whatever reason. Oh and battery development + still happens with ``cvs`` which is so much 90ties :-) - support for richer interactive interaction with command line utilities or e.g. pygame-based shells is missing. From hpk at codespeak.net Tue Oct 19 21:31:46 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Oct 2004 21:31:46 +0200 (MEST) Subject: [py-svn] r7045 - py/dist/doc Message-ID: <20041019193146.33BFE5A15E@thoth.codespeak.net> Author: hpk Date: Tue Oct 19 21:31:45 2004 New Revision: 7045 Modified: py/dist/doc/why_py.txt Log: added a para of appreciation of the python dev community. Modified: py/dist/doc/why_py.txt ============================================================================== --- py/dist/doc/why_py.txt (original) +++ py/dist/doc/why_py.txt Tue Oct 19 21:31:45 2004 @@ -49,6 +49,12 @@ And it seems there are `related thoughts from AMK`_ about standard library development. +Please note that we very much appreicate the great work +of all the python developers. Criticism of old ways and +experimenting with new ways doesn't imply that the old +ways are bad. But we all strive for the holy development +grail at least some of the time, don't we? + What is the py libs current focus? ================================== From hpk at codespeak.net Tue Oct 19 21:43:13 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Oct 2004 21:43:13 +0200 (MEST) Subject: [py-svn] r7046 - py/dist/doc Message-ID: <20041019194313.CE8345A15E@thoth.codespeak.net> Author: hpk Date: Tue Oct 19 21:43:13 2004 New Revision: 7046 Modified: py/dist/doc/execnet.txt Log: fixed the execnet history slightly. Modified: py/dist/doc/execnet.txt ============================================================================== --- py/dist/doc/execnet.txt (original) +++ py/dist/doc/execnet.txt Tue Oct 19 21:43:13 2004 @@ -20,17 +20,19 @@ Some of you may have seen the pygame-based editor **shpy** that Armin Rigo, Holger Krekel and Michael Hudson demonstrated -at EuroPython 2004. Is is a multiline interactive python -environment which allows multiple remote users to have their -own cursors and shared interaction with the graphical shell -which can execute python code, of course. +at EuroPython 2004 during the lightning talks. It is a +multiline interactive python environment which allows multiple +remote users to have their own cursors and shared interaction +with the graphical shell which can execute python code, of +course. **py.execnet** extracts and refines the basic mechanism that was originally developed from the one-week hack that is the -current **shpy**. No, its is not released yet but hopefully +current **shpy**. No, the latter is not released but hopefully Armin, Holger and others get to do a second one-week sprint at some point to be able to release it. If you are interested -drop them a note. This is real fun. +drop them a note. This is real fun if you are willing to +bend your mind a bit. A new view on distributed execution ----------------------------------- From hpk at codespeak.net Tue Oct 19 21:52:35 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Oct 2004 21:52:35 +0200 (MEST) Subject: [py-svn] r7047 - py/dist/doc Message-ID: <20041019195235.446005A15E@thoth.codespeak.net> Author: hpk Date: Tue Oct 19 21:52:34 2004 New Revision: 7047 Modified: py/dist/doc/execnet.txt Log: added a link to kernel level jails Modified: py/dist/doc/execnet.txt ============================================================================== --- py/dist/doc/execnet.txt (original) +++ py/dist/doc/execnet.txt Tue Oct 19 21:52:34 2004 @@ -40,8 +40,8 @@ **py.execnet** takes the view of **asynchronously executing client-provided code fragments** to help address a core problem of distributed programs. A core feature of -**py.execnet** is that ''the communication protocols can be -defined by the client side'''. Usually, with server/client +**py.execnet** is that **the communication protocols can be +defined by the client side**. Usually, with server/client apps and especially RMI systems you often have to upgrade your server if you upgrade your client. @@ -62,7 +62,7 @@ communication protocols from the client side, making server maintenance superflous. In fact, there is no such thing as a server. It's just another computer ... if it - doesn't run in a kernel-level jail in which case + doesn't run in a kernel-level jail [#]_ in which case even that is virtualized. High Level Interface: **remote_exec** @@ -229,3 +229,8 @@ proxygw.exit() .. _`py API`: api.html + +.. [#] There is an interesting emerging `Jail`_ linux technology + as well as a host of others, of course. + +.. _`Jail`: http://books.rsbac.org/unstable/x2223.html From hpk at codespeak.net Tue Oct 19 22:49:09 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Oct 2004 22:49:09 +0200 (MEST) Subject: [py-svn] r7048 - py/dist/doc Message-ID: <20041019204909.D31EE5A15E@thoth.codespeak.net> Author: hpk Date: Tue Oct 19 22:49:09 2004 New Revision: 7048 Modified: py/dist/doc/coding-style.txt py/dist/doc/getting_started.txt py/dist/doc/index.txt Log: - improving getting started and other documentation - put some more emphasize on documentation & test driven development, literally. Modified: py/dist/doc/coding-style.txt ============================================================================== --- py/dist/doc/coding-style.txt (original) +++ py/dist/doc/coding-style.txt Tue Oct 19 22:49:09 2004 @@ -12,7 +12,15 @@ Style Guide for Python Code`_ which, if in doubt, serves as the default coding-style for the py lib. -Naming +Documentation and Testing +------------------------- + +- generally we want to drive and interweave coding of + documentation, tests and real code as much as possible. + Without good documentation others may never know about + the latest and greatest feature. + +naming ------ - directories, modules and namespaces are always **lowercase** @@ -28,7 +36,7 @@ so that tab-completion on the shell level is as easy as possible. -Committing +committing ---------- - adding features requires adding appropriate tests. Modified: py/dist/doc/getting_started.txt ============================================================================== --- py/dist/doc/getting_started.txt (original) +++ py/dist/doc/getting_started.txt Tue Oct 19 22:49:09 2004 @@ -4,8 +4,8 @@ .. contents:: .. sectnum:: -getting the current py lib -========================== +Obtaining the current py lib +============================ Due to the nature of its goals the py lib can't be easily released without a certain API consistency. Nevertheless, @@ -28,12 +28,12 @@ setting it up ------------- -You need to put the checkout-directory into your PYTHONPATH +You need to put the checkout-directory into your ``PYTHONPATH`` and you want to have the ``dist-py/py/bin/py.test`` script in your system path, which lets you execute test files and directories. There already is a convenient way for Bash/Shell based systems -to setup the PYTHONPATH as well as the shell PATH, insert:: +to setup the ``PYTHONPATH`` as well as the shell ``PATH``, insert:: eval `python ~/path/to/dist-py/dist/py/env.py` @@ -46,31 +46,69 @@ And no, we don't provide a distutils-install currently, because we really want have the py lib installable on multiple python versions, want to have automated -upgrades ... +upgrades ... upgrading it ------------ -Well, simple. Go to your checkout and issue:: +Well, easy. Go to your checkout directory and issue:: svn up -:-) +have fun and `get an account`_ :-) Participating in development ============================ +Coding and communication +------------------------ + +We are practicing what could be called documentation, +vision, discussion and automated test driven development. +In the `future`_ book we try to layout visions and ideas for +the near coding feature to give a means for preliminary +feedback before code hits the ground. + +With our `coding style` we are mostly following +cpython guidance with some additional restrictions +some of which projects like twisted_ or zope3_ have +adopted in similar ways. + +.. _`zope3`: http://www.zope3.org +.. _twisted: http://www.twistedmatrix.org +.. _`get an account`: +.. _future: future.html + +The py-dev and py-svn mailing lists +----------------------------------- + If you feel the desire to help tackle bugs and fixes, -or support resolution of some `frustrations`_ then -please subscribe to our mailinglist: +or support resolution of some `frustrations`_ or to +just lurk in then please subscribe to one or both +of our mailinglists: + + `the py lib's py-dev developers list`_ + +and + + `the py lib's py-svn general commit mailing list`_ - http://codespeak.net/mailman/listinfo/py-dev +get an account on codespeak +--------------------------- -or start communicating by submitting improved code -if you already have an account on codespeak_. +codespeak_ is generally deploying a very liberal committing +scheme. If you know someone who is active on codespeak already +or you are otherwise known in the community then you will most +probably just get access. But even if you are new to the +python developer community you may still get one if you +want to improve things and can be expected to honour the +style of coding and communication. +.. _`coding style`: coding-style.html .. _`frustrations`: .. _`the frustrations with current python package development`: why_py.html#frustrations .. _us: http://codespeak.net/mailman/listinfo/py-dev .. _codespeak: http://codespeak.net/ +.. _`the py lib's developers list`: http://codespeak.net/mailman/listinfo/py-dev +.. _`the py lib's general commit mailing list`: http://codespeak.net/mailman/listinfo/py-svn Modified: py/dist/doc/index.txt ============================================================================== --- py/dist/doc/index.txt (original) +++ py/dist/doc/index.txt Tue Oct 19 22:49:09 2004 @@ -20,8 +20,9 @@ `future`_ handles development visions and plans for the near future. Note that some parts of these texts refer to future development and -do not exactly reflect the current state. Welcome to -documentation & test driven development :-) +do not exactly reflect the current state. + +**Welcome to documentation and test driven development** :-) There is some preliminary documentation for `getting started`_ From hpk at codespeak.net Tue Oct 19 23:06:49 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Oct 2004 23:06:49 +0200 (MEST) Subject: [py-svn] r7049 - py/dist/doc Message-ID: <20041019210649.418165A15E@thoth.codespeak.net> Author: hpk Date: Tue Oct 19 23:06:48 2004 New Revision: 7049 Modified: py/dist/doc/future.txt py/dist/doc/getting_started.txt Log: - fixes/improvements to the getting started document - a new small future chapter for a "rest"-checker implemented with the possibly upcoming other future features of the py lib. Modified: py/dist/doc/future.txt ============================================================================== --- py/dist/doc/future.txt (original) +++ py/dist/doc/future.txt Tue Oct 19 23:06:48 2004 @@ -380,8 +380,11 @@ in the neverending `future book`_. -Generating xml/html -=================== + +.. _`lightweight xml generation`: + +Lightweight, convenient xml generation +====================================== The py lib strives to offer enough functionality to represent itself and especially its API in html or xml. Moreover, @@ -395,8 +398,8 @@ The starting point for this, btw, is the `Reporter`_ from the test module. -lightweight xml generation --------------------------- +a pythonic object model, please +------------------------------- Besides DOM implementations there are some interesting techniques for marrying XML and python, most notable xist_ @@ -442,6 +445,24 @@ more experimentations and a comparison with xist_ i guess. +Extending rest-tests to check links +=================================== + +Currently if you have properly set up your py lib (see `getting started`_) +you can go to the ``doc/`` subtree and execute ``py.test`` in order +to test that the `restructured text`_ documentation does not +produce errors or warnings. There is a ``test_rest.py`` script +with a custom collector that does it. It would be nice if it +parsed the generated html and checked that all links actually +resolve correctly to ensure high quality of documentation. + +However, this is crossconnected with `a more general view on path objects`_ +and `lightweight xml generation`_ as the codification of these +visions would make the link-checker a snap. It could serve +as a nice script in our ``example`` tree. + +.. _`getting started`: getting_started.html +.. _`restructured text`: http://docutils.sourceforge.net/docs/user/rst/quickref.html .. _`python standard library`: http://www.python.org/doc/2.3.4/lib/lib.html .. _`xpython EuroPython 2004 talk`: http://codespeak.net/svn/user/hpk/talks/xpython-talk.txt .. _`under the xpy tree`: http://codespeak.net/svn/user/hpk/xpy/xml.py Modified: py/dist/doc/getting_started.txt ============================================================================== --- py/dist/doc/getting_started.txt (original) +++ py/dist/doc/getting_started.txt Tue Oct 19 23:06:48 2004 @@ -88,11 +88,11 @@ just lurk in then please subscribe to one or both of our mailinglists: - `the py lib's py-dev developers list`_ + `py-dev developers list`_ -and +and our - `the py lib's py-svn general commit mailing list`_ + `py-svn general commit mailing list`_ get an account on codespeak --------------------------- @@ -110,5 +110,5 @@ .. _`the frustrations with current python package development`: why_py.html#frustrations .. _us: http://codespeak.net/mailman/listinfo/py-dev .. _codespeak: http://codespeak.net/ -.. _`the py lib's developers list`: http://codespeak.net/mailman/listinfo/py-dev -.. _`the py lib's general commit mailing list`: http://codespeak.net/mailman/listinfo/py-svn +.. _`py-dev developers list`: http://codespeak.net/mailman/listinfo/py-dev +.. _`py-svn general commit mailing list`: http://codespeak.net/mailman/listinfo/py-svn From hpk at codespeak.net Tue Oct 19 23:14:00 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Oct 2004 23:14:00 +0200 (MEST) Subject: [py-svn] r7050 - py/dist/doc Message-ID: <20041019211400.3D7925A15E@thoth.codespeak.net> Author: hpk Date: Tue Oct 19 23:13:59 2004 New Revision: 7050 Modified: py/dist/doc/execnet.txt py/dist/doc/future.txt Log: some fixes and some more cross-references (did i mention that i begin to really like ReST?) Modified: py/dist/doc/execnet.txt ============================================================================== --- py/dist/doc/execnet.txt (original) +++ py/dist/doc/execnet.txt Tue Oct 19 23:13:59 2004 @@ -87,6 +87,7 @@ you are running your code at. .. _`channel-api`: +.. _`exchange data`: The **Channel** interface for exchanging data across gateways ------------------------------------------------------------- Modified: py/dist/doc/future.txt ============================================================================== --- py/dist/doc/future.txt (original) +++ py/dist/doc/future.txt Tue Oct 19 23:13:59 2004 @@ -390,7 +390,7 @@ itself and especially its API in html or xml. Moreover, `py.test`_ wants to run distributedly in order to execute tests on different processes and platforms. It will need to -exchange data between its running program fragments. +`exchange data`_ between its running program fragments. For example, test ``results`` could be represented in lightweight xml or we could export the whole result of the testing process @@ -402,7 +402,7 @@ ------------------------------- Besides DOM implementations there are some interesting -techniques for marrying XML and python, most notable xist_ +techniques for marrying XML and python, most notably xist_ which `uses python class objects`_ to build xml trees. However, its implementation seems slightly heavy because it @@ -461,6 +461,7 @@ visions would make the link-checker a snap. It could serve as a nice script in our ``example`` tree. +.. _`exchange data`: execnet.html#exchange-data .. _`getting started`: getting_started.html .. _`restructured text`: http://docutils.sourceforge.net/docs/user/rst/quickref.html .. _`python standard library`: http://www.python.org/doc/2.3.4/lib/lib.html From hpk at codespeak.net Tue Oct 19 23:36:01 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Oct 2004 23:36:01 +0200 (MEST) Subject: [py-svn] r7051 - py/dist/doc Message-ID: <20041019213601.D08EB5A15E@thoth.codespeak.net> Author: hpk Date: Tue Oct 19 23:36:01 2004 New Revision: 7051 Modified: py/dist/doc/why_py.txt Log: - some more structure for the why/py document - added special appreciation for Fred Drake Modified: py/dist/doc/why_py.txt ============================================================================== --- py/dist/doc/why_py.txt (original) +++ py/dist/doc/why_py.txt Tue Oct 19 23:36:01 2004 @@ -5,11 +5,15 @@ .. contents:: .. sectnum:: -.. _frustrations: Why did we start the py lib? ============================ +.. _frustrations: + +Frustrations and the fun of developing new ways +----------------------------------------------- + Among the main motivations for writing the py lib is frustration at existing python modules and packages, among them: @@ -49,11 +53,18 @@ And it seems there are `related thoughts from AMK`_ about standard library development. -Please note that we very much appreicate the great work -of all the python developers. Criticism of old ways and -experimenting with new ways doesn't imply that the old -ways are bad. But we all strive for the holy development -grail at least some of the time, don't we? +Experimenting with new days, appreciating the existing ones +----------------------------------------------------------- + +First note that we very much appreciate the great work of all +the python developers and the python-dev team in particular. +Criticism of old ways and experimenting with new ways doesn't +imply that the old ways are bad. But we all strive for the +holy development grail at least some of the time, don't we? + +And because it happens far too rarely, let us especially emphasize +our appreciation for the `great work that Fred L. Drake, Jr. +is doing`_ with maintaining the core Python documentation. What is the py libs current focus? ================================== @@ -196,6 +207,7 @@ .. _`py.test tool and library`: test.html .. _`py API`: api.html .. _`related thoughts from AMK`: http://www.amk.ca/diary/archives/cat_python.html +.. _`great work that Fred L. Drake, Jr. is doing`: http://www.python.org/doc/2.3.4/lib/lib.html -- From hpk at codespeak.net Tue Oct 19 23:41:17 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Oct 2004 23:41:17 +0200 (MEST) Subject: [py-svn] r7052 - py/dist/tool Message-ID: <20041019214117.421AE5A15E@thoth.codespeak.net> Author: hpk Date: Tue Oct 19 23:41:16 2004 New Revision: 7052 Modified: py/dist/tool/rest.py Log: get rid of producing obsolete .svninfo files (we are not incorporating history/user/time information when generating html yet) Modified: py/dist/tool/rest.py ============================================================================== --- py/dist/tool/rest.py (original) +++ py/dist/tool/rest.py Tue Oct 19 23:41:16 2004 @@ -40,7 +40,7 @@ log("processing %s" % txtpath) assert txtpath.check(ext='.txt') htmlpath = txtpath.localpath.new(ext='.html') - svninfopath = txtpath.localpath.new(ext='.svninfo') + #svninfopath = txtpath.localpath.new(ext='.svninfo') style = txtpath.dirpath('style.css') if style.check(): @@ -58,9 +58,9 @@ else: htmlpath.write(doc) #log("wrote %r" % htmlpath) - if txtpath.check(svnwc=1, versioned=1): - info = txtpath.info() - svninfopath.dumpobj(info) + #if txtpath.check(svnwc=1, versioned=1): + # info = txtpath.info() + # svninfopath.dumpobj(info) if __name__=='__main__': basedir = os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0]))) From hpk at codespeak.net Tue Oct 19 23:49:49 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Oct 2004 23:49:49 +0200 (MEST) Subject: [py-svn] r7053 - py/dist/doc Message-ID: <20041019214949.D79F55A15E@thoth.codespeak.net> Author: hpk Date: Tue Oct 19 23:49:49 2004 New Revision: 7053 Modified: py/dist/doc/coding-style.txt Log: added a note about ``types`` that - in contrast to the default CamelCase for classes are lowercase. Modified: py/dist/doc/coding-style.txt ============================================================================== --- py/dist/doc/coding-style.txt (original) +++ py/dist/doc/coding-style.txt Tue Oct 19 23:49:49 2004 @@ -18,7 +18,7 @@ - generally we want to drive and interweave coding of documentation, tests and real code as much as possible. Without good documentation others may never know about - the latest and greatest feature. + your latest and greatest feature. naming ------ @@ -27,6 +27,9 @@ - classes and especially Exceptions are most often **CamelCase** +- types, i.e. very widely usable classes like the ``py.path`` + family are all lower case. + - never use plural names in directory and file names - functions/methods are lowercase and ``_`` - separated if From hpk at codespeak.net Wed Oct 20 02:57:44 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 20 Oct 2004 02:57:44 +0200 (MEST) Subject: [py-svn] r7055 - in py/dist: doc py/path py/path/local py/path/local/popen5 py/path/svn py/path/test py/test Message-ID: <20041020005744.909A25A15E@thoth.codespeak.net> Author: hpk Date: Wed Oct 20 02:57:43 2004 New Revision: 7055 Added: py/dist/py/path/local/popen5/ (props changed) py/dist/py/path/local/popen5/__init__.py py/dist/py/path/local/popen5/_subprocess.c - copied unchanged from r7054, vendor/popen5/_subprocess.c py/dist/py/path/local/popen5/subprocess.py - copied unchanged from r7054, vendor/popen5/subprocess.py py/dist/py/path/local/popen5/test_subprocess.py - copied, changed from r7054, vendor/popen5/test_subprocess.py Modified: py/dist/doc/execnet.txt py/dist/doc/future.txt py/dist/doc/misc.txt py/dist/py/path/common.py py/dist/py/path/local/local.py py/dist/py/path/local/posix.py py/dist/py/path/local/test_local.py py/dist/py/path/svn/svntestbase.py py/dist/py/path/test/common.py py/dist/py/path/test/fscommon.py py/dist/py/test/raises.py py/dist/py/test/run.py Log: - some more of the future is there - a local path now has sysfind() and sysexec() the latter based on code copied from the popen5-vendor branch. note that the _subprocess.c file is also available but not honoured in any way so far. We want to have a transparent compile for this one. - moved future documentation to the misc-text accordingly - improved the local-path test handling slightly - fixed some small issues here and there - the cmdexec/sysexec Exception needs some refactoring and the whole 'process' business may need reconsideration because we could put a "shellexec" on a local path as well after which there may be no reason for the whole 'process' namespace anymore. Modified: py/dist/doc/execnet.txt ============================================================================== --- py/dist/doc/execnet.txt (original) +++ py/dist/doc/execnet.txt Wed Oct 20 02:57:43 2004 @@ -113,8 +113,8 @@ # # API for sending and receiving anonymous values # - channel.send(*items): - sends the given items to the other side of the channel, + channel.send(item): + sends the given item to the other side of the channel, possibly blocking if the sender queue is full. Note that each value V of the items needs to have the following property (all basic types in python have it): @@ -134,7 +134,6 @@ reraised as gateway.RemoteError exceptions containing a textual representation of the remote traceback. - A simple and useful Example for Channels ........................................ Modified: py/dist/doc/future.txt ============================================================================== --- py/dist/doc/future.txt (original) +++ py/dist/doc/future.txt Wed Oct 20 02:57:43 2004 @@ -12,6 +12,7 @@ so read with some caution. This is not a reference guide (tm).* +.. _`general-path`: .. _`a more general view on path objects`: A more general view on ``py.path`` objects @@ -280,88 +281,15 @@ .. _`py.execnet`: execnet.html .. _`wrapping techniques PyPy uses`: http://codespeak.net/pypy/index.cgi?doc/wrapping.html +.. _`lightweight xml generation`: +Extension of the py.path.local.sysexec() +======================================== -Supporting interaction with system utilities/binaries -===================================================== - -Currently, the py lib offers a simple way to interact with -executables in shell style:: - - out = py.process.cmdexec('ls -v') - -which will raise an exception in case of a return-code other than 0 -and otherwise return the output of the child process. - -shortcomings of the current approach ------------------------------------- - -However, the ``cmdexec`` approach has a few shortcomings: - -- it relies on the underlying system shell -- it neccessitates shell-escaping for expressing arguments -- it does not easily allow to "fix" the binary you want to run. -- it only allows to execute executables from the local - filesystem - -path objects come to rescue ---------------------------- - -We probably want to offer a stripped down functionality of what -the new `PEP-324 subprocess module`_ offers. The main functionality -of synchronously executing [#]_ a system executable should have a -straightforward api, maybe:: - - binsvn.execute('ls', 'http://codespeak.net/svn') - -where ``binsvn`` is a path that points to the ``svn`` commandline -binary. Note that this function would not offer any shell-escaping -so you really have to pass in separated arguments. This idea -fits nicely into `a more general view on path objects`_. - -For a first go, we could simply reuse the existing `subprocess -implementation`_ but would not expose any of its API apart -from the above "execute()" method. - -.. [#] of course we could also think about replicating the `channel api`_ - of gateways and return a special ``Channel`` object where you - could call ``receive()`` and ``send()`` on. But these methods - probably don't really fit here. Hum, food for thought. - -.. _`channel api`: execnet.html#channel-api - -finding an executable ---------------------- - -Finding an executable is quite different on multiple platforms. -At least, the ``PATH`` environment variable based search on -unix platforms should be supported with something like:: - - py.path.local.sysfind('svn') - -which would return the first path found to the ``svn`` exectuable. -In principle, 'sysfind' deploys platform specific algorithms -to perform the search. On Windows, for example, it may look -at the registry. - -To make the story complete, we can allow to pass in a "checker" -that would be called for each found executable. For example, -if you have multiple binaries available you may want to select -the right version:: - - def mysvn(p): - """ check that the given svn binary has version 1.1. """ - line = p.execute('--version'').readlines()[0] - if line.find('version 1.1'): - return p - - py.path.local.sysfind('svn', checker=mysvn) - -world wide subversive interaction? ----------------------------------- - -While we are at it, we may want to run python scripts from -the net:: +The `sysexec mechanism`_ allows to directly execute +binaries on your system. Especially after we'll have this +nicely integrated into Win32 we may also want to run python +scripts both locally and from the net:: vadm = py.path.svnurl('http://codespeak.net/svn/vadm/dist/vadm/cmdline.py') stdoutput = vadm.execute('diff') @@ -379,10 +307,9 @@ interesting can of worms, suitable for another chapter in the neverending `future book`_. +.. _`sysexec mechanism`: misc.html#sysexec -.. _`lightweight xml generation`: - Lightweight, convenient xml generation ====================================== Modified: py/dist/doc/misc.txt ============================================================================== --- py/dist/doc/misc.txt (original) +++ py/dist/doc/misc.txt Wed Oct 20 02:57:43 2004 @@ -81,3 +81,85 @@ be imported in any case. .. _`the relative/absolute import PEP-328`: http://www.python.org/peps/pep-0328.html + +Supporting interaction with system utilities/binaries +===================================================== + +Currently, the py lib offers two ways to interact with +system executables. ``py.process.cmdexec()`` invokes +the shell in order to execute a string. The other +one lets you directly execute a binary (XXX not implemented). + +Both approaches will raise an exception in case of a return- +code other than 0 and otherwise return the stdout-output +of the child process. + +The shell based approach +------------------------ + +You can execute a command via your system shell +by doing something like:: + + out = py.process.cmdexec('ls -v') + +However, the ``cmdexec`` approach has a few shortcomings: + +- it relies on the underlying system shell +- it neccessitates shell-escaping for expressing arguments +- it does not easily allow to "fix" the binary you want to run. +- it only allows to execute executables from the local + filesystem + + +.. _sysexec: + +local paths have ``sysexec`` +---------------------------- + +The py lib currently offers a stripped down functionality of what +the new `PEP-324 subprocess module`_ offers. The main functionality +of synchronously executing a system executable has a +straightforward api:: + + binsvn.sysexec('ls', 'http://codespeak.net/svn') + +where ``binsvn`` is a path that points to the ``svn`` commandline +binary. Note that this function would not offer any shell-escaping +so you really have to pass in separated arguments. This idea +fits nicely into `a more general view on path objects`_. + +For a first go, we are just reusing the existing `subprocess +implementation`_ but don't expose any of its API apart +from the above "execute()" method. + +.. _`future book`: future.html +.. _`PEP-324 subprocess module`: http://www.python.org/peps/pep-0324.html +.. _`subprocess implementation`: http://www.lysator.liu.se/~astrand/popen5/ +.. _`a more general view on path objects`: future.html#general-path + +finding an executable local path +-------------------------------- + +Finding an executable is quite different on multiple platforms. +Currently, the ``PATH`` environment variable based search on +unix platforms is supported:: + + py.path.local.sysfind('svn') + +which returns the first path whose ``basename`` matches ``svn``. +In principle, `sysfind` deploys platform specific algorithms +to perform the search. On Windows, for example, it may look +at the registry (XXX). + +To make the story complete, we allow to pass in a second ``checker`` +argument that is called for each found executable. For example, if +you have multiple binaries available you may want to select the +right version:: + + def mysvn(p): + """ check that the given svn binary has version 1.1. """ + line = p.execute('--version'').readlines()[0] + if line.find('version 1.1'): + return p + binsvn = py.path.local.sysfind('svn', checker=mysvn) + Modified: py/dist/py/path/common.py ============================================================================== --- py/dist/py/path/common.py (original) +++ py/dist/py/path/common.py Wed Oct 20 02:57:43 2004 @@ -303,3 +303,4 @@ s = self.read() # XXX str(self) should show up somewhere in the code's filename return py.magic.dyncode.compile2(s) + Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Wed Oct 20 02:57:43 2004 @@ -245,7 +245,7 @@ def copy(self, target, archive=False): try: - assert not archive + assert not archive, "XXX archive-mode not supported" if self.check(file=1): if target.check(dir=1): target = target.join(self.basename) @@ -304,6 +304,8 @@ def _ensuredirs(self): parent = self.dirpath() + if parent == self: + return self if parent.check(dir=0): parent._ensuredirs() if self.check(dir=0): @@ -312,8 +314,8 @@ def ensure(self, *args, **kwargs): """ ensure that an args-joined path exists (by default as - a file). if you specify a keyword argument 'directory=True' - then the path is forced to be a directory path. + a file). if you specify a keyword argument 'dir=True' + then the path is forced to be a directory path. """ try: p = self.join(*args) @@ -414,6 +416,37 @@ pass return codeobj + def sysexec(self, *argv): + """ return stdout-put from executing a system child process, + where the self path points to the binary (XXX or script) + to be executed. Note that this process is directly + invoked and not through a system shell. + """ + from popen5.subprocess import Popen, PIPE + proc = Popen([str(self)] + list(argv), stdout=PIPE, stderr=PIPE) + ret = proc.wait() + if ret != 0: + raise py.process.cmdexec.Error(ret, ret, str(self), + proc.stdout.read(), + proc.stderr.read(),) + return proc.stdout.read() + + def sysfind(self, name, checker=None): + """ return a path object found by looking at the systems + underlying PATH specification. (XXX not working + for plain win32 yet, we want transparent c-compilation + through distutils for that. See the future. + """ + for x in py.std.os.environ['PATH'].split(':'): + x = py.path.local(x) + if x.check(dir=1) and name in x: + p = x.join(name) + if checker: + if not checker(p): + continue + return p + raise py.path.NotFound(self) + sysfind = classmethod(sysfind) #""" #special class constructors for local filesystem paths #""" Added: py/dist/py/path/local/popen5/__init__.py ============================================================================== --- (empty file) +++ py/dist/py/path/local/popen5/__init__.py Wed Oct 20 02:57:43 2004 @@ -0,0 +1 @@ +# Copied: py/dist/py/path/local/popen5/test_subprocess.py (from r7054, vendor/popen5/test_subprocess.py) ============================================================================== --- vendor/popen5/test_subprocess.py (original) +++ py/dist/py/path/local/popen5/test_subprocess.py Wed Oct 20 02:57:43 2004 @@ -1,12 +1,7 @@ -import unittest -from test import test_support import subprocess import sys -import signal import os -import tempfile -import time -import re +import py mswindows = (sys.platform == "win32") @@ -24,15 +19,16 @@ # shutdown time. That frustrates tests trying to check stderr produced # from a spawned Python process. def remove_stderr_debug_decorations(stderr): - return re.sub(r"\[\d+ refs\]\r?\n?$", "", stderr) + return py.std.re.sub(r"\[\d+ refs\]\r?\n?$", "", stderr) -class ProcessTestCase(unittest.TestCase): +class TestPopen5(py.test.compat.TestCase): + disabled = True def mkstemp(self): """wrapper for mkstemp, calling mktemp if mkstemp is not available""" if hasattr(tempfile, "mkstemp"): - return tempfile.mkstemp() + return py.std.tempfile.mkstemp() else: - fname = tempfile.mktemp() + fname = py.std.tempfile.mktemp() return os.open(fname, os.O_RDWR|os.O_CREAT), fname # @@ -97,7 +93,7 @@ def test_stdin_filedes(self): # stdin is set to open file descriptor - tf = tempfile.TemporaryFile() + tf = py.std.tempfile.TemporaryFile() d = tf.fileno() os.write(d, "pear") os.lseek(d, 0, 0) @@ -109,7 +105,7 @@ def test_stdin_fileobj(self): # stdin is set to open file object - tf = tempfile.TemporaryFile() + tf = py.std.tempfile.TemporaryFile() tf.write("pear") tf.seek(0) p = subprocess.Popen([sys.executable, "-c", @@ -127,7 +123,7 @@ def test_stdout_filedes(self): # stdout is set to open file descriptor - tf = tempfile.TemporaryFile() + tf = py.std.tempfile.TemporaryFile() d = tf.fileno() p = subprocess.Popen([sys.executable, "-c", 'import sys; sys.stdout.write("orange")'], @@ -138,7 +134,7 @@ def test_stdout_fileobj(self): # stdout is set to open file object - tf = tempfile.TemporaryFile() + tf = py.std.tempfile.TemporaryFile() p = subprocess.Popen([sys.executable, "-c", 'import sys; sys.stdout.write("orange")'], stdout=tf) @@ -156,7 +152,7 @@ def test_stderr_filedes(self): # stderr is set to open file descriptor - tf = tempfile.TemporaryFile() + tf = py.std.tempfile.TemporaryFile() d = tf.fileno() p = subprocess.Popen([sys.executable, "-c", 'import sys; sys.stderr.write("strawberry")'], @@ -168,7 +164,7 @@ def test_stderr_fileobj(self): # stderr is set to open file object - tf = tempfile.TemporaryFile() + tf = py.std.tempfile.TemporaryFile() p = subprocess.Popen([sys.executable, "-c", 'import sys; sys.stderr.write("strawberry")'], stderr=tf) @@ -192,7 +188,7 @@ def test_stdout_stderr_file(self): # capture stdout and stderr to the same open file - tf = tempfile.TemporaryFile() + tf = py.std.tempfile.TemporaryFile() p = subprocess.Popen([sys.executable, "-c", 'import sys;' \ 'sys.stdout.write("apple");' \ @@ -371,7 +367,7 @@ "-c", "import time; time.sleep(1)"]) count = 0 while p.poll() is None: - time.sleep(0.1) + py.std.time.sleep(0.1) count += 1 # We expect that the poll loop probably went around about 10 times, # but, based on system scheduling we can't control, it's possible @@ -410,7 +406,7 @@ p = subprocess.Popen([sys.executable, "-c", "import os; os.abort()"]) p.wait() - self.assertEqual(-p.returncode, signal.SIGABRT) + self.assertEqual(-p.returncode, py.std.signal.SIGABRT) def test_preexec(self): # preexec function @@ -548,8 +544,8 @@ self.assertEqual(rc, 47) -def test_main(): - test_support.run_unittest(ProcessTestCase) - -if __name__ == "__main__": - test_main() +#def test_main(): +# test_support.run_unittest(ProcessTestCase) +# +#if __name__ == "__main__": +# test_main() Modified: py/dist/py/path/local/posix.py ============================================================================== --- py/dist/py/path/local/posix.py (original) +++ py/dist/py/path/local/posix.py Wed Oct 20 02:57:43 2004 @@ -73,7 +73,7 @@ self._except(sys.exc_info()) def mklinkto(self, oldname): - """ hard link to an old name. """ + """ posix style hard link to another name. """ try: os.link(str(oldname), str(self)) except: Modified: py/dist/py/path/local/test_local.py ============================================================================== --- py/dist/py/path/local/test_local.py (original) +++ py/dist/py/path/local/test_local.py Wed Oct 20 02:57:43 2004 @@ -1,25 +1,31 @@ -import sys, os -from py.test import raises, config +import py from py.path import local, checker from py.__impl__.path.test.fscommon import CommonFSTests, setuptestfs -class TestLocalPath(CommonFSTests): +class LocalSetup: def setup_class(cls): - cls.root = config.tmpdir / 'TestLocalPath' + cls.root = py.test.config.tmpdir / 'TestLocalPath' cls.root.ensure(dir=1) setuptestfs(cls.root) + + def setup_method(self, method): + self.tmpdir = self.root.ensure('tmpdir', dir=1) + + def teardown_method(self, method): + self.tmpdir.remove(rec=1) +class TestLocalPath(LocalSetup, CommonFSTests): def test_initialize_curdir(self): - assert str(local()) == os.getcwd() + assert str(local()) == py.std.os.getcwd() def test_initialize_reldir(self): - curdir = os.curdir + curdir = py.std.os.curdir try: - os.chdir(str(self.root)) + py.std.os.chdir(str(self.root)) p = local('samplefile') assert p.check() finally: - os.chdir(curdir) + py.std.os.chdir(curdir) def test_eq_with_strings(self): path1 = self.root.join('sampledir') @@ -34,7 +40,7 @@ import tempfile try: fd, name = tempfile.mkstemp() - f = os.fdopen(fd) + f = py.std.os.fdopen(fd) except AttributeError: name = tempfile.mktemp() f = open(name, 'w+') @@ -47,14 +53,14 @@ assert d == dnew finally: f.close() - os.remove(name) + py.std.os.remove(name) def test_setmtime(self): import tempfile import time try: fd, name = tempfile.mkstemp() - os.close(fd) + py.std.os.close(fd) except AttributeError: name = tempfile.mktemp() open(name, 'w').close() @@ -67,7 +73,7 @@ path.setmtime() assert path.mtime() != mtime finally: - os.remove(name) + py.std.os.remove(name) def test_normpath(self): new1 = self.root.join("/otherdir") @@ -90,84 +96,104 @@ finally: d.remove(rec=1) - def test_ensure_filepath_withdir(self): - tmpdir = local.mkdtemp() - try: - newfile = tmpdir.join('test1','test2') - newfile.ensure() - assert newfile.check(file=1) - finally: - tmpdir.remove(rec=1) - - def test_ensure_filepath_withoutdir(self): - tmpdir = local.mkdtemp() - try: - newfile = tmpdir.join('test1') - t = newfile.ensure() - assert t == newfile - assert newfile.check(file=1) - finally: - tmpdir.remove(rec=1) - - def test_ensure_dirpath(self): - tmpdir = local.mkdtemp() - try: - newfile = tmpdir.join('test1','test2') - t = newfile.ensure(dir=1) - assert t == newfile - assert newfile.check(dir=1) - finally: - tmpdir.remove(rec=1) - def test_mkdir(self): - tmpdir = local.mkdtemp() - try: - new = tmpdir.join('test1') - new.mkdir() - assert new.check(dir=1) - - new = tmpdir.mkdir('test2') - assert new.check(dir=1) - assert tmpdir.join('test2') == new - finally: - tmpdir.remove(rec=1) + tmpdir = self.tmpdir + new = tmpdir.join('test1') + new.mkdir() + assert new.check(dir=1) + + new = tmpdir.mkdir('test2') + assert new.check(dir=1) + assert tmpdir.join('test2') == new def test_chdir(self): - import os + tmpdir = self.tmpdir old = local() - tmpdir = local.mkdtemp() try: res = tmpdir.chdir() assert str(res) == str(old) - assert os.getcwd() == str(tmpdir) + assert py.std.os.getcwd() == str(tmpdir) finally: old.chdir() - tmpdir.remove(rec=1) + def test_ensure_filepath_withdir(self): + tmpdir = self.tmpdir + newfile = tmpdir.join('test1','test2') + newfile.ensure() + assert newfile.check(file=1) -class TestMisc: - root = local(TestLocalPath.root) + def test_ensure_filepath_withoutdir(self): + tmpdir = self.tmpdir + newfile = tmpdir.join('test1') + t = newfile.ensure() + assert t == newfile + assert newfile.check(file=1) + + def test_ensure_dirpath(self): + tmpdir = self.tmpdir + newfile = tmpdir.join('test1','test2') + t = newfile.ensure(dir=1) + assert t == newfile + assert newfile.check(dir=1) + +class TestExecution(LocalSetup): + disabled = py.std.sys.platform == 'win32' + + def test_sysfind(self): + x = py.path.local.sysfind('ls') + assert x.check(file=1) + py.test.raises(py.path.NotFound, """ + py.path.local.sysfind('jaksdkasldqwe') + """) + + def test_sysfind_multiple(self): + dir = py.test.config.tmpdir.ensure('sysfind', dir=1) + env = py.std.os.environ + oldpath = env['PATH'] + try: + env['PATH'] += ":%s:%s" % (dir.ensure('a'), + dir.join('b')) + dir.ensure('b', 'a') + checker = lambda x: x.dirpath().basename == 'b' + x = py.path.local.sysfind('a', checker=checker) + assert x.basename == 'a' + assert x.dirpath().basename == 'b' + checker = lambda x: None + py.test.raises(py.path.NotFound, """ + py.path.local.sysfind('a', checker=checker) + """) + finally: + env['PATH'] = oldpath + #dir.remove() + + def test_sysexec(self): + x = py.path.local.sysfind('ls') + out = x.sysexec() + for x in py.path.local(): + assert out.find(x.basename) != -1 + + def test_sysexec_failing(self): + x = py.path.local.sysfind('ls') + py.test.raises(py.process.cmdexec.Error, """ + x.sysexec('aksjdkasjd') + """) def test_make_numbered_dir(self): - root = local.mkdtemp() - try: - for i in range(10): - numdir = local.make_numbered_dir(root, 'base.', keep=2) - assert numdir.check() - assert numdir.get('basename') == 'base.%d' %i - if i>=1: - assert numdir.new(ext=str(i-1)).check() - if i>=2: - assert numdir.new(ext=str(i-2)).check() - if i>=3: - assert not numdir.new(ext=str(i-3)).check() - finally: - #print "root was", root - root.remove(rec=1) + root = self.tmpdir + for i in range(10): + numdir = local.make_numbered_dir(root, 'base.', keep=2) + assert numdir.check() + assert numdir.get('basename') == 'base.%d' %i + if i>=1: + assert numdir.new(ext=str(i-1)).check() + if i>=2: + assert numdir.new(ext=str(i-2)).check() + if i>=3: + assert not numdir.new(ext=str(i-3)).check() def test_error_preservation(self): - raises (OSError, self.root.join('qwoeqiwe').mtime) - raises (IOError, self.root.join('qwoeqiwe').read) + py.test.raises (OSError, self.root.join('qwoeqiwe').mtime) + py.test.raises (IOError, self.root.join('qwoeqiwe').read) #def test_parentdirmatch(self): # local.parentdirmatch('std', startmodule=__name__) Modified: py/dist/py/path/svn/svntestbase.py ============================================================================== --- py/dist/py/path/svn/svntestbase.py (original) +++ py/dist/py/path/svn/svntestbase.py Wed Oct 20 02:57:43 2004 @@ -7,9 +7,10 @@ def setup_method(self, meth): bn = meth.func_name - if bn.startswith('test_remove') or bn.startswith('test_move'): - raise py.test.Skipped(msg= - "tests for (re)move require better svn state management") + for x in 'test_remove', 'test_move': + if bn.startswith(x): + raise py.test.Skipped(msg= + "tests for modifying svn needs better test state management") def test_propget(self): url = self.root.join("samplefile") Modified: py/dist/py/path/test/common.py ============================================================================== --- py/dist/py/path/test/common.py (original) +++ py/dist/py/path/test/common.py Wed Oct 20 02:57:43 2004 @@ -199,4 +199,3 @@ for name in dir(local.Checkers): if name[0] != '_': assert name in doc - Modified: py/dist/py/path/test/fscommon.py ============================================================================== --- py/dist/py/path/test/fscommon.py (original) +++ py/dist/py/path/test/fscommon.py Wed Oct 20 02:57:43 2004 @@ -197,3 +197,4 @@ # because this would mean confusion with respect to # py.path.extpy assert not hasattr(self.root, 'resolve') + Modified: py/dist/py/test/raises.py ============================================================================== --- py/dist/py/test/raises.py (original) +++ py/dist/py/test/raises.py Wed Oct 20 02:57:43 2004 @@ -1,5 +1,7 @@ import sys -from py import test, magic +import py + +from py.__impl__ import test def raises(ExpectedException, *args, **kwargs): """ raise AssertionError, if target code does not raise the expected @@ -14,7 +16,7 @@ loc.update(kwargs) #print "raises frame scope: %r" % frame.f_locals try: - eval(magic.dyncode.compile2(expr), frame.f_globals, loc) + eval(py.magic.dyncode.compile2(expr), frame.f_globals, loc) # XXX didn'T mean f_globals == f_locals something special? # this is destroyed here ... except ExpectedException: Modified: py/dist/py/test/run.py ============================================================================== --- py/dist/py/test/run.py (original) +++ py/dist/py/test/run.py Wed Oct 20 02:57:43 2004 @@ -113,6 +113,7 @@ self._instance = method.im_class() method = method.__get__(self._instance, method.im_class) if hasattr(self._instance, 'setup_method'): + print "execting setup", self._instance.setup_method self._instance.setup_method(method) return (method, getattr(self._instance, 'teardown_method', None)) From hpk at codespeak.net Wed Oct 20 03:38:48 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 20 Oct 2004 03:38:48 +0200 (MEST) Subject: [py-svn] r7056 - py/dist/doc Message-ID: <20041020013848.5E8C25AEE0@thoth.codespeak.net> Author: hpk Date: Wed Oct 20 03:38:46 2004 New Revision: 7056 Modified: py/dist/doc/future.txt py/dist/doc/misc.txt Log: - future: added a new section about transparent compilation - fixes to misc Modified: py/dist/doc/future.txt ============================================================================== --- py/dist/doc/future.txt (original) +++ py/dist/doc/future.txt Wed Oct 20 03:38:46 2004 @@ -372,6 +372,56 @@ more experimentations and a comparison with xist_ i guess. +.. _`compile-on-the-fly`: + +Crossing the barriers of compilation +==================================== + +Python developers are very used to the fact +that compilation of python programs happens +automatically. This is unlike Java and C where +you need an explicit compilation step. +With Python, the language does it for you. +With the py lib we extend the **compile-on-the-fly** +notion for other program source than Python. +Well, we start with C-files files but want to make sure +that the mechanism is extensible towards other languages. + +And of course, internally we just reuse `CPython's distutils`_ +which already provides the right abstractions to run the +underlying system compiler. + +development convenience rules +----------------------------- + +As described in the `getting started`_ chapter the main +current method to utilize the py lib is to work from a +subversion checkout. The **compile-on-the-fly** feature +allows us to keep up with this work style. You don't have to +issue any manual ``python setup.py install`` incantations +and you don't have to remember dependency issues. You +can easily add a small C-coded module and make it +seemlessly available through the path object:: + + mod = somepath.getcompiled() + +And you can grab objects from the freshly +compiled C-module. + +we need a persistent storage for the py lib +------------------------------------------- + +A somewhat open question is where to store the underlying +generated object and library files from `CPython's +distutils`_. We probably want to have the possibility of a +*persistent location* in order to avoid runtime-penalties. A +*persistent location* for the py lib would be a good idea +anyway. We could cache some of the expensive test setups, like +the multi-revision subversion repository that is created for +each run of the tests. + +.. _`CPython's distutils`: http://www.python.org/dev/doc/devel/lib/module-distutils.html + Extending rest-tests to check links =================================== Modified: py/dist/doc/misc.txt ============================================================================== --- py/dist/doc/misc.txt (original) +++ py/dist/doc/misc.txt Wed Oct 20 03:38:46 2004 @@ -88,7 +88,7 @@ Currently, the py lib offers two ways to interact with system executables. ``py.process.cmdexec()`` invokes the shell in order to execute a string. The other -one lets you directly execute a binary (XXX not implemented). +one, ``localpath.sysexec()`` lets you directly execute a binary. Both approaches will raise an exception in case of a return- code other than 0 and otherwise return the stdout-output @@ -110,7 +110,6 @@ - it only allows to execute executables from the local filesystem - .. _sysexec: local paths have ``sysexec`` @@ -118,8 +117,7 @@ The py lib currently offers a stripped down functionality of what the new `PEP-324 subprocess module`_ offers. The main functionality -of synchronously executing a system executable has a -straightforward api:: +of synchronously executing a system executable has a straightforward API:: binsvn.sysexec('ls', 'http://codespeak.net/svn') @@ -130,8 +128,14 @@ For a first go, we are just reusing the existing `subprocess implementation`_ but don't expose any of its API apart -from the above "execute()" method. +from the above ``sysexec()`` method. + +Note, however, that we currently don't support the ``sysexec`` +interface on win32. We want to integrate our `compile-on-the-fly`_ +and use the ``_subprocess.c`` file from the `PEP-324 subprocess module`_. + +.. _`compile-on-the-fly`: future.html#compile-on-the-fly .. _`future book`: future.html .. _`PEP-324 subprocess module`: http://www.python.org/peps/pep-0324.html .. _`subprocess implementation`: http://www.lysator.liu.se/~astrand/popen5/ From hpk at codespeak.net Wed Oct 20 15:34:06 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 20 Oct 2004 15:34:06 +0200 (MEST) Subject: [py-svn] r7069 - py/dist/py/path/local Message-ID: <20041020133406.B05545AC84@thoth.codespeak.net> Author: hpk Date: Wed Oct 20 15:34:06 2004 New Revision: 7069 Modified: py/dist/py/path/local/local.py Log: - improved sysfind by not triggering any listdir() it's faster by a factor of 10 or 50 or so now :-) Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Wed Oct 20 15:34:06 2004 @@ -438,9 +438,8 @@ through distutils for that. See the future. """ for x in py.std.os.environ['PATH'].split(':'): - x = py.path.local(x) - if x.check(dir=1) and name in x: - p = x.join(name) + p = py.path.local(x).join(name) + if p.check(file=1): if checker: if not checker(p): continue From hpk at codespeak.net Wed Oct 20 15:46:30 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 20 Oct 2004 15:46:30 +0200 (MEST) Subject: [py-svn] r7070 - in py/dist/py/path: . local Message-ID: <20041020134630.EE5535AC84@thoth.codespeak.net> Author: hpk Date: Wed Oct 20 15:46:30 2004 New Revision: 7070 Modified: py/dist/py/path/common.py py/dist/py/path/local/local.py Log: - optimization for the general __contains__ method of the PathBase, similar to the previous local one: avoid possibly expensive listdir() Modified: py/dist/py/path/common.py ============================================================================== --- py/dist/py/path/common.py (original) +++ py/dist/py/path/common.py Wed Oct 20 15:46:30 2004 @@ -98,14 +98,14 @@ yield i def __contains__(self, other): - if type(other) is str: - for x in self: - if x.basename == other: - return True + if isinstance(other, str): + p = self.join(str(other)) + return p.check() else: - if other in self.listdir(): - return True - return False + if other.dirpath() != self: + return False + p = self.join(other.basename) + return p.check() def basename(self): return self.get('basename') Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Wed Oct 20 15:46:30 2004 @@ -433,9 +433,9 @@ def sysfind(self, name, checker=None): """ return a path object found by looking at the systems - underlying PATH specification. (XXX not working - for plain win32 yet, we want transparent c-compilation - through distutils for that. See the future. + underlying PATH specification. If the checker is not None + it will be invoked to filter matching paths. + Note: This is not working on plain win32 systems yet. """ for x in py.std.os.environ['PATH'].split(':'): p = py.path.local(x).join(name) From hpk at codespeak.net Wed Oct 20 15:49:11 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 20 Oct 2004 15:49:11 +0200 (MEST) Subject: [py-svn] r7071 - py/dist/py/path Message-ID: <20041020134911.17B855AC84@thoth.codespeak.net> Author: hpk Date: Wed Oct 20 15:49:10 2004 New Revision: 7071 Modified: py/dist/py/path/common.py Log: doh, some cleanup of the previous commit, should not commit so fast. Modified: py/dist/py/path/common.py ============================================================================== --- py/dist/py/path/common.py (original) +++ py/dist/py/path/common.py Wed Oct 20 15:49:10 2004 @@ -99,8 +99,7 @@ def __contains__(self, other): if isinstance(other, str): - p = self.join(str(other)) - return p.check() + return self.join(other).check() else: if other.dirpath() != self: return False From hpk at codespeak.net Wed Oct 20 16:39:47 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 20 Oct 2004 16:39:47 +0200 (MEST) Subject: [py-svn] r7072 - py/dist/py/path/local Message-ID: <20041020143947.C753F5AC84@thoth.codespeak.net> Author: hpk Date: Wed Oct 20 16:39:47 2004 New Revision: 7072 Modified: py/dist/py/path/local/local.py Log: - cleanup of the invocation of the exception-conversion method (you get a py.path.NotFound/Denied/NoDirectory/Invalid etc.pp exception instead of funny OS or IOErrors.) Further cleanup is probably needed especially with respect to the other fs-implementations. It will be slightly tricky though for the svn-command-based paths because we will have to parse the cmdline-output. Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Wed Oct 20 16:39:47 2004 @@ -73,9 +73,6 @@ def __hash__(self): return hash(self.strpath) - def _except(self, excinfo): - return error.error_enhance(excinfo) - def new(self, **kw): """ create a modified version of this path. the following keyword arguments modify various path parts: @@ -156,8 +153,8 @@ def join(self, *args, **kwargs): """ return a new path by appending all 'args' as path - components. if abs=1 is used start from root if any if the args - is an absolute path. + components. if abs=1 is used restart from root if any + of the args is an absolute path. """ if not args: return self @@ -183,10 +180,7 @@ def open(self, mode='r'): """ return an opened file with the given mode. """ - try: - return open(self.strpath, mode) - except: - self._except(sys.exc_info()) + return callex(open, self.strpath, mode) def listdir(self, fil=None, sort=None): """ list directory contents, possibly filter by the given fil func @@ -195,13 +189,10 @@ if isinstance(fil, str): fil = common.fnmatch(fil) res = [] - try: - for name in os.listdir(self.strpath): - childurl = self.join(name) - if fil is None or fil(childurl): - res.append(childurl) - except: - self._except(sys.exc_info()) + for name in callex(os.listdir, self.strpath): + childurl = self.join(name) + if fil is None or fil(childurl): + res.append(childurl) if callable(sort): res.sort(sort) elif sort: @@ -227,72 +218,51 @@ def remove(self, rec=1): """ remove a file or directory (or a directory tree if rec=1). """ - try: - if self.check(dir=1, link=0): - if rec: - import shutil - #def call(*args): - # print args - - shutil.rmtree(self.strpath) - #, onerror=call) - else: - os.rmdir(self.strpath) + if self.check(dir=1, link=0): + if rec: + callex(py.std.shutil.rmtree, self.strpath) else: - os.remove(self.strpath) - except: - self._except(sys.exc_info()) + callex(os.rmdir, self.strpath) + else: + callex(os.remove, self.strpath) def copy(self, target, archive=False): - try: - assert not archive, "XXX archive-mode not supported" - if self.check(file=1): - if target.check(dir=1): - target = target.join(self.basename) - assert self!=target - copychunked(self, target) - else: - target.ensure(dir=1) - def rec(p): - return p.check(link=0) - for x in self.visit(rec=rec): - relpath = x.relto(self) - newx = target.join(relpath) - if x.check(link=1): - newx.mksymlinkto(x.readlink()) - elif x.check(file=1): - copychunked(x, newx) - elif x.check(dir=1): - newx.ensure(dir=1) - except: - self._except(sys.exc_info()) + assert not archive, "XXX archive-mode not supported" + if self.check(file=1): + if target.check(dir=1): + target = target.join(self.basename) + assert self!=target + copychunked(self, target) + else: + target.ensure(dir=1) + def rec(p): + return p.check(link=0) + for x in self.visit(rec=rec): + relpath = x.relto(self) + newx = target.join(relpath) + if x.check(link=1): + newx.mksymlinkto(x.readlink()) + elif x.check(file=1): + copychunked(x, newx) + elif x.check(dir=1): + newx.ensure(dir=1) def rename(self, target): - try: - os.rename(str(self), str(target)) - except: - self._except(sys.exc_info()) + return callex(os.rename, str(self), str(target)) def dumpobj(self, obj): """ pickle object into path location""" + f = self.open('wb') try: - f = self.open('wb') - try: - from cPickle import dump - dump(obj, f) - finally: - f.close() - except: - self._except(sys.exc_info()) + callex(py.std.cPickle.dump, obj, f) + finally: + f.close() def mkdir(self, *args): """ create & return the directory joined with args. """ p = self.join(*args) - try: - os.mkdir(str(p)) - except: - self._except(sys.exc_info()) - return p + callex(os.mkdir, str(p)) + return p def write(self, content): """ write string content into path. """ @@ -317,30 +287,21 @@ a file). if you specify a keyword argument 'dir=True' then the path is forced to be a directory path. """ - try: - p = self.join(*args) - if kwargs.get('dir', 0): - return p._ensuredirs() - parent = p.dirpath() - parent._ensuredirs() + p = self.join(*args) + if kwargs.get('dir', 0): + return p._ensuredirs() + else: + p.dirpath()._ensuredirs() p.write("") return p - except: - self._except(sys.exc_info()) def stat(self): """ Return an os.stat() tuple. """ - try: - return os.stat(self.strpath) - except: - self._except(sys.exc_info()) + return callex(os.stat, self.strpath) def lstat(self): """ Return an os.lstat() tuple. """ - try: - return os.lstat(self.strpath) - except: - self._except(sys.exc_info()) + return callex(os.lstat, self.strpath) # xlocal implementation def setmtime(self, mtime=None): @@ -350,19 +311,19 @@ Note that the resolution for 'mtime' is platform dependent. """ if mtime is None: - return os.utime(self.strpath, mtime) + return callex(os.utime, self.strpath, mtime) try: return os.utime(self.strpath, (-1, mtime)) except OSError, e: if e.errno != 22: - self._except(sys.exc_info()) + error.error_enhance(sys.exc_info()) raise - return os.utime(self.strpath, (self.atime(), mtime)) + return callex(os.utime, self.strpath, (self.atime(), mtime)) def chdir(self): """ change directory to self and return old current directory """ old = self.__class__() - os.chdir(self.strpath) + callex(os.chdir, self.strpath) return old def realpath(self): @@ -542,3 +503,12 @@ fdest.close() finally: fsrc.close() + +def callex(func, *args, **kwargs): + try: + return func(*args, **kwargs) + except (KeyboardInterrupt, SystemExit): + raise + except: + error.error_enhance(sys.exc_info() ) + From hpk at codespeak.net Wed Oct 20 16:48:37 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 20 Oct 2004 16:48:37 +0200 (MEST) Subject: [py-svn] r7073 - py/dist/py/bin Message-ID: <20041020144837.05F7C5AC84@thoth.codespeak.net> Author: hpk Date: Wed Oct 20 16:48:37 2004 New Revision: 7073 Removed: py/dist/py/bin/rename_import.py Log: removed bogus file Deleted: /py/dist/py/bin/rename_import.py ============================================================================== --- /py/dist/py/bin/rename_import.py Wed Oct 20 16:48:37 2004 +++ (empty file) @@ -1,23 +0,0 @@ -#!/usr/bin/python - -import py, sys -import bike -import rlcompleter2 -rlcompleter2.setup() - -if __name__ == '__main__': - source = py.path.local(sys.argv[1]) - assert source.check(dir=1) - - #dest = std.path.local(sys.argv[2]) - - #dest.ensure(dir=1) - - import bike - ctx = bike.init().brmctx - - for x in source.visit(py.path.checker(file=1, fnmatch='*.py'), - py.path.checker(dotfile=0)): - ctx.paths.append(str(x)) - - From hpk at codespeak.net Thu Oct 21 23:29:50 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 21 Oct 2004 23:29:50 +0200 (MEST) Subject: [py-svn] r7085 - py/dist/doc Message-ID: <20041021212950.957555AB26@thoth.codespeak.net> Author: hpk Date: Thu Oct 21 23:29:46 2004 New Revision: 7085 Modified: py/dist/doc/test.txt Log: some cleanup of the description of basic features Modified: py/dist/doc/test.txt ============================================================================== --- py/dist/doc/test.txt (original) +++ py/dist/doc/test.txt Thu Oct 21 23:29:46 2004 @@ -31,8 +31,8 @@ on your system. -Features of ``py.test`` -======================= +Basic Features of ``py.test`` +============================= assert with the ``assert`` statement ------------------------------------ @@ -73,11 +73,20 @@ automatic collection of tests on all levels ------------------------------------------- -The automated test (see `collection process`_ for -implemenetation details) takes care that all -files with a leading ``test_`` and then every function -with a leading ``test_`` or ``Test`` in the case of classes -are collected. +The automated test collection process collects files +from all subdirectories with a leading ``test_`` in their +filename and then every function with a leading ``test_`` +or ``Test`` in the case of classes. The collecting process +can be customized at each level. (see `collection process`_ +for some implemenetation details). + +testing starts immediately +-------------------------- + +Testing starts as soon as the first ``test item``, +usually a test function or method is collected. +There is no need to first collect all tests +before a test starts. no interference with cmdline utilities -------------------------------------- @@ -97,10 +106,11 @@ order of execution is guaranteed -------------------------------- -Great care is taken that by default all tests run in the order -in which they appear in the files. If you run tests multiple times -you will find that they execute in exactly the same order -within each file. +Great care is taken that all tests run in the order in which +they appear in the files. If you run tests multiple times you +will find that they execute in exactly the same order within +each file. This allows multi-stage tests where you can +rely on your test to iteratively build up a test structure. useful tracebacks, recursion detection -------------------------------------- From hpk at codespeak.net Fri Oct 22 00:43:20 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 22 Oct 2004 00:43:20 +0200 (MEST) Subject: [py-svn] r7086 - py/dist/doc Message-ID: <20041021224320.BE2C85AC1D@thoth.codespeak.net> Author: hpk Date: Fri Oct 22 00:43:19 2004 New Revision: 7086 Modified: py/dist/doc/why_py.txt Log: - explained a possible upcoming release policy, at least my idea of it. It relies and defines everything by virtue of automated tests. - inserted a reference to the jorendorff path.py implementation which people keep refering me to. Modified: py/dist/doc/why_py.txt ============================================================================== --- py/dist/doc/why_py.txt (original) +++ py/dist/doc/why_py.txt Fri Oct 22 00:43:19 2004 @@ -69,6 +69,9 @@ What is the py libs current focus? ================================== +testing testing testing +----------------------- + Currently, the main focus of the py lib is to get a decent `test environment`_, indeed to produce the best one out there. Writing, distributing and deploying tests should become @@ -79,39 +82,100 @@ cycles. Automated tests are a means of communication as well. -Most importantly, we try to allow test scripts with minimal -boilerplate code or no boilerplate at all. With the py lib -you can simply use ``assert`` statements in order to - well - -assert something about objects in your code. No -``assertEqual(s)`` and all the other kinds of funny names -which only cover part of what you want to assert about an -expression, anyway. +We try to allow test scripts with minimal boilerplate code or +no boilerplate at all. With the py lib you can simply use +``assert`` statements in order to - well - assert something +about objects in your code. No ``assertEqual(s)`` and all the +other kinds of funny names which only cover part of what you +want to assert about an expression, anyway. + +allowing maximum refactoring in the future ... +---------------------------------------------- + +explicit name export control +............................ In order, to allow a fast development pace across versions of the py lib there is **explicit name export control**. You will only see names which make sense to use from the outside -and which the py lib developers want to guarantee across major -versions. - -No *tested feature* of the exported `py API`_ will -vanish across major releases until it is marked deprecated. -But if deprecated an API could go with every following major -release. Indeed, much thought is given to reduce the exported -*name complexity*. This is an area where the python "batteries" -lack a lot. They expose so many names that it becomes very -hard to change APIs across releases. This kills the fun of -refactoring and improving things. - -Another focus are well tested *Path* implementations that -allow to seemlessly work with different backends, currently -a local filesystem and subversion working copies and subversion -remote URLs. Moreover, it provides an experimental ``extpy`` -path to address a Python object on a possibly remote filesystem. +and which the py lib developers want to guarantee across multiple +revisions. However, you don't need to treat the ``py`` lib as +anything special. You can simply use the usual ``import`` +statement and will not notice much of a difference - except that +the namespaces you'll see from the ``py`` lib will be extreamly +clean and have no clutter. + +Indeed, much thought is given to reduce the exported *name +complexity*. This is an area where the python "batteries" and +many python packages unfortunately lack a lot. They expose so +many names that it becomes very hard to change APIs across +releases. People have been adding whole interface systems +because of that but arguably this adds another layer of names +instead of reducing the original problem. + +Anyway, exporting to many names kills the fun of refactoring +and improving things. We want to avoid that as much as +possible. + +Upcoming Release policy & API guarantees +........................................ + +We'll talk about major, minor and micro numbers as the three +numbers in "1.2.3" respectively. These are the (draft!) +release policies: + +- Micro-releases are bug fix releases and may not introduce + new names to the public API. They may add tests and thus + further define the behaviour of the py lib. They may + completly change the implementation but the public API + tests will continue to run. + +- No **tested feature** of the exported `py API`_ is to vanish + across minor releases until it is marked deprecated. + + For example, pure API tests of a future version 1.0 are to + continue to fully run on 1.1 and so on. If an API gets + deprecated with a minor release it goes with the next minor + release. Thus if you don't use deprecated APIs you should + be able to use the next two minor releases. However, if + you relied on some untested implementation behaviour, + you may still get screwed. Solution: add API tests to the + py lib :-) It's really the tests that make the difference. + +- Pure API tests are not allowed to access any implementation + level details. For example, accessing names starting with + a single leading '_' is generally seen as an implementation + level detail. + +- we intend to involve expert developers who give new APIs an + independent review before they go into a minor release + and even more so before they go into a major release. + +- major releases *should*, but are not required to, pass + all API tests of the previous latest major released + version. A full list of changes is to be included in + the release notes, including the tests that got abandoned. + +the need to find the right *paths* ... +-------------------------------------- + +Another focus are well tested so called *path* implementations +that allow you to seemlessly work with different backends, +currently a local filesystem, subversion working copies and +subversion remote URLs. Moreover, there is an experimental +``extpy`` path to address a Python object on the (possibly +remote) filesystem. The `jorendorff path.py`_ implementation +goes somewhat in the same direction but doesn't try to +systematically access local and remote file systems as well as +other hierarchic namespaces. The latter is the focus of the +``py.path`` API. If you are ready to grasp more then you may try reading about future_ coding goals of the py lib, which reflect the current developers thoughts and discussions. +.. _`jorendorff path.py`: http://www.jorendorff.com/articles/python/path/ + How does py development work? ============================= @@ -121,10 +185,12 @@ We are discussing things on our `py-dev mailing list`_ and collaborate via the codespeak subversion repository. -We follow a `coding style`_ which builds on `PEP 8`_, the basic -python coding style document. It's easy to get commit rights -especially if you are an experienced python developer -and share some of the frustrations described above. +We follow a `coding style`_ which strongly builds on `PEP 8`_, +the basic python coding style document. + +It's easy to get commit rights especially if you are an +experienced python developer and share some of the +frustrations described above. Moreover, the world will be granted svn commit rights to all py test files so that you can easily add bug-tests or tests @@ -135,12 +201,11 @@ follow the `py-dev mailing list`_ and grasp some of the things that are going on in the `future`_ book. - Licensing, please ----------------- -Oh right, and you should also agree to release your code under -an ``MIT license`` and consequently any other OSI-approved +Oh right, and you should also agree to release your contributions +under an ``MIT license`` and consequently any other OSI-approved license. This is FOSS [#]_ and we want to have the py lib interopable with whatever license style you prefer. Copyright generally stays with the contributors. We are following the @@ -156,8 +221,10 @@ --------------------------------- Some of the motivation for writing the py lib stems from needs -during PyPy_ development, most notably testing and -file system access issues. +during PyPy_ development, most importantly testing and +file system access issues. PyPy puts a lot of pressure +on a testing environment and thus is a great **reality test** +kind of thing. More importantly, the development perspective taken from the PyPy developers has some influence. For example, the @@ -169,10 +236,10 @@ Who is "we"? Some history ... ============================= -Some of the initial code was written from *Jens-Uwe Mager* -and *Holger Krekel*, after which Holger continued on a previous -incarnation of the py.test tool (known first as 'utest', then as -'std.utest', now, finally and at last 'py.test'). +Some initial code was written from *Jens-Uwe Mager* and *Holger +Krekel*, after which Holger continued on a previous +incarnation of the py.test tool (known first as 'utest', then +as 'std.utest', now, finally and at last 'py.test'). Helpful discussions took place with *Martijn Faassen*, *Stephan Schwarzer* and then *Armin Rigo* who contributed important parts. From hpk at codespeak.net Fri Oct 22 01:11:39 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 22 Oct 2004 01:11:39 +0200 (MEST) Subject: [py-svn] r7087 - in py/dist: doc example/test py/path py/path/extpy py/path/test Message-ID: <20041021231139.3DD375AC1D@thoth.codespeak.net> Author: hpk Date: Fri Oct 22 01:11:38 2004 New Revision: 7087 Modified: py/dist/doc/misc.txt py/dist/example/test/failure_demo.py py/dist/py/path/common.py py/dist/py/path/extpy/extpy.py py/dist/py/path/extpy/inc_pseudofs.py py/dist/py/path/extpy/test_extpy.py py/dist/py/path/test/common.py py/dist/py/path/test/fscommon.py Log: - added a read() method even for ``extpy`` paths. This returns str(extpy.resolve()) where extpy.resolve() find the live python object of an extpy-path. - removed the YAGNI "num" parameter to the general read() method. If anything, we need a method to read in chunks but not just the first n-bytes. maybe we want a new read_chunked(size) to be a generator returning subsequent chunks ..., but this makes it hard to do finalization on other impls than CPython ... - fixed the failing failure_demo.py ... uhm :-) - small other fixes Modified: py/dist/doc/misc.txt ============================================================================== --- py/dist/doc/misc.txt (original) +++ py/dist/doc/misc.txt Fri Oct 22 01:11:38 2004 @@ -82,8 +82,8 @@ .. _`the relative/absolute import PEP-328`: http://www.python.org/peps/pep-0328.html -Supporting interaction with system utilities/binaries -===================================================== +Support for interaction with system utilities/binaries +====================================================== Currently, the py lib offers two ways to interact with system executables. ``py.process.cmdexec()`` invokes Modified: py/dist/example/test/failure_demo.py ============================================================================== --- py/dist/example/test/failure_demo.py (original) +++ py/dist/example/test/failure_demo.py Fri Oct 22 01:11:38 2004 @@ -1,4 +1,4 @@ -from py.test import raises, main +from py.test import raises def otherfunc(a,b): assert a==b @@ -87,5 +87,3 @@ def globf(x): return x+1 - -main() Modified: py/dist/py/path/common.py ============================================================================== --- py/dist/py/path/common.py (original) +++ py/dist/py/path/common.py Fri Oct 22 01:11:38 2004 @@ -240,16 +240,13 @@ return self.get('purebasename') purebasename = property(purebasename, None, None, 'basename without extension') - def read(self, num=-1): - #""" read up to 'num' bytes. (if num==-1 then read all)""" + def read(self): + """ read and return a bytestring from reading the path. """ + f = self.open('rb') try: - f = self.open('rb') - try: - return f.read(num) - finally: - f.close() - except: - self._except(sys.exc_info()) + return f.read() + finally: + f.close() def readlines(self, cr=1): if not cr: Modified: py/dist/py/path/extpy/extpy.py ============================================================================== --- py/dist/py/path/extpy/extpy.py (original) +++ py/dist/py/path/extpy/extpy.py Fri Oct 22 01:11:38 2004 @@ -131,9 +131,6 @@ #print "listdir on", repr(self) return l - def setfile(self, filepath): - self._filepath = filepath - def getfilelineno(self, scrapinit=0): x = obj = self.resolve() if inspect.ismodule(obj): @@ -201,6 +198,10 @@ if str(self.root) == x: return True + def read(self): + """ return a bytestring from looking at our underlying object. """ + return str(self.resolve()) + class Checkers(common.Checkers): _depend_on_existence = (common.Checkers._depend_on_existence + ('func', 'class_', 'exists', 'dir')) Modified: py/dist/py/path/extpy/inc_pseudofs.py ============================================================================== --- py/dist/py/path/extpy/inc_pseudofs.py (original) +++ py/dist/py/path/extpy/inc_pseudofs.py Fri Oct 22 01:11:38 2004 @@ -1,5 +1,5 @@ -samplefile = 'samplefile' +samplefile = 'samplefile\n' samplepickle = {} class sampledir: Modified: py/dist/py/path/extpy/test_extpy.py ============================================================================== --- py/dist/py/path/extpy/test_extpy.py (original) +++ py/dist/py/path/extpy/test_extpy.py Fri Oct 22 01:11:38 2004 @@ -1,4 +1,4 @@ -import sys, os +import os import py from py.__impl__.path.test import common Modified: py/dist/py/path/test/common.py ============================================================================== --- py/dist/py/path/test/common.py (original) +++ py/dist/py/path/test/common.py Fri Oct 22 01:11:38 2004 @@ -199,3 +199,8 @@ for name in dir(local.Checkers): if name[0] != '_': assert name in doc + + def test_simple_read(self): + x = self.root.join('samplefile').read() + assert x == 'samplefile\n' + Modified: py/dist/py/path/test/fscommon.py ============================================================================== --- py/dist/py/path/test/fscommon.py (original) +++ py/dist/py/path/test/fscommon.py Fri Oct 22 01:11:38 2004 @@ -61,11 +61,6 @@ assert newext.basename == "samplefile.txt" assert newext.purebasename == "samplefile" - def test_read(self): - url = self.root.join("samplefile") - contents = url.read() - assert contents.find("samplefile") != -1 - def test_readlines(self): fn = self.root.join('samplefile') contents = fn.readlines() From hpk at codespeak.net Fri Oct 22 13:56:29 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 22 Oct 2004 13:56:29 +0200 (MEST) Subject: [py-svn] r7094 - in py/dist/py: path/local test/report/text Message-ID: <20041022115629.DFF4B5AC1D@thoth.codespeak.net> Author: hpk Date: Fri Oct 22 13:56:29 2004 New Revision: 7094 Modified: py/dist/py/path/local/local.py py/dist/py/test/report/text/summary.py Log: - fix to handle the contained popen5/subprocess module correctly - make the reporter pretty-print too-long values when reporting "locals()" values of frames in tracebacks Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Fri Oct 22 13:56:29 2004 @@ -385,12 +385,13 @@ """ from popen5.subprocess import Popen, PIPE proc = Popen([str(self)] + list(argv), stdout=PIPE, stderr=PIPE) + stdout, stderr = proc.communicate() ret = proc.wait() if ret != 0: raise py.process.cmdexec.Error(ret, ret, str(self), - proc.stdout.read(), - proc.stderr.read(),) - return proc.stdout.read() + stdout, + stderr,) + return stdout def sysfind(self, name, checker=None): """ return a path object found by looking at the systems Modified: py/dist/py/test/report/text/summary.py ============================================================================== --- py/dist/py/test/report/text/summary.py (original) +++ py/dist/py/test/report/text/summary.py Fri Oct 22 13:56:29 2004 @@ -222,7 +222,13 @@ if self.option.showlocals: self.out.sep('- ', 'locals') for name, value in tb.tb_frame.f_locals.items(): - self.out.line("%-10s = %r" %(name, value)) + if len(repr(value)) < 70 or not isinstance(value, + (list, tuple, dict)): + self.out.line("%-10s = %r" %(name, value)) + else: + self.out.line("%-10s =\\" % (name,)) + py.std.pprint.pprint(value, stream=self.out) + #self.out.line("%-10s = %r" %(name, value)) key = (filename, lineno) if key not in recursioncache: recursioncache.setdefault(key, []).append(tb.tb_frame.f_locals) From hpk at codespeak.net Fri Oct 22 17:03:19 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 22 Oct 2004 17:03:19 +0200 (MEST) Subject: [py-svn] r7096 - in py/dist/py: . path path/test Message-ID: <20041022150319.F13155B545@thoth.codespeak.net> Author: hpk Date: Fri Oct 22 17:03:19 2004 New Revision: 7096 Modified: py/dist/py/path/common.py py/dist/py/path/test/common.py py/dist/py/pytest.py Log: add an __add__ method to all paths that allows easily adding stuff to the basename of a path. Modified: py/dist/py/path/common.py ============================================================================== --- py/dist/py/path/common.py (original) +++ py/dist/py/path/common.py Fri Oct 22 17:03:19 2004 @@ -139,6 +139,9 @@ last = x return last + def __add__(self, other): + return self.new(basename=self.basename+str(other)) + def __cmp__(self, other): try: try: Modified: py/dist/py/path/test/common.py ============================================================================== --- py/dist/py/path/test/common.py (original) +++ py/dist/py/path/test/common.py Fri Oct 22 17:03:19 2004 @@ -30,6 +30,11 @@ newpath = self.root.join() assert self.root == newpath + def test_add_something(self): + p = self.root.join('sample') + p = p + 'dir' + assert p.check() + def test_parts(self): newpath = self.root.join('sampledir', 'otherfile') par = newpath.parts()[-3:] Modified: py/dist/py/pytest.py ============================================================================== --- py/dist/py/pytest.py (original) +++ py/dist/py/pytest.py Fri Oct 22 17:03:19 2004 @@ -7,7 +7,7 @@ # mod.module = 23 # directory = pypath.root.dirpath() -# standard options (modified from cmdline) +# default values for options (modified from cmdline) verbose = 0 nocapture = False collectonly = False From hpk at codespeak.net Fri Oct 22 17:16:28 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 22 Oct 2004 17:16:28 +0200 (MEST) Subject: [py-svn] r7097 - py/dist/py/path/extpy Message-ID: <20041022151628.D126F5B545@thoth.codespeak.net> Author: hpk Date: Fri Oct 22 17:16:28 2004 New Revision: 7097 Modified: py/dist/py/path/extpy/extpy.py py/dist/py/path/extpy/test_extpy.py Log: treat all non-modules/classes as "files" on extpy-paths Modified: py/dist/py/path/extpy/extpy.py ============================================================================== --- py/dist/py/path/extpy/extpy.py (original) +++ py/dist/py/path/extpy/extpy.py Fri Oct 22 17:16:28 2004 @@ -228,3 +228,6 @@ def dir(self): obj = self._obj() return inspect.isclass(obj) or inspect.ismodule(obj) + + def file(self): + return not self.dir() Modified: py/dist/py/path/extpy/test_extpy.py ============================================================================== --- py/dist/py/path/extpy/test_extpy.py (original) +++ py/dist/py/path/extpy/test_extpy.py Fri Oct 22 17:16:28 2004 @@ -9,6 +9,10 @@ def setup_class(cls): cls.root = py.path.extpy( py.magic.autopath().dirpath('inc_pseudofs.py')) + + def test_file(self): + assert self.root.join('samplefile').check(file=1) + assert self.root.join('otherdir').check(file=0) class TestExtPy: def setup_class(cls): From hpk at codespeak.net Sat Oct 23 17:26:09 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 23 Oct 2004 17:26:09 +0200 (MEST) Subject: [py-svn] r7106 - in py/dist/py: . bin test Message-ID: <20041023152609.5335D5B546@thoth.codespeak.net> Author: hpk Date: Sat Oct 23 17:26:08 2004 New Revision: 7106 Modified: py/dist/py/bin/py.test py/dist/py/pytest.py py/dist/py/test/cmdline.py py/dist/py/test/defaultconfig.py py/dist/py/test/run.py Log: small refactoring and internal renaming ... Modified: py/dist/py/bin/py.test ============================================================================== --- py/dist/py/bin/py.test (original) +++ py/dist/py/bin/py.test Sat Oct 23 17:26:08 2004 @@ -2,4 +2,4 @@ from _findpy import py from py.__impl__.test.cmdline import main -main(py.std.sys.argv) +main() Modified: py/dist/py/pytest.py ============================================================================== --- py/dist/py/pytest.py (original) +++ py/dist/py/pytest.py Sat Oct 23 17:26:08 2004 @@ -6,6 +6,9 @@ # mod = extpy.resolve() # mod.module = 23 # directory = pypath.root.dirpath() + +import py +pkgdir = py.magic.autopath().pkgdir # default values for options (modified from cmdline) verbose = 0 Modified: py/dist/py/test/cmdline.py ============================================================================== --- py/dist/py/test/cmdline.py (original) +++ py/dist/py/test/cmdline.py Sat Oct 23 17:26:08 2004 @@ -6,9 +6,7 @@ # configbasename = 'pytest.py' -def main(argv=None): - # the collectors we will be running - collectors = [] +def old(argv): if argv is None: argv = py.std.sys.argv frame = py.std.sys._getframe(1) @@ -16,31 +14,30 @@ if name != '__main__': return # called from an imported test file import __main__ - collectors.append(py.test.collect.Module(py.std.sys.argv[0])) - - args = argv[1:] + return [py.test.collect.Module(py.std.sys.argv[0])] + +def main(): + args = py.std.sys.argv[1:] py.test.config.readconfiguration(*getanchors(args)) - if py.test.config.restartpython(): return + filenames = py.test.config.parseargs(args) - collectors.extend(getcollectors(filenames)) - if not collectors: - collectors.append(py.test.collect.Directory(py.path.local())) + collectors = getcollectors(filenames) reporter = py.test.config.getfirst('getreporter') () - runner = py.test.Driver(reporter) - runner.setup() + driver = py.test.Driver(reporter) + driver.setup() try: try: reporter.start() try: for collector in collectors: - runner.run(collector) - except runner.Exit: + driver.run_collector_or_item(collector) + except driver.Exit: pass finally: - runner.teardown() + driver.teardown() except KeyboardInterrupt: print >>py.std.sys.stderr, "KEYBOARD INTERRUPT" py.std.sys.exit(2) @@ -61,6 +58,8 @@ else: raise RuntimeError, "%r does not exist" % fn yielded = True + if not yielded: + yield py.test.collect.Directory(py.path.local()) def getanchors(args): """ yield anchors from skimming the args for existing files/dirs. """ Modified: py/dist/py/test/defaultconfig.py ============================================================================== --- py/dist/py/test/defaultconfig.py (original) +++ py/dist/py/test/defaultconfig.py Sat Oct 23 17:26:08 2004 @@ -29,4 +29,7 @@ Option('', '--collectonly', action="store_true", dest="collectonly", default=False, help="only collect tests, don't execute them. "), + Option('', '--session', + action="store_true", dest="session", default=False, + help="run a test session to rerun only failing tests. "), ]) Modified: py/dist/py/test/run.py ============================================================================== --- py/dist/py/test/run.py (original) +++ py/dist/py/test/run.py Sat Oct 23 17:26:08 2004 @@ -14,6 +14,9 @@ self._instance = None def run(self, obj): + raise ValueError + + def run_collector_or_item(self, obj): """ run (possibly many) testitems and/or collectors. """ collect = py.test.collect if isinstance(obj, Item): @@ -45,7 +48,7 @@ close = self.reporter.open(collector) try: for obj in collector: - self.run(obj) + self.run_collector_or_item(obj) finally: if close: close() @@ -82,7 +85,7 @@ """ setup any neccessary resources. """ def teardown(self): - """ teardown any resources the runner knows about. """ + """ teardown any resources the driver knows about. """ while self._setupstack: self._teardownone(self._setupstack.pop()[1]) @@ -129,9 +132,9 @@ self.name = pypath.basename self.args = args - def execute(self, runner): - runner.setup_path(self.pypath) - target, teardown = runner.setup_method(self.pypath) + def execute(self, driver): + driver.setup_path(self.pypath) + target, teardown = driver.setup_method(self.pypath) try: target(*self.args) finally: From hpk at codespeak.net Sat Oct 23 20:59:22 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 23 Oct 2004 20:59:22 +0200 (MEST) Subject: [py-svn] r7107 - in py/dist: doc py/execnet Message-ID: <20041023185922.753485B546@thoth.codespeak.net> Author: hpk Date: Sat Oct 23 20:59:21 2004 New Revision: 7107 Modified: py/dist/doc/execnet.txt py/dist/py/execnet/gateway.py Log: don't use eval/repr but marshal/unmarshal. Modified: py/dist/doc/execnet.txt ============================================================================== --- py/dist/doc/execnet.txt (original) +++ py/dist/doc/execnet.txt Sat Oct 23 20:59:21 2004 @@ -116,9 +116,8 @@ channel.send(item): sends the given item to the other side of the channel, possibly blocking if the sender queue is full. - Note that each value V of the items needs to have the - following property (all basic types in python have it): - eval(repr(V)) == V. + Note that items need to be marshallable (all basic + python types are): channel.receive(): receives an item that was sent from the other side, Modified: py/dist/py/execnet/gateway.py ============================================================================== --- py/dist/py/execnet/gateway.py (original) +++ py/dist/py/execnet/gateway.py Sat Oct 23 20:59:21 2004 @@ -1,5 +1,6 @@ import sys, os, threading, struct, Queue, traceback -import atexit + +import py # XXX the following line should not be here from py.__impl__.execnet.source import Source @@ -39,7 +40,7 @@ for x in self.iothreads + w: x.start() if not _gateways: - atexit.register(cleanup_atexit) + py.std.atexit.register(cleanup_atexit) _gateways.append(self) def _stopexec(self): @@ -198,10 +199,10 @@ def send(self, item): """sends the given item to the other side of the channel, possibly blocking if the sender queue is full. - Note that each value V of the items needs to have the - following property (all basic types in python have it): - eval(repr(V)) == V.""" - self.gateway._outgoing.put(Message.CHANNEL_DATA(self.id, repr(item))) + Note that an item needs to be marshallable. + """ + s = py.std.marshal.dumps(item) + self.gateway._outgoing.put(Message.CHANNEL_DATA(self.id, s)) def receive(self): """receives an item that was sent from the other side, @@ -306,12 +307,12 @@ def _setupmessages(): # # EXIT_GATEWAY and STOP_RECEIVING are messages to cleanly - # bring down the IO connection, i.e. it shouldn't die - # unexpectedly. + # bring down the IO and gateway connection # # First an EXIT_GATEWAY message is send which results - # on the other side's receive_handle - # which cleanly + # on the other side's receive_handle to send send + # a STOP_RECEIVING message + # class EXIT_GATEWAY(Message): def received(self, gateway): gateway._stopexec() @@ -339,7 +340,8 @@ class CHANNEL_DATA(Message): def received(self, gateway): channel = gateway.channelfactory[self.channelid] - channel._items.put(eval(self.data)) + x = py.std.marshal.loads(self.data) + channel._items.put(x) class CHANNEL_CLOSE(Message): def received(self, gateway): channel = gateway.channelfactory[self.channelid] From hpk at codespeak.net Sat Oct 23 23:06:22 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 23 Oct 2004 23:06:22 +0200 (MEST) Subject: [py-svn] r7108 - py/dist/py/execnet Message-ID: <20041023210622.902595B546@thoth.codespeak.net> Author: hpk Date: Sat Oct 23 23:06:22 2004 New Revision: 7108 Modified: py/dist/py/execnet/gateway.py py/dist/py/execnet/test_gateway.py Log: new feature: allow passing channels over channels (experimental) this allows two program fragments to open multiple channels to each other. Modified: py/dist/py/execnet/gateway.py ============================================================================== --- py/dist/py/execnet/gateway.py (original) +++ py/dist/py/execnet/gateway.py Sat Oct 23 23:06:22 2004 @@ -162,6 +162,11 @@ self._outgoing.put(Message.CHANNEL_OPEN(channel.id, source)) return channel + def newchannel(self): + """ return a new independent channel. """ + return self.channelfactory.new() + + class Channel(object): """Communication channel between two possibly remote threads of code. """ def __init__(self, gateway, id): @@ -201,8 +206,13 @@ possibly blocking if the sender queue is full. Note that an item needs to be marshallable. """ - s = py.std.marshal.dumps(item) - self.gateway._outgoing.put(Message.CHANNEL_DATA(self.id, s)) + if isinstance(item, Channel): + s = py.std.marshal.dumps(item.id) + data = Message.CHANNEL_NEW(self.id, s) + else: + s = py.std.marshal.dumps(item) + data = Message.CHANNEL_DATA(self.id, s) + self.gateway._outgoing.put(data) def receive(self): """receives an item that was sent from the other side, @@ -337,11 +347,21 @@ channel = Channel(gateway, self.channelid) gateway.channelfactory[self.channelid] = channel gateway._execqueue.put((channel, self.data)) + + class CHANNEL_NEW(Message): + def received(self, gateway): + newid = py.std.marshal.loads(self.data) + newchannel = Channel(gateway, newid) + gateway.channelfactory[newid] = newchannel + channel = gateway.channelfactory[self.channelid] + channel._items.put(newchannel) + class CHANNEL_DATA(Message): def received(self, gateway): channel = gateway.channelfactory[self.channelid] x = py.std.marshal.loads(self.data) channel._items.put(x) + class CHANNEL_CLOSE(Message): def received(self, gateway): channel = gateway.channelfactory[self.channelid] Modified: py/dist/py/execnet/test_gateway.py ============================================================================== --- py/dist/py/execnet/test_gateway.py (original) +++ py/dist/py/execnet/test_gateway.py Sat Oct 23 23:06:22 2004 @@ -96,6 +96,16 @@ assert x == 42 py.test.raises(gateway.RemoteError, channel.receive) + def test_channel_passing_over_channel(self): + channel = self.gw.remote_exec(''' + c = channel.gateway.newchannel() + channel.send(c) + c.send(42) + ''') + c = channel.receive() + x = c.receive() + assert x == 42 + class TestBasicPopenGateway(PopenGatewayTestSetup, BasicRemoteExecution): def test_many_popen(self): num = 4 From hpk at codespeak.net Sat Oct 23 23:58:06 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 23 Oct 2004 23:58:06 +0200 (MEST) Subject: [py-svn] r7109 - py/dist/py/execnet Message-ID: <20041023215806.1A3D65B546@thoth.codespeak.net> Author: hpk Date: Sat Oct 23 23:58:05 2004 New Revision: 7109 Modified: py/dist/py/execnet/gateway.py py/dist/py/execnet/test_gateway.py Log: dependent channels (ones created from a channel) are now automatically close if a parent channel closes down. Modified: py/dist/py/execnet/gateway.py ============================================================================== --- py/dist/py/execnet/gateway.py (original) +++ py/dist/py/execnet/gateway.py Sat Oct 23 23:58:05 2004 @@ -8,10 +8,12 @@ debug = 0 sysex = (KeyboardInterrupt, SystemExit) -class RemoteError(Exception): +class Closed: + pass + +class RemoteError(Closed): """ Contains an Exceptions from the other side. """ def __init__(self, formatted): - Exception.__init__(self) self.formatted = formatted def __str__(self): @@ -127,23 +129,26 @@ break channel, source = task try: - loc = { 'channel' : channel } - self.trace("execution starts:", repr(source)[:50]) - try: - co = compile(source+'\n', '', 'exec') - exec co in loc - finally: - self.trace("execution finished:", repr(source)[:50]) - except (KeyboardInterrupt, SystemExit): - raise - except: - excinfo = sys.exc_info() - l = traceback.format_exception(*excinfo) - errortext = "".join(l) - self._outgoing.put(Message.CHANNEL_CLOSE_ERROR(channel.id, errortext)) - self.trace(errortext) - else: - self._outgoing.put(Message.CHANNEL_CLOSE(channel.id)) + try: + loc = { 'channel' : channel } + self.trace("execution starts:", repr(source)[:50]) + try: + co = compile(source+'\n', '', 'exec') + exec co in loc + finally: + self.trace("execution finished:", repr(source)[:50]) + except (KeyboardInterrupt, SystemExit): + raise + except: + excinfo = sys.exc_info() + l = traceback.format_exception(*excinfo) + errortext = "".join(l) + self._outgoing.put(Message.CHANNEL_CLOSE_ERROR(channel.id, errortext)) + self.trace(errortext) + else: + self._outgoing.put(Message.CHANNEL_CLOSE(channel.id)) + finally: + channel._close() finally: self.trace('leaving %r' % threading.currentThread()) @@ -162,11 +167,6 @@ self._outgoing.put(Message.CHANNEL_OPEN(channel.id, source)) return channel - def newchannel(self): - """ return a new independent channel. """ - return self.channelfactory.new() - - class Channel(object): """Communication channel between two possibly remote threads of code. """ def __init__(self, gateway, id): @@ -175,19 +175,33 @@ self.id = id self._items = Queue.Queue() self._closeevent = threading.Event() + self._depchannel = [] - def _close(self, error=None): - if error is not None: - self._error = RemoteError(error) - self._items.put(self._error) - else: - self._error = None + def _close(self, finalitem=Closed()): + self._finalitem = finalitem + for x in self._depchannel: + x._close(Closed()) + del self.gateway.channelfactory[self.id] + self._items.put(finalitem) self._closeevent.set() def __repr__(self): flag = self._closeevent.isSet() and "closed" or "open" return "" % (self.id, flag) + def newchannel(self): + """ return a new channel whose lifetimes depends on this channel. """ + chan = self.gateway.channelfactory.new() + self._depchannel.append(chan) + return chan + + def _receivechannel(self, newid): + """ receive a remotely created new (sub)channel. """ + newchannel = Channel(self.gateway, newid) + self.gateway.channelfactory[newid] = newchannel + self._depchannel.append(newchannel) + self._items.put(newchannel) + def waitclose(self, timeout): """ wait until this channel is closed. Note that a closed channel may still hold items that will be received or @@ -198,8 +212,8 @@ self._closeevent.wait(timeout=timeout) if not self._closeevent.isSet(): raise IOError, "Timeout" - if self._error: - raise self._error + if isinstance(self._finalitem, RemoteError): + raise self._finalitem def send(self, item): """sends the given item to the other side of the channel, @@ -222,7 +236,7 @@ a textual representation of the remote traceback. """ x = self._items.get() - if isinstance(x, RemoteError): + if isinstance(x, Closed): raise x return x @@ -246,7 +260,14 @@ finally: self.count += 2 self._lock.release() - + + def __contains__(self, key): + self._lock.acquire() + try: + return key in self._dict + finally: + self._lock.release() + def __getitem__(self, key): self._lock.acquire() try: @@ -351,10 +372,8 @@ class CHANNEL_NEW(Message): def received(self, gateway): newid = py.std.marshal.loads(self.data) - newchannel = Channel(gateway, newid) - gateway.channelfactory[newid] = newchannel channel = gateway.channelfactory[self.channelid] - channel._items.put(newchannel) + channel._receivechannel(newid) class CHANNEL_DATA(Message): def received(self, gateway): @@ -365,12 +384,11 @@ class CHANNEL_CLOSE(Message): def received(self, gateway): channel = gateway.channelfactory[self.channelid] - channel._close() - del gateway.channelfactory[channel.id] + channel._close(Closed()) class CHANNEL_CLOSE_ERROR(Message): def received(self, gateway): channel = gateway.channelfactory[self.channelid] - channel._close(self.data) + channel._close(RemoteError(self.data)) classes = [x for x in locals().values() if hasattr(x, '__bases__')] classes.sort(lambda x,y : cmp(x.__name__, y.__name__)) i = 0 Modified: py/dist/py/execnet/test_gateway.py ============================================================================== --- py/dist/py/execnet/test_gateway.py (original) +++ py/dist/py/execnet/test_gateway.py Sat Oct 23 23:58:05 2004 @@ -50,16 +50,6 @@ channel = self.fac.new() py.test.raises(IOError, channel.waitclose, timeout=0.01) - def test_channel_close(self): - channel = self.fac.new() - channel._close() - channel.waitclose(0.1) - - def test_channel_close_error(self): - channel = self.fac.new() - channel._close("error") - py.test.raises(gateway.RemoteError, channel.waitclose, 0.01) - class PopenGatewayTestSetup: def setup_class(cls): cls.gw = py.execnet.PopenGateway() @@ -96,9 +86,19 @@ assert x == 42 py.test.raises(gateway.RemoteError, channel.receive) + def test_channel_close(self): + channel = self.gw.channelfactory.new() + channel._close() + channel.waitclose(0.1) + + def test_channel_close_error(self): + channel = self.gw.channelfactory.new() + channel._close(gateway.RemoteError("error")) + py.test.raises(gateway.RemoteError, channel.waitclose, 0.01) + def test_channel_passing_over_channel(self): channel = self.gw.remote_exec(''' - c = channel.gateway.newchannel() + c = channel.newchannel() channel.send(c) c.send(42) ''') @@ -106,6 +106,16 @@ x = c.receive() assert x == 42 + # check that the both sides previous channels are really gone + channel.waitclose(0.3) + assert channel.id not in self.gw.channelfactory + assert c.id not in self.gw.channelfactory + newchan = self.gw.remote_exec(''' + assert %d not in channel.gateway.channelfactory + assert %d not in channel.gateway.channelfactory + ''' % (c.id, channel.id)) + newchan.waitclose(0.3) + class TestBasicPopenGateway(PopenGatewayTestSetup, BasicRemoteExecution): def test_many_popen(self): num = 4 From hpk at codespeak.net Sun Oct 24 00:07:53 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 24 Oct 2004 00:07:53 +0200 (MEST) Subject: [py-svn] r7110 - py/dist/py/execnet Message-ID: <20041023220753.B41405B546@thoth.codespeak.net> Author: hpk Date: Sun Oct 24 00:07:53 2004 New Revision: 7110 Modified: py/dist/py/execnet/gateway.py Log: some code cleanup, an explicit "channel.close()" method to close down a channel properly. Modified: py/dist/py/execnet/gateway.py ============================================================================== --- py/dist/py/execnet/gateway.py (original) +++ py/dist/py/execnet/gateway.py Sun Oct 24 00:07:53 2004 @@ -5,7 +5,7 @@ # XXX the following line should not be here from py.__impl__.execnet.source import Source -debug = 0 +debug = 1 sysex = (KeyboardInterrupt, SystemExit) class Closed: @@ -129,26 +129,23 @@ break channel, source = task try: - try: - loc = { 'channel' : channel } - self.trace("execution starts:", repr(source)[:50]) - try: - co = compile(source+'\n', '', 'exec') - exec co in loc - finally: - self.trace("execution finished:", repr(source)[:50]) - except (KeyboardInterrupt, SystemExit): - raise - except: - excinfo = sys.exc_info() - l = traceback.format_exception(*excinfo) - errortext = "".join(l) - self._outgoing.put(Message.CHANNEL_CLOSE_ERROR(channel.id, errortext)) - self.trace(errortext) - else: - self._outgoing.put(Message.CHANNEL_CLOSE(channel.id)) - finally: - channel._close() + loc = { 'channel' : channel } + self.trace("execution starts:", repr(source)[:50]) + try: + co = compile(source+'\n', '', 'exec') + exec co in loc + finally: + self.trace("execution finished:", repr(source)[:50]) + except (KeyboardInterrupt, SystemExit): + raise + except: + excinfo = sys.exc_info() + l = traceback.format_exception(*excinfo) + errortext = "".join(l) + channel.close(errortext) + self.trace(errortext) + else: + channel.close() finally: self.trace('leaving %r' % threading.currentThread()) @@ -177,6 +174,15 @@ self._closeevent = threading.Event() self._depchannel = [] + def close(self, error=None): + """ close down this channel on both sides. """ + put = self.gateway._outgoing.put + if error is not None: + put(Message.CHANNEL_CLOSE_ERROR(self.id, error)) + else: + put(Message.CHANNEL_CLOSE(self.id)) + self._close() + def _close(self, finalitem=Closed()): self._finalitem = finalitem for x in self._depchannel: @@ -384,7 +390,7 @@ class CHANNEL_CLOSE(Message): def received(self, gateway): channel = gateway.channelfactory[self.channelid] - channel._close(Closed()) + channel._close() class CHANNEL_CLOSE_ERROR(Message): def received(self, gateway): channel = gateway.channelfactory[self.channelid] From hpk at codespeak.net Sun Oct 24 00:15:09 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 24 Oct 2004 00:15:09 +0200 (MEST) Subject: [py-svn] r7111 - py/dist/py/execnet Message-ID: <20041023221509.CB46F5B546@thoth.codespeak.net> Author: hpk Date: Sun Oct 24 00:15:09 2004 New Revision: 7111 Modified: py/dist/py/execnet/gateway.py Log: minor cleanup, set debug to 0 Modified: py/dist/py/execnet/gateway.py ============================================================================== --- py/dist/py/execnet/gateway.py (original) +++ py/dist/py/execnet/gateway.py Sun Oct 24 00:15:09 2004 @@ -5,7 +5,7 @@ # XXX the following line should not be here from py.__impl__.execnet.source import Source -debug = 1 +debug = 0 sysex = (KeyboardInterrupt, SystemExit) class Closed: @@ -174,6 +174,10 @@ self._closeevent = threading.Event() self._depchannel = [] + def __repr__(self): + flag = self._closeevent.isSet() and "closed" or "open" + return "" % (self.id, flag) + def close(self, error=None): """ close down this channel on both sides. """ put = self.gateway._outgoing.put @@ -191,12 +195,8 @@ self._items.put(finalitem) self._closeevent.set() - def __repr__(self): - flag = self._closeevent.isSet() and "closed" or "open" - return "" % (self.id, flag) - def newchannel(self): - """ return a new channel whose lifetimes depends on this channel. """ + """ return a new channel whose life cycle depends on this channel. """ chan = self.gateway.channelfactory.new() self._depchannel.append(chan) return chan @@ -211,7 +211,7 @@ def waitclose(self, timeout): """ wait until this channel is closed. Note that a closed channel may still hold items that will be received or - send. Note that exceptions from the other side will be + send. Note also that exceptions from the other side will be reraised as gateway.ExecutionFailed exceptions containing a textual representation of the remote traceback. """ @@ -266,14 +266,14 @@ finally: self.count += 2 self._lock.release() - + def __contains__(self, key): self._lock.acquire() try: - return key in self._dict + return key in self._dict finally: self._lock.release() - + def __getitem__(self, key): self._lock.acquire() try: From hpk at codespeak.net Sun Oct 24 16:42:30 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 24 Oct 2004 16:42:30 +0200 (MEST) Subject: [py-svn] r7113 - in py/dist/py: . execnet path/svn test test/report test/report/text Message-ID: <20041024144230.223065B546@thoth.codespeak.net> Author: hpk Date: Sun Oct 24 16:42:27 2004 New Revision: 7113 Added: py/dist/py/execnet/channel.py py/dist/py/test/item.py Modified: py/dist/py/__init__.py py/dist/py/execnet/gateway.py py/dist/py/path/svn/svntestbase.py py/dist/py/path/svn/test_urlcommand.py py/dist/py/path/svn/test_wccommand.py py/dist/py/test/cmdline.py py/dist/py/test/collect.py py/dist/py/test/compat.py py/dist/py/test/raises.py py/dist/py/test/report/memo.py py/dist/py/test/report/text/reporter.py py/dist/py/test/report/text/summary.py py/dist/py/test/run.py py/dist/py/test/test_compat.py Log: pretty large refactoring most of the testing process is now outsourced into a child process. This is the foundation to implement a "test session" which will rerun the set of failing tests. Note that without running the tests in a child process it's somewhat hard to restart the testing process because we can't easily "reload()" changed code. also there are some API enhancements (which were purposedly not described in the documentation anyway) most notably, there now are direct py.test.fail("my message") py.test.skip("my reason") py.test.exit("why i want it immediately") functions, so you don't have to raise some exception anymore. also the exposed namespace from py.test is somewhat smaller and cleaner now. there are still some problems with the shutdown procedure but nothing too serious. Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Sun Oct 24 16:42:27 2004 @@ -16,17 +16,17 @@ 'test.collect.Module': './test/collect.Module', 'test.collect.PyCollector':'./test/collect.PyCollector', 'test.collect.Error': './test/collect.Error', - 'test.Failed': './test/run.Failed', - 'test.Passed': './test/run.Passed', - 'test.Skipped': './test/run.Skipped', - 'test.raises': './test/raises.raises', - 'test.config': './test/config.config', - 'test.compat.TestCase': './test/compat.TestCase', + 'test.Item': './test/item.Item', 'test.Driver': './test/run.Driver', - 'test.Item': './test/run.Item', 'test.Option': './test/tool/optparse.Option', 'test.TextReporter': './test/report/text/reporter.TextReporter', 'test.MemoReporter': './test/report/memo.MemoReporter', + 'test.exit': './test/run.exit', + 'test.fail': './test/run.fail', + 'test.skip': './test/run.skip', + 'test.raises': './test/raises.raises', + 'test.config': './test/config.config', + 'test.compat.TestCase': './test/compat.TestCase', 'process.cmdexec': './process/cmdexec.cmdexec', Added: py/dist/py/execnet/channel.py ============================================================================== --- (empty file) +++ py/dist/py/execnet/channel.py Sun Oct 24 16:42:27 2004 @@ -0,0 +1,31 @@ + +class ChannelFile: + def __init__(self, channel): + self.channel = channel + + def write(self, out): + if self.channel.isclosed(): + raise IOError, "cannot write to %r" % self + self.channel.send(out) + + def flush(self): + pass + + def close(self): + if self.channel.isclosed(): + raise IOError, "cannot close %r" % self + self.channel.close() + + def __repr__(self): + state = self.channel.isclosed() and 'closed' or 'open' + return '' %(self.channel.id, state) + +def receive2file(channel, f): + while 1: + try: + out = channel.receive() + except EOFError: + break + f.write(out) + f.flush() + Modified: py/dist/py/execnet/gateway.py ============================================================================== --- py/dist/py/execnet/gateway.py (original) +++ py/dist/py/execnet/gateway.py Sun Oct 24 16:42:27 2004 @@ -8,13 +8,11 @@ debug = 0 sysex = (KeyboardInterrupt, SystemExit) -class Closed: - pass - -class RemoteError(Closed): +class RemoteError(EOFError): """ Contains an Exceptions from the other side. """ def __init__(self, formatted): self.formatted = formatted + EOFError.__init__(self) def __str__(self): return self.formatted @@ -175,9 +173,12 @@ self._depchannel = [] def __repr__(self): - flag = self._closeevent.isSet() and "closed" or "open" + flag = self.isclosed() and "closed" or "open" return "" % (self.id, flag) + def isclosed(self): + return self._closeevent.isSet() + def close(self, error=None): """ close down this channel on both sides. """ put = self.gateway._outgoing.put @@ -187,10 +188,10 @@ put(Message.CHANNEL_CLOSE(self.id)) self._close() - def _close(self, finalitem=Closed()): + def _close(self, finalitem=EOFError()): self._finalitem = finalitem for x in self._depchannel: - x._close(Closed()) + x._close() del self.gateway.channelfactory[self.id] self._items.put(finalitem) self._closeevent.set() @@ -242,7 +243,7 @@ a textual representation of the remote traceback. """ x = self._items.get() - if isinstance(x, Closed): + if isinstance(x, EOFError): raise x return x Modified: py/dist/py/path/svn/svntestbase.py ============================================================================== --- py/dist/py/path/svn/svntestbase.py (original) +++ py/dist/py/path/svn/svntestbase.py Sun Oct 24 16:42:27 2004 @@ -9,8 +9,8 @@ bn = meth.func_name for x in 'test_remove', 'test_move': if bn.startswith(x): - raise py.test.Skipped(msg= - "tests for modifying svn needs better test state management") + py.test.skip( + "tests for modifying svn needs better test state management") def test_propget(self): url = self.root.join("samplefile") Modified: py/dist/py/path/svn/test_urlcommand.py ============================================================================== --- py/dist/py/path/svn/test_urlcommand.py (original) +++ py/dist/py/path/svn/test_urlcommand.py Sun Oct 24 16:42:27 2004 @@ -12,7 +12,7 @@ raise py.test.Skipped(msg="XXX fix svnurl first") def xtest_copy_dir(self): - raise py.test.Skipped(msg="XXX fix svnurl first") + py.test.skipp("XXX fix svnurl first") def XXXtest_info_log(self): url = self.root.join("samplefile") Modified: py/dist/py/path/svn/test_wccommand.py ============================================================================== --- py/dist/py/path/svn/test_wccommand.py (original) +++ py/dist/py/path/svn/test_wccommand.py Sun Oct 24 16:42:27 2004 @@ -15,7 +15,7 @@ py.process.cmdexec('svnadmin create %s' % repo) except py.process.cmdexec.Error: repo.remove() - raise py.test.Skipped(msg='could not create temporary svn test repository') + raise py.test.skip('could not create temporary svn test repository') wcdir.ensure(dir=1) print "created svn repository", repo wc = py.path.svnwc(wcdir) Modified: py/dist/py/test/cmdline.py ============================================================================== --- py/dist/py/test/cmdline.py (original) +++ py/dist/py/test/cmdline.py Sun Oct 24 16:42:27 2004 @@ -1,5 +1,7 @@ from __future__ import generators import py +import sys +from py.__impl__.execnet.channel import ChannelFile, receive2file # # main entry point @@ -16,39 +18,101 @@ import __main__ return [py.test.collect.Module(py.std.sys.argv[0])] +def waitfilechange(): + """ wait until project files are changed. """ + pkgdir = py.test.config.getfirst('pkgdir') + pkgdir = py.path.local(pkgdir) + fil = py.path.checker(fnmatch='*.py') + rec = py.path.checker(dotfile=0) + statcache = {} + for path in pkgdir.visit(fil, rec): + statcache[path] = path.stat() + + while 1: + py.std.time.sleep(0.2) + for path in pkgdir.visit(fil, rec): + st = path.stat() + cst = statcache[path] + if st.st_mtime != cst.st_mtime or \ + st.st_size != cst.st_size: + return + +class FailingCollector(py.test.collect.Collector): + def __init__(self, faileditems): + self._faileditems = faileditems + + def __iter__(self): + for x in self._faileditems: + yield x + +def master(): + #if not py.test.config.option.session: + # break + #l = driver.getfailed() + #if l: + # collectors = [FailingCollector(l)] + #elif isinstance(fncollectors[0], FailingCollector): + # collectors = fncollectors + #else: + # break + waitfilechange() + def main(): args = py.std.sys.argv[1:] py.test.config.readconfiguration(*getanchors(args)) - if py.test.config.restartpython(): - return filenames = py.test.config.parseargs(args) - collectors = getcollectors(filenames) + if not filenames: + filenames.append(str(py.path.local())) - reporter = py.test.config.getfirst('getreporter') () - driver = py.test.Driver(reporter) - driver.setup() - try: + gw = py.execnet.PopenGateway() + channel = gw.remote_exec(""" + from py.__impl__.test.cmdline import slave + slave(channel) + """) + print "sending (args, filenames)" + channel.send((args, filenames)) + print "receiving stdout/stderr" + stdout = channel.receive() + stderr = channel.receive() + py.std.threading.Thread(target=receive2file, + args=(stdout, sys.stdout)).start() + py.std.threading.Thread(target=receive2file, + args=(stderr, sys.stderr)).start() + print "waiting" + while 1: try: - reporter.start() - try: - for collector in collectors: - driver.run_collector_or_item(collector) - except driver.Exit: - pass - finally: - driver.teardown() - except KeyboardInterrupt: - print >>py.std.sys.stderr, "KEYBOARD INTERRUPT" - py.std.sys.exit(2) - except SystemExit: - print >>py.std.sys.stderr, "SYSTEM Exit" - py.std.sys.exit(-1) - reporter.end() - + channel.waitclose(0.1) + except IOError: + continue + except KeyboardInterrupt: + print + print "keyboard interrupt" + except: + py.std.traceback.print_exc() + channel.close() + gw.exit() + break + +def slave(channel): + """ we run this on the other side. """ + args, filenames = channel.receive() + out, err = channel.newchannel(), channel.newchannel() + channel.send(out) + channel.send(err) + sys.stdout = ChannelFile(out) + sys.stderr = ChannelFile(err) + + filenames = map(py.path.local, filenames) + py.test.config.readconfiguration(*filenames) + py.test.config.parseargs(args) + fncollectors = list(getcollectors(filenames)) + + driver = py.test.Driver(channel) + driver.run(fncollectors) + def getcollectors(filenames): current = py.path.local() - yielded = False for fn in filenames: fullfn = current.join(fn, abs=1) if fullfn.check(file=1): @@ -56,13 +120,10 @@ elif fullfn.check(dir=1): yield py.test.collect.Directory(fullfn) else: - raise RuntimeError, "%r does not exist" % fn - yielded = True - if not yielded: - yield py.test.collect.Directory(py.path.local()) + raise IOError, "%r does not exist" % fn def getanchors(args): - """ yield anchors from skimming the args for existing files/dirs. """ + """ yield "anchors" from skimming the args for existing files/dirs. """ current = py.path.local() l = [] for arg in args: Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Sun Oct 24 16:42:27 2004 @@ -23,6 +23,7 @@ Collector instances during iteration. """ Item = test.Item + Error = Error def iterunits(self): """ yield all units of the Collector instance. """ @@ -86,9 +87,9 @@ # we want to sort according to lineno, so here goes # the generator lazyness l = [] - for pypath in self.extpy.listdir(): + for extpy in self.extpy.listdir(): for meth in self.yielders: - for x in meth(pypath): + for x in meth(extpy): x.fspath = self.extpy.root sortvalue = self.getsortvalue(x) l.append((sortvalue, x)) @@ -103,7 +104,7 @@ the same order as int he file. """ if isinstance(obj, self.Item): - obj = obj.pypath.resolve() + obj = obj.extpy.resolve() elif isinstance(obj, PyCollector): for x in obj: return self.getsortvalue(x) @@ -128,24 +129,24 @@ except: yield self._except() - def collect_function(self, pypath): - if pypath.check(func=1, basestarts='test_'): - if self.extpy.samefile(pypath): - yield self.Item(pypath) - - def collect_class(self, pypath): - #print "checking %r (pypath: %r)" % (pypath.resolve(), pypath) - if pypath.check(basestarts='Test') and self.extpy.samefile(pypath): - obj = pypath.resolve() + def collect_function(self, extpy): + if extpy.check(func=1, basestarts='test_'): + if self.extpy.samefile(extpy): + yield self.Item(extpy) + + def collect_class(self, extpy): + #print "checking %r (extpy: %r)" % (extpy.resolve(), extpy) + if extpy.check(basestarts='Test') and self.extpy.samefile(extpy): + obj = extpy.resolve() if inspect.isclass(obj) and not getattr(obj, 'disabled', 0): - yield Class(pypath) + yield Class(extpy) class Class(PyCollector): - def collect_method(self, pypath): + def collect_method(self, extpy): # note that we want to allow inheritance and thus # we don't check for "samemodule"-ness of test # methods like in the Module Collector - if pypath.check(basestarts='test_', func=1): - func = pypath.resolve() - yield getattr(func.im_class, 'Item', self.Item)(pypath) + if extpy.check(basestarts='test_', func=1): + func = extpy.resolve() + yield getattr(func.im_class, 'Item', self.Item)(extpy) Modified: py/dist/py/test/compat.py ============================================================================== --- py/dist/py/test/compat.py (original) +++ py/dist/py/test/compat.py Sun Oct 24 16:42:27 2004 @@ -6,7 +6,7 @@ honouring setUp and tearDown semantics. """ def execute(self, runner): - unboundmethod = self.pypath.resolve() + unboundmethod = self.extpy.resolve() cls = unboundmethod.im_class instance = cls() instance.setUp() @@ -14,7 +14,7 @@ unboundmethod(instance) finally: instance.tearDown() - return py.test.Passed() + return py.test.Item.Passed() class TestCase: """compatibility class of unittest's TestCase. """ @@ -28,7 +28,7 @@ def fail(self, msg=None): """ fail immediate with given message. """ - raise py.test.Failed(msg=msg) + raise py.test.Item.Failed(msg=msg) def assertRaises(self, excclass, func, *args, **kwargs): py.test.raises(excclass, func, *args, **kwargs) @@ -49,7 +49,7 @@ items.append(""" def %(name)s(self, %(sig)s): if %(expr)s: - raise py.test.Failed(tbindex=-2, msg=%(sigsubst)r %% (%(sig)s)) + raise py.test.Item.Failed(tbindex=-2, msg=%(sigsubst)r %% (%(sig)s)) """ % locals() ) source = "".join(items) Added: py/dist/py/test/item.py ============================================================================== --- (empty file) +++ py/dist/py/test/item.py Sun Oct 24 16:42:27 2004 @@ -0,0 +1,35 @@ + +# ---------------------------------------------- +# Basic Test Item +# ---------------------------------------------- +class Item(object): + _setupcache = [] + _lastinstance = None + + def __init__(self, extpy, *args): + self.extpy = extpy + self.name = extpy.basename + self.args = args + + def execute(self, driver): + driver.setup_path(self.extpy) + target, teardown = driver.setup_method(self.extpy) + try: + target(*self.args) + finally: + if teardown: + teardown(target) + + class Outcome: + def __init__(self, **kwargs): + assert 'msg' not in kwargs or isinstance(kwargs['msg'], str), ( + "given 'msg' argument is not a string" ) + self.__dict__.update(kwargs) + def __repr__(self): + return getattr(self, 'msg', object.__repr__(self)) + class Passed(Outcome): pass + class Failed(Outcome): pass + class ExceptionFailure(Failed): pass + class Skipped(Outcome): pass + + Modified: py/dist/py/test/raises.py ============================================================================== --- py/dist/py/test/raises.py (original) +++ py/dist/py/test/raises.py Sun Oct 24 16:42:27 2004 @@ -1,7 +1,6 @@ import sys import py - -from py.__impl__ import test +ExceptionFailure = py.test.Item.ExceptionFailure def raises(ExpectedException, *args, **kwargs): """ raise AssertionError, if target code does not raise the expected @@ -25,8 +24,8 @@ excinfo = sys.exc_info() else: excinfo = None - raise test.run.ExceptionFailure(expr=expr, expected=ExpectedException, - innerexcinfo=excinfo, tbindex = -2) + raise ExceptionFailure(expr=expr, expected=ExpectedException, + innerexcinfo=excinfo, tbindex = -2) else: func = args[0] assert callable @@ -42,5 +41,5 @@ if k: k = ', ' + k expr = '%s(%r%s)' %(func.__name__, args, k) - raise test.run.ExceptionFailure(expr=args, expected=ExpectedException, - innerexcinfo=excinfo, tbindex = -2) + raise ExceptionFailure(expr=args, expected=ExpectedException, + innerexcinfo=excinfo, tbindex = -2) Modified: py/dist/py/test/report/memo.py ============================================================================== --- py/dist/py/test/report/memo.py (original) +++ py/dist/py/test/report/memo.py Sun Oct 24 16:42:27 2004 @@ -1,10 +1,10 @@ from __future__ import generators -import py -from py.__impl__.test import run + +from py.test import Item class MemoReporter: typemap = { - run.Passed: '.', run.Skipped: 's', run.Failed: 'F', + Item.Passed: '.', Item.Skipped: 's', Item.Failed: 'F', } def append(self, restype, testresult): Modified: py/dist/py/test/report/text/reporter.py ============================================================================== --- py/dist/py/test/report/text/reporter.py (original) +++ py/dist/py/test/report/text/reporter.py Sun Oct 24 16:42:27 2004 @@ -1,21 +1,27 @@ from __future__ import generators import py +Item = py.test.Item +Collector = py.test.collect.Collector -from py.__impl__.test import run from time import time as now # lazy relative Implementation imports from summary import Summary from out import getout + class TextReporter(object): Summary = Summary typemap = { - run.Passed: '.', run.Skipped: 's', run.Failed: 'F', - py.test.collect.Error: 'C', + Item.Passed: '.', + Item.Skipped: 's', + Item.Failed: 'F', + Collector.Error: 'C', } namemap = { - run.Passed: 'ok', run.Skipped: 'SKIP', run.Failed: 'FAIL', - py.test.collect.Error: 'COLLECT ERROR', + Item.Passed: 'ok', + Item.Skipped: 'SKIP', + Item.Failed: 'FAIL', + Collector.Error: 'COLLECT ERROR', } def __init__(self, f=None): @@ -66,7 +72,7 @@ numunits = len(list(collector.iterunits())) if numunits > 0: #if verbose < 2: - # self.out.write('%s[%d]' % (collector.pypath, + # self.out.write('%s[%d]' % (collector.extpy, # numunits)) #else: #self.out.line("collector.fspy.modpath) @@ -92,8 +98,8 @@ from py.__impl__.test.tool.outerrcapture import SimpleOutErrCapture item.iocapture = SimpleOutErrCapture() if self.out.tty: - realpath, lineno = item.pypath.getfilelineno() - location = "running %s:%d %s" % (realpath.basename, lineno, str(item.pypath.modpath)) + realpath, lineno = item.extpy.getfilelineno() + location = "running %s:%d %s" % (realpath.basename, lineno, str(item.extpy.modpath)) self.out.rewrite(location) self._started[item] = now() @@ -111,9 +117,9 @@ restype, c = self.processresult(result) writeinfo = None if self.out.tty: - if not isinstance(result, run.Passed) or self.option.verbose >=1: + if not isinstance(result, py.test.Item.Passed) or self.option.verbose >=1: writeinfo = '\n' - elif isinstance(result, run.Passed): + elif isinstance(result, py.test.Item.Passed): writeinfo = '' elif self.option.verbose >= 1: writeinfo = '\n' @@ -121,15 +127,15 @@ self.out.write(c) if writeinfo is not None: - realpath, lineno = item.pypath.getfilelineno() + realpath, lineno = item.extpy.getfilelineno() location = "%s:%d" % (realpath.basename, lineno) resultstring = self.namemap.get(restype, result.__class__.__name__) self.out.rewrite("%.3f %-2s %-20s %s%s" % ( - elapsed, resultstring, location, str(item.pypath.modpath), writeinfo + elapsed, resultstring, location, str(item.extpy.modpath), writeinfo )) if self.option.usepdb: - if (issubclass(restype, py.test.collect.Error) or - issubclass(restype, run.Failed)): + if (issubclass(restype, Collector.Error) or + issubclass(restype, Item.Failed)): import pdb self.out.rewrite( '\n%s: %s\n' @@ -137,17 +143,17 @@ result.excinfo[1])) pdb.post_mortem(result.excinfo[2]) if self.option.exitfirstproblem: - if (issubclass(restype, py.test.collect.Error) or - issubclass(restype, run.Failed)): - raise run.Exit("first problem, exit configured.") + if (issubclass(restype, Collector.Error) or + issubclass(restype, Item.Failed)): + py.test.exit("first problem, exit configured.") def report_collect_error(self, error): restype, c = self.processresult(error) writeinfo = None if self.out.tty: - if not isinstance(result, run.Passed) or self.option.verbose >=1: + if not isinstance(result, Item.Passed) or self.option.verbose >=1: writeinfo = '\n' - elif isinstance(result, run.Passed): + elif isinstance(result, Item.Passed): writeinfo = '' elif self.option.verbose >= 1: writeinfo = '\n' Modified: py/dist/py/test/report/text/summary.py ============================================================================== --- py/dist/py/test/report/text/summary.py (original) +++ py/dist/py/test/report/text/summary.py Sun Oct 24 16:42:27 2004 @@ -1,6 +1,6 @@ from __future__ import generators import py -from py.__impl__.test import run +Item = py.test.Item from py.__impl__.magic import exprinfo, assertion class Summary(object): @@ -45,7 +45,7 @@ self.summary_collect_errors() outlist = [] sum = 0 - for typ in run.Passed, run.Failed, run.Skipped: + for typ in Item.Passed, Item.Failed, Item.Skipped: l = self.getlist(typ) outlist.append('%d %s' % (len(l), typ.__name__.lower())) sum += len(l) @@ -62,8 +62,9 @@ def skippedreasons(self): d = {} - for res in self.getlist(run.Skipped): - raisingtb = py.magic.dyncode.listtb(res.excinfo[2])[-1] + for res in self.getlist(Item.Skipped): + tbindex = getattr(res, 'tbindex', -1) + raisingtb = py.magic.dyncode.listtb(res.excinfo[2])[tbindex] fn = raisingtb.tb_frame.f_code.co_filename lineno = raisingtb.tb_lineno d[(fn,lineno)] = res @@ -75,7 +76,7 @@ self.out.line() def failures(self): - for res in self.getlist(run.Failed): + for res in self.getlist(Item.Failed): self.repr_failure(res) def repr_failure(self, res): @@ -83,7 +84,7 @@ self.out.line() self.out.sep("_") self.out.line() - #self.out.sep("_", "Test Failure") # %s" % res.unit.pypath) + #self.out.sep("_", "Test Failure") # %s" % res.unit.extpy) #self.out.sep("_") #self.out.line() self.repr_traceback(res.item, res.excinfo[2], @@ -105,7 +106,7 @@ def repr_failure_result(self, res): cls = res.excinfo[0] - if issubclass(cls, run.ExceptionFailure): + if issubclass(cls, Item.ExceptionFailure): if not res.innerexcinfo: self.out.line("%s <<< DID NOT RAISE" % (res.expr,)) self.out.line("expected: %r" % (res.expected,)) @@ -184,14 +185,14 @@ return old def repr_traceback(self, item, tb, tbindex=-1): - t_file, t_lineno = item.pypath.getfilelineno() - self.out.line("%s, line %d" % (item.pypath, t_lineno) ) - fspath = item.pypath.root + t_file, t_lineno = item.extpy.getfilelineno() + self.out.line("%s, line %d" % (item.extpy, t_lineno) ) + fspath = item.extpy.root self.out.sep('_') self.repr_traceback_raw(fspath, tb, tbindex) - #t_file, t_lineno = unit.pypath.getfilelineno() + #t_file, t_lineno = unit.extpy.getfilelineno() #if t_file != filename or lineno != t_lineno: - #self.out.line("%s, line %d" % (unit.pypath, t_lineno) ) + #self.out.line("%s, line %d" % (unit.extpy, t_lineno) ) self.out.sep('-') def repr_traceback_raw(self, fspath, tb, tbindex=-1): Modified: py/dist/py/test/run.py ============================================================================== --- py/dist/py/test/run.py (original) +++ py/dist/py/test/run.py Sun Oct 24 16:42:27 2004 @@ -1,33 +1,58 @@ from __future__ import generators import py +collect = py.test.collect class Exit(Exception): - """ for immediate program exits without tracebacks. """ + """ for immediate program exits without tracebacks and reporter/summary. """ + +def exit(*args): + raise Exit(*args) + +def skip(msg="unknown reason"): + """ skip with the given Message. """ + raise py.test.Item.Skipped(msg=msg, tbindex=-2) + +def fail(msg="unknown failure"): + """ fail with the given Message. """ + raise py.test.Item.Failed(msg=msg, tbindex=-2) class Driver: option = py.test.config.option Exit = Exit - def __init__(self, reporter): - self.reporter = reporter + def __init__(self, channel): + self.reporter = py.test.config.getfirst('getreporter') () self._setupstack = [] self._instance = None + self._channel = channel - def run(self, obj): - raise ValueError + def run(self, collectors): + """ main loop for running tests. """ + self.setup() + try: + self.reporter.start() + try: + for x in collectors: + self.run_collector_or_item(x) + except self.Exit: + pass + finally: + self.teardown() + self.reporter.end() def run_collector_or_item(self, obj): """ run (possibly many) testitems and/or collectors. """ - collect = py.test.collect - if isinstance(obj, Item): + if self._channel.isclosed(): + raise SystemExit, "Channel is closed" + if isinstance(obj, py.test.Item): if not self.option.collectonly: #if self.option.args: # if str(obj.path).find(self.option.args[0]) == -1: # return self.runitem(obj) - elif isinstance(obj, collect.Collector): + elif isinstance(obj, py.test.collect.Collector): self.runcollector(obj) - elif isinstance(obj, collect.Error): + elif isinstance(obj, py.test.collect.Collector.Error): try: self.reporter.report_collect_error(obj) except (KeyboardInterrupt, SystemExit): @@ -56,41 +81,47 @@ def runitem(self, item): self.reporter.startitem(item) try: - res = item.execute(self) or Passed() - except Outcome, res: + res = item.execute(self) or item.Passed() + except item.Outcome, res: res.excinfo = py.std.sys.exc_info() except (KeyboardInterrupt, SystemExit): raise except: - res = Failed(excinfo=py.std.sys.exc_info()) + res = item.Failed(excinfo=py.std.sys.exc_info()) + if not isinstance(res, item.Passed): + self._failed.append(item) res.item = item self.reporter.enditem(res) - def setup_path(self, pypath): + def setup_path(self, extpy): """ setup objects along the path to the test-method - (pointed to by pypath). Tear down any previously + (pointed to by extpy). Tear down any previously setup objects which are not directly needed. """ - # setupstack contains (pypath, obj)'s of already setup objects - # strict ordering is maintained, i.e. each pypath in - # the stack is "relto" the previous pypath. + # setupstack contains (extpy, obj)'s of already setup objects + # strict ordering is maintained, i.e. each extpy in + # the stack is "relto" the previous extpy. stack = self._setupstack - while stack and not pypath.relto(stack[-1][0]): + while stack and not extpy.relto(stack[-1][0]): self._teardownone(stack.pop()[1]) - rest = pypath.parts()[len(stack):-1] + rest = extpy.parts()[len(stack):-1] for x in rest: stack.append((x, self._setupone(x))) + def getfailed(self): + return self._failed + def setup(self): """ setup any neccessary resources. """ + self._failed = [] def teardown(self): """ teardown any resources the driver knows about. """ while self._setupstack: self._teardownone(self._setupstack.pop()[1]) - def _setupone(self, pypath): - obj = pypath.resolve() + def _setupone(self, extpy): + obj = extpy.resolve() if py.std.inspect.ismodule(obj): if hasattr(obj, 'setup_module'): obj.setup_module(obj) @@ -107,9 +138,9 @@ if hasattr(obj, 'teardown_class'): obj.teardown_class.im_func(obj) - def setup_method(self, pypath): + def setup_method(self, extpy): """ return a tuple of (bound method or callable, teardown method). """ - method = pypath.resolve() + method = extpy.resolve() if not hasattr(method, 'im_class'): return method, None if self._instance.__class__ != method.im_class: @@ -119,38 +150,3 @@ print "execting setup", self._instance.setup_method self._instance.setup_method(method) return (method, getattr(self._instance, 'teardown_method', None)) - -# ---------------------------------------------- -# Basic Test Unit Executor -# ---------------------------------------------- -class Item(object): - _setupcache = [] - _lastinstance = None - - def __init__(self, pypath, *args): - self.pypath = pypath - self.name = pypath.basename - self.args = args - - def execute(self, driver): - driver.setup_path(self.pypath) - target, teardown = driver.setup_method(self.pypath) - try: - target(*self.args) - finally: - if teardown: - teardown(target) - -class Outcome: - def __init__(self, **kwargs): - assert 'msg' not in kwargs or isinstance(kwargs['msg'], str), ( - "given 'msg' argument is not a string" ) - self.__dict__.update(kwargs) - def __repr__(self): - return getattr(self, 'msg', object.__repr__(self)) -class Passed(Outcome): pass -class Failed(Outcome): pass -class ExceptionFailure(Failed): pass -class Skipped(Outcome): pass - - Modified: py/dist/py/test/test_compat.py ============================================================================== --- py/dist/py/test/test_compat.py (original) +++ py/dist/py/test/test_compat.py Sun Oct 24 16:42:27 2004 @@ -44,7 +44,7 @@ #self.%(name)s(%(paramfail)s) def test_%(name)s_failing(self): - self.assertRaises(py.test.Failed, + self.assertRaises(py.test.Item.Failed, self.%(name)s, %(paramfail)s) """ % locals() co = py.magic.dyncode.compile2(source) From hpk at codespeak.net Sun Oct 24 18:31:35 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 24 Oct 2004 18:31:35 +0200 (MEST) Subject: [py-svn] r7114 - py/dist/py/execnet Message-ID: <20041024163135.57E275B546@thoth.codespeak.net> Author: hpk Date: Sun Oct 24 18:31:34 2004 New Revision: 7114 Added: py/dist/py/execnet/message.py Modified: py/dist/py/execnet/channel.py py/dist/py/execnet/gateway.py py/dist/py/execnet/register.py py/dist/py/execnet/test_gateway.py Log: refactored the execnet implementation into multiple files. Now 'Message' encapsulates the complete wire protocol. shutdown works better but KeyboardInterrupt sometimes doesn't stop reliably and cleanly ... Modified: py/dist/py/execnet/channel.py ============================================================================== --- py/dist/py/execnet/channel.py (original) +++ py/dist/py/execnet/channel.py Sun Oct 24 18:31:34 2004 @@ -1,4 +1,9 @@ +import threading +import Queue +if 'Message' not in globals(): + from py.__impl__.execnet.message import Message + class ChannelFile: def __init__(self, channel): self.channel = channel @@ -28,4 +33,148 @@ break f.write(out) f.flush() + +class Channel(object): + """Communication channel between two possibly remote threads of code. """ + def __init__(self, gateway, id): + assert isinstance(id, int) + self.gateway = gateway + self.id = id + self._items = Queue.Queue() + self._closeevent = threading.Event() + self._depchannel = [] + + def __repr__(self): + flag = self.isclosed() and "closed" or "open" + return "" % (self.id, flag) + + def isclosed(self): + return self._closeevent.isSet() + + def close(self, error=None): + """ close down this channel on both sides. """ + if self.id not in self.gateway.channelfactory: + return + put = self.gateway._outgoing.put + if error is not None: + put(Message.CHANNEL_CLOSE_ERROR(self.id, str(error))) + else: + put(Message.CHANNEL_CLOSE(self.id)) + self._close() + + def _close(self, finalitem=EOFError()): + if self.id in self.gateway.channelfactory: + del self.gateway.channelfactory[self.id] + self._finalitem = finalitem + for x in self._depchannel: + x._close() + self._items.put(finalitem) + self._closeevent.set() + + def newchannel(self): + """ return a new channel whose life cycle depends on this channel. """ + chan = self.gateway.channelfactory.new() + self._depchannel.append(chan) + return chan + + def _receivechannel(self, newid): + """ receive a remotely created new (sub)channel. """ + newchannel = Channel(self.gateway, newid) + self.gateway.channelfactory[newid] = newchannel + self._depchannel.append(newchannel) + self._items.put(newchannel) + + def waitclose(self, timeout): + """ wait until this channel is closed. Note that a closed + channel may still hold items that will be received or + send. Note also that exceptions from the other side will be + reraised as gateway.ExecutionFailed exceptions containing + a textual representation of the remote traceback. + """ + self._closeevent.wait(timeout=timeout) + if not self._closeevent.isSet(): + raise IOError, "Timeout" + if isinstance(self._finalitem, self.gateway.RemoteError): + raise self._finalitem + + def send(self, item): + """sends the given item to the other side of the channel, + possibly blocking if the sender queue is full. + Note that an item needs to be marshallable. + """ + if isinstance(item, Channel): + data = Message.CHANNEL_NEW(self.id, item.id) + else: + data = Message.CHANNEL_DATA(self.id, item) + self.gateway._outgoing.put(data) + + def receive(self): + """receives an item that was sent from the other side, + possibly blocking if there is none. + Note that exceptions from the other side will be + reraised as gateway.ExecutionFailed exceptions containing + a textual representation of the remote traceback. + """ + x = self._items.get() + if isinstance(x, EOFError): + raise x + return x + +# +# helpers +# + +class ChannelFactory(object): + def __init__(self, gateway, startcount=1): + self._dict = dict() + self._lock = threading.RLock() + self.gateway = gateway + self.count = startcount + + def new(self, id=None): + """ create a new Channel with 'id' (or create new id if None). """ + self._lock.acquire() + try: + if id is None: + id = self.count + self.count += 2 + channel = Channel(self.gateway, id) + self._dict[id] = channel + return channel + finally: + self._lock.release() + + def __contains__(self, key): + self._lock.acquire() + try: + return key in self._dict + finally: + self._lock.release() + + def values(self): + self._lock.acquire() + try: + return self._dict.values() + finally: + self._lock.release() + + def __getitem__(self, key): + self._lock.acquire() + try: + return self._dict[key] + finally: + self._lock.release() + def __setitem__(self, key, value): + self._lock.acquire() + try: + self._dict[key] = value + finally: + self._lock.release() + def __delitem__(self, key): + self._lock.acquire() + try: + del self._dict[key] + finally: + self._lock.release() + Modified: py/dist/py/execnet/gateway.py ============================================================================== --- py/dist/py/execnet/gateway.py (original) +++ py/dist/py/execnet/gateway.py Sun Oct 24 18:31:34 2004 @@ -1,11 +1,21 @@ -import sys, os, threading, struct, Queue, traceback - -import py +import sys +import os +import threading +import Queue +import traceback +import atexit # XXX the following line should not be here -from py.__impl__.execnet.source import Source +g = globals() +if 'Source' not in g: + from py.__impl__.execnet.source import Source + from py.__impl__.execnet.channel import ChannelFactory, Channel + from py.__impl__.execnet.message import Message + +assert Message and Source and ChannelFactory, "Import/Configuration Error" + +debug = open('/tmp/execnet-debug', 'wa') -debug = 0 sysex = (KeyboardInterrupt, SystemExit) class RemoteError(EOFError): @@ -40,7 +50,7 @@ for x in self.iothreads + w: x.start() if not _gateways: - py.std.atexit.register(cleanup_atexit) + atexit.register(cleanup_atexit) _gateways.append(self) def _stopexec(self): @@ -64,7 +74,7 @@ current = threading.currentThread() for x in self.iothreads: if x != current and x.isAlive(): - print "joining", x + self.trace("joining %s" % x) x.join() def trace(self, *args): @@ -73,11 +83,11 @@ l = "\n".join(args).split(os.linesep) id = getid(self) for x in l: - print id, x + print >>debug, x + debug.flush() except sysex: raise except: - import traceback traceback.print_exc() def traceex(self, excinfo): l = traceback.format_exception(*excinfo) @@ -162,252 +172,6 @@ self._outgoing.put(Message.CHANNEL_OPEN(channel.id, source)) return channel -class Channel(object): - """Communication channel between two possibly remote threads of code. """ - def __init__(self, gateway, id): - assert isinstance(id, int) - self.gateway = gateway - self.id = id - self._items = Queue.Queue() - self._closeevent = threading.Event() - self._depchannel = [] - - def __repr__(self): - flag = self.isclosed() and "closed" or "open" - return "" % (self.id, flag) - - def isclosed(self): - return self._closeevent.isSet() - - def close(self, error=None): - """ close down this channel on both sides. """ - put = self.gateway._outgoing.put - if error is not None: - put(Message.CHANNEL_CLOSE_ERROR(self.id, error)) - else: - put(Message.CHANNEL_CLOSE(self.id)) - self._close() - - def _close(self, finalitem=EOFError()): - self._finalitem = finalitem - for x in self._depchannel: - x._close() - del self.gateway.channelfactory[self.id] - self._items.put(finalitem) - self._closeevent.set() - - def newchannel(self): - """ return a new channel whose life cycle depends on this channel. """ - chan = self.gateway.channelfactory.new() - self._depchannel.append(chan) - return chan - - def _receivechannel(self, newid): - """ receive a remotely created new (sub)channel. """ - newchannel = Channel(self.gateway, newid) - self.gateway.channelfactory[newid] = newchannel - self._depchannel.append(newchannel) - self._items.put(newchannel) - - def waitclose(self, timeout): - """ wait until this channel is closed. Note that a closed - channel may still hold items that will be received or - send. Note also that exceptions from the other side will be - reraised as gateway.ExecutionFailed exceptions containing - a textual representation of the remote traceback. - """ - self._closeevent.wait(timeout=timeout) - if not self._closeevent.isSet(): - raise IOError, "Timeout" - if isinstance(self._finalitem, RemoteError): - raise self._finalitem - - def send(self, item): - """sends the given item to the other side of the channel, - possibly blocking if the sender queue is full. - Note that an item needs to be marshallable. - """ - if isinstance(item, Channel): - s = py.std.marshal.dumps(item.id) - data = Message.CHANNEL_NEW(self.id, s) - else: - s = py.std.marshal.dumps(item) - data = Message.CHANNEL_DATA(self.id, s) - self.gateway._outgoing.put(data) - - def receive(self): - """receives an item that was sent from the other side, - possibly blocking if there is none. - Note that exceptions from the other side will be - reraised as gateway.ExecutionFailed exceptions containing - a textual representation of the remote traceback. - """ - x = self._items.get() - if isinstance(x, EOFError): - raise x - return x - -# -# helpers -# - -class ChannelFactory(object): - def __init__(self, gateway, startcount=1): - self._dict = dict() - self._lock = threading.RLock() - self.gateway = gateway - self.count = startcount - - def new(self): - self._lock.acquire() - try: - channel = Channel(self.gateway, self.count) - self._dict[self.count] = channel - return channel - finally: - self.count += 2 - self._lock.release() - - def __contains__(self, key): - self._lock.acquire() - try: - return key in self._dict - finally: - self._lock.release() - - def __getitem__(self, key): - self._lock.acquire() - try: - return self._dict[key] - finally: - self._lock.release() - def __setitem__(self, key, value): - self._lock.acquire() - try: - self._dict[key] = value - finally: - self._lock.release() - def __delitem__(self, key): - self._lock.acquire() - try: - del self._dict[key] - finally: - self._lock.release() - -# ___________________________________________________________________________ -# -# Messages -# ___________________________________________________________________________ -# the size of a number on the wire -numsize = struct.calcsize("!i") -# header of a packet -# int message_type: 0==exitgateway, -# 1==channelfinished_ok, -# 2==channelfinished_err, -# 3==channelopen # executes source code -# 4==channelsend # marshals obj -class Message: - """ encapsulates Messages and their wire protocol. """ - _types = {} - def __init__(self, channelid=0, data=''): - self.channelid = channelid - self.data = str(data) - - def writeto(self, io): - data = str(self.data) - header = struct.pack("!iii", self.msgtype, self.channelid, len(data)) - io.write(header) - io.write(data) - - def readfrom(cls, io): - header = io.read(numsize*3) - msgtype, senderid, stringlen = struct.unpack("!iii", header) - if stringlen: - string = io.read(stringlen) - else: - string = '' - msg = cls._types[msgtype](senderid, string) - return msg - readfrom = classmethod(readfrom) - - def post_sent(self, gateway, excinfo=None): - pass - - def __repr__(self): - if len(self.data) > 50: - return "" %(self.__class__.__name__, - self.channelid, len(self.data)) - else: - return "" %(self.__class__.__name__, - self.channelid, self.data) - - -def _setupmessages(): - # - # EXIT_GATEWAY and STOP_RECEIVING are messages to cleanly - # bring down the IO and gateway connection - # - # First an EXIT_GATEWAY message is send which results - # on the other side's receive_handle to send send - # a STOP_RECEIVING message - # - class EXIT_GATEWAY(Message): - def received(self, gateway): - gateway._stopexec() - gateway._outgoing.put(self.STOP_RECEIVING()) - raise SystemExit - def post_sent(self, gateway, excinfo=None): - gateway.io.close_write() - raise SystemExit - class STOP_RECEIVING(Message): - def received(self, gateway): - # note that we don't need to close io.close_read() - # as the sender side will have closed the io - # already. With sockets closing it would raise - # a Transport Not Connected exception - raise SystemExit - def post_sent(self, gateway, excinfo=None): - gateway.io.close_write() - raise SystemExit - - class CHANNEL_OPEN(Message): - def received(self, gateway): - channel = Channel(gateway, self.channelid) - gateway.channelfactory[self.channelid] = channel - gateway._execqueue.put((channel, self.data)) - - class CHANNEL_NEW(Message): - def received(self, gateway): - newid = py.std.marshal.loads(self.data) - channel = gateway.channelfactory[self.channelid] - channel._receivechannel(newid) - - class CHANNEL_DATA(Message): - def received(self, gateway): - channel = gateway.channelfactory[self.channelid] - x = py.std.marshal.loads(self.data) - channel._items.put(x) - - class CHANNEL_CLOSE(Message): - def received(self, gateway): - channel = gateway.channelfactory[self.channelid] - channel._close() - class CHANNEL_CLOSE_ERROR(Message): - def received(self, gateway): - channel = gateway.channelfactory[self.channelid] - channel._close(RemoteError(self.data)) - classes = [x for x in locals().values() if hasattr(x, '__bases__')] - classes.sort(lambda x,y : cmp(x.__name__, y.__name__)) - i = 0 - for cls in classes: - Message._types[i] = cls - cls.msgtype = i - setattr(Message, cls.__name__, cls) - i+=1 - -_setupmessages() - - def getid(gw, cache={}): name = gw.__class__.__name__ try: @@ -418,7 +182,9 @@ _gateways = [] def cleanup_atexit(): - print "="*20 + "cleaning up" + "=" * 20 + if debug: + print >>debug, "="*20 + "cleaning up" + "=" * 20 + debug.flush() for x in _gateways: if x.workerthreads: x.exit() Added: py/dist/py/execnet/message.py ============================================================================== --- (empty file) +++ py/dist/py/execnet/message.py Sun Oct 24 18:31:34 2004 @@ -0,0 +1,115 @@ +import struct +import marshal + +# ___________________________________________________________________________ +# +# Messages +# ___________________________________________________________________________ +# the size of a number on the wire +numsize = struct.calcsize("!i") + +class Message: + """ encapsulates Messages and their wire protocol. """ + _types = {} + def __init__(self, channelid=0, data=''): + self.channelid = channelid + self.data = data + + def writeto(self, io): + data = marshal.dumps(self.data) + header = struct.pack("!iii", self.msgtype, self.channelid, len(data)) + io.write(header) + io.write(data) + + def readfrom(cls, io): + header = io.read(numsize*3) + msgtype, senderid, stringlen = struct.unpack("!iii", header) + if stringlen: + string = io.read(stringlen) + else: + string = '' + string = marshal.loads(string) + msg = cls._types[msgtype](senderid, string) + return msg + readfrom = classmethod(readfrom) + + def post_sent(self, gateway, excinfo=None): + pass + + def __repr__(self): + r = repr(self.data) + if len(r) > 50: + return "" %(self.__class__.__name__, + self.channelid, len(r)) + else: + return "" %(self.__class__.__name__, + self.channelid, self.data) + + +def _setupmessages(): + # + # EXIT_GATEWAY and STOP_RECEIVING are messages to cleanly + # bring down the IO and gateway connection + # + # First an EXIT_GATEWAY message is send which results + # on the other side's receive_handle to send send + # a STOP_RECEIVING message + # + class EXIT_GATEWAY(Message): + def received(self, gateway): + gateway._stopexec() + gateway._outgoing.put(self.STOP_RECEIVING()) + for x in gateway.channelfactory.values(): + x._close() + raise SystemExit + def post_sent(self, gateway, excinfo=None): + gateway.io.close_write() + raise SystemExit + class STOP_RECEIVING(Message): + def received(self, gateway): + # note that we don't need to close io.close_read() + # as the sender side will have closed the io + # already. With sockets closing it would raise + # a Transport Not Connected exception + for x in gateway.channelfactory.values(): + x._close() + raise SystemExit + def post_sent(self, gateway, excinfo=None): + gateway.io.close_write() + raise SystemExit + + class CHANNEL_OPEN(Message): + def received(self, gateway): + channel = gateway.channelfactory.new(self.channelid) + gateway._execqueue.put((channel, self.data)) + + class CHANNEL_NEW(Message): + def received(self, gateway): + newid = self.data + channel = gateway.channelfactory[self.channelid] + channel._receivechannel(newid) + + class CHANNEL_DATA(Message): + def received(self, gateway): + channel = gateway.channelfactory[self.channelid] + channel._items.put(self.data) + + class CHANNEL_CLOSE(Message): + def received(self, gateway): + channel = gateway.channelfactory[self.channelid] + channel._close() + class CHANNEL_CLOSE_ERROR(Message): + def received(self, gateway): + channel = gateway.channelfactory[self.channelid] + channel._close(gateway.RemoteError(self.data)) + classes = [x for x in locals().values() if hasattr(x, '__bases__')] + classes.sort(lambda x,y : cmp(x.__name__, y.__name__)) + i = 0 + for cls in classes: + Message._types[i] = cls + cls.msgtype = i + setattr(Message, cls.__name__, cls) + i+=1 + +_setupmessages() + Modified: py/dist/py/execnet/register.py ============================================================================== --- py/dist/py/execnet/register.py (original) +++ py/dist/py/execnet/register.py Sun Oct 24 18:31:34 2004 @@ -4,7 +4,7 @@ import sys import py -from py.__impl__.execnet import inputoutput, gateway +from py.__impl__.execnet import inputoutput, gateway, channel, message class InstallableGateway(gateway.Gateway): """ initialize gateways on both sides of a inputoutput object. """ @@ -22,6 +22,8 @@ """ bootstrap = [ inspect.getsource(inputoutput), + inspect.getsource(message), + inspect.getsource(channel), inspect.getsource(gateway), io.server_stmt, "Gateway(io=io, startcount=2).join()", Modified: py/dist/py/execnet/test_gateway.py ============================================================================== --- py/dist/py/execnet/test_gateway.py (original) +++ py/dist/py/execnet/test_gateway.py Sun Oct 24 18:31:34 2004 @@ -80,8 +80,6 @@ def test_channel_close_and_then_receive_error_multiple(self): channel = self.gw.remote_exec('channel.send(42) ; raise ValueError') - import time - time.sleep(0.1) x = channel.receive() assert x == 42 py.test.raises(gateway.RemoteError, channel.receive) From hpk at codespeak.net Sun Oct 24 18:47:23 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 24 Oct 2004 18:47:23 +0200 (MEST) Subject: [py-svn] r7115 - in py/dist/py: execnet test Message-ID: <20041024164723.D46255B546@thoth.codespeak.net> Author: hpk Date: Sun Oct 24 18:47:23 2004 New Revision: 7115 Modified: py/dist/py/execnet/channel.py py/dist/py/test/cmdline.py Log: KeyboardInterrupt works 99% (it's hard to test for the rest) Modified: py/dist/py/execnet/channel.py ============================================================================== --- py/dist/py/execnet/channel.py (original) +++ py/dist/py/execnet/channel.py Sun Oct 24 18:47:23 2004 @@ -10,15 +10,13 @@ def write(self, out): if self.channel.isclosed(): - raise IOError, "cannot write to %r" % self + return # raise IOError, "cannot write to %r" % self self.channel.send(out) def flush(self): pass def close(self): - if self.channel.isclosed(): - raise IOError, "cannot close %r" % self self.channel.close() def __repr__(self): Modified: py/dist/py/test/cmdline.py ============================================================================== --- py/dist/py/test/cmdline.py (original) +++ py/dist/py/test/cmdline.py Sun Oct 24 18:47:23 2004 @@ -65,6 +65,9 @@ if not filenames: filenames.append(str(py.path.local())) + master(args, filenames) + +def master(args, filenames): gw = py.execnet.PopenGateway() channel = gw.remote_exec(""" from py.__impl__.test.cmdline import slave From hpk at codespeak.net Mon Oct 25 22:05:15 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 25 Oct 2004 22:05:15 +0200 (MEST) Subject: [py-svn] r7120 - in py/dist: . doc py py/execnet py/test py/test/report/text Message-ID: <20041025200515.38CEE5B546@thoth.codespeak.net> Author: hpk Date: Mon Oct 25 22:05:14 2004 New Revision: 7120 Added: py/dist/pytestconf.py - copied, changed from r7106, py/dist/py/pytest.py Removed: py/dist/py/pytest.py Modified: py/dist/ (props changed) py/dist/doc/rest_test.py py/dist/py/execnet/channel.py py/dist/py/execnet/gateway.py py/dist/py/execnet/register.py py/dist/py/execnet/test_gateway.py py/dist/py/test/cmdline.py py/dist/py/test/config.py py/dist/py/test/defaultconfig.py py/dist/py/test/report/text/reporter.py py/dist/py/test/run.py py/dist/py/test/test_config.py Log: - first implementation of "py.test --session" mode which lets you rerun failing tests until they are resolved. If all tests pass the session is finished. - a medium size fight at terminating correctly with no left-over processes when you have a KeyboardIntterupt there are still some cases where a process is left over. humpf. but it often works so it's probably some race condition. Welcome to threads and signals. - some internal renaming to make things more consistent 'py.path.extpy()' paths are now generally abbreviated 'extpy' for e.g. instance attributes. Modified: py/dist/doc/rest_test.py ============================================================================== --- py/dist/doc/rest_test.py (original) +++ py/dist/doc/rest_test.py Mon Oct 25 22:05:14 2004 @@ -7,9 +7,9 @@ rest = mydir.dirpath('tool', 'rest.py') class RestItem(py.test.Item): - def __init__(self, path): + def __init__(self, path): self.path = path - self.pypath = py.path.extpy(mypath, 'RestItem.execute') + self.extpy = py.path.extpy(mypath, 'RestItem.execute') def execute(self, *args): out = py.process.cmdexec("%s %s 2>&1" %(rest, self.path)) Modified: py/dist/py/execnet/channel.py ============================================================================== --- py/dist/py/execnet/channel.py (original) +++ py/dist/py/execnet/channel.py Mon Oct 25 22:05:14 2004 @@ -1,37 +1,8 @@ - import threading import Queue if 'Message' not in globals(): from py.__impl__.execnet.message import Message -class ChannelFile: - def __init__(self, channel): - self.channel = channel - - def write(self, out): - if self.channel.isclosed(): - return # raise IOError, "cannot write to %r" % self - self.channel.send(out) - - def flush(self): - pass - - def close(self): - self.channel.close() - - def __repr__(self): - state = self.channel.isclosed() and 'closed' or 'open' - return '' %(self.channel.id, state) - -def receive2file(channel, f): - while 1: - try: - out = channel.receive() - except EOFError: - break - f.write(out) - f.flush() - class Channel(object): """Communication channel between two possibly remote threads of code. """ def __init__(self, gateway, id): @@ -40,7 +11,7 @@ self.id = id self._items = Queue.Queue() self._closeevent = threading.Event() - self._depchannel = [] + #self._depchannel = [] def __repr__(self): flag = self.isclosed() and "closed" or "open" @@ -49,43 +20,45 @@ def isclosed(self): return self._closeevent.isSet() + def open(self, mode='w'): + assert mode == 'w' + return ChannelFile(self) + def close(self, error=None): """ close down this channel on both sides. """ - if self.id not in self.gateway.channelfactory: - return - put = self.gateway._outgoing.put - if error is not None: - put(Message.CHANNEL_CLOSE_ERROR(self.id, str(error))) - else: - put(Message.CHANNEL_CLOSE(self.id)) - self._close() + if self.id in self.gateway.channelfactory: + put = self.gateway._outgoing.put + if error is not None: + put(Message.CHANNEL_CLOSE_ERROR(self.id, str(error))) + else: + put(Message.CHANNEL_CLOSE(self.id)) + self._close() def _close(self, finalitem=EOFError()): if self.id in self.gateway.channelfactory: del self.gateway.channelfactory[self.id] - self._finalitem = finalitem - for x in self._depchannel: - x._close() - self._items.put(finalitem) - self._closeevent.set() + self._finalitem = finalitem + #for x in self._depchannel: + # x._close() + self._items.put(finalitem) + self._closeevent.set() def newchannel(self): """ return a new channel whose life cycle depends on this channel. """ chan = self.gateway.channelfactory.new() - self._depchannel.append(chan) return chan def _receivechannel(self, newid): """ receive a remotely created new (sub)channel. """ newchannel = Channel(self.gateway, newid) self.gateway.channelfactory[newid] = newchannel - self._depchannel.append(newchannel) + #self._depchannel.append(newchannel) self._items.put(newchannel) def waitclose(self, timeout): """ wait until this channel is closed. Note that a closed - channel may still hold items that will be received or - send. Note also that exceptions from the other side will be + channel may still hold items that can be received or + send. Also note that exceptions from the other side will be reraised as gateway.ExecutionFailed exceptions containing a textual representation of the remote traceback. """ @@ -175,4 +148,32 @@ finally: self._lock.release() - + +class ChannelFile: + def __init__(self, channel): + self.channel = channel + + def write(self, out): + if self.channel.isclosed(): + return + self.channel.send(out) + + def flush(self): + pass + + def close(self): + self.channel.close() + + def __repr__(self): + state = self.channel.isclosed() and 'closed' or 'open' + return '' %(self.channel.id, state) + +def receive2file(channel, f): + while 1: + try: + out = channel.receive() + except EOFError: + break + f.write(out) + f.flush() + Modified: py/dist/py/execnet/gateway.py ============================================================================== --- py/dist/py/execnet/gateway.py (original) +++ py/dist/py/execnet/gateway.py Mon Oct 25 22:05:14 2004 @@ -14,7 +14,8 @@ assert Message and Source and ChannelFactory, "Import/Configuration Error" -debug = open('/tmp/execnet-debug', 'wa') +import os +debug = 0 # open('/tmp/execnet-debug-%d' % os.getpid() , 'wa') sysex = (KeyboardInterrupt, SystemExit) @@ -39,6 +40,7 @@ self._execqueue = Queue.Queue() self._outgoing = Queue.Queue() self.channelfactory = ChannelFactory(self, startcount) + self._exitlock = threading.Lock() self.iothreads = [ threading.Thread(target=self.thread_receiver, name='receiver'), threading.Thread(target=self.thread_sender, name='sender'), @@ -62,13 +64,19 @@ self.trace("joining %r" % x) x.join() self.workerthreads[:] = [] + self.trace("workerthreads are empty now") def exit(self): - if self.workerthreads: - self._stopexec() - self._outgoing.put(Message.EXIT_GATEWAY()) - else: - self.trace("exit() called, but gateway has not threads anymore!") + self._exitlock.acquire() + try: + #for channel in self.channelfactory.values(): + # channel.close() + if self.workerthreads: + self._stopexec() + self._outgoing.put(Message.EXIT_GATEWAY()) + return True + finally: + self._exitlock.release() def join(self): current = threading.currentThread() @@ -186,5 +194,4 @@ print >>debug, "="*20 + "cleaning up" + "=" * 20 debug.flush() for x in _gateways: - if x.workerthreads: - x.exit() + x.exit() Modified: py/dist/py/execnet/register.py ============================================================================== --- py/dist/py/execnet/register.py (original) +++ py/dist/py/execnet/register.py Mon Oct 25 22:05:14 2004 @@ -41,10 +41,11 @@ self._pidchannel = self.remote_exec("import os ; channel.send(os.getpid())") def exit(self): - super(PopenGateway, self).exit() + if not super(PopenGateway, self).exit(): + return try: - self._pidchannel.waitclose(timeout=0.5) pid = self._pidchannel.receive() + self._pidchannel.waitclose(timeout=0.5) except IOError: self.trace("could not receive child PID") else: Modified: py/dist/py/execnet/test_gateway.py ============================================================================== --- py/dist/py/execnet/test_gateway.py (original) +++ py/dist/py/execnet/test_gateway.py Mon Oct 25 22:05:14 2004 @@ -107,11 +107,10 @@ # check that the both sides previous channels are really gone channel.waitclose(0.3) assert channel.id not in self.gw.channelfactory - assert c.id not in self.gw.channelfactory + #assert c.id not in self.gw.channelfactory newchan = self.gw.remote_exec(''' assert %d not in channel.gateway.channelfactory - assert %d not in channel.gateway.channelfactory - ''' % (c.id, channel.id)) + ''' % (channel.id)) newchan.waitclose(0.3) class TestBasicPopenGateway(PopenGatewayTestSetup, BasicRemoteExecution): Deleted: /py/dist/py/pytest.py ============================================================================== --- /py/dist/py/pytest.py Mon Oct 25 22:05:14 2004 +++ (empty file) @@ -1,20 +0,0 @@ -#pythonexecutables = ('python2.2', 'python2.3',) -#pythonexecutable = 'python2.2' - -# in the future we want to be able to say here: -#def setup_module(extpy): -# mod = extpy.resolve() -# mod.module = 23 -# directory = pypath.root.dirpath() - -import py -pkgdir = py.magic.autopath().pkgdir - -# default values for options (modified from cmdline) -verbose = 0 -nocapture = False -collectonly = False -exitfirstproblem = False -fulltrace = False -showlocals = False -nomagic = False Modified: py/dist/py/test/cmdline.py ============================================================================== --- py/dist/py/test/cmdline.py (original) +++ py/dist/py/test/cmdline.py Mon Oct 25 22:05:14 2004 @@ -6,7 +6,7 @@ # # main entry point # -configbasename = 'pytest.py' +configbasename = 'pytestconf.py' def old(argv): if argv is None: @@ -20,19 +20,19 @@ def waitfilechange(): """ wait until project files are changed. """ - pkgdir = py.test.config.getfirst('pkgdir') - pkgdir = py.path.local(pkgdir) + rootdir = py.test.config.getfirst('rootdir') + rootdir = py.path.local(rootdir) fil = py.path.checker(fnmatch='*.py') rec = py.path.checker(dotfile=0) statcache = {} - for path in pkgdir.visit(fil, rec): + for path in rootdir.visit(fil, rec): statcache[path] = path.stat() + print "waiting for file change below", str(rootdir) while 1: - py.std.time.sleep(0.2) - for path in pkgdir.visit(fil, rec): + py.std.time.sleep(0.4) + for path, cst in statcache.items(): st = path.stat() - cst = statcache[path] if st.st_mtime != cst.st_mtime or \ st.st_size != cst.st_size: return @@ -45,18 +45,6 @@ for x in self._faileditems: yield x -def master(): - #if not py.test.config.option.session: - # break - #l = driver.getfailed() - #if l: - # collectors = [FailingCollector(l)] - #elif isinstance(fncollectors[0], FailingCollector): - # collectors = fncollectors - #else: - # break - waitfilechange() - def main(): args = py.std.sys.argv[1:] py.test.config.readconfiguration(*getanchors(args)) @@ -65,54 +53,119 @@ if not filenames: filenames.append(str(py.path.local())) - master(args, filenames) + try: + while 1: + failures = master(args, filenames) + if not failures or not py.test.config.option.session: + break + while failures: + print "session mode: %d failures remaining" % len(failures) + waitfilechange() + failures = failure_master(args, filenames, failures) + except KeyboardInterrupt: + print + print "Keybordinterrupt" + raise SystemExit, 2 + +class StdouterrProxy: + def __init__(self, gateway): + self.gateway = gateway + def setup(self): + channel = self.gateway.remote_exec(""" + import sys + out, err = channel.newchannel(), channel.newchannel() + channel.send(out) + channel.send(err) + sys.stdout, sys.stderr = out.open('w'), err.open('w') + """) + self.stdout = channel.receive() + self.stderr = channel.receive() + channel.waitclose(1.0) + py.std.threading.Thread(target=receive2file, + args=(self.stdout, sys.stdout)).start() + py.std.threading.Thread(target=receive2file, + args=(self.stderr, sys.stderr)).start() + def teardown(self): + self.stdout.close() + self.stderr.close() + +def waitfinish(channel): + try: + while 1: + try: + channel.waitclose(0.1) + except IOError: + continue + else: + failures = channel.receive() + return failures + break + finally: + #print "closing down channel and gateway" + channel.close() + channel.gateway.exit() + +def failure_master(args, filenames, failures): + gw = py.execnet.PopenGateway() + outproxy = StdouterrProxy(gw) + outproxy.setup() + try: + channel = gw.remote_exec(""" + from py.__impl__.test.cmdline import failure_slave + failure_slave(channel) + """) + channel.send((args, filenames)) + channel.send(failures) + return waitfinish(channel) + finally: + outproxy.teardown() +def failure_slave(channel): + """ we run this on the other side. """ + args, filenames = channel.receive() + filenames = map(py.path.local, filenames) + py.test.config.readconfiguration(*filenames) + py.test.config.parseargs(args) + + failures = channel.receive() + col = FailureCollector(failures) + driver = py.test.Driver(channel) + failures = driver.run(col) + channel.send(failures) + +class FailureCollector(py.test.collect.Collector): + def __init__(self, failures): + self.failures = failures + def __iter__(self): + for root,modpath in self.failures: + extpy = py.path.extpy(root, modpath) + yield self.Item(extpy) + def master(args, filenames): gw = py.execnet.PopenGateway() - channel = gw.remote_exec(""" - from py.__impl__.test.cmdline import slave - slave(channel) - """) - print "sending (args, filenames)" - channel.send((args, filenames)) - print "receiving stdout/stderr" - stdout = channel.receive() - stderr = channel.receive() - py.std.threading.Thread(target=receive2file, - args=(stdout, sys.stdout)).start() - py.std.threading.Thread(target=receive2file, - args=(stderr, sys.stderr)).start() - print "waiting" - while 1: - try: - channel.waitclose(0.1) - except IOError: - continue - except KeyboardInterrupt: - print - print "keyboard interrupt" - except: - py.std.traceback.print_exc() - channel.close() - gw.exit() - break + outproxy = StdouterrProxy(gw) + outproxy.setup() + try: + channel = gw.remote_exec(""" + from py.__impl__.test.cmdline import slave + slave(channel) + """) + channel.send((args, filenames)) + return waitfinish(channel) + finally: + outproxy.teardown() def slave(channel): """ we run this on the other side. """ args, filenames = channel.receive() - out, err = channel.newchannel(), channel.newchannel() - channel.send(out) - channel.send(err) - sys.stdout = ChannelFile(out) - sys.stderr = ChannelFile(err) - filenames = map(py.path.local, filenames) py.test.config.readconfiguration(*filenames) py.test.config.parseargs(args) fncollectors = list(getcollectors(filenames)) driver = py.test.Driver(channel) - driver.run(fncollectors) + failures = driver.run(fncollectors) + channel.send(failures) def getcollectors(filenames): current = py.path.local() Modified: py/dist/py/test/config.py ============================================================================== --- py/dist/py/test/config.py (original) +++ py/dist/py/test/config.py Mon Oct 25 22:05:14 2004 @@ -10,7 +10,7 @@ # # config file handling (utest.conf) # -configbasename = 'pytest.py' +configbasename = 'pytestconf.py' class Config: def __init__(self): @@ -81,18 +81,6 @@ # setattr(self.option, name, cmdlineoption.__dict__[name]) return remaining - def restartpython(self): - # XXX better hack to restart with correct python version? - pythonexecutable = self.getfirst('pythonexecutable', None) - if pythonexecutable: - bn = py.path.local(py.std.sys.executable).basename - if bn != pythonexecutable: - # XXX shell escaping - print "restarting with", pythonexecutable - print "%s %s" % (pythonexecutable, " ".join(py.std.sys.argv[0:])) - py.std.os.system("%s %s" % ( - pythonexecutable, " ".join(py.std.sys.argv[0:]))) - return True config = Config() Modified: py/dist/py/test/defaultconfig.py ============================================================================== --- py/dist/py/test/defaultconfig.py (original) +++ py/dist/py/test/defaultconfig.py Mon Oct 25 22:05:14 2004 @@ -17,6 +17,9 @@ Option('-l', '--showlocals', action="store_true", dest="showlocals", default=False, help="show locals in tracebacks (disabled by default)"), + Option('', '--session', + action="store_true", dest="session", default=False, + help="run a test session/rerun only failing tests. "), Option('', '--fulltrace', action="store_true", dest="fulltrace", default=False, help="Don't try to cut any tracebacks (default is to cut)"), @@ -29,7 +32,4 @@ Option('', '--collectonly', action="store_true", dest="collectonly", default=False, help="only collect tests, don't execute them. "), - Option('', '--session', - action="store_true", dest="session", default=False, - help="run a test session to rerun only failing tests. "), ]) Modified: py/dist/py/test/report/text/reporter.py ============================================================================== --- py/dist/py/test/report/text/reporter.py (original) +++ py/dist/py/test/report/text/reporter.py Mon Oct 25 22:05:14 2004 @@ -142,10 +142,6 @@ % (result.excinfo[1].__class__.__name__, result.excinfo[1])) pdb.post_mortem(result.excinfo[2]) - if self.option.exitfirstproblem: - if (issubclass(restype, Collector.Error) or - issubclass(restype, Item.Failed)): - py.test.exit("first problem, exit configured.") def report_collect_error(self, error): restype, c = self.processresult(error) Modified: py/dist/py/test/run.py ============================================================================== --- py/dist/py/test/run.py (original) +++ py/dist/py/test/run.py Mon Oct 25 22:05:14 2004 @@ -2,8 +2,11 @@ import py collect = py.test.collect -class Exit(Exception): +class Exit(Exception): """ for immediate program exits without tracebacks and reporter/summary. """ + def __init__(self, item=None): + self.item = None + Exception.__init__(self) def exit(*args): raise Exit(*args) @@ -25,20 +28,25 @@ self._setupstack = [] self._instance = None self._channel = channel + self._failed = [] def run(self, collectors): """ main loop for running tests. """ self.setup() try: - self.reporter.start() try: + self.reporter.start() for x in collectors: self.run_collector_or_item(x) - except self.Exit: - pass - finally: - self.teardown() + finally: + self.teardown() + except self.Exit, ex: + pass self.reporter.end() + l = [] + for x in self._failed: + l.append((str(x.extpy.root), str(x.extpy.modpath))) + return l def run_collector_or_item(self, obj): """ run (possibly many) testitems and/or collectors. """ @@ -65,7 +73,7 @@ traceback.print_exc() raise SystemExit, 1 if self.option.exitfirstproblem: - raise SystemExit, 2 + raise self.Exit() else: raise TypeError("%r is not a Item or Collector instance" % obj) @@ -88,10 +96,12 @@ raise except: res = item.Failed(excinfo=py.std.sys.exc_info()) - if not isinstance(res, item.Passed): - self._failed.append(item) res.item = item self.reporter.enditem(res) + if isinstance(res, (item.Failed,)): + self._failed.append(item) + if py.test.config.option.exitfirstproblem: + raise self.Exit(res.item) def setup_path(self, extpy): """ setup objects along the path to the test-method @@ -108,12 +118,9 @@ for x in rest: stack.append((x, self._setupone(x))) - def getfailed(self): - return self._failed - def setup(self): """ setup any neccessary resources. """ - self._failed = [] + self._failed[:] = [] def teardown(self): """ teardown any resources the driver knows about. """ Modified: py/dist/py/test/test_config.py ============================================================================== --- py/dist/py/test/test_config.py (original) +++ py/dist/py/test/test_config.py Mon Oct 25 22:05:14 2004 @@ -30,9 +30,9 @@ def test_config_order(): from py.__impl__.test import config o = py.test.config.tmpdir.ensure('configorder', dir=1) - o.ensure('pytest.py').write('x=1 ; import py ; py._x = [x]') - o.ensure('a/pytest.py').write('x=2 ; import py ; py._x.append(x)') - o.ensure('a/b/c/pytest.py').write('x=3 ; import py ; py._x.append(x)') + o.ensure('pytestconf.py').write('x=1 ; import py ; py._x = [x]') + o.ensure('a/pytestconf.py').write('x=2 ; import py ; py._x.append(x)') + o.ensure('a/b/c/pytestconf.py').write('x=3 ; import py ; py._x.append(x)') cfg = config.Config() cfg.readconfiguration(o) assert cfg.getfirst('x') == 1 Copied: py/dist/pytestconf.py (from r7106, py/dist/py/pytest.py) ============================================================================== --- py/dist/py/pytest.py (original) +++ py/dist/pytestconf.py Mon Oct 25 22:05:14 2004 @@ -8,7 +8,7 @@ # directory = pypath.root.dirpath() import py -pkgdir = py.magic.autopath().pkgdir +rootdir = py.magic.autopath().dirpath() # default values for options (modified from cmdline) verbose = 0 From hpk at codespeak.net Mon Oct 25 22:15:43 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 25 Oct 2004 22:15:43 +0200 (MEST) Subject: [py-svn] r7121 - in py/dist: . py/test Message-ID: <20041025201543.D0E8C5B546@thoth.codespeak.net> Author: hpk Date: Mon Oct 25 22:15:43 2004 New Revision: 7121 Added: py/dist/conftest.py - copied unchanged from r7120, py/dist/pytestconf.py Removed: py/dist/pytestconf.py Modified: py/dist/py/test/cmdline.py py/dist/py/test/config.py py/dist/py/test/test_config.py Log: minor renaming and fix. 'conftest.py' does not conflict with 'py' itself regarding tab-completion anymore :-) Modified: py/dist/py/test/cmdline.py ============================================================================== --- py/dist/py/test/cmdline.py (original) +++ py/dist/py/test/cmdline.py Mon Oct 25 22:15:43 2004 @@ -2,11 +2,11 @@ import py import sys from py.__impl__.execnet.channel import ChannelFile, receive2file +from py.__impl__.test.config import configbasename # # main entry point # -configbasename = 'pytestconf.py' def old(argv): if argv is None: Modified: py/dist/py/test/config.py ============================================================================== --- py/dist/py/test/config.py (original) +++ py/dist/py/test/config.py Mon Oct 25 22:15:43 2004 @@ -10,7 +10,7 @@ # # config file handling (utest.conf) # -configbasename = 'pytestconf.py' +configbasename = 'conftest.py' class Config: def __init__(self): Modified: py/dist/py/test/test_config.py ============================================================================== --- py/dist/py/test/test_config.py (original) +++ py/dist/py/test/test_config.py Mon Oct 25 22:15:43 2004 @@ -30,9 +30,9 @@ def test_config_order(): from py.__impl__.test import config o = py.test.config.tmpdir.ensure('configorder', dir=1) - o.ensure('pytestconf.py').write('x=1 ; import py ; py._x = [x]') - o.ensure('a/pytestconf.py').write('x=2 ; import py ; py._x.append(x)') - o.ensure('a/b/c/pytestconf.py').write('x=3 ; import py ; py._x.append(x)') + o.ensure('conftest.py').write('x=1 ; import py ; py._x = [x]') + o.ensure('a/conftest.py').write('x=2 ; import py ; py._x.append(x)') + o.ensure('a/b/c/conftest.py').write('x=3 ; import py ; py._x.append(x)') cfg = config.Config() cfg.readconfiguration(o) assert cfg.getfirst('x') == 1 Deleted: /py/dist/pytestconf.py ============================================================================== --- /py/dist/pytestconf.py Mon Oct 25 22:15:43 2004 +++ (empty file) @@ -1,20 +0,0 @@ -#pythonexecutables = ('python2.2', 'python2.3',) -#pythonexecutable = 'python2.2' - -# in the future we want to be able to say here: -#def setup_module(extpy): -# mod = extpy.resolve() -# mod.module = 23 -# directory = pypath.root.dirpath() - -import py -rootdir = py.magic.autopath().dirpath() - -# default values for options (modified from cmdline) -verbose = 0 -nocapture = False -collectonly = False -exitfirstproblem = False -fulltrace = False -showlocals = False -nomagic = False From hpk at codespeak.net Tue Oct 26 00:15:55 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 26 Oct 2004 00:15:55 +0200 (MEST) Subject: [py-svn] r7124 - in py/dist/py/test: . report/text Message-ID: <20041025221555.55FBB5B546@thoth.codespeak.net> Author: hpk Date: Tue Oct 26 00:15:54 2004 New Revision: 7124 Modified: py/dist/py/test/defaultconfig.py py/dist/py/test/report/text/reporter.py Log: commented out usage of --pdb because that really doesn't work well with the new distributed style of testing. Some more infrastructure needs to be put in place to make interactively working with a remote process nice enough. which is a good idea anyway. Modified: py/dist/py/test/defaultconfig.py ============================================================================== --- py/dist/py/test/defaultconfig.py (original) +++ py/dist/py/test/defaultconfig.py Tue Oct 26 00:15:54 2004 @@ -26,9 +26,9 @@ Option('', '--nomagic', action="store_true", dest="nomagic", default=False, help="don't invoke magic to e.g. beautify failing/error statements."), - Option('', '--pdb', - action="store_true", dest="usepdb", default=False, - help="Start pdb (the Python debugger) on errors."), + #Option('', '--pdb', + # action="store_true", dest="usepdb", default=False, + # help="Start pdb (the Python debugger) on errors."), Option('', '--collectonly', action="store_true", dest="collectonly", default=False, help="only collect tests, don't execute them. "), Modified: py/dist/py/test/report/text/reporter.py ============================================================================== --- py/dist/py/test/report/text/reporter.py (original) +++ py/dist/py/test/report/text/reporter.py Tue Oct 26 00:15:54 2004 @@ -133,15 +133,15 @@ self.out.rewrite("%.3f %-2s %-20s %s%s" % ( elapsed, resultstring, location, str(item.extpy.modpath), writeinfo )) - if self.option.usepdb: - if (issubclass(restype, Collector.Error) or - issubclass(restype, Item.Failed)): - import pdb - self.out.rewrite( - '\n%s: %s\n' - % (result.excinfo[1].__class__.__name__, - result.excinfo[1])) - pdb.post_mortem(result.excinfo[2]) + #if self.option.usepdb: + # if (issubclass(restype, Collector.Error) or + # issubclass(restype, Item.Failed)): + # import pdb + # self.out.rewrite( + # '\n%s: %s\n' + # % (result.excinfo[1].__class__.__name__, + # result.excinfo[1])) + # pdb.post_mortem(result.excinfo[2]) def report_collect_error(self, error): restype, c = self.processresult(error) From hpk at codespeak.net Thu Oct 28 02:00:21 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 28 Oct 2004 02:00:21 +0200 (MEST) Subject: [py-svn] r7146 - py/dist/py/test Message-ID: <20041028000021.AD6D25B546@thoth.codespeak.net> Author: hpk Date: Thu Oct 28 02:00:21 2004 New Revision: 7146 Modified: py/dist/py/test/cmdline.py Log: allow files to vanish in test-session mode. Modified: py/dist/py/test/cmdline.py ============================================================================== --- py/dist/py/test/cmdline.py (original) +++ py/dist/py/test/cmdline.py Thu Oct 28 02:00:21 2004 @@ -32,10 +32,14 @@ while 1: py.std.time.sleep(0.4) for path, cst in statcache.items(): - st = path.stat() - if st.st_mtime != cst.st_mtime or \ - st.st_size != cst.st_size: + try: + st = path.stat() + except path.NotFound: return + else: + if st.st_mtime != cst.st_mtime or \ + st.st_size != cst.st_size: + return class FailingCollector(py.test.collect.Collector): def __init__(self, faileditems): From hpk at codespeak.net Thu Oct 28 14:38:57 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 28 Oct 2004 14:38:57 +0200 (MEST) Subject: [py-svn] r7147 - in py/dist/py/path: . local local/popen5 Message-ID: <20041028123857.B6D555B546@thoth.codespeak.net> Author: hpk Date: Thu Oct 28 14:38:56 2004 New Revision: 7147 Modified: py/dist/py/path/common.py py/dist/py/path/local/local.py py/dist/py/path/local/popen5/test_subprocess.py Log: reverted Armin's __path__ hack which allowed relative imports with getmodule()ed imports because it doesn't work reliably. This means that for now, imports need to be absolute within the py lib which i think is a sensible restriction until we have further looked at the import mess. Modified: py/dist/py/path/common.py ============================================================================== --- py/dist/py/path/common.py (original) +++ py/dist/py/path/common.py Thu Oct 28 14:38:56 2004 @@ -293,7 +293,7 @@ co = self.getcodeobj() mod = py.std.new.module(modname) mod.__file__ = str(self) - mod.__path__ = [str(self.get('dirname'))] + #mod.__path__ = [str(self.get('dirname'))] sys.modules[modname] = mod exec co in mod.__dict__ return mod Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Thu Oct 28 14:38:56 2004 @@ -383,7 +383,7 @@ to be executed. Note that this process is directly invoked and not through a system shell. """ - from popen5.subprocess import Popen, PIPE + from py.__impl__.path.local.popen5.subprocess import Popen, PIPE proc = Popen([str(self)] + list(argv), stdout=PIPE, stderr=PIPE) stdout, stderr = proc.communicate() ret = proc.wait() Modified: py/dist/py/path/local/popen5/test_subprocess.py ============================================================================== --- py/dist/py/path/local/popen5/test_subprocess.py (original) +++ py/dist/py/path/local/popen5/test_subprocess.py Thu Oct 28 14:38:56 2004 @@ -1,7 +1,7 @@ -import subprocess import sys import os import py +from py.__impl__.path.local.popen5 import subprocess mswindows = (sys.platform == "win32") From hpk at codespeak.net Thu Oct 28 15:24:18 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 28 Oct 2004 15:24:18 +0200 (MEST) Subject: [py-svn] r7148 - in py/dist/py: . test test/report/text Message-ID: <20041028132418.70FAA5B546@thoth.codespeak.net> Author: hpk Date: Thu Oct 28 15:24:17 2004 New Revision: 7148 Added: py/dist/py/test/drive.py - copied, changed from r7140, py/dist/py/test/run.py Modified: py/dist/py/__init__.py py/dist/py/test/cmdline.py py/dist/py/test/report/text/reporter.py py/dist/py/test/report/text/summary.py py/dist/py/test/run.py Log: - reorganized py.test implementation - now in "normal" mode no subprocess and no thread is currently started in session mode threads and a child process are started - improved/enriched the reporter output slightly. Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Thu Oct 28 15:24:17 2004 @@ -17,13 +17,13 @@ 'test.collect.PyCollector':'./test/collect.PyCollector', 'test.collect.Error': './test/collect.Error', 'test.Item': './test/item.Item', - 'test.Driver': './test/run.Driver', + 'test.Driver': './test/drive.Driver', 'test.Option': './test/tool/optparse.Option', 'test.TextReporter': './test/report/text/reporter.TextReporter', 'test.MemoReporter': './test/report/memo.MemoReporter', - 'test.exit': './test/run.exit', - 'test.fail': './test/run.fail', - 'test.skip': './test/run.skip', + 'test.exit': './test/drive.exit', + 'test.fail': './test/drive.fail', + 'test.skip': './test/drive.skip', 'test.raises': './test/raises.raises', 'test.config': './test/config.config', 'test.compat.TestCase': './test/compat.TestCase', Modified: py/dist/py/test/cmdline.py ============================================================================== --- py/dist/py/test/cmdline.py (original) +++ py/dist/py/test/cmdline.py Thu Oct 28 15:24:17 2004 @@ -1,14 +1,13 @@ from __future__ import generators import py import sys -from py.__impl__.execnet.channel import ChannelFile, receive2file -from py.__impl__.test.config import configbasename +from py.__impl__.test import run # # main entry point # -def old(argv): +def _old(argv): if argv is None: argv = py.std.sys.argv frame = py.std.sys._getframe(1) @@ -18,178 +17,19 @@ import __main__ return [py.test.collect.Module(py.std.sys.argv[0])] -def waitfilechange(): - """ wait until project files are changed. """ - rootdir = py.test.config.getfirst('rootdir') - rootdir = py.path.local(rootdir) - fil = py.path.checker(fnmatch='*.py') - rec = py.path.checker(dotfile=0) - statcache = {} - for path in rootdir.visit(fil, rec): - statcache[path] = path.stat() - - print "waiting for file change below", str(rootdir) - while 1: - py.std.time.sleep(0.4) - for path, cst in statcache.items(): - try: - st = path.stat() - except path.NotFound: - return - else: - if st.st_mtime != cst.st_mtime or \ - st.st_size != cst.st_size: - return - -class FailingCollector(py.test.collect.Collector): - def __init__(self, faileditems): - self._faileditems = faileditems - - def __iter__(self): - for x in self._faileditems: - yield x - def main(): args = py.std.sys.argv[1:] - py.test.config.readconfiguration(*getanchors(args)) + py.test.config.readconfiguration(*run.getanchors(args)) filenames = py.test.config.parseargs(args) if not filenames: filenames.append(str(py.path.local())) - try: - while 1: - failures = master(args, filenames) - if not failures or not py.test.config.option.session: - break - while failures: - print "session mode: %d failures remaining" % len(failures) - waitfilechange() - failures = failure_master(args, filenames, failures) + if py.test.config.option.session: + run.session(args, filenames) + else: + run.inprocess(args, filenames) except KeyboardInterrupt: print print "Keybordinterrupt" raise SystemExit, 2 - -class StdouterrProxy: - def __init__(self, gateway): - self.gateway = gateway - def setup(self): - channel = self.gateway.remote_exec(""" - import sys - out, err = channel.newchannel(), channel.newchannel() - channel.send(out) - channel.send(err) - sys.stdout, sys.stderr = out.open('w'), err.open('w') - """) - self.stdout = channel.receive() - self.stderr = channel.receive() - channel.waitclose(1.0) - py.std.threading.Thread(target=receive2file, - args=(self.stdout, sys.stdout)).start() - py.std.threading.Thread(target=receive2file, - args=(self.stderr, sys.stderr)).start() - def teardown(self): - self.stdout.close() - self.stderr.close() - -def waitfinish(channel): - try: - while 1: - try: - channel.waitclose(0.1) - except IOError: - continue - else: - failures = channel.receive() - return failures - break - finally: - #print "closing down channel and gateway" - channel.close() - channel.gateway.exit() - -def failure_master(args, filenames, failures): - gw = py.execnet.PopenGateway() - outproxy = StdouterrProxy(gw) - outproxy.setup() - try: - channel = gw.remote_exec(""" - from py.__impl__.test.cmdline import failure_slave - failure_slave(channel) - """) - channel.send((args, filenames)) - channel.send(failures) - return waitfinish(channel) - finally: - outproxy.teardown() - -def failure_slave(channel): - """ we run this on the other side. """ - args, filenames = channel.receive() - filenames = map(py.path.local, filenames) - py.test.config.readconfiguration(*filenames) - py.test.config.parseargs(args) - - failures = channel.receive() - col = FailureCollector(failures) - driver = py.test.Driver(channel) - failures = driver.run(col) - channel.send(failures) - -class FailureCollector(py.test.collect.Collector): - def __init__(self, failures): - self.failures = failures - def __iter__(self): - for root,modpath in self.failures: - extpy = py.path.extpy(root, modpath) - yield self.Item(extpy) - -def master(args, filenames): - gw = py.execnet.PopenGateway() - outproxy = StdouterrProxy(gw) - outproxy.setup() - try: - channel = gw.remote_exec(""" - from py.__impl__.test.cmdline import slave - slave(channel) - """) - channel.send((args, filenames)) - return waitfinish(channel) - finally: - outproxy.teardown() - -def slave(channel): - """ we run this on the other side. """ - args, filenames = channel.receive() - filenames = map(py.path.local, filenames) - py.test.config.readconfiguration(*filenames) - py.test.config.parseargs(args) - fncollectors = list(getcollectors(filenames)) - - driver = py.test.Driver(channel) - failures = driver.run(fncollectors) - channel.send(failures) - -def getcollectors(filenames): - current = py.path.local() - for fn in filenames: - fullfn = current.join(fn, abs=1) - if fullfn.check(file=1): - yield py.test.collect.Module(fullfn) - elif fullfn.check(dir=1): - yield py.test.collect.Directory(fullfn) - else: - raise IOError, "%r does not exist" % fn - -def getanchors(args): - """ yield "anchors" from skimming the args for existing files/dirs. """ - current = py.path.local() - l = [] - for arg in args: - anchor = current.join(arg, abs=1) - if anchor.check(): - l.append(anchor) - if not l: - l = [current] - return l Copied: py/dist/py/test/drive.py (from r7140, py/dist/py/test/run.py) ============================================================================== --- py/dist/py/test/run.py (original) +++ py/dist/py/test/drive.py Thu Oct 28 15:24:17 2004 @@ -50,7 +50,7 @@ def run_collector_or_item(self, obj): """ run (possibly many) testitems and/or collectors. """ - if self._channel.isclosed(): + if self._channel and self._channel.isclosed(): raise SystemExit, "Channel is closed" if isinstance(obj, py.test.Item): if not self.option.collectonly: Modified: py/dist/py/test/report/text/reporter.py ============================================================================== --- py/dist/py/test/report/text/reporter.py (original) +++ py/dist/py/test/report/text/reporter.py Thu Oct 28 15:24:17 2004 @@ -33,14 +33,17 @@ self.summary.option = self.option = py.test.config.option def start(self, conf=None): - self.summary.starttime = now() if conf is not None and conf.mypath is not None: self.out.line("using %s" % conf.mypath) - self.out.sep("_") - self.out.sep("_", " TESTS STARTING ") - self.out.sep("_") + self.out.sep("=", "test process starts") + mode = py.test.config.option.session and 'session/child process' or 'inprocess' + self.out.line("testing-mode: %s" % mode) + self.out.line("executable : %s (%s)" % + (py.std.sys.executable, repr_pythonversion())) + self.out.sep("=") if not self.option.nomagic: py.magic.invoke(assertion=1) + self.summary.starttime = now() def end(self): if not self.option.nomagic: @@ -175,3 +178,11 @@ # offendingline = misc.getline(tb) # return (origin, offendingline) + +def repr_pythonversion(): + v = py.std.sys.version_info + try: + return "%s.%s.%s-%s-%s" % v + except ValueError: + return str(v) + Modified: py/dist/py/test/report/text/summary.py ============================================================================== --- py/dist/py/test/report/text/summary.py (original) +++ py/dist/py/test/report/text/summary.py Thu Oct 28 15:24:17 2004 @@ -47,11 +47,13 @@ sum = 0 for typ in Item.Passed, Item.Failed, Item.Skipped: l = self.getlist(typ) - outlist.append('%d %s' % (len(l), typ.__name__.lower())) + if l: + outlist.append('%d %s' % (len(l), typ.__name__.lower())) sum += len(l) - self.out.sep('=', '%d TESTS FINISHED' % sum) - self.out.write('%4.2f seconds' % (self.endtime-self.starttime)) - self.out.line(" (%s)" % ", ".join(outlist)) + elapsed = self.endtime-self.starttime + status = "%s" % ", ".join(outlist) + self.out.sep('=', 'tests finished: %s in %4.2f seconds' % + (status, elapsed)) def getexinfo(self, res): _,exc,tb = res.excinfo Modified: py/dist/py/test/run.py ============================================================================== --- py/dist/py/test/run.py (original) +++ py/dist/py/test/run.py Thu Oct 28 15:24:17 2004 @@ -1,159 +1,179 @@ from __future__ import generators import py -collect = py.test.collect - -class Exit(Exception): - """ for immediate program exits without tracebacks and reporter/summary. """ - def __init__(self, item=None): - self.item = None - Exception.__init__(self) - -def exit(*args): - raise Exit(*args) - -def skip(msg="unknown reason"): - """ skip with the given Message. """ - raise py.test.Item.Skipped(msg=msg, tbindex=-2) - -def fail(msg="unknown failure"): - """ fail with the given Message. """ - raise py.test.Item.Failed(msg=msg, tbindex=-2) - -class Driver: - option = py.test.config.option - Exit = Exit - - def __init__(self, channel): - self.reporter = py.test.config.getfirst('getreporter') () - self._setupstack = [] - self._instance = None - self._channel = channel - self._failed = [] - - def run(self, collectors): - """ main loop for running tests. """ - self.setup() - try: - try: - self.reporter.start() - for x in collectors: - self.run_collector_or_item(x) - finally: - self.teardown() - except self.Exit, ex: - pass - self.reporter.end() - l = [] - for x in self._failed: - l.append((str(x.extpy.root), str(x.extpy.modpath))) - return l - - def run_collector_or_item(self, obj): - """ run (possibly many) testitems and/or collectors. """ - if self._channel.isclosed(): - raise SystemExit, "Channel is closed" - if isinstance(obj, py.test.Item): - if not self.option.collectonly: - #if self.option.args: - # if str(obj.path).find(self.option.args[0]) == -1: - # return - self.runitem(obj) - elif isinstance(obj, py.test.collect.Collector): - self.runcollector(obj) - elif isinstance(obj, py.test.collect.Collector.Error): +from py.__impl__.execnet.channel import ChannelFile, receive2file +from py.__impl__.test.config import configbasename +import sys + +def waitfilechange(): + """ wait until project files are changed. """ + rootdir = py.test.config.getfirst('rootdir') + rootdir = py.path.local(rootdir) + fil = py.path.checker(fnmatch='*.py') + rec = py.path.checker(dotfile=0) + statcache = {} + for path in rootdir.visit(fil, rec): + statcache[path] = path.stat() + + print "waiting for file change below", str(rootdir) + while 1: + py.std.time.sleep(0.4) + for path, cst in statcache.items(): try: - self.reporter.report_collect_error(obj) - except (KeyboardInterrupt, SystemExit): - raise - except: - print "*" * 80 - print "Reporter Error" - print "*" * 80 - import traceback - traceback.print_exc() - raise SystemExit, 1 - if self.option.exitfirstproblem: - raise self.Exit() - else: - raise TypeError("%r is not a Item or Collector instance" % obj) - - def runcollector(self, collector): - close = self.reporter.open(collector) - try: - for obj in collector: - self.run_collector_or_item(obj) - finally: - if close: - close() - - def runitem(self, item): - self.reporter.startitem(item) - try: - res = item.execute(self) or item.Passed() - except item.Outcome, res: - res.excinfo = py.std.sys.exc_info() - except (KeyboardInterrupt, SystemExit): - raise - except: - res = item.Failed(excinfo=py.std.sys.exc_info()) - res.item = item - self.reporter.enditem(res) - if isinstance(res, (item.Failed,)): - self._failed.append(item) - if py.test.config.option.exitfirstproblem: - raise self.Exit(res.item) - - def setup_path(self, extpy): - """ setup objects along the path to the test-method - (pointed to by extpy). Tear down any previously - setup objects which are not directly needed. - """ - # setupstack contains (extpy, obj)'s of already setup objects - # strict ordering is maintained, i.e. each extpy in - # the stack is "relto" the previous extpy. - stack = self._setupstack - while stack and not extpy.relto(stack[-1][0]): - self._teardownone(stack.pop()[1]) - rest = extpy.parts()[len(stack):-1] - for x in rest: - stack.append((x, self._setupone(x))) - + st = path.stat() + except path.NotFound: + return + else: + if st.st_mtime != cst.st_mtime or \ + st.st_size != cst.st_size: + return + +class FailingCollector(py.test.collect.Collector): + def __init__(self, faileditems): + self._faileditems = faileditems + + def __iter__(self): + for x in self._faileditems: + yield x + + +class StdouterrProxy: + def __init__(self, gateway): + self.gateway = gateway def setup(self): - """ setup any neccessary resources. """ - self._failed[:] = [] - + channel = self.gateway.remote_exec(""" + import sys + out, err = channel.newchannel(), channel.newchannel() + channel.send(out) + channel.send(err) + sys.stdout, sys.stderr = out.open('w'), err.open('w') + """) + self.stdout = channel.receive() + self.stderr = channel.receive() + channel.waitclose(1.0) + py.std.threading.Thread(target=receive2file, + args=(self.stdout, sys.stdout)).start() + py.std.threading.Thread(target=receive2file, + args=(self.stderr, sys.stderr)).start() def teardown(self): - """ teardown any resources the driver knows about. """ - while self._setupstack: - self._teardownone(self._setupstack.pop()[1]) - - def _setupone(self, extpy): - obj = extpy.resolve() - if py.std.inspect.ismodule(obj): - if hasattr(obj, 'setup_module'): - obj.setup_module(obj) - elif py.std.inspect.isclass(obj): - if hasattr(obj, 'setup_class'): - obj.setup_class.im_func(obj) - return obj - - def _teardownone(self, obj): - if py.std.inspect.ismodule(obj): - if hasattr(obj, 'teardown_module'): - obj.teardown_module(obj) - elif py.std.inspect.isclass(obj): - if hasattr(obj, 'teardown_class'): - obj.teardown_class.im_func(obj) - - def setup_method(self, extpy): - """ return a tuple of (bound method or callable, teardown method). """ - method = extpy.resolve() - if not hasattr(method, 'im_class'): - return method, None - if self._instance.__class__ != method.im_class: - self._instance = method.im_class() - method = method.__get__(self._instance, method.im_class) - if hasattr(self._instance, 'setup_method'): - print "execting setup", self._instance.setup_method - self._instance.setup_method(method) - return (method, getattr(self._instance, 'teardown_method', None)) + self.stdout.close() + self.stderr.close() + +def waitfinish(channel): + try: + while 1: + try: + channel.waitclose(0.1) + except IOError: + continue + else: + failures = channel.receive() + return failures + break + finally: + #print "closing down channel and gateway" + channel.close() + channel.gateway.exit() + +def failure_master(args, filenames, failures): + gw = py.execnet.PopenGateway() + outproxy = StdouterrProxy(gw) + outproxy.setup() + try: + channel = gw.remote_exec(""" + from py.__impl__.test.run import failure_slave + failure_slave(channel) + """) + channel.send((args, filenames)) + channel.send(failures) + return waitfinish(channel) + finally: + outproxy.teardown() + +def failure_slave(channel): + """ we run this on the other side. """ + args, filenames = channel.receive() + filenames = map(py.path.local, filenames) + py.test.config.readconfiguration(*filenames) + py.test.config.parseargs(args) + + failures = channel.receive() + col = FailureCollector(failures) + driver = py.test.Driver(channel) + failures = driver.run(col) + channel.send(failures) + +class FailureCollector(py.test.collect.Collector): + def __init__(self, failures): + self.failures = failures + def __iter__(self): + for root,modpath in self.failures: + extpy = py.path.extpy(root, modpath) + yield self.Item(extpy) + +def master(args, filenames): + gw = py.execnet.PopenGateway() + outproxy = StdouterrProxy(gw) + outproxy.setup() + try: + channel = gw.remote_exec(""" + from py.__impl__.test.run import slave + slave(channel) + """) + channel.send((args, filenames)) + return waitfinish(channel) + finally: + outproxy.teardown() + +def slave(channel): + """ we run this on the other side. """ + args, filenames = channel.receive() + filenames = map(py.path.local, filenames) + py.test.config.readconfiguration(*filenames) + py.test.config.parseargs(args) + fncollectors = list(getcollectors(filenames)) + + driver = py.test.Driver(channel) + failures = driver.run(fncollectors) + channel.send(failures) + +def getcollectors(filenames): + current = py.path.local() + for fn in filenames: + fullfn = current.join(fn, abs=1) + if fullfn.check(file=1): + yield py.test.collect.Module(fullfn) + elif fullfn.check(dir=1): + yield py.test.collect.Directory(fullfn) + else: + raise IOError, "%r does not exist" % fn + +def getanchors(args): + """ yield "anchors" from skimming the args for existing files/dirs. """ + current = py.path.local() + l = [] + for arg in args: + anchor = current.join(arg, abs=1) + if anchor.check(): + l.append(anchor) + if not l: + l = [current] + return l + +# +# Testing Modes +# +def inprocess(args, filenames): + """ we run this on the other side. """ + fncollectors = list(getcollectors(filenames)) + driver = py.test.Driver(None) + driver.run(fncollectors) + +def session(args, filenames): + while 1: + failures = master(args, filenames) + if not failures or not py.test.config.option.session: + break + while failures: + print "session mode: %d failures remaining" % len(failures) + waitfilechange() + failures = failure_master(args, filenames, failures) From hpk at codespeak.net Fri Oct 29 15:44:31 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 29 Oct 2004 15:44:31 +0200 (MEST) Subject: [py-svn] r7153 - in py/dist/py/test: . report/text Message-ID: <20041029134431.C4C685B659@thoth.codespeak.net> Author: hpk Date: Fri Oct 29 15:44:31 2004 New Revision: 7153 Modified: py/dist/py/test/collect.py py/dist/py/test/drive.py py/dist/py/test/report/text/reporter.py Log: - improved --collectonly output (now shows items) - fixes, disabled a "samefile()" check because they don't work reliably when you have multiple mount points (as happens with autofs && symlinks where path != realpath(path) ) Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Fri Oct 29 15:44:31 2004 @@ -89,7 +89,9 @@ l = [] for extpy in self.extpy.listdir(): for meth in self.yielders: + #print "looking at", extpy for x in meth(extpy): + #print "found", x x.fspath = self.extpy.root sortvalue = self.getsortvalue(x) l.append((sortvalue, x)) @@ -131,8 +133,8 @@ def collect_function(self, extpy): if extpy.check(func=1, basestarts='test_'): - if self.extpy.samefile(extpy): - yield self.Item(extpy) + #if self.extpy.samefile(extpy): not nfs/different mountpoint safe + yield self.Item(extpy) def collect_class(self, extpy): #print "checking %r (extpy: %r)" % (extpy.resolve(), extpy) Modified: py/dist/py/test/drive.py ============================================================================== --- py/dist/py/test/drive.py (original) +++ py/dist/py/test/drive.py Fri Oct 29 15:44:31 2004 @@ -53,11 +53,7 @@ if self._channel and self._channel.isclosed(): raise SystemExit, "Channel is closed" if isinstance(obj, py.test.Item): - if not self.option.collectonly: - #if self.option.args: - # if str(obj.path).find(self.option.args[0]) == -1: - # return - self.runitem(obj) + self.runitem(obj) elif isinstance(obj, py.test.collect.Collector): self.runcollector(obj) elif isinstance(obj, py.test.collect.Collector.Error): @@ -88,20 +84,21 @@ def runitem(self, item): self.reporter.startitem(item) - try: - res = item.execute(self) or item.Passed() - except item.Outcome, res: - res.excinfo = py.std.sys.exc_info() - except (KeyboardInterrupt, SystemExit): - raise - except: - res = item.Failed(excinfo=py.std.sys.exc_info()) - res.item = item - self.reporter.enditem(res) - if isinstance(res, (item.Failed,)): - self._failed.append(item) - if py.test.config.option.exitfirstproblem: - raise self.Exit(res.item) + if not self.option.collectonly: + try: + res = item.execute(self) or item.Passed() + except item.Outcome, res: + res.excinfo = py.std.sys.exc_info() + except (KeyboardInterrupt, SystemExit): + raise + except: + res = item.Failed(excinfo=py.std.sys.exc_info()) + res.item = item + self.reporter.enditem(res) + if isinstance(res, (item.Failed,)): + self._failed.append(item) + if py.test.config.option.exitfirstproblem: + raise self.Exit(res.item) def setup_path(self, extpy): """ setup objects along the path to the test-method @@ -154,6 +151,6 @@ self._instance = method.im_class() method = method.__get__(self._instance, method.im_class) if hasattr(self._instance, 'setup_method'): - print "execting setup", self._instance.setup_method + #print "execting setup", self._instance.setup_method self._instance.setup_method(method) return (method, getattr(self._instance, 'teardown_method', None)) Modified: py/dist/py/test/report/text/reporter.py ============================================================================== --- py/dist/py/test/report/text/reporter.py (original) +++ py/dist/py/test/report/text/reporter.py Fri Oct 29 15:44:31 2004 @@ -32,9 +32,7 @@ self.summary = self.Summary() self.summary.option = self.option = py.test.config.option - def start(self, conf=None): - if conf is not None and conf.mypath is not None: - self.out.line("using %s" % conf.mypath) + def start(self): self.out.sep("=", "test process starts") mode = py.test.config.option.session and 'session/child process' or 'inprocess' self.out.line("testing-mode: %s" % mode) @@ -97,6 +95,12 @@ return close_directory def startitem(self, item): + if self.option.collectonly: + cols = self._opencollectors + if len(cols): + print ' ' * len(cols), + print "Item", item.extpy.modpath + return if not self.option.nocapture: from py.__impl__.test.tool.outerrcapture import SimpleOutErrCapture item.iocapture = SimpleOutErrCapture() @@ -107,13 +111,15 @@ self._started[item] = now() def enditem(self, result): + if self.option.collectonly: + return endtime = now() item = result.item starttime = self._started[item] del self._started[item] elapsed = endtime - starttime item.elapsed = elapsed - + if not self.option.nocapture: result.out, result.err = item.iocapture.reset() From hpk at codespeak.net Fri Oct 29 15:58:51 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 29 Oct 2004 15:58:51 +0200 (MEST) Subject: [py-svn] r7154 - py/dist/py/path/local Message-ID: <20041029135851.034FC5B659@thoth.codespeak.net> Author: hpk Date: Fri Oct 29 15:58:51 2004 New Revision: 7154 Modified: py/dist/py/path/local/local.py Log: - getcodeobj(): use path methods instead of string operations this makes it easier to inherit from local and reuse the function Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Fri Oct 29 15:58:51 2004 @@ -342,16 +342,16 @@ return self.strpath def getcodeobj(self): - dotpy = self.strpath.endswith('.py') + dotpy = self.check(ext='.py') if dotpy: my_magic = py.std.imp.get_magic() my_timestamp = int(self.mtime()) if __debug__: - pycfile = self.strpath + 'c' + pycfile = self + 'c' else: - pycfile = self.strpath + 'o' + pycfile = self + 'o' try: - f = open(pycfile, 'rb') + f = pycfile.open('rb') try: header = f.read(8) if len(header) == 8: @@ -363,10 +363,10 @@ except IOError: pass s = self.read() - codeobj = compile(s, self.strpath, 'exec') + codeobj = compile(s, str(self), 'exec') if dotpy: try: - f = open(pycfile, 'wb') + f = pycfile.open('wb') f.write(py.std.struct.pack('<4si', 'TEMP', -1)) # fixed below py.std.marshal.dump(codeobj, f) f.flush() From hpk at codespeak.net Fri Oct 29 16:33:18 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 29 Oct 2004 16:33:18 +0200 (MEST) Subject: [py-svn] r7155 - py/dist/py/magic Message-ID: <20041029143318.1A19F5B659@thoth.codespeak.net> Author: hpk Date: Fri Oct 29 16:33:17 2004 New Revision: 7155 Modified: py/dist/py/magic/dyncode.py py/dist/py/magic/test_dyncode.py Log: removed duplicate function, extended a test Modified: py/dist/py/magic/dyncode.py ============================================================================== --- py/dist/py/magic/dyncode.py (original) +++ py/dist/py/magic/dyncode.py Fri Oct 29 16:33:17 2004 @@ -199,19 +199,6 @@ return linecache_getlines(filename) -def findsource(obj): - try: - return inspect_findsource(obj) - except (TypeError, IOError): - code = getcode(obj) - filename = obj._co_filename - if not _dynfileregistry.has_key(filename): - raise - origin, lines, gencode = _dynfileregistry[filename] - firstlineno = code.co_firstlineno - 1 - lines = inspect.getblock(lines[firstlineno:]) - return "".join(lines), firstlineno - def getcode(obj): if inspect.iscode(obj): return obj @@ -245,20 +232,22 @@ return ''.join(lines) def findsource(obj): - try: - return inspect_findsource(obj) - except IOError: + print "internal findsource", obj obj = getcode(obj) filename = obj.co_filename if _dynfileregistry.has_key(filename): origin, lines, gencode = _dynfileregistry[filename] lnum = obj.co_firstlineno - 1 + print "firstlineno", lnum pat = re.compile(r'^(\s*def\s)|(.*\slambda(:|\s))') while lnum > 0: - if pat.match(lines[lnum]): break + if pat.match(lines[lnum]): + break lnum = lnum - 1 return lines, lnum + else: + return inspect_findsource(obj) raise IOError, "could not get source (even after looking in the code registry)" def getpyfile(obj): Modified: py/dist/py/magic/test_dyncode.py ============================================================================== --- py/dist/py/magic/test_dyncode.py (original) +++ py/dist/py/magic/test_dyncode.py Fri Oct 29 16:33:17 2004 @@ -48,6 +48,9 @@ source = dyncode.getsource(f) assert dyncode.getsource(f) == 'def f():\n raise ValueError\n' assert dyncode.getsource(g) == 'def g(): pass\n' + lines, lnum = dyncode.findsource(g) + lines = lines[lnum:] + assert lines and lines[0] == 'def g(): pass\n' def test_getpyfile(): fn = dyncode.getpyfile(dyncode) @@ -91,3 +94,4 @@ tb = dyncode.gettb(excinfo[2], -1) source = dyncode.getparseablestartingblock(tb) assert source.strip() == 'c(1)' + From hpk at codespeak.net Fri Oct 29 16:38:30 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 29 Oct 2004 16:38:30 +0200 (MEST) Subject: [py-svn] r7156 - py/dist/py/magic Message-ID: <20041029143830.C64C85B659@thoth.codespeak.net> Author: hpk Date: Fri Oct 29 16:38:30 2004 New Revision: 7156 Modified: py/dist/py/magic/dyncode.py Log: more little cleanup Modified: py/dist/py/magic/dyncode.py ============================================================================== --- py/dist/py/magic/dyncode.py (original) +++ py/dist/py/magic/dyncode.py Fri Oct 29 16:38:30 2004 @@ -232,23 +232,21 @@ return ''.join(lines) def findsource(obj): - print "internal findsource", obj - obj = getcode(obj) - filename = obj.co_filename - if _dynfileregistry.has_key(filename): - origin, lines, gencode = _dynfileregistry[filename] - - lnum = obj.co_firstlineno - 1 - print "firstlineno", lnum - pat = re.compile(r'^(\s*def\s)|(.*\slambda(:|\s))') - while lnum > 0: - if pat.match(lines[lnum]): - break - lnum = lnum - 1 - return lines, lnum - else: - return inspect_findsource(obj) - raise IOError, "could not get source (even after looking in the code registry)" + obj = getcode(obj) + filename = obj.co_filename + if _dynfileregistry.has_key(filename): + origin, lines, gencode = _dynfileregistry[filename] + + lnum = obj.co_firstlineno - 1 + print "firstlineno", lnum + pat = re.compile(r'^(\s*def\s)|(.*\slambda(:|\s))') + while lnum > 0: + if pat.match(lines[lnum]): + break + lnum = lnum - 1 + return lines, lnum + else: + return inspect_findsource(obj) def getpyfile(obj): fn = inspect.getfile(obj) From hpk at codespeak.net Fri Oct 29 17:10:30 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 29 Oct 2004 17:10:30 +0200 (MEST) Subject: [py-svn] r7157 - py/dist/py/test Message-ID: <20041029151030.D561D5B659@thoth.codespeak.net> Author: hpk Date: Fri Oct 29 17:10:30 2004 New Revision: 7157 Modified: py/dist/py/test/run.py Log: nicer output for "py.test --session" mode (which is really nice, btw!) Modified: py/dist/py/test/run.py ============================================================================== --- py/dist/py/test/run.py (original) +++ py/dist/py/test/run.py Fri Oct 29 17:10:30 2004 @@ -4,28 +4,28 @@ from py.__impl__.test.config import configbasename import sys -def waitfilechange(): +def checkpyfilechange(rootdir, statcache): """ wait until project files are changed. """ - rootdir = py.test.config.getfirst('rootdir') - rootdir = py.path.local(rootdir) fil = py.path.checker(fnmatch='*.py') rec = py.path.checker(dotfile=0) - statcache = {} + changed = False for path in rootdir.visit(fil, rec): - statcache[path] = path.stat() - - print "waiting for file change below", str(rootdir) - while 1: - py.std.time.sleep(0.4) - for path, cst in statcache.items(): - try: - st = path.stat() - except path.NotFound: - return + oldstat = statcache.get(path, None) + try: + statcache[path] = curstat = path.stat() + except path.NotFound: + if oldstat: + del statcache[path] + print "# WARN: race condition on", path + else: + if oldstat: + if oldstat.st_mtime != curstat.st_mtime or \ + oldstat.st_size != curstat.st_size: + changed = True + print "# MODIFIED", path else: - if st.st_mtime != cst.st_mtime or \ - st.st_size != cst.st_size: - return + changed = True + return changed class FailingCollector(py.test.collect.Collector): def __init__(self, faileditems): @@ -169,11 +169,18 @@ driver.run(fncollectors) def session(args, filenames): + statcache = {} + failures = [] + rootdir = py.path.local(py.test.config.getfirst('rootdir')) + l = len(str(rootdir)) while 1: - failures = master(args, filenames) - if not failures or not py.test.config.option.session: - break - while failures: - print "session mode: %d failures remaining" % len(failures) - waitfilechange() + while not checkpyfilechange(rootdir, statcache): + py.std.time.sleep(0.4) + if failures: failures = failure_master(args, filenames, failures) + else: + failures = master(args, filenames) + print "#" * 60 + print "# session mode: %d failures remaining" % len(failures) + print "# checking py files below %s" % rootdir + print "# ", "^" * l From hpk at codespeak.net Sat Oct 30 13:09:59 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 30 Oct 2004 13:09:59 +0200 (MEST) Subject: [py-svn] r7158 - in py/dist/py: bin test2 Message-ID: <20041030110959.1A08D5B659@thoth.codespeak.net> Author: hpk Date: Sat Oct 30 13:09:57 2004 New Revision: 7158 Added: py/dist/py/bin/py.test2 (contents, props changed) py/dist/py/test2/ - copied from r7156, py/dist/py/test/ py/dist/py/test2/run.py - copied unchanged from r7157, py/dist/py/test/run.py Log: opening up a large py.test refactoring directory while keeping the original py.test intact. This allwos to use the current py.test for development of the refactored testing process. Note that opening up a root-level branch wouldn't help because if we modify py/test we don't have a test environment anymore that we could use to test the new stuff. Now, the goals of the refactoring: - make it more interactive, allow the "reporter" to potentially become a user-driven application that can fully interact with all aspects of the testing process - introduce "generative tests" (see my mail to Ian Bicking on py-dev yesterday) - reduce the py.test code base - see if the new ideas really work :-) Added: py/dist/py/bin/py.test2 ============================================================================== --- (empty file) +++ py/dist/py/bin/py.test2 Sat Oct 30 13:09:57 2004 @@ -0,0 +1,5 @@ +#!/usr/bin/env python + +from _findpy import py +from py.__impl__.test2.cmdline import main +main() From hpk at codespeak.net Sun Oct 31 17:31:16 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 31 Oct 2004 17:31:16 +0100 (MET) Subject: [py-svn] r7161 - py/dist/py/path/extpy Message-ID: <20041031163116.9482D5B659@thoth.codespeak.net> Author: hpk Date: Sun Oct 31 17:31:15 2004 New Revision: 7161 Modified: py/dist/py/path/extpy/extpy.py py/dist/py/path/extpy/inc_test_extpy.py py/dist/py/path/extpy/test_extpy.py Log: added a "genfunc" checker that looks if a path points to a generator function (i.e. a function that will produce a generator when called) Modified: py/dist/py/path/extpy/extpy.py ============================================================================== --- py/dist/py/path/extpy/extpy.py (original) +++ py/dist/py/path/extpy/extpy.py Sun Oct 31 17:31:15 2004 @@ -231,3 +231,9 @@ def file(self): return not self.dir() + + def genfunc(self): + try: + return self._obj().func_code.co_flags & 32 + except AttributeError: + return False Modified: py/dist/py/path/extpy/inc_test_extpy.py ============================================================================== --- py/dist/py/path/extpy/inc_test_extpy.py (original) +++ py/dist/py/path/extpy/inc_test_extpy.py Sun Oct 31 17:31:15 2004 @@ -1,9 +1,13 @@ +from __future__ import generators class A: x1 = 42 def func(self): pass + def genfunc(self): + yield 2 + class B: x2 = 23 Modified: py/dist/py/path/extpy/test_extpy.py ============================================================================== --- py/dist/py/path/extpy/test_extpy.py (original) +++ py/dist/py/path/extpy/test_extpy.py Sun Oct 31 17:31:15 2004 @@ -119,6 +119,14 @@ p = self.root.join('A.x1') assert p.check(func=0) + def test_generatorfunction(self): + p = self.root.join('A.genfunc') + assert p.check(genfunc=1) + p = self.root.join('A.func') + assert p.check(genfunc=0) + p = self.root.join('A') + assert p.check(genfunc=0) + def test_hashing_equality(self): x = self.root y = self.root.new() From hpk at codespeak.net Sun Oct 31 22:42:54 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 31 Oct 2004 22:42:54 +0100 (MET) Subject: [py-svn] r7162 - in py: branch/test2 branch/test2/doc branch/test2/py branch/test2/py/path/extpy branch/test2/py/path/local branch/test2/py/test branch/test2/py/test2 branch/test2/py/test2/report branch/test2/py/test2/report/text dist/py/bin dist/py/path/local dist/py/test2 Message-ID: <20041031214254.3D2EE5B659@thoth.codespeak.net> Author: hpk Date: Sun Oct 31 22:42:53 2004 New Revision: 7162 Added: py/branch/test2/ - copied from r7156, py/dist/ py/branch/test2/py/path/extpy/extpy.py - copied unchanged from r7161, py/dist/py/path/extpy/extpy.py py/branch/test2/py/path/extpy/inc_test_extpy.py - copied unchanged from r7161, py/dist/py/path/extpy/inc_test_extpy.py py/branch/test2/py/path/extpy/test_extpy.py - copied unchanged from r7161, py/dist/py/path/extpy/test_extpy.py py/branch/test2/py/test/run.py - copied unchanged from r7157, py/dist/py/test/run.py py/branch/test2/py/test2/ - copied from r7158, py/dist/py/test2/ Removed: py/dist/py/bin/py.test2 py/dist/py/test2/ Modified: py/branch/test2/conftest.py py/branch/test2/doc/test.txt py/branch/test2/py/__init__.py py/branch/test2/py/path/local/local.py py/branch/test2/py/test2/cmdline.py py/branch/test2/py/test2/config.py py/branch/test2/py/test2/defaultconfig.py py/branch/test2/py/test2/item.py py/branch/test2/py/test2/report/memo.py py/branch/test2/py/test2/report/text/reporter.py py/dist/py/path/local/local.py Log: get the test2-stuff out of the trunk (dist) in favour of a true branch. After all opening up a branch within the trunk wasn't such a great idea. Implementing the test-refactoring will take some time and experiments ... Modified: py/branch/test2/conftest.py ============================================================================== --- py/dist/conftest.py (original) +++ py/branch/test2/conftest.py Sun Oct 31 22:42:53 2004 @@ -14,7 +14,8 @@ verbose = 0 nocapture = False collectonly = False -exitfirstproblem = False +exitfirstproblem = True fulltrace = False showlocals = False nomagic = False +session = False Modified: py/branch/test2/doc/test.txt ============================================================================== --- py/dist/doc/test.txt (original) +++ py/branch/test2/doc/test.txt Sun Oct 31 22:42:53 2004 @@ -245,139 +245,128 @@ your setup function callable. Did we mention that lazyness is a virtue? -The three components of ``py.test`` -=================================== +The underlying model of the ``py.test`` process +=============================================== + + WARNING: THIS IS FUTURISTIC (documentation driven development) In order to customize ``py.test`` it's good to understand -its basic architure:: +the basic testing process:: - ___________________ - | | - | Collector | - |___________________| - / \ - | Item.execute(driver) - | ^ - receive test Items / - | /execute test Item - | / - ___________________/ ________________ - | | send events | | - | Driver |----------------------->| Reporter | + iter(Item) + ^ ......................... sends events + . . + . asks for items & results . + . . + ___________________ __________________ + | | send events >| | + | RootItem |.......................>| Reporter | |___________________| |________________| - ....................... - . configuration . - . cmdline options . - ....................... - -The *Driver* basically receives test *Items* from a *Collector*, -executes them via the ``Item.execute()`` method, and takes the -outcome (possibly an exception) and sends it over to the -*reporter* instance. +The RootItem iterates over its start items in order to receive +more items triggers To start the testing process you call the RootItem.run() +method. + +The *Driver* receives Items or Test results and sends according +events to the reporter which interacts with the user, usually +by just showing progress output (we do want to allow real +user interaction later for implementing a "test console" or +a gui-based frontend). .. _`collection process`: -Collectors and the test collection process +generative test Items and final test Items ------------------------------------------ -The collecting process is iterative, i.e. the driver -get test Items one by one and can thus start immediately -to execute the first collected test Item. At the same time, -the collectors are completly shielded from any driving, -execution or reporting details. - -The ``driver`` invokes the iteration protocol, i.e. the -``__iter__`` method of Collectors. These methods yield more (sub) -*Collectors* or test *Items*. They should usually not raise -exceptions but yield back a specific CollectError. This is to -avoid that a collecting error breaks the whole collection -chain. It is at the drivers discretion to react to errors -from collectors. - -The default DirectoryCollector collects test files that -match the glob patterns ``test_*.py`` or ``*_test.py`` and it -recurses into any directory that doesn't start with a leading -dot (e.g. ``.svn`` direcotries is ignored). - -Another ``PyCollector`` then recurses into the test files and -collects functions and methods that have a leading ``test_``. -By default, methods are only collected if their class starts -with ``Test``. - -Drivers bind it all together ----------------------------- - -Drivers serve as the glue code between the various parts of -the interacting ``py.test`` objects, more specifically -they: - -- iterate over Collectors, which yield more Collectors or Items. - -- call the ``Item.execute()`` method in order to execute - a test - -- send "result" and other events to the reporter so that - the users gets informed about progress and problems - in possibly custom ways. + WARNING: THIS IS FUTURISTIC (documentation driven development) + +The testing process is iterative, i.e. the driver immediately +starts to execute test Items. + +For directories, modules and classes there are default +**generative Items** which yield more **test Items** from your +python source tree. + +- generative items can be iter()ated yielding back more + generative items or ``Result`` objects. + +- if iter()ating a generative item results in an exception + this is interpreted as a test failure related to the + generative item that failed. + +- generative items are restartable, i.e. multiple calls + to ``iter(genitem)`` allow to iterate a generative + item multiple times. + +- A ``driver`` uses ``iter(genitem)`` to iterate over + generative items the reporter will receive an + IterationFailure Result. + +- The default DirectoryItem yields ModuleItems for files + matching the glob patterns ``test_*.py`` or ``*_test.py`` and it + recurses into any directory that doesn't start with a leading + dot (e.g. ``.svn`` direcotries are ignored by default). + +- The default ModuleItem yields back GeneratorItems, ClassItems, + FunctionItems. ClassItems can also yield back FunctionItems + or GeneratorItems. + +- FunctionItems usually yield back Results obtaining from + executing their own ``funcitem.execute()`` which will just + call the underlying callable by default. .. _reporter: Reporters process test events ----------------------------- -A driver typically invokes certain methods of a Reporter -for various points in the testing process. A reporter -is reponsible for representing the testing process -to the user or other programs. These are the events + WARNING: THIS IS FUTURISTIC (documentation driven development) + +A driver invokes Reporter methods for various interesting points +in the testing process. A reporter is reponsible for representing +the testing process to the user or other programs. These are the events (function calls) that the reporter receives:: - start(), end() are invoked only from the outmost driver - to allow a reporter to write headers and - footers - - open(collector) is called when a collector is about - to be iterated. This method should return - a "close" method (or None) which will be - called when the collector is finished iterating. - - startitem(item) is called before executing a single test item - enditem(result) is called when execution of a single test item - finished. The result.item attribute points back - to the Test item the result object belongs to. + begin(startitems) before the testing process begins + end(startitems) after the testing process finished + open(item) before an item is about to be iterated + result(result) for obtained ``Result`` objects + end(item) after iteration of an item finished + + error(excinfo) signals severe internal/iteration failures + Customizing the py.test process =============================== -Customizing the collection process in a module ----------------------------------------------- + WARNING: THIS IS FUTURISTIC (documentation driven development) + +*test generators* can produce test items on the fly +--------------------------------------------------- + + WARNING: THIS IS FUTURISTIC (documentation driven development) If you have a module where you want to take responsibility for -collecting your own test Items and possibly even for executing -a test then you can provide your own ``Collector`` at module -level. The default ModuleCollector looks for the name -``Collector`` in the modules namespace and turns over -reponsibility by invoking it with the "module-path". - -The module path is a ``py.path.py()`` instance and carries -information needed to traverse to to the module. In general, -a pypath allows to address a python object on the filesystem. -*Addressability of test Items* is a major concern because we -want to memorize failing tests across py.test invocations. A -``pypath`` has two parts, a filesystem path and a dotted path -leading to the python object. Another benefits, apart from -from addressability, is that invoking setup/teardown operations -can simply be implemented by walking the path to the python -object. +collecting your own test Items you can provide a test generator +that yields back callables which will be seemlessly integrated +into the testing process. **Test generators** are automatically +picked up and are recognized by the usual ``test_`` prefix. +If iterating over your generator raises an exception this +is reported as a test failure result. + +If a **test generator** raises an ``EOFError`` the parent (generative) +Item will stop further iteration. This allows your **test generator** +to control further inspection of a module or class. + +Customizing the execution of Items +---------------------------------- -Customizing execution of Items ------------------------------- + WARNING: THIS IS FUTURISTIC (documentation driven development) - Items allow total control of executing their contained test - method. ``iteminstance.execute()`` will get called by the - driver in order to actually execute a test. Thus a custom - ``execute()`` method can pass arguments to test functions. + method. ``iteminstance.execute()`` will get called + in order to actually execute a test. - Item.execute() methods can provide custom paramters (a db-connection, some initialized application entity, whatever) Modified: py/branch/test2/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/branch/test2/py/__init__.py Sun Oct 31 22:42:53 2004 @@ -28,6 +28,24 @@ 'test.config': './test/config.config', 'test.compat.TestCase': './test/compat.TestCase', + 'test2.DefaultOptions': './test2/option.DefaultOptions', + 'test2.Option': './test2/tool/optparse.Option', + 'test2.TextReporter': './test2/report/text/reporter.TextReporter', + 'test2.Actor': './test2/actor.Actor', + 'test2.CmdlineActor': './test2/cmdline.CmdlineActor', + #'test2.MemoActor': './test2/actor.MemoActor', + 'test2.exit': './test/drive.exit', + 'test2.fail': './test/drive.fail', + 'test2.skip': './test/drive.skip', + 'test2.raises': './test/raises.raises', + 'test2.config': './test/config.config', + 'test2.compat.TestCase': './test/compat.TestCase', + 'test2.item.Directory': './test/item.Directory', + 'test2.item.Module': './test/item.Module', + 'test2.item.Class': './test/item.Class', + 'test2.item.Callable': './test/item.Callable', + 'test2.item.Generator': './test/item.Generator', + 'process.cmdexec': './process/cmdexec.cmdexec', 'execnet.PopenGateway': './execnet/register.PopenGateway', Modified: py/branch/test2/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/branch/test2/py/path/local/local.py Sun Oct 31 22:42:53 2004 @@ -266,9 +266,10 @@ def write(self, content): """ write string content into path. """ + s = str(content) f = self.open('wb') try: - f.write(content) + f.write(s) finally: f.close() Modified: py/branch/test2/py/test2/cmdline.py ============================================================================== --- py/dist/py/test2/cmdline.py (original) +++ py/branch/test2/py/test2/cmdline.py Sun Oct 31 22:42:53 2004 @@ -1,35 +1,84 @@ from __future__ import generators import py -import sys -from py.__impl__.test import run + +from py.__impl__.test2.tool import optparse + +from py.__impl__.test2.item import Directory, Module # # main entry point # -def _old(argv): - if argv is None: - argv = py.std.sys.argv - frame = py.std.sys._getframe(1) - name = frame.f_locals.get('__name__') - if name != '__main__': - return # called from an imported test file - import __main__ - return [py.test.collect.Module(py.std.sys.argv[0])] - def main(): - args = py.std.sys.argv[1:] - py.test.config.readconfiguration(*run.getanchors(args)) + actor = CmdlineActor(py.std.sys.argv) + actor.start() + +class CmdlineActor(py.test2.Actor): + def __init__(self, argv): + anchors = getanchors(argv[1:]) + self.options = py.test2.DefaultOptions(anchors) + self.parse(argv) + self.reporter = self.options.Reporter(self) + assert self.items, "startup should result in runnable Items" + super(CmdlineActor, self).__init__() + + def parse(self, argv): + """ parse command line options and return items. """ + # first a small fight with optparse to merge the + # pytest.py file options correctly + parser = optparse.OptionParser() + for config in self.options._configpaths: + meth = config.join('options') + if meth.check(): + groupname, groupoptions = meth.resolve() + optgroup = optparse.OptionGroup(parser, groupname) + optgroup.add_options(groupoptions) + parser.add_option_group(optgroup) + + # extract and remove defaults from options + for option in flattenoptions(parser): + if option.dest: + value = self.options._getfirst(option.dest, option.default) + #print "setting %r to %r" %(option.dest, value) + setattr(self.options, option.dest, value) + option.default = 'NODEFAULT' + + # parse cmdline args + _, remaining = parser.parse_args(argv[1:], self.options) + # override previously computed defaults + #for name in cmdlineoption.__dict__: + # if not name.startswith('_'): + # print "resetting %r to %r" %(name, cmdlineoption.__dict__[name]) + # setattr(self.options, name, cmdlineoption.__dict__[name]) + self.items = [] + for x in remaining: + p = py.path.local(x) + if p.check(dir=1): + self.items.append(Directory(p, self)) + elif p.check(file=1): + self.items.append(Module(p, self)) + else: + raise ValueError(x) + if not self.items: + self.items.append(Directory(py.path.local(), self)) + +def getanchors(args): + """ yield "anchors" from skimming the args for existing files/dirs. """ + current = py.path.local() + l = [] + for arg in args: + anchor = current.join(arg, abs=1) + if anchor.check(): + l.append(anchor) + if not l: + l = [current] + return l + +# helpers - filenames = py.test.config.parseargs(args) - if not filenames: - filenames.append(str(py.path.local())) - try: - if py.test.config.option.session: - run.session(args, filenames) - else: - run.inprocess(args, filenames) - except KeyboardInterrupt: - print - print "Keybordinterrupt" - raise SystemExit, 2 +def flattenoptions(parser): + for group in parser.option_groups: + for y in group.option_list: + yield y + for x in parser.option_list: + yield x Modified: py/branch/test2/py/test2/config.py ============================================================================== --- py/dist/py/test2/config.py (original) +++ py/branch/test2/py/test2/config.py Sun Oct 31 22:42:53 2004 @@ -1,7 +1,7 @@ from __future__ import generators import py -from py.__impl__.test.tool import optparse +from py.__impl__.test2.tool import optparse defaultconfig = py.magic.autopath().dirpath('defaultconfig.py') defaultconfig = py.path.extpy(defaultconfig) @@ -11,10 +11,42 @@ # config file handling (utest.conf) # configbasename = 'conftest.py' +import py -class Config: - def __init__(self): - self.configpaths = [] +Option = py.test2.Option +options = ('py.test standard options', [ + Option('-v', '--verbose', + action="count", dest="verbose", default=0, + help="increase verbosity"), + Option('-x', '--exitfirst', + action="store_true", dest="exitfirstproblem", default=False, + help="exit instantly on first error or failed test."), + Option('-S', '--nocapture', + action="store_true", dest="nocapture", default=False, + help="disable catching of sys.stdout/stderr output."), + Option('-l', '--showlocals', + action="store_true", dest="showlocals", default=False, + help="show locals in tracebacks (disabled by default)"), + Option('', '--session', + action="store_true", dest="session", default=False, + help="run a test session/rerun only failing tests. "), + Option('', '--fulltrace', + action="store_true", dest="fulltrace", default=False, + help="Don't try to cut any tracebacks (default is to cut)"), + Option('', '--nomagic', + action="store_true", dest="nomagic", default=False, + help="don't invoke magic to e.g. beautify failing/error statements."), + #Option('', '--pdb', + # action="store_true", dest="usepdb", default=False, + # help="Start pdb (the Python debugger) on errors."), + Option('', '--collectonly', + action="store_true", dest="collectonly", default=False, + help="only collect tests, don't execute them. "), +]) + +class DefaultOptionsOptions: + def __init__(self, config=None): + self.configpaths = filter(None, [config, defaultconfig]) self.option = optparse.Values() def _gettmpdir(self, sessiondir=[]): @@ -26,19 +58,11 @@ return d tmpdir = property(_gettmpdir, None, None, "Temporary Directory") - def readconfiguration(self, *anchors): - """ read configuration files left-to-right for the given anchor file. """ - self.configpaths = [] - for anchor in anchors: - for p in anchor.parts(): - x = p.join(configbasename) - if x.check(file=1): - extpy = py.path.extpy(x) - extpy.resolve() # trigger loading it - self.configpaths.append(extpy) - self.configpaths.sort(lambda x,y: cmp(len(str(x)), len(str(y)))) - self.configpaths.append(defaultconfig) - + def __getattr__(self, name): + if name[0] == '_': + raise AttributeError(name) + return self.getfirst(name) + def getfirst(self, param, default=dummy): """ return first found parameter. """ for config in self.configpaths: @@ -51,8 +75,20 @@ #if default is not dummy: # return getattr(self, param, default) #return getattr(self, param) + + def _getreporter(self): + try: + return self._reporter + except AttributeError: + self._reporter = r = self.Reporter() + return r + def _setreporter(self, rep): + self._reporter = rep + reporter = property(_getreporter, _setreporter, None, "reporter instance") def parseargs(self, args): + anchors = getanchors(args) + self.configpaths = readconfiguration(anchors) + self.configpaths # first a small fight with optparse to merge the # pytest.py file options correctly parser = optparse.OptionParser() @@ -79,10 +115,33 @@ # if not name.startswith('_'): # print "resetting %r to %r" %(name, cmdlineoption.__dict__[name]) # setattr(self.option, name, cmdlineoption.__dict__[name]) - return remaining - + self.testpaths = [py.path.local(x) for x in remaining] + #return remaining -config = Config() +def readconfiguration(anchors): + """ read configuration files left-to-right for the given anchor file. """ + configpaths = [] + for anchor in anchors: + for p in anchor.parts(): + x = p.join(configbasename) + if x.check(file=1): + extpy = py.path.extpy(x) + extpy.resolve() # trigger loading it XXX why? + configpaths.append(extpy) + configpaths.sort(lambda x,y: cmp(len(str(x)), len(str(y)))) + return configpaths + +def getanchors(args): + """ yield "anchors" from skimming the args for existing files/dirs. """ + current = py.path.local() + l = [] + for arg in args: + anchor = current.join(arg, abs=1) + if anchor.check(): + l.append(anchor) + if not l: + l = [current] + return l # helpers Modified: py/branch/test2/py/test2/defaultconfig.py ============================================================================== --- py/dist/py/test2/defaultconfig.py (original) +++ py/branch/test2/py/test2/defaultconfig.py Sun Oct 31 22:42:53 2004 @@ -1,9 +1,6 @@ import py -Option = py.test.Option - -def getreporter(): - return py.test.TextReporter() +Option = py.test2.Option options = ('py.test standard options', [ Option('-v', '--verbose', action="count", dest="verbose", default=0, @@ -33,3 +30,7 @@ action="store_true", dest="collectonly", default=False, help="only collect tests, don't execute them. "), ]) + +from py.__impl__.test2.item import DirectoryItem, ModuleItem +from py.__impl__.test2.item import CmdlineItem, StartItem, ModuleItem, CallableItem + Modified: py/branch/test2/py/test2/item.py ============================================================================== --- py/dist/py/test2/item.py (original) +++ py/branch/test2/py/test2/item.py Sun Oct 31 22:42:53 2004 @@ -1,25 +1,10 @@ +from __future__ import generators +import py # ---------------------------------------------- # Basic Test Item # ---------------------------------------------- class Item(object): - _setupcache = [] - _lastinstance = None - - def __init__(self, extpy, *args): - self.extpy = extpy - self.name = extpy.basename - self.args = args - - def execute(self, driver): - driver.setup_path(self.extpy) - target, teardown = driver.setup_method(self.extpy) - try: - target(*self.args) - finally: - if teardown: - teardown(target) - class Outcome: def __init__(self, **kwargs): assert 'msg' not in kwargs or isinstance(kwargs['msg'], str), ( @@ -28,8 +13,164 @@ def __repr__(self): return getattr(self, 'msg', object.__repr__(self)) class Passed(Outcome): pass - class Failed(Outcome): pass - class ExceptionFailure(Failed): pass + class Failure(Outcome): pass + class ExceptionFailure(Failure): pass class Skipped(Outcome): pass + parentitem = None + + def __init__(self, actor): + self.actor = actor + + def run(self): + print "self", self + print "actor", self.actor + assert isinstance(self.actor, py.test2.Actor) + items = self.actor.call('collect', self) + for item in items: + self.actor.optcall('open', item) + try: + try: + item.run() + except (KeyboardInterrupt, SystemExit): + raise + except: + self.actor.error(py.std.sys.exc_info()) + finally: + self.actor.optcall('close', item) + +class Directory(Item): + def __init__(self, fspath, actor): + assert fspath.check(dir=1) + assert isinstance(actor, py.test2.Actor) + self.fspath = fspath + super(Directory, self).__init__(actor) + + def strlocation(self): + return str(self.fspath) + + def fil(self, fspath): + return (fspath.check(file=1, fnmatch='test_*.py') or + fspath.check(file=1, fnmatch='*_test.py')) + def rec(self, fspath): + return fspath.check(dir=1, dotfile=0, link=0) + + def collect(self): + l = [] + append = l.append + for fspath in self.fspath.listdir(sort=True): + if self.rec(fspath): + append(Directory(fspath, self.actor)) + elif self.fil(fspath): + extpy = py.path.extpy(fspath) + append(Module(extpy, self.actor)) + return l + +class PyItem(Item): + def __init__(self, extpy, actor): + self.extpy = extpy + self.yielders = [getattr(self, x) + for x in dir(self.__class__) + if x.startswith('collect_')] + super(PyItem, self).__init__(actor) + + def strlocation(self): + return str(self.extpy) + + def sorted(self): + l = [] + for extpy in self.extpy.listdir(): + for meth in self.yielders: + for x in meth(extpy): + x.fspath = self.extpy.root + sortkey = self.getsortkey(x) + l.append((sortkey, x)) + l.sort() + return [x[1] for x in l] + + def getsortkey(self, obj): + """ sorting function to bring test methods in + the same order as int he file. + """ + if isinstance(obj, PyItem): + obj = obj.extpy.resolve() + elif isinstance(obj, PyItem): + return obj.getsortkey() + if hasattr(obj, 'im_func'): + obj = obj.im_func + if hasattr(obj, 'func_code'): + obj = obj.func_code + # enough is enough + return (getattr(obj, 'co_filename', None), + getattr(obj, 'co_firstlineno', py.std.sys.maxint)) + +class Module(PyItem): + def collect(self): + l = [] + append = l.append + for x in self.extpy.listdir(): + basename = x.basename + if basename.startswith('test_'): + if x.check(genfunc=1): + append(Generator(x, self.actor) ) + elif x.check(func=1): + append(Callable(x, self.actor)) + elif basename.startswith('Test') and self.extpy.samefile(x) \ + and x.check(class_=1): + obj = x.resolve() + print "found", obj + if not getattr(obj, 'disabled', 0): + append(Class(x)) + + slist = [(x.getsortkey(x),x) for x in l] + slist.sort() + l = [x[1] for x in slist] + return l + +class Callable(PyItem): + def __init__(self, extpy, actor, args=()): + super(Callable, self).__init__(extpy, actor) + self.args = args + + def run(self): + target = self.actor.optcall('setup', self) + try: + try: + self.execute(target) + except (KeyboardInterrupt, SystemExit): + raise + except: + self.actor.failed(self, py.std.sys.exc_info()) + else: + self.actor.passed(self) + finally: + self.actor.optcall('teardown', self) + + def setup(self): + """ setup objects along the path to the test-method + (pointed to by extpy). Tear down any previously + setup objects which are not directly needed. + """ + #self.conf.setup_path(self.extpy) + return self.extpy.resolve() + + def execute(self, target): + target(*self.args) + +class Generator(Callable): + def execute(self, obj): + for x in obj(*self.args): + if isinstance(x, (tuple, list)): + callable = x.pop(0) + args = tuple(x) + else: + callable = x + args = () + #if isgenerator(callable): + # item = self.LiveGenerator(callable, self, args) + if callable(callable): + item = Callable(callable, self, args) + else: + raise TypeError("not a callable or generator: %r" % callable) + item.run() Modified: py/branch/test2/py/test2/report/memo.py ============================================================================== --- py/dist/py/test2/report/memo.py (original) +++ py/branch/test2/py/test2/report/memo.py Sun Oct 31 22:42:53 2004 @@ -3,6 +3,34 @@ from py.test import Item class MemoReporter: + def __init__(self): + self._calls = [] + + def optcall(self, methodname, obj): + c = getattr(obj, methodname, None) + if c is not None: + c() + self._calls.append((methodname, obj)) + + def call(self, methodname, obj): + c = getattr(obj, methodname, None) + if c is not None: + c() + self._calls.append((methodname, obj)) + + def _dispatchmethod(self, methodname, item): + cls = getattr(item, '__class__', None) + if cls is not None: + for typ in py.std.inspect.getmro(cls): + reportmethodname = "%s_%s" % (methodname, typ.__name__) + reportmeth = getattr(reporter, reportmethodname, None) + if reportmeth is not None: + reportmeth(item) + break + call = getattr(item, methodname, None) + if call is not None: + return call(item) + typemap = { Item.Passed: '.', Item.Skipped: 's', Item.Failed: 'F', } Modified: py/branch/test2/py/test2/report/text/reporter.py ============================================================================== --- py/dist/py/test2/report/text/reporter.py (original) +++ py/branch/test2/py/test2/report/text/reporter.py Sun Oct 31 22:42:53 2004 @@ -24,10 +24,9 @@ Collector.Error: 'COLLECT ERROR', } - def __init__(self, f=None): - if f is None: - f = py.std.sys.stdout - self.out = getout(f) + def __init__(self, actor): + self.actor = actor + self.out = getout(py.std.sys.stdout) self._started = {} self.summary = self.Summary() self.summary.option = self.option = py.test.config.option Deleted: /py/dist/py/bin/py.test2 ============================================================================== --- /py/dist/py/bin/py.test2 Sun Oct 31 22:42:53 2004 +++ (empty file) @@ -1,5 +0,0 @@ -#!/usr/bin/env python - -from _findpy import py -from py.__impl__.test2.cmdline import main -main() Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Sun Oct 31 22:42:53 2004 @@ -266,9 +266,10 @@ def write(self, content): """ write string content into path. """ + s = str(content) f = self.open('wb') try: - f.write(content) + f.write(s) finally: f.close()