From hpk at codespeak.net Sun Mar 1 08:50:47 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 1 Mar 2009 08:50:47 +0100 (CET) Subject: [py-svn] r62281 - in py/trunk/py: . misc/testing Message-ID: <20090301075047.10C651684AA@codespeak.net> Author: hpk Date: Sun Mar 1 08:50:45 2009 New Revision: 62281 Modified: py/trunk/py/_com.py py/trunk/py/misc/testing/test_com.py Log: allowing a plugin to see its registration and unregistration events. Modified: py/trunk/py/_com.py ============================================================================== --- py/trunk/py/_com.py (original) +++ py/trunk/py/_com.py Sun Mar 1 08:50:45 2009 @@ -105,8 +105,8 @@ self.notify("plugin_registered", plugin) def unregister(self, plugin): - self._plugins.remove(plugin) self.notify("plugin_unregistered", plugin) + self._plugins.remove(plugin) def getplugins(self): return list(self._plugins) Modified: py/trunk/py/misc/testing/test_com.py ============================================================================== --- py/trunk/py/misc/testing/test_com.py (original) +++ py/trunk/py/misc/testing/test_com.py Sun Mar 1 08:50:45 2009 @@ -72,10 +72,7 @@ assert not plugins.isregistered(my) assert plugins.getplugins() == [my2] - #@py.test.mark.xfail def test_onregister(self): - py.test.skip("implement exitfirst plugin and " - "modify xfail plugin to override exitfirst behaviour?") plugins = PyPlugins() l = [] class MyApi: From hpk at codespeak.net Sun Mar 1 09:00:22 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 1 Mar 2009 09:00:22 +0100 (CET) Subject: [py-svn] r62282 - py/trunk/py/misc/testing Message-ID: <20090301080022.C70D5168478@codespeak.net> Author: hpk Date: Sun Mar 1 09:00:20 2009 New Revision: 62282 Modified: py/trunk/py/misc/testing/test_com.py Log: refine a test Modified: py/trunk/py/misc/testing/test_com.py ============================================================================== --- py/trunk/py/misc/testing/test_com.py (original) +++ py/trunk/py/misc/testing/test_com.py Sun Mar 1 09:00:20 2009 @@ -162,7 +162,6 @@ assert l == ["hellospecific", "helloanonymous"] def test_consider_env(self, monkeypatch): - # XXX write a helper for preserving os.environ plugins = PyPlugins() monkeypatch.setitem(os.environ, 'PYLIB', "unknownconsider_env") py.test.raises(ImportError, "plugins.consider_env()") @@ -185,25 +184,18 @@ def test_api_and_defaults(): assert isinstance(py._com.pyplugins, PyPlugins) -def test_subprocess_env(): - # XXX write a helper for preserving os.environ +def test_subprocess_env(testdir, monkeypatch): plugins = PyPlugins() - KEY = "PYLIB" - old = os.environ.get(KEY, None) - olddir = py.path.local(py.__file__).dirpath().dirpath().chdir() + old = py.path.local(py.__file__).dirpath().dirpath().chdir() try: - os.environ[KEY] = "unknownconsider_env" + monkeypatch.setitem(os.environ, "PYLIB", 'unknownconsider') excinfo = py.test.raises(py.process.cmdexec.Error, """ py.process.cmdexec("python -c 'import py'") """) assert str(excinfo.value).find("ImportError") != -1 assert str(excinfo.value).find("unknownconsider") != -1 finally: - olddir.chdir() - if old is None: - del os.environ[KEY] - else: - os.environ[KEY] = old + old.chdir() class TestPyPluginsEvents: def test_pyevent_named_dispatch(self): From hpk at codespeak.net Sun Mar 1 12:24:55 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 1 Mar 2009 12:24:55 +0100 (CET) Subject: [py-svn] r62287 - in py/trunk/py: code code/testing misc/testing test test/plugin test/testing Message-ID: <20090301112455.A9B78168519@codespeak.net> Author: hpk Date: Sun Mar 1 12:24:52 2009 New Revision: 62287 Modified: py/trunk/py/code/testing/test_excinfo.py py/trunk/py/code/traceback2.py py/trunk/py/misc/testing/test_com.py py/trunk/py/test/plugin/pytest_doctest.py py/trunk/py/test/plugin/pytest_pytester.py py/trunk/py/test/plugin/pytest_tmpdir.py py/trunk/py/test/pycollect.py py/trunk/py/test/testing/test_outcome.py py/trunk/py/test/testing/test_pytestplugin.py Log: By default cut traceback such that py lib code does not appear test tracebacks. Modified: py/trunk/py/code/testing/test_excinfo.py ============================================================================== --- py/trunk/py/code/testing/test_excinfo.py (original) +++ py/trunk/py/code/testing/test_excinfo.py Sun Mar 1 12:24:52 2009 @@ -108,6 +108,15 @@ newtraceback = traceback.cut(path=path, lineno=firstlineno+2) assert len(newtraceback) == 1 + def test_traceback_cut_excludepath(self, testdir): + p = testdir.makepyfile("def f(): raise ValueError") + excinfo = py.test.raises(ValueError, "p.pyimport().f()") + print excinfo.traceback + pydir = py.path.local(py.__file__).dirpath() + newtraceback = excinfo.traceback.cut(excludepath=pydir) + assert len(newtraceback) == 1 + assert newtraceback[0].frame.code.path == p + def test_traceback_filter(self): traceback = self.excinfo.traceback ntraceback = traceback.filter() Modified: py/trunk/py/code/traceback2.py ============================================================================== --- py/trunk/py/code/traceback2.py (original) +++ py/trunk/py/code/traceback2.py Sun Mar 1 12:24:52 2009 @@ -118,7 +118,7 @@ else: list.__init__(self, tb) - def cut(self, path=None, lineno=None, firstlineno=None): + def cut(self, path=None, lineno=None, firstlineno=None, excludepath=None): """ return a Traceback instance wrapping part of this Traceback by provding any combination of path, lineno and firstlineno, the @@ -129,7 +129,11 @@ with handling of the exception/traceback) """ for x in self: - if ((path is None or x.frame.code.path == path) and + code = x.frame.code + codepath = code.path + if ((path is None or codepath == path) and + (excludepath is None or (hasattr(codepath, 'relto') and + not codepath.relto(excludepath))) and (lineno is None or x.lineno == lineno) and (firstlineno is None or x.frame.code.firstlineno == firstlineno)): return Traceback(x._rawentry) Modified: py/trunk/py/misc/testing/test_com.py ============================================================================== --- py/trunk/py/misc/testing/test_com.py (original) +++ py/trunk/py/misc/testing/test_com.py Sun Mar 1 12:24:52 2009 @@ -49,6 +49,7 @@ call = MultiCall([n, m]) res = call.execute(firstresult=True) assert res == 2 + class TestPyPlugins: def test_MultiCall(self): Modified: py/trunk/py/test/plugin/pytest_doctest.py ============================================================================== --- py/trunk/py/test/plugin/pytest_doctest.py (original) +++ py/trunk/py/test/plugin/pytest_doctest.py Sun Mar 1 12:24:52 2009 @@ -3,7 +3,8 @@ class DoctestPlugin: def pytest_addoption(self, parser): parser.addoption("--doctest-modules", - action="store_true", dest="doctestmodules") + action="store_true", default=False, + dest="doctestmodules") def pytest_collect_file(self, path, parent): if path.ext == ".py": Modified: py/trunk/py/test/plugin/pytest_pytester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_pytester.py (original) +++ py/trunk/py/test/plugin/pytest_pytester.py Sun Mar 1 12:24:52 2009 @@ -371,10 +371,10 @@ assert numskipped == 1 assert numfailed == 2 + recorder.unregister() recorder.clear() assert not recorder.events assert not recorder.getfailures() - recorder.unregister() bus.notify("itemtestreport", rep) assert not recorder.events assert not recorder.getfailures() Modified: py/trunk/py/test/plugin/pytest_tmpdir.py ============================================================================== --- py/trunk/py/test/plugin/pytest_tmpdir.py (original) +++ py/trunk/py/test/plugin/pytest_tmpdir.py Sun Mar 1 12:24:52 2009 @@ -42,4 +42,5 @@ plugin.pytest_configure(item._config) p = plugin.pytest_pyfuncarg_tmpdir(item) assert p.check() - assert p.basename.endswith("test_func") + bn = p.basename.strip("0123456789") + assert bn.endswith("test_func") Modified: py/trunk/py/test/pycollect.py ============================================================================== --- py/trunk/py/test/pycollect.py (original) +++ py/trunk/py/test/pycollect.py Sun Mar 1 12:24:52 2009 @@ -19,6 +19,7 @@ import py from py.__.test.collect import configproperty, warnoldcollect from py.__.code.source import findsource +pydir = py.path.local(py.__file__).dirpath() class PyobjMixin(object): def obj(): @@ -273,6 +274,8 @@ ntraceback = traceback.cut(path=path, firstlineno=firstlineno) if ntraceback == traceback: ntraceback = ntraceback.cut(path=path) + if ntraceback == traceback: + ntraceback = ntraceback.cut(excludepath=pydir) traceback = ntraceback.filter() return traceback Modified: py/trunk/py/test/testing/test_outcome.py ============================================================================== --- py/trunk/py/test/testing/test_outcome.py (original) +++ py/trunk/py/test/testing/test_outcome.py Sun Mar 1 12:24:52 2009 @@ -1,7 +1,6 @@ import py import marshal -from py.__.test.outcome import Skipped class TestRaises: def test_raises(self): @@ -59,12 +58,8 @@ py.test.deprecated_call(dep_explicit, 0) py.test.deprecated_call(dep_explicit, 0) -def test_skip_simple(): - excinfo = py.test.raises(Skipped, 'py.test.skip("xxx")') - assert excinfo.traceback[-1].frame.code.name == "skip" - assert excinfo.traceback[-1].ishidden() - def test_importorskip(): + from py.__.test.outcome import Skipped try: sys = py.test.importorskip("sys") assert sys == py.std.sys Modified: py/trunk/py/test/testing/test_pytestplugin.py ============================================================================== --- py/trunk/py/test/testing/test_pytestplugin.py (original) +++ py/trunk/py/test/testing/test_pytestplugin.py Sun Mar 1 12:24:52 2009 @@ -271,3 +271,4 @@ assert len(call.methods) == 3 results = call.execute() assert results == [1,2,2] + From hpk at codespeak.net Sun Mar 1 14:16:16 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 1 Mar 2009 14:16:16 +0100 (CET) Subject: [py-svn] r62288 - py/trunk/py/test Message-ID: <20090301131616.D35791684C4@codespeak.net> Author: hpk Date: Sun Mar 1 14:16:14 2009 New Revision: 62288 Modified: py/trunk/py/test/conftesthandle.py Log: better error handling Modified: py/trunk/py/test/conftesthandle.py ============================================================================== --- py/trunk/py/test/conftesthandle.py (original) +++ py/trunk/py/test/conftesthandle.py Sun Mar 1 14:16:14 2009 @@ -30,12 +30,16 @@ if anchor.check(): # we found some file object self._path2confmods[None] = self.getconftestmodules(anchor) break + else: + assert 0, "no root of filesystem?" def getconftestmodules(self, path): """ return a list of imported conftest modules for the given path. """ try: clist = self._path2confmods[path] except KeyError: + if path is None: + raise ValueError("missing default conftest.") dp = path.dirpath() if dp == path: return [self.importconftest(defaultconftestpath)] From hpk at codespeak.net Sun Mar 1 14:43:54 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 1 Mar 2009 14:43:54 +0100 (CET) Subject: [py-svn] r62290 - in py/trunk/py/test: . plugin testing Message-ID: <20090301134354.39B3B168569@codespeak.net> Author: hpk Date: Sun Mar 1 14:43:53 2009 New Revision: 62290 Modified: py/trunk/py/test/config.py py/trunk/py/test/plugin/pytest_pytester.py py/trunk/py/test/testing/test_config.py Log: slightly sanitizing initialization of serialised config objects. Modified: py/trunk/py/test/config.py ============================================================================== --- py/trunk/py/test/config.py (original) +++ py/trunk/py/test/config.py Sun Mar 1 14:43:53 2009 @@ -28,8 +28,9 @@ _initialized = False _sessionclass = None - def __init__(self, pytestplugins=None): + def __init__(self, pytestplugins=None, topdir=None): self.option = CmdOptions() + self.topdir = topdir self._parser = parseopt.Parser( usage="usage: %prog [options] [file_or_dir] [file_or_dir] [...]", processopt=self._processopt, @@ -46,6 +47,11 @@ if not hasattr(self.option, opt.dest): setattr(self.option, opt.dest, opt.default) + def _preparse(self, args): + self._conftest.setinitial(args) + self.pytestplugins.consider_env() + self.pytestplugins.do_addoption(self._parser) + def parse(self, args): """ parse cmdline arguments into this config object. Note that this can only be called once per testing process. @@ -53,9 +59,7 @@ assert not self._initialized, ( "can only parse cmdline args at most once per Config object") self._initialized = True - self._conftest.setinitial(args) - self.pytestplugins.consider_env() - self.pytestplugins.do_addoption(self._parser) + self._preparse(args) args = self._parser.parse_setoption(args, self.option) if not args: args.append(py.std.os.getcwd()) @@ -74,11 +78,11 @@ def _initafterpickle(self, topdir): self.__init__( #issue1 - #pytestplugins=py.test._PytestPlugins(py._com.pyplugins) + #pytestplugins=py.test._PytestPlugins(py._com.pyplugins), + topdir=topdir, ) - self._initialized = True - self.topdir = py.path.local(topdir) self._mergerepr(self._repr) + self._initialized = True del self._repr def _makerepr(self): @@ -98,7 +102,7 @@ args, cmdlineopts = repr self.args = [self.topdir.join(x) for x in args] self.option = cmdlineopts - self._conftest.setinitial(self.args) + self._preparse(self.args) def getcolitems(self): return [self.getfsnode(arg) for arg in self.args] Modified: py/trunk/py/test/plugin/pytest_pytester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_pytester.py (original) +++ py/trunk/py/test/plugin/pytest_pytester.py Sun Mar 1 14:43:53 2009 @@ -4,6 +4,7 @@ import py from py.__.test import event +from py.__.test.config import Config as pytestConfig class PytesterPlugin: def pytest_pyfuncarg_linecomp(self, pyfuncitem): @@ -49,8 +50,11 @@ self.tmpdir = tmpdir.mkdir(name) self.plugins = [] self._syspathremove = [] - from py.__.test.config import Config - self.Config = Config + + def Config(self, pyplugins=None, topdir=None): + if topdir is None: + topdir = self.tmpdir.dirpath() + return pytestConfig(pyplugins, topdir=topdir) def finalize(self): for p in self._syspathremove: Modified: py/trunk/py/test/testing/test_config.py ============================================================================== --- py/trunk/py/test/testing/test_config.py (original) +++ py/trunk/py/test/testing/test_config.py Sun Mar 1 14:43:53 2009 @@ -285,10 +285,10 @@ tmp.ensure("conftest.py").write("x=1 ; y=2") hello = tmp.ensure("test_hello.py") config = py.test.config._reparse([hello]) - config2 = py.test.config._reparse([tmp.dirpath()]) + config2 = testdir.Config() config2._initialized = False # we have to do that from tests config2._repr = config._makerepr() - config2._initafterpickle(topdir=tmp.dirpath()) + config2._initafterpickle(tmp.dirpath()) for col1, col2 in zip(config.getcolitems(), config2.getcolitems()): assert col1.fspath == col2.fspath @@ -305,12 +305,21 @@ tmp.ensure("conftest.py").write("x=1") config = py.test.config._reparse([tmp]) repr = config._makerepr() + config.option.verbose = 42 repr2 = config._makerepr() - config = py.test.config._reparse([tmp.dirpath()]) - py.test.raises(KeyError, "config.getvalue('x')") + + print "hello" + config = testdir.Config() config._mergerepr(repr) + print config._conftest.getconftestmodules(None) assert config.getvalue('x') == 1 + + config = testdir.Config() + config._preparse([]) + py.test.raises(KeyError, "config.getvalue('x')") + + config = testdir.Config() config._mergerepr(repr2) assert config.option.verbose == 42 @@ -327,13 +336,16 @@ config = py.test.config._reparse([tmp, "-G", "11"]) assert config.option.gdest == 11 repr = config._makerepr() - config = py.test.config._reparse([tmp.dirpath()]) + + config = testdir.Config() py.test.raises(AttributeError, "config.option.gdest") - config._mergerepr(repr) - option = config.addoptions("testing group", - config.Option('-G', '--glong', action="store", default=42, + + config2 = testdir.Config() + config2._mergerepr(repr) + option = config2.addoptions("testing group", + config2.Option('-G', '--glong', action="store", default=42, type="int", dest="gdest", help="g value.")) - assert config.option.gdest == 11 + assert config2.option.gdest == 11 assert option.gdest == 11 def test_config_picklability(self, tmpdir): From hpk at codespeak.net Sun Mar 1 14:56:30 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 1 Mar 2009 14:56:30 +0100 (CET) Subject: [py-svn] r62292 - in py/trunk/py/test: . dsession dsession/testing looponfail plugin testing Message-ID: <20090301135630.47C5D16856C@codespeak.net> Author: hpk Date: Sun Mar 1 14:56:29 2009 New Revision: 62292 Modified: py/trunk/py/test/cmdline.py py/trunk/py/test/dsession/masterslave.py py/trunk/py/test/dsession/testing/test_dsession.py py/trunk/py/test/looponfail/remote.py py/trunk/py/test/plugin/pytest_default.py py/trunk/py/test/plugin/pytest_pytester.py py/trunk/py/test/pytestplugin.py py/trunk/py/test/testing/test_pytestplugin.py Log: use prefix "do_" for configure/unconfigure calls to plugins in addition to addoption. Modified: py/trunk/py/test/cmdline.py ============================================================================== --- py/trunk/py/test/cmdline.py (original) +++ py/trunk/py/test/cmdline.py Sun Mar 1 14:56:29 2009 @@ -10,10 +10,10 @@ args = py.std.sys.argv[1:] config = py.test.config config.parse(args) - config.pytestplugins.configure(config) + config.pytestplugins.do_configure(config) session = config.initsession() exitstatus = session.main() - config.pytestplugins.unconfigure(config) + config.pytestplugins.do_unconfigure(config) raise SystemExit(exitstatus) def warn_about_missing_assertion(): Modified: py/trunk/py/test/dsession/masterslave.py ============================================================================== --- py/trunk/py/test/dsession/masterslave.py (original) +++ py/trunk/py/test/dsession/masterslave.py Sun Mar 1 14:56:29 2009 @@ -113,7 +113,7 @@ self.channel.send((eventname, args, kwargs)) def run(self): - self.config.pytestplugins.configure(self.config) + self.config.pytestplugins.do_configure(self.config) from py.__.test.dsession.hostmanage import makehostup channel = self.channel self.host = host = channel.receive() Modified: py/trunk/py/test/dsession/testing/test_dsession.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_dsession.py (original) +++ py/trunk/py/test/dsession/testing/test_dsession.py Sun Mar 1 14:56:29 2009 @@ -26,7 +26,7 @@ class TestDSession: def test_fixoptions(self, testdir): config = testdir.parseconfig("--exec=xxx") - config.pytestplugins.configure(config) + config.pytestplugins.do_configure(config) config.initsession().fixoptions() assert config.option.numprocesses == 1 config = testdir.parseconfig("--exec=xxx", '-n3') Modified: py/trunk/py/test/looponfail/remote.py ============================================================================== --- py/trunk/py/test/looponfail/remote.py (original) +++ py/trunk/py/test/looponfail/remote.py Sun Mar 1 14:56:29 2009 @@ -129,7 +129,7 @@ config.option.usepdb = False config.option.executable = None trails = channel.receive() - config.pytestplugins.configure(config) + config.pytestplugins.do_configure(config) DEBUG("SLAVE: initsession()") session = config.initsession() # XXX configure the reporter object's terminal writer more directly Modified: py/trunk/py/test/plugin/pytest_default.py ============================================================================== --- py/trunk/py/test/plugin/pytest_default.py (original) +++ py/trunk/py/test/plugin/pytest_default.py Sun Mar 1 14:56:29 2009 @@ -100,7 +100,7 @@ def x(*args): config = py.test.config._reparse([tmpdir] + list(args)) try: - config.pytestplugins.configure(config) + config.pytestplugins.do_configure(config) except ValueError: return Exception return getattr(config._sessionclass, '__name__', None) Modified: py/trunk/py/test/plugin/pytest_pytester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_pytester.py (original) +++ py/trunk/py/test/plugin/pytest_pytester.py Sun Mar 1 14:56:29 2009 @@ -134,28 +134,28 @@ def inline_runsession(self, session): config = session.config - config.pytestplugins.configure(config) + config.pytestplugins.do_configure(config) sorter = EventRecorder(config.bus) session.main() - config.pytestplugins.unconfigure(config) + config.pytestplugins.do_unconfigure(config) return sorter def inline_run(self, *args): config = self.parseconfig(*args) - config.pytestplugins.configure(config) + config.pytestplugins.do_configure(config) session = config.initsession() sorter = EventRecorder(config.bus) session.main() - config.pytestplugins.unconfigure(config) + config.pytestplugins.do_unconfigure(config) return sorter def inline_run_with_plugins(self, *args): config = self.parseconfig(*args) - config.pytestplugins.configure(config) + config.pytestplugins.do_configure(config) session = config.initsession() sorter = EventRecorder(config.bus) session.main() - config.pytestplugins.unconfigure(config) + config.pytestplugins.do_unconfigure(config) return sorter def config_preparse(self): Modified: py/trunk/py/test/pytestplugin.py ============================================================================== --- py/trunk/py/test/pytestplugin.py (original) +++ py/trunk/py/test/pytestplugin.py Sun Mar 1 14:56:29 2009 @@ -85,13 +85,13 @@ self.pyplugins.call_plugin(plugin, "pytest_addoption", parser=self._config._parser) self.pyplugins.call_plugin(plugin, "pytest_configure", config=self._config) - def configure(self, config): + def do_configure(self, config): assert not hasattr(self, '_config') config.bus.register(self) self._config = config self.pyplugins.call_each("pytest_configure", config=self._config) - def unconfigure(self, config): + def do_unconfigure(self, config): config = self._config del self._config self.pyplugins.call_each("pytest_unconfigure", config=config) Modified: py/trunk/py/test/testing/test_pytestplugin.py ============================================================================== --- py/trunk/py/test/testing/test_pytestplugin.py (original) +++ py/trunk/py/test/testing/test_pytestplugin.py Sun Mar 1 14:56:29 2009 @@ -158,7 +158,7 @@ from py.__.test.config import Config config = Config() config.parse([]) - config.pytestplugins.configure(config=config) + config.pytestplugins.do_configure(config=config) assert not hasattr(config.option, 'test123') p = testdir.makepyfile(""" class ConftestPlugin: @@ -181,7 +181,7 @@ config.bus.register(A()) assert len(l) == 0 - config.pytestplugins.configure(config=config) + config.pytestplugins.do_configure(config=config) assert len(l) == 1 config.bus.register(A()) # this should lead to a configured() plugin assert len(l) == 2 @@ -191,7 +191,7 @@ assert len(events) == 2 assert events == [42,42] - config.pytestplugins.unconfigure(config=config) + config.pytestplugins.do_unconfigure(config=config) config.bus.register(A()) assert len(l) == 2 From py-svn at codespeak.net Sun Mar 1 17:01:36 2009 From: py-svn at codespeak.net (py-svn at codespeak.net) Date: Sun, 1 Mar 2009 17:01:36 +0100 (CET) Subject: [py-svn] You'll get 10 girls per night! Message-ID: <20090301160136.25C40168481@codespeak.net> An HTML attachment was scrubbed... URL: From hpk at codespeak.net Mon Mar 2 11:58:16 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 2 Mar 2009 11:58:16 +0100 (CET) Subject: [py-svn] r62333 - py/trunk/py/test/plugin Message-ID: <20090302105816.95A16168466@codespeak.net> Author: hpk Date: Mon Mar 2 11:58:13 2009 New Revision: 62333 Removed: py/trunk/py/test/plugin/pytest_apigen.py Log: remove apigen here, it is now at svn/apigen/trunk/pytest_apigen.py Deleted: /py/trunk/py/test/plugin/pytest_apigen.py ============================================================================== --- /py/trunk/py/test/plugin/pytest_apigen.py Mon Mar 2 11:58:13 2009 +++ (empty file) @@ -1,82 +0,0 @@ -import py - -class ApigenPlugin: - def pytest_addoption(self, parser): - group = parser.addgroup("apigen options") - group.addoption('--apigen', action="store_true", dest="apigen", - help="generate api documentation") - #group.addoption('--apigenpath', - # action="store", dest="apigenpath", - # default="../apigen", - # type="string", - # help="relative path to apigen doc output location (relative from py/)") - #group.addoption('--docpath', - # action='store', dest='docpath', - # default="doc", type='string', - # help="relative path to doc output location (relative from py/)") - - def pytest_configure(self, config): - if config.option.apigen: - from py.__.apigen.tracer.tracer import Tracer, DocStorage - self.pkgdir = py.path.local(config.args[0]).pypkgpath() - apigenscriptpath = py.path.local(py.__file__).dirpath("apigen", "apigen.py") - apigenscript = apigenscriptpath.pyimport() - if not hasattr(apigenscript, 'get_documentable_items'): - raise NotImplementedError("%r needs to provide get_documentable_items" %( - apigenscriptpath,)) - self.apigenscript = apigenscript - pkgname, items = apigenscript.get_documentable_items(self.pkgdir) - self.docstorage = DocStorage().from_dict(items, - module_name=pkgname) - self.tracer = Tracer(self.docstorage) - - def pytest_pyfunc_call(self, pyfuncitem, args, kwargs): - if hasattr(self, 'tracer'): - self.tracer.start_tracing() - try: - pyfuncitem.obj(*args, **kwargs) - finally: - self.tracer.end_tracing() - return True - - def pytest_terminal_summary(self, terminalreporter): - if hasattr(self, 'tracer'): - tr = terminalreporter - from py.__.apigen.tracer.docstorage import DocStorageAccessor - terminalreporter.write_sep("=", "apigen: building documentation") - #assert hasattr(tr.config.option, 'apigenpath') - capture = py.io.StdCaptureFD() - try: - self.apigenscript.build( - tr.config, - self.pkgdir, - DocStorageAccessor(self.docstorage), - capture) - finally: - capture.reset() - terminalreporter.write_line("apigen build completed") - -def test_generic(plugintester): - plugintester.apicheck(ApigenPlugin) - -def test_functional_simple(testdir): - sub = testdir.tmpdir.mkdir("test_simple") - sub.join("__init__.py").write(py.code.Source(""" - from py import initpkg - initpkg(__name__, exportdefs={ - 'simple.f': ('./test_simple.py', 'f',), - }) - """)) - pyfile = sub.join("test_simple.py") - pyfile.write(py.code.Source(""" - def f(arg): - pass - def test_f(): - f(42) - """)) - testdir.makepyfile(conftest="pytest_plugins='apigen'") - result = testdir.runpytest(pyfile, "--apigen") - result.stdout.fnmatch_lines([ - "*apigen: building documentation*", - "apigen build completed", - ]) From hpk at codespeak.net Mon Mar 2 12:15:00 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 2 Mar 2009 12:15:00 +0100 (CET) Subject: [py-svn] r62348 - in py/trunk/py: doc test test/testing Message-ID: <20090302111500.326D31684E9@codespeak.net> Author: hpk Date: Mon Mar 2 12:14:59 2009 New Revision: 62348 Added: py/trunk/py/doc/test-config.txt - copied unchanged from r62347, user/hpk/branch/hostmanage/py/doc/test-config.txt Modified: py/trunk/py/test/config.py py/trunk/py/test/testing/test_config.py Log: add support fo setting command line options from PYTEST_OPTION_NAME environment vars add a first bit of documentation (merge of 62304:HEAD from the hostmanage branch) Modified: py/trunk/py/test/config.py ============================================================================== --- py/trunk/py/test/config.py (original) +++ py/trunk/py/test/config.py Mon Mar 2 12:14:59 2009 @@ -1,4 +1,4 @@ -import py +import py, os from conftesthandle import Conftest from py.__.test import parseopt @@ -44,6 +44,17 @@ def _processopt(self, opt): if hasattr(opt, 'default') and opt.dest: + val = os.environ.get("PYTEST_OPTION_" + opt.dest.upper(), None) + if val is not None: + if opt.type == "int": + val = int(val) + elif opt.type == "long": + val = long(val) + elif opt.type == "float": + val = float(val) + elif not opt.type and opt.action in ("store_true", "store_false"): + val = eval(val) + opt.default = val if not hasattr(self.option, opt.dest): setattr(self.option, opt.dest, opt.default) @@ -147,6 +158,16 @@ def addoption(self, *optnames, **attrs): return self._parser.addoption(*optnames, **attrs) + def getvalueorskip(self, name, path=None): + """ return getvalue() or call py.test.skip if no value exists. """ + try: + val = self.getvalue(name, path) + if val is None: + raise KeyError(name) + return val + except KeyError: + py.test.skip("no %r value found" %(name,)) + def getvalue(self, name, path=None): """ return 'name' value looked up from the 'options' and then from the first conftest file found up Modified: py/trunk/py/test/testing/test_config.py ============================================================================== --- py/trunk/py/test/testing/test_config.py (original) +++ py/trunk/py/test/testing/test_config.py Mon Mar 2 12:14:59 2009 @@ -19,6 +19,26 @@ config = py.test.config._reparse(['-G', '17']) assert config.option.gdest == 17 + def test_parser_addoption_default_env(self, testdir, monkeypatch): + import os + config = testdir.Config() + group = config._parser.addgroup("hello") + + monkeypatch.setitem(os.environ, 'PYTEST_OPTION_OPTION1', 'True') + group.addoption("--option1", action="store_true") + assert group.options[0].default == True + + monkeypatch.setitem(os.environ, 'PYTEST_OPTION_OPTION2', 'abc') + group.addoption("--option2", action="store", default="x") + assert group.options[1].default == "abc" + + monkeypatch.setitem(os.environ, 'PYTEST_OPTION_OPTION3', '32') + group.addoption("--option3", action="store", type="int") + assert group.options[2].default == 32 + + group.addoption("--option4", action="store", type="int") + assert group.options[3].default == ("NO", "DEFAULT") + def test_config_cmdline_options_only_lowercase(self, testdir): testdir.makepyfile(conftest=""" import py @@ -79,6 +99,15 @@ assert config.getvalue("x", o) == 1 py.test.raises(KeyError, 'config.getvalue("y", o)') + def test_config_getvalueorskip(self, testdir): + from py.__.test.outcome import Skipped + config = testdir.parseconfig() + py.test.raises(Skipped, "config.getvalueorskip('hello')") + verbose = config.getvalueorskip("verbose") + assert verbose == config.option.verbose + config.option.hello = None + py.test.raises(Skipped, "config.getvalueorskip('hello')") + def test_config_overwrite(self, testdir): o = testdir.tmpdir o.ensure("conftest.py").write("x=1") From hpk at codespeak.net Mon Mar 2 12:22:11 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 2 Mar 2009 12:22:11 +0100 (CET) Subject: [py-svn] r62352 - py/trunk/py/doc Message-ID: <20090302112211.B91A7168544@codespeak.net> Author: hpk Date: Mon Mar 2 12:22:11 2009 New Revision: 62352 Modified: py/trunk/py/doc/test-config.txt Log: add a note about how to specify plugins Modified: py/trunk/py/doc/test-config.txt ============================================================================== --- py/trunk/py/doc/test-config.txt (original) +++ py/trunk/py/doc/test-config.txt Mon Mar 2 12:22:11 2009 @@ -1,7 +1,20 @@ - Test configuration ======================== +specifying plugins +--------------------------- + +you can instruct py.test to use additional plugins by: + +* setting the PYTEST_PLUGINS environment variable + to a comma-separated list of plugins +* XXX supplying "--plugins=NAME1,NAME2,..." at the command line +* setting "pytest_plugins='name1', 'name2'" in + ``conftest.py`` files or in python test modules. + +py.test will load all plugins along with their dependencies +(plugins may specify "pytest_plugins" as well). + test option values ----------------------------- From py-svn at codespeak.net Mon Mar 2 16:50:25 2009 From: py-svn at codespeak.net (py-svn at codespeak.net) Date: Mon, 2 Mar 2009 16:50:25 +0100 (CET) Subject: [py-svn] Your classmate reports in! Message-ID: <20090302155025.9442C1684E4@codespeak.net> An HTML attachment was scrubbed... URL: From py-svn at codespeak.net Mon Mar 2 17:43:44 2009 From: py-svn at codespeak.net (py-svn at codespeak.net) Date: Mon, 2 Mar 2009 17:43:44 +0100 (CET) Subject: [py-svn] Wanna do her 3 hours? Ok! Message-ID: <20090302164344.B9833168575@codespeak.net> An HTML attachment was scrubbed... URL: From py-svn at codespeak.net Mon Mar 2 22:30:46 2009 From: py-svn at codespeak.net (py-svn at codespeak.net) Date: Mon, 2 Mar 2009 22:30:46 +0100 (CET) Subject: [py-svn] He said he lost your info Message-ID: <20090302213046.36B8016857A@codespeak.net> An HTML attachment was scrubbed... URL: From hpk at codespeak.net Mon Mar 2 23:43:31 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 2 Mar 2009 23:43:31 +0100 (CET) Subject: [py-svn] r62430 - in py/trunk/py/test: . testing Message-ID: <20090302224331.06F09168571@codespeak.net> Author: hpk Date: Mon Mar 2 23:43:31 2009 New Revision: 62430 Modified: py/trunk/py/test/pycollect.py py/trunk/py/test/testing/test_pycollect.py Log: honour unitttest function default values for pyfuncarg protocol Modified: py/trunk/py/test/pycollect.py ============================================================================== --- py/trunk/py/test/pycollect.py (original) +++ py/trunk/py/test/pycollect.py Mon Mar 2 23:43:31 2009 @@ -360,8 +360,18 @@ # standard Python Test function/method case funcobj = self.obj startindex = getattr(funcobj, 'im_self', None) and 1 or 0 - for argname in py.std.inspect.getargs(self.obj.func_code)[0][startindex:]: - kwargs[argname] = self.lookup_onearg(argname) + argnames = py.std.inspect.getargs(self.obj.func_code)[0] + for i, argname in py.builtin.enumerate(argnames): + if i < startindex: + continue + try: + kwargs[argname] = self.lookup_onearg(argname) + except LookupError, e: + numdefaults = len(funcobj.func_defaults or ()) + if i + numdefaults >= len(argnames): + continue # continue # seems that our args have defaults + else: + raise else: pass # XXX lookup of arguments for yielded/generated tests as well return kwargs Modified: py/trunk/py/test/testing/test_pycollect.py ============================================================================== --- py/trunk/py/test/testing/test_pycollect.py (original) +++ py/trunk/py/test/testing/test_pycollect.py Mon Mar 2 23:43:31 2009 @@ -229,9 +229,30 @@ assert not f1 != f1_b def test_pyfuncarg_lookupfails(self, testdir): - item = testdir.getitem("def test_func(some, other): pass") + item = testdir.getitem("def test_func(some): pass") kw = py.test.raises(LookupError, "item.lookup_allargs()") + def test_pyfuncarg_lookup_default(self, testdir): + item = testdir.getitem("def test_func(some, other=42): pass") + class Provider: + def pytest_pyfuncarg_some(self, pyfuncitem): + return pyfuncitem.name + item._config.pytestplugins.register(Provider()) + kw = item.lookup_allargs() + assert len(kw) == 1 + + def test_pyfuncarg_lookup_default_gets_overriden(self, testdir): + item = testdir.getitem("def test_func(some=42, other=13): pass") + class Provider: + def pytest_pyfuncarg_other(self, pyfuncitem): + return pyfuncitem.name + item._config.pytestplugins.register(Provider()) + kw = item.lookup_allargs() + assert len(kw) == 1 + name, value = kw.popitem() + assert name == "other" + assert value == item.name + def test_pyfuncarg_basic(self, testdir): item = testdir.getitem("def test_func(some, other): pass") class Provider: From py-svn at codespeak.net Tue Mar 3 05:34:08 2009 From: py-svn at codespeak.net (py-svn at codespeak.net) Date: Tue, 3 Mar 2009 05:34:08 +0100 (CET) Subject: [py-svn] Your activity was detected Message-ID: <20090303043408.3BE56169DB2@codespeak.net> An HTML attachment was scrubbed... URL: From hpk at codespeak.net Tue Mar 3 12:52:44 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 3 Mar 2009 12:52:44 +0100 (CET) Subject: [py-svn] r62451 - py/extradoc/talk/pycon-us-2009/tut2 Message-ID: <20090303115244.E75B0168576@codespeak.net> Author: hpk Date: Tue Mar 3 12:52:44 2009 New Revision: 62451 Added: py/extradoc/talk/pycon-us-2009/tut2/ py/extradoc/talk/pycon-us-2009/tut2/examples.txt Log: writing down some notes on what i want to do as examples Added: py/extradoc/talk/pycon-us-2009/tut2/examples.txt ============================================================================== --- (empty file) +++ py/extradoc/talk/pycon-us-2009/tut2/examples.txt Tue Mar 3 12:52:44 2009 @@ -0,0 +1,21 @@ + +example: writing a plugin for setting up directories + +use case example: + + def test_version_and_listfiles(setupfs): + setupfs.create(""" + version: 0.0 + readme.txt: hello + package/module.py: + ''' module doc string + ''' + """) + app = MyApp(setupfs.tmpdir) + assert app.getversion() == "0.0" + l = app.listfiles(ext=".py") + assert len(l) == 1 + +goal: write a pytest_setupfs plugin providing a setupfs object. +then: write MyApp satisfying the tests :) + From hpk at codespeak.net Tue Mar 3 13:06:46 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 3 Mar 2009 13:06:46 +0100 (CET) Subject: [py-svn] r62453 - py/extradoc/talk/pycon-us-2009/tut2 Message-ID: <20090303120646.7FAE9168507@codespeak.net> Author: hpk Date: Tue Mar 3 13:06:44 2009 New Revision: 62453 Added: py/extradoc/talk/pycon-us-2009/tut2/basic-points.txt py/extradoc/talk/pycon-us-2009/tut2/example_setupfs.txt - copied unchanged from r62451, py/extradoc/talk/pycon-us-2009/tut2/examples.txt Removed: py/extradoc/talk/pycon-us-2009/tut2/examples.txt Log: some basic points, rename fn Added: py/extradoc/talk/pycon-us-2009/tut2/basic-points.txt ============================================================================== --- (empty file) +++ py/extradoc/talk/pycon-us-2009/tut2/basic-points.txt Tue Mar 3 13:06:44 2009 @@ -0,0 +1,10 @@ +(maybe going to first tutorial) + +basic points +--------------- + +- test readability counts +- group tests logically + look at: how many tests does a refactoring affect? +- evolve your test suite with your application +- don't mind the unit/functional/acceptance distinctions too much Deleted: /py/extradoc/talk/pycon-us-2009/tut2/examples.txt ============================================================================== --- /py/extradoc/talk/pycon-us-2009/tut2/examples.txt Tue Mar 3 13:06:44 2009 +++ (empty file) @@ -1,21 +0,0 @@ - -example: writing a plugin for setting up directories - -use case example: - - def test_version_and_listfiles(setupfs): - setupfs.create(""" - version: 0.0 - readme.txt: hello - package/module.py: - ''' module doc string - ''' - """) - app = MyApp(setupfs.tmpdir) - assert app.getversion() == "0.0" - l = app.listfiles(ext=".py") - assert len(l) == 1 - -goal: write a pytest_setupfs plugin providing a setupfs object. -then: write MyApp satisfying the tests :) - From hpk at codespeak.net Tue Mar 3 13:41:35 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 3 Mar 2009 13:41:35 +0100 (CET) Subject: [py-svn] r62465 - py/extradoc/talk/pycon-us-2009/tut2 Message-ID: <20090303124135.3C32F16855B@codespeak.net> Author: hpk Date: Tue Mar 3 13:41:34 2009 New Revision: 62465 Added: py/extradoc/talk/pycon-us-2009/tut2/example_monkeypatch.txt Log: more or less complete example Added: py/extradoc/talk/pycon-us-2009/tut2/example_monkeypatch.txt ============================================================================== --- (empty file) +++ py/extradoc/talk/pycon-us-2009/tut2/example_monkeypatch.txt Tue Mar 3 13:41:34 2009 @@ -0,0 +1,117 @@ + +problem: patching a dict value for the duration of a test function. + +direct approach: code everything inline into the test function. + + def test_envreading(self): + old = os.environ['ENV1'] + os.environ['ENV1'] = 'myval' + try: + monkeypatch.setitem(os.environ, 'ENV1', 'myval') + val = myapp().readenv() + assert val == "myval" + finally: + os.environ['ENV1'] = old + +this does not even take into account that ENV1 might not be +set which would require more code. + +a better and traditional approach would involve +setup_method/teardown_methods like this: + + def setup_method(self, method): + self._oldenv = os.environ.copy() + + def teardown_method(self, method): + os.environ.update(self._oldenv) + + def test_evnreading(self): + os.environ['ENV1'] + val = myapp().readenv() + assert val == "myval" + +There now is code belonging to the same testing aspect +on three functions. As tests grew larger and more +things gets setup readability suffers, refactoring is hard. + +Here is the "monkeypatch" pyfuncarg plugin: + + def test_envreading(self, monkeypatch): + monkeypatch.setitem(os.environ, 'ENV1', 'myval') + val = myapp().readenv() + assert val == "myval" + +the monkeypatch object will unset any modifications +done through "monkeypatch.setitem|setattr" when the test +function finishes. + +The "monkeypatch" plugin which implements this functionality +has 66 lines of code including tests: + +class MonkeypatchPlugin: + """ setattr-monkeypatching with automatical reversal after test. """ + def pytest_pyfuncarg_monkeypatch(self, pyfuncitem): + monkeypatch = MonkeyPatch() + pyfuncitem.addfinalizer(monkeypatch.finalize) + return monkeypatch + +notset = object() + +class MonkeyPatch: + def __init__(self): + self._setattr = [] + self._setitem = [] + + def setattr(self, obj, name, value): + self._setattr.insert(0, (obj, name, getattr(obj, name, notset))) + setattr(obj, name, value) + + def setitem(self, dictionary, name, value): + self._setitem.insert(0, (dictionary, name, dictionary.get(name, notset))) + dictionary[name] = value + + def finalize(self): + for obj, name, value in self._setattr: + if value is not notset: + setattr(obj, name, value) + for dictionary, name, value in self._setitem: + if value is notset: + del dictionary[name] + else: + dictionary[name] = value + + +def test_setattr(): + class A: + x = 1 + monkeypatch = MonkeyPatch() + monkeypatch.setattr(A, 'x', 2) + assert A.x == 2 + monkeypatch.setattr(A, 'x', 3) + assert A.x == 3 + monkeypatch.finalize() + assert A.x == 1 + +def test_setitem(): + d = {'x': 1} + monkeypatch = MonkeyPatch() + monkeypatch.setitem(d, 'x', 2) + monkeypatch.setitem(d, 'y', 1700) + assert d['x'] == 2 + assert d['y'] == 1700 + monkeypatch.setitem(d, 'x', 3) + assert d['x'] == 3 + monkeypatch.finalize() + assert d['x'] == 1 + assert 'y' not in d + +def test_monkeypatch_plugin(testdir): + sorter = testdir.inline_runsource(""" + pytest_plugins = 'pytest_monkeypatch', + def test_method(monkeypatch): + assert monkeypatch.__class__.__name__ == "MonkeyPatch" + """) + res = sorter.countoutcomes() + assert tuple(res) == (1, 0, 0), res + + From hpk at codespeak.net Tue Mar 3 18:42:34 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 3 Mar 2009 18:42:34 +0100 (CET) Subject: [py-svn] r62487 - in py/trunk/py: execnet execnet/testing test test/dsession test/dsession/testing test/looponfail test/plugin test/testing Message-ID: <20090303174234.51C23168512@codespeak.net> Author: hpk Date: Tue Mar 3 18:42:32 2009 New Revision: 62487 Added: py/trunk/py/execnet/gwmanage.py - copied unchanged from r62485, user/hpk/branch/hostmanage/py/execnet/gwmanage.py py/trunk/py/execnet/testing/test_gwmanage.py - copied unchanged from r62485, user/hpk/branch/hostmanage/py/execnet/testing/test_gwmanage.py py/trunk/py/test/testing/test_pickling.py - copied unchanged from r62485, user/hpk/branch/hostmanage/py/test/testing/test_pickling.py Removed: py/trunk/py/test/testing/test_collect_pickle.py Modified: py/trunk/py/execnet/register.py py/trunk/py/execnet/rsync.py py/trunk/py/execnet/testing/test_gateway.py py/trunk/py/execnet/testing/test_rsync.py py/trunk/py/test/collect.py py/trunk/py/test/config.py py/trunk/py/test/dsession/dsession.py py/trunk/py/test/dsession/hostmanage.py py/trunk/py/test/dsession/masterslave.py py/trunk/py/test/dsession/testing/test_dsession.py py/trunk/py/test/dsession/testing/test_functional_dsession.py py/trunk/py/test/dsession/testing/test_hostmanage.py py/trunk/py/test/dsession/testing/test_masterslave.py py/trunk/py/test/looponfail/remote.py py/trunk/py/test/plugin/pytest_default.py py/trunk/py/test/plugin/pytest_monkeypatch.py py/trunk/py/test/plugin/pytest_pytester.py py/trunk/py/test/plugin/pytest_terminal.py py/trunk/py/test/pycollect.py py/trunk/py/test/pytestplugin.py py/trunk/py/test/session.py py/trunk/py/test/testing/test_config.py Log: merging hostmanage branch: * cleanup of the way distributed/remote sessions are setup up * simplified config pickling * configs are now more correctly wired on receival at remote sides * introduced py.__.execnet.gwmanage helps managing calls to multiple hosts * grouping all pickling related tests in test_pickle.py and showcasing a nice pyfunc_call hack Modified: py/trunk/py/execnet/register.py ============================================================================== --- py/trunk/py/execnet/register.py (original) +++ py/trunk/py/execnet/register.py Tue Mar 3 18:42:32 2009 @@ -72,10 +72,12 @@ """ This Gateway provides interaction with a newly started python subprocess. """ - def __init__(self, python=sys.executable): + def __init__(self, python=None): """ instantiate a gateway to a subprocess started with the given 'python' executable. """ + if python is None: + python = sys.executable cmd = '%s -u -c "exec input()"' % python super(PopenGateway, self).__init__(cmd) @@ -143,7 +145,7 @@ established via the 'ssh' command line binary. The remote side needs to have a Python interpreter executable. """ - def __init__(self, sshaddress, remotepython='python', + def __init__(self, sshaddress, remotepython=None, identity=None, ssh_config=None): """ instantiate a remote ssh process with the given 'sshaddress' and remotepython version. @@ -151,6 +153,8 @@ DEPRECATED: you may specify an 'identity' filepath. """ self.remoteaddress = sshaddress + if remotepython is None: + remotepython = "python" remotecmd = '%s -u -c "exec input()"' % (remotepython,) cmdline = [sshaddress, remotecmd] # XXX Unix style quoting Modified: py/trunk/py/execnet/rsync.py ============================================================================== --- py/trunk/py/execnet/rsync.py (original) +++ py/trunk/py/execnet/rsync.py Tue Mar 3 18:42:32 2009 @@ -73,10 +73,11 @@ if channel not in self._to_send: self._to_send[channel] = [] self._to_send[channel].append(modified_rel_path) + #print "sending", modified_rel_path, data and len(data) or 0, checksum if data is not None: f.close() - if checksum is not None and checksum == md5.md5(data).digest(): + if checksum is not None and checksum == md5(data).digest(): data = None # not really modified else: # ! there is a reason for the interning: Modified: py/trunk/py/execnet/testing/test_gateway.py ============================================================================== --- py/trunk/py/execnet/testing/test_gateway.py (original) +++ py/trunk/py/execnet/testing/test_gateway.py Tue Mar 3 18:42:32 2009 @@ -589,10 +589,12 @@ def test_sshaddress(self): assert self.gw.remoteaddress == py.test.config.option.sshhost + @py.test.mark.xfail("XXX ssh-gateway error handling") def test_connexion_failes_on_non_existing_hosts(self): py.test.raises(IOError, "py.execnet.SshGateway('nowhere.codespeak.net')") + @py.test.mark.xfail("XXX ssh-gateway error handling") def test_deprecated_identity(self): py.test.deprecated_call( py.test.raises, IOError, Modified: py/trunk/py/execnet/testing/test_rsync.py ============================================================================== --- py/trunk/py/execnet/testing/test_rsync.py (original) +++ py/trunk/py/execnet/testing/test_rsync.py Tue Mar 3 18:42:32 2009 @@ -30,7 +30,7 @@ dest2 = self.dest2 source = self.source - for s in ('content1', 'content2-a-bit-longer'): + for s in ('content1', 'content2', 'content2-a-bit-longer'): source.ensure('subdir', 'file1').write(s) rsync = RSync(self.source) rsync.add_target(gw, dest) @@ -42,6 +42,9 @@ assert dest2.join('subdir').check(dir=1) assert dest2.join('subdir', 'file1').check(file=1) assert dest2.join('subdir', 'file1').read() == s + for x in dest, dest2: + fn = x.join("subdir", "file1") + fn.setmtime(0) source.join('subdir').remove('file1') rsync = RSync(source) Modified: py/trunk/py/test/collect.py ============================================================================== --- py/trunk/py/test/collect.py (original) +++ py/trunk/py/test/collect.py Tue Mar 3 18:42:32 2009 @@ -431,9 +431,6 @@ if len(picklestate) == 3: # root node name, config, relpath = picklestate - if not config._initialized: - raise ValueError("incomplete unpickling of " - "config object, need call to _initafterpickle()?") fspath = config.topdir.join(relpath) fsnode = config.getfsnode(fspath) self.__dict__.update(fsnode.__dict__) Modified: py/trunk/py/test/config.py ============================================================================== --- py/trunk/py/test/config.py (original) +++ py/trunk/py/test/config.py Tue Mar 3 18:42:32 2009 @@ -25,7 +25,6 @@ class Config(object): """ central bus for dealing with configuration/initialization data. """ Option = py.compat.optparse.Option # deprecated - _initialized = False _sessionclass = None def __init__(self, pytestplugins=None, topdir=None): @@ -67,9 +66,8 @@ """ parse cmdline arguments into this config object. Note that this can only be called once per testing process. """ - assert not self._initialized, ( + assert not hasattr(self, 'args'), ( "can only parse cmdline args at most once per Config object") - self._initialized = True self._preparse(args) args = self._parser.parse_setoption(args, self.option) if not args: @@ -80,40 +78,31 @@ # config objects are usually pickled across system # barriers but they contain filesystem paths. # upon getstate/setstate we take care to do everything - # relative to our "topdir". + # relative to "topdir". def __getstate__(self): - return self._makerepr() - def __setstate__(self, repr): - self._repr = repr - - def _initafterpickle(self, topdir): - self.__init__( - #issue1 - #pytestplugins=py.test._PytestPlugins(py._com.pyplugins), - topdir=topdir, - ) - self._mergerepr(self._repr) - self._initialized = True - del self._repr - - def _makerepr(self): l = [] for path in self.args: path = py.path.local(path) l.append(path.relto(self.topdir)) return l, self.option - def _mergerepr(self, repr): - # before any conftests are loaded we - # need to set the per-process singleton - # (also seens py.test.config) to have consistent - # option handling - global config_per_process - config_per_process = self + def __setstate__(self, repr): + # warning global side effects: + # * registering to py lib plugins + # * setting py.test.config + self.__init__( + pytestplugins=py.test._PytestPlugins(py._com.pyplugins), + topdir=py.path.local(), + ) + # we have to set py.test.config because preparse() + # might load conftest files which have + # py.test.config.addoptions() lines in them + py.test.config = self args, cmdlineopts = repr - self.args = [self.topdir.join(x) for x in args] + args = [self.topdir.join(x) for x in args] self.option = cmdlineopts - self._preparse(self.args) + self._preparse(args) + self.args = args def getcolitems(self): return [self.getfsnode(arg) for arg in self.args] Modified: py/trunk/py/test/dsession/dsession.py ============================================================================== --- py/trunk/py/test/dsession/dsession.py (original) +++ py/trunk/py/test/dsession/dsession.py Tue Mar 3 18:42:32 2009 @@ -6,10 +6,6 @@ import py from py.__.test import event -import py.__.test.custompdb -from py.__.test.dsession.hostmanage import HostManager -Item = py.test.collect.Item -Collector = py.test.collect.Collector from py.__.test.runner import basic_run_report, basic_collect_report from py.__.test.session import Session from py.__.test import outcome @@ -80,11 +76,9 @@ def main(self, colitems=None): colitems = self.getinitialitems(colitems) - #self.bus.notify(event.TestrunStart()) self.sessionstarts() self.setup_hosts() exitstatus = self.loop(colitems) - #self.bus.notify(event.TestrunFinish(exitstatus=exitstatus)) self.teardown_hosts() self.sessionfinishes() return exitstatus @@ -189,7 +183,7 @@ colitems = self.filteritems(colitems) senditems = [] for next in colitems: - if isinstance(next, Item): + if isinstance(next, py.test.collect.Item): senditems.append(next) else: self.bus.notify("collectionstart", event.CollectionStart(next)) @@ -235,14 +229,13 @@ def setup_hosts(self): """ setup any neccessary resources ahead of the test run. """ - self.hm = HostManager(self) + from py.__.test.dsession.hostmanage import HostManager + self.hm = HostManager(self.config) self.hm.setup_hosts(putevent=self.queue.put) def teardown_hosts(self): """ teardown any resources after a test run. """ - for host in self.host2pending: - host.gw.exit() - + self.hm.teardown_hosts() # debugging function def dump_picklestate(item): Modified: py/trunk/py/test/dsession/hostmanage.py ============================================================================== --- py/trunk/py/test/dsession/hostmanage.py (original) +++ py/trunk/py/test/dsession/hostmanage.py Tue Mar 3 18:42:32 2009 @@ -1,206 +1,76 @@ import py import sys, os from py.__.test.dsession.masterslave import MasterNode +from py.__.execnet.gwmanage import GatewayManager from py.__.test import event -class Host(object): - """ Host location representation for distributed testing. """ - _hostname2list = {} - - def __init__(self, spec, addrel="", python=None): - parts = spec.split(':', 1) - self.hostname = parts.pop(0) - self.relpath = parts and parts.pop(0) or "" - if self.hostname == "localhost" and not self.relpath: - self.inplacelocal = True - addrel = "" # inplace localhosts cannot have additions - else: - self.inplacelocal = False - if not self.relpath: - self.relpath = "pytestcache-%s" % self.hostname - if addrel: - self.relpath += "/" + addrel # XXX too os-dependent - assert not parts - assert self.inplacelocal or self.relpath - self.hostid = self._getuniqueid(self.hostname) - self.python = python - - def __getstate__(self): - return (self.hostname, self.relpath, self.hostid) - - def __setstate__(self, repr): - self.hostname, self.relpath, self.hostid = repr - - def _getuniqueid(self, hostname): - l = self._hostname2list.setdefault(hostname, []) - hostid = hostname + "-%d" % len(l) - l.append(hostid) - return hostid - - def initgateway(self): - python = self.python or "python" - if self.hostname == "localhost": - self.gw = py.execnet.PopenGateway(python=python) - else: - self.gw = py.execnet.SshGateway(self.hostname, - remotepython=python) - if self.inplacelocal: - self.gw.remote_exec(py.code.Source( - sethomedir, "sethomedir()" - )).waitclose() - self.gw_remotepath = None - else: - assert self.relpath - channel = self.gw.remote_exec(py.code.Source( - gethomedir, - sethomedir, "sethomedir()", - getpath_relto_home, """ - channel.send(getpath_relto_home(%r)) - """ % self.relpath, - )) - self.gw_remotepath = channel.receive() - - def __str__(self): - return "" % (self.hostid, self.hostname, self.relpath) - __repr__ = __str__ - - def __hash__(self): - return hash(self.hostid) - - def __eq__(self, other): - return self.hostid == other.hostid - - def __ne__(self, other): - return not self.hostid == other.hostid - -class HostRSync(py.execnet.RSync): - """ RSyncer that filters out common files - """ - def __init__(self, sourcedir, *args, **kwargs): - self._synced = {} - ignores= None - if 'ignores' in kwargs: - ignores = kwargs.pop('ignores') - self._ignores = ignores or [] - super(HostRSync, self).__init__(sourcedir=sourcedir, **kwargs) - - def filter(self, path): - path = py.path.local(path) - if not path.ext in ('.pyc', '.pyo'): - if not path.basename.endswith('~'): - if path.check(dotfile=0): - for x in self._ignores: - if path == x: - break - else: - return True - - def add_target_host(self, host, destrelpath="", notify=None): - remotepath = host.gw_remotepath - key = host.hostname, host.relpath - if host.inplacelocal: - remotepath = self._sourcedir - self._synced[key] = True - elif destrelpath: - remotepath = os.path.join(remotepath, destrelpath) - synced = key in self._synced - if notify: - notify( - event.HostRSyncing(host, py.path.local(self._sourcedir), - remotepath, synced)) - def hostrsynced(host=host): - if notify: - notify( - event.HostRSyncRootReady(host, self._sourcedir)) - if key in self._synced: - hostrsynced() - return - self._synced[key] = True - super(HostRSync, self).add_target(host.gw, remotepath, - finishedcallback=hostrsynced, - delete=True, - ) - return remotepath - -def gethosts(config, addrel): +def getconfighosts(config): if config.option.numprocesses: hosts = ['localhost'] * config.option.numprocesses else: hosts = config.getvalue("dist_hosts") - python = config.option.executable or "python" - hosts = [Host(x, addrel, python=python) for x in hosts] + assert hosts is not None return hosts class HostManager(object): - def __init__(self, session, hosts=None): - self.session = session - roots = self.session.config.getvalue_pathlist("dist_rsync_roots") - addrel = "" - if roots is None: - roots = [self.session.config.topdir] - addrel = self.session.config.topdir.basename - self._addrel = addrel + def __init__(self, config, hosts=None): + self.config = config + roots = self.config.getvalue_pathlist("rsyncroots") + if not roots: + roots = self.config.getvalue_pathlist("dist_rsync_roots") self.roots = roots if hosts is None: - hosts = gethosts(self.session.config, addrel) - self.hosts = hosts + hosts = getconfighosts(self.config) + self.gwmanager = GatewayManager(hosts) - def prepare_gateways(self): - for host in self.hosts: - host.initgateway() - self.session.bus.notify("hostgatewayready", event.HostGatewayReady(host, self.roots)) - - def init_rsync(self): - self.prepare_gateways() - # send each rsync root - ignores = self.session.config.getvalue_pathlist("dist_rsync_ignore") - for root in self.roots: - rsync = HostRSync(root, ignores=ignores, - verbose=self.session.config.option.verbose) - if self._addrel: - destrelpath = "" - else: - destrelpath = root.basename - for host in self.hosts: - rsync.add_target_host(host, destrelpath) - rsync.send(raises=False) - self.session.bus.notify("rsyncfinished", event.RsyncFinished()) + def makegateways(self): + old = self.config.topdir.chdir() + try: + self.gwmanager.makegateways() + finally: + old.chdir() + + def rsync_roots(self): + """ make sure that all remote gateways + have the same set of roots in their + current directory. + """ + # we change to the topdir sot that + # PopenGateways will have their cwd + # such that unpickling configs will + # pick it up as the right topdir + # (for other gateways this chdir is irrelevant) + self.makegateways() + options = { + 'ignores': self.config.getvalue_pathlist("dist_rsync_ignore"), + 'verbose': self.config.option.verbose + } + if self.roots: + # send each rsync root + for root in self.roots: + self.gwmanager.rsync(root, **options) + else: + # we transfer our topdir as the root + # but need to be careful regarding + self.gwmanager.rsync(self.config.topdir, **options) + self.gwmanager.multi_chdir(self.config.topdir.basename, inplacelocal=False) + self.config.bus.notify("rsyncfinished", event.RsyncFinished()) def setup_hosts(self, putevent): - self.init_rsync() - for host in self.hosts: + self.rsync_roots() + nice = self.config.getvalue("dist_nicelevel") + if nice != 0: + self.gwmanager.multi_exec(""" + import os + if hasattr(os, 'nice'): + os.nice(%r) + """ % nice).wait() + + for host, gateway in self.gwmanager.spec2gateway.items(): host.node = MasterNode(host, - self.session.config, + gateway, + self.config, putevent) -# -# helpers -# -def gethomedir(): - import os - homedir = os.environ.get('HOME', '') - if not homedir: - homedir = os.environ.get('HOMEPATH', '.') - return os.path.abspath(homedir) - -def getpath_relto_home(targetpath): - import os - if not os.path.isabs(targetpath): - homedir = gethomedir() - targetpath = os.path.join(homedir, targetpath) - return os.path.normpath(targetpath) - -def sethomedir(): - import os - homedir = os.environ.get('HOME', '') - if not homedir: - homedir = os.environ.get('HOMEPATH', '.') - os.chdir(homedir) - -def makehostup(host=None): - if host is None: - host = Host("localhost") - platinfo = {} - for name in 'platform', 'executable', 'version_info': - platinfo["sys."+name] = getattr(sys, name) - return event.HostUp(host, platinfo) + def teardown_hosts(self): + self.gwmanager.exit() Modified: py/trunk/py/test/dsession/masterslave.py ============================================================================== --- py/trunk/py/test/dsession/masterslave.py (original) +++ py/trunk/py/test/dsession/masterslave.py Tue Mar 3 18:42:32 2009 @@ -8,11 +8,11 @@ class MasterNode(object): ENDMARK = -1 - def __init__(self, host, config, putevent): + def __init__(self, host, gateway, config, putevent): self.host = host self.config = config self.putevent = putevent - self.channel = install_slave(host, config) + self.channel = install_slave(host, gateway, config) self.channel.setcallback(self.callback, endmarker=self.ENDMARK) self._down = False @@ -23,7 +23,7 @@ """ this gets called for each object we receive from the other side and if the channel closes. - Note that the callback runs in the receiver + Note that channel callbacks run in the receiver thread of execnet gateways - we need to avoid raising exceptions or doing heavy work. """ @@ -60,63 +60,33 @@ def shutdown(self): self.channel.send(None) -# -# a config needs to be available on the other side for options -# and for reconstructing collection trees (topdir, conftest access) -# - -def send_and_receive_pickled_config(channel, config, remote_topdir): - channel.send((config, remote_topdir)) - backconfig = channel.receive() - assert config is backconfig # ImmutablePickling :) - return backconfig - -def receive_and_send_pickled_config(channel): - config,topdir = channel.receive() - config._initafterpickle(topdir) - channel.send(config) - return config - # setting up slave code -def install_slave(host, config): - channel = host.gw.remote_exec(source=""" +def install_slave(host, gateway, config): + channel = gateway.remote_exec(source=""" from py.__.test.dsession.mypickle import PickleChannel + from py.__.test.dsession.masterslave import SlaveNode channel = PickleChannel(channel) - from py.__.test.dsession import masterslave - config = masterslave.receive_and_send_pickled_config(channel) - slavenode = masterslave.SlaveNode(channel, config) + slavenode = SlaveNode(channel) slavenode.run() """) channel = PickleChannel(channel) - remote_topdir = host.gw_remotepath - if remote_topdir is None: - assert host.inplacelocal - remote_topdir = config.topdir - send_and_receive_pickled_config(channel, config, remote_topdir) - channel.send(host) + channel.send((host, config)) return channel class SlaveNode(object): - def __init__(self, channel, config): + def __init__(self, channel): self.channel = channel - self.config = config - import os - if hasattr(os, 'nice'): - nice_level = config.getvalue('dist_nicelevel') - os.nice(nice_level) def __repr__(self): - host = getattr(self, 'host', '') - return "<%s host=%s>" %(self.__class__.__name__, host) + return "<%s channel=%s>" %(self.__class__.__name__, self.channel) def sendevent(self, eventname, *args, **kwargs): self.channel.send((eventname, args, kwargs)) def run(self): - self.config.pytestplugins.do_configure(self.config) - from py.__.test.dsession.hostmanage import makehostup channel = self.channel - self.host = host = channel.receive() + host, self.config = channel.receive() + self.config.pytestplugins.do_configure(self.config) self.sendevent("hostup", makehostup(host)) try: while 1: @@ -140,3 +110,14 @@ runner = item._getrunner() testrep = runner(item) self.sendevent("itemtestreport", testrep) + + +def makehostup(host=None): + from py.__.execnet.gwmanage import GatewaySpec + import sys + if host is None: + host = GatewaySpec("localhost") + platinfo = {} + for name in 'platform', 'executable', 'version_info': + platinfo["sys."+name] = getattr(sys, name) + return event.HostUp(host, platinfo) Modified: py/trunk/py/test/dsession/testing/test_dsession.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_dsession.py (original) +++ py/trunk/py/test/dsession/testing/test_dsession.py Tue Mar 3 18:42:32 2009 @@ -1,5 +1,6 @@ from py.__.test.dsession.dsession import DSession, LoopState -from py.__.test.dsession.hostmanage import Host, makehostup +from py.__.test.dsession.masterslave import makehostup +from py.__.execnet.gwmanage import GatewaySpec from py.__.test.runner import basic_collect_report from py.__.test import event from py.__.test import outcome @@ -37,7 +38,7 @@ item = testdir.getitem("def test_func(): pass") rep = run(item) session = DSession(item._config) - host = Host("localhost") + host = GatewaySpec("localhost") host.node = MockNode() assert not session.host2pending session.addhost(host) @@ -52,7 +53,7 @@ item = testdir.getitem("def test_func(): pass") rep = run(item) session = DSession(item._config) - host = Host("localhost") + host = GatewaySpec("localhost") host.node = MockNode() session.addhost(host) session.senditems([item]) @@ -77,9 +78,9 @@ def test_triggertesting_item(self, testdir): item = testdir.getitem("def test_func(): pass") session = DSession(item._config) - host1 = Host("localhost") + host1 = GatewaySpec("localhost") host1.node = MockNode() - host2 = Host("localhost") + host2 = GatewaySpec("localhost") host2.node = MockNode() session.addhost(host1) session.addhost(host2) @@ -114,7 +115,7 @@ def test_rescheduleevent(self, testdir): item = testdir.getitem("def test_func(): pass") session = DSession(item._config) - host1 = Host("localhost") + host1 = GatewaySpec("localhost") host1.node = MockNode() session.addhost(host1) ev = event.RescheduleItems([item]) @@ -138,7 +139,7 @@ item = testdir.getitem("def test_func(): pass") # setup a session with one host session = DSession(item._config) - host1 = Host("localhost") + host1 = GatewaySpec("localhost") host1.node = MockNode() session.addhost(host1) @@ -163,10 +164,10 @@ # setup a session with two hosts session = DSession(item1._config) - host1 = Host("localhost") + host1 = GatewaySpec("localhost") host1.node = MockNode() session.addhost(host1) - host2 = Host("localhost") + host2 = GatewaySpec("localhost") host2.node = MockNode() session.addhost(host2) @@ -184,13 +185,13 @@ assert testrep.failed assert testrep.colitem == item1 assert str(testrep.longrepr).find("crashed") != -1 - assert str(testrep.longrepr).find(host.hostname) != -1 + assert str(testrep.longrepr).find(host.address) != -1 def test_hostup_adds_to_available(self, testdir): item = testdir.getitem("def test_func(): pass") # setup a session with two hosts session = DSession(item._config) - host1 = Host("localhost") + host1 = GatewaySpec("localhost") hostup = makehostup(host1) session.queueevent("hostup", hostup) loopstate = LoopState([item]) @@ -210,7 +211,7 @@ def runthrough(self, item): session = DSession(item._config) - host1 = Host("localhost") + host1 = GatewaySpec("localhost") host1.node = MockNode() session.addhost(host1) loopstate = LoopState([item]) @@ -247,7 +248,7 @@ """) modcol._config.option.exitfirst = True session = DSession(modcol._config) - host1 = Host("localhost") + host1 = GatewaySpec("localhost") host1.node = MockNode() session.addhost(host1) items = basic_collect_report(modcol).result @@ -269,7 +270,7 @@ def test_shuttingdown_filters_events(self, testdir, EventRecorder): item = testdir.getitem("def test_func(): pass") session = DSession(item._config) - host = Host("localhost") + host = GatewaySpec("localhost") session.addhost(host) loopstate = LoopState([]) loopstate.shuttingdown = True @@ -315,7 +316,7 @@ item = testdir.getitem("def test_func(): pass") session = DSession(item._config) - host = Host("localhost") + host = GatewaySpec("localhost") host.node = MockNode() session.addhost(host) session.senditems([item]) @@ -337,7 +338,7 @@ pass """) session = DSession(modcol._config) - host = Host("localhost") + host = GatewaySpec("localhost") host.node = MockNode() session.addhost(host) Modified: py/trunk/py/test/dsession/testing/test_functional_dsession.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_functional_dsession.py (original) +++ py/trunk/py/test/dsession/testing/test_functional_dsession.py Tue Mar 3 18:42:32 2009 @@ -4,39 +4,39 @@ import py from py.__.test.dsession.dsession import DSession -from py.__.test.dsession.hostmanage import HostManager, Host from test_masterslave import EventQueue - import os class TestAsyncFunctional: def test_conftest_options(self, testdir): - testdir.makepyfile(conftest=""" - print "importing conftest" + p1 = testdir.tmpdir.ensure("dir", 'p1.py') + p1.dirpath("__init__.py").write("") + p1.dirpath("conftest.py").write(py.code.Source(""" + print "importing conftest", __file__ import py Option = py.test.config.Option option = py.test.config.addoptions("someopt", Option('--someopt', action="store_true", dest="someopt", default=False)) - """, - ) - p1 = testdir.makepyfile(""" + dist_rsync_roots = ['../dir'] + print "added options", option + print "config file seen from conftest", py.test.config + """)) + p1.write(py.code.Source(""" + import py, conftest def test_1(): - import py, conftest + print "config from test_1", py.test.config + print "conftest from test_1", conftest.__file__ print "test_1: py.test.config.option.someopt", py.test.config.option.someopt print "test_1: conftest", conftest print "test_1: conftest.option.someopt", conftest.option.someopt assert conftest.option.someopt - """, __init__="#") - print p1 - config = py.test.config._reparse(['-n1', p1, '--someopt']) - dsession = DSession(config) - eq = EventQueue(config.bus) - dsession.main() - ev, = eq.geteventargs("itemtestreport") - if not ev.passed: - print ev - assert 0 + """)) + result = testdir.runpytest('-n1', p1, '--someopt') + assert result.ret == 0 + extra = result.stdout.fnmatch_lines([ + "*1 passed*", + ]) def test_dist_some_tests(self, testdir): testdir.makepyfile(conftest="dist_hosts=['localhost']\n") @@ -61,7 +61,7 @@ assert ev.failed # see that the host is really down ev, = eq.geteventargs("hostdown") - assert ev.host.hostname == "localhost" + assert ev.host.address == "localhost" ev, = eq.geteventargs("testrunfinish") def test_distribution_rsync_roots_example(self, testdir): Modified: py/trunk/py/test/dsession/testing/test_hostmanage.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_hostmanage.py (original) +++ py/trunk/py/test/dsession/testing/test_hostmanage.py Tue Mar 3 18:42:32 2009 @@ -3,186 +3,16 @@ """ import py -from py.__.test.dsession.hostmanage import HostRSync, Host, HostManager, gethosts -from py.__.test.dsession.hostmanage import sethomedir, gethomedir, getpath_relto_home -from py.__.test import event +from py.__.test.dsession.hostmanage import HostManager, getconfighosts +from py.__.execnet.gwmanage import GatewaySpec as Host -class TestHost: - def _gethostinfo(self, testdir, relpath=""): - exampledir = testdir.mkdir("gethostinfo") - if relpath: - exampledir = exampledir.join(relpath) - hostinfo = Host("localhost:%s" % exampledir) - return hostinfo - - def test_defaultpath(self): - x = Host("localhost:") - assert x.hostname == "localhost" - assert not x.relpath - - def test_addrel(self): - host = Host("localhost:", addrel="whatever") - assert host.inplacelocal - assert not host.relpath - host = Host("localhost:/tmp", addrel="base") - assert host.relpath == "/tmp/base" - host = Host("localhost:tmp", addrel="base2") - assert host.relpath == "tmp/base2" - - def test_path(self): - x = Host("localhost:/tmp") - assert x.relpath == "/tmp" - assert x.hostname == "localhost" - assert not x.inplacelocal - - def test_equality(self): - x = Host("localhost:") - y = Host("localhost:") - assert x != y - assert not (x == y) - - def test_hostid(self): - x = Host("localhost:") - y = Host("localhost:") - assert x.hostid != y.hostid - x = Host("localhost:/tmp") - y = Host("localhost:") - assert x.hostid != y.hostid - - def test_non_existing_hosts(self): - host = Host("alskdjalsdkjasldkajlsd") - py.test.raises((py.process.cmdexec.Error, IOError, EOFError), - host.initgateway) - - def test_remote_has_homedir_as_currentdir(self, testdir): - host = self._gethostinfo(testdir) - old = py.path.local.get_temproot().chdir() - try: - host.initgateway() - channel = host.gw.remote_exec(py.code.Source( - gethomedir, """ - import os - homedir = gethomedir() - curdir = os.getcwd() - channel.send((curdir, homedir)) - """)) - remote_curdir, remote_homedir = channel.receive() - assert remote_curdir == remote_homedir - finally: - old.chdir() - - def test_initgateway_localhost_relpath(self): - host = Host("localhost:somedir") - host.initgateway() - assert host.gw - try: - homedir = py.path.local._gethomedir() - expected = homedir.join("somedir") - assert host.gw_remotepath == str(expected) - finally: - host.gw.exit() - - def test_initgateway_python(self): - host = Host("localhost", python="python2.4") - l = [] - def p(python): - l.append(python) - raise ValueError - py.magic.patch(py.execnet, 'PopenGateway', p) - try: - py.test.raises(ValueError, host.initgateway) - finally: - py.magic.revert(py.execnet, 'PopenGateway') - assert l[0] == host.python - - def test_initgateway_ssh_and_remotepath(self): - hostspec = py.test.config.option.sshhost - if not hostspec: - py.test.skip("no known ssh target, use -S to set one") - host = Host("%s" % (hostspec)) - # this test should be careful to not write/rsync anything - # as the remotepath is the default location - # and may be used in the real world - host.initgateway() - assert host.gw - assert host.gw_remotepath.endswith(host.relpath) - channel = host.gw.remote_exec(""" - import os - homedir = os.environ['HOME'] - relpath = channel.receive() - path = os.path.join(homedir, relpath) - channel.send(path) - """) - channel.send(host.relpath) - res = channel.receive() - assert res == host.gw_remotepath +from py.__.test import event def pytest_pyfuncarg_source(pyfuncitem): return py.test.ensuretemp(pyfuncitem.getmodpath()).mkdir("source") def pytest_pyfuncarg_dest(pyfuncitem): - return py.test.ensuretemp(pyfuncitem.getmodpath()).mkdir("dest") - -class TestSyncing: - def _gethostinfo(self, dest): - hostinfo = Host("localhost:%s" % dest) - return hostinfo - - def test_hrsync_filter(self, source, dest): - source.ensure("dir", "file.txt") - source.ensure(".svn", "entries") - source.ensure(".somedotfile", "moreentries") - source.ensure("somedir", "editfile~") - syncer = HostRSync(source) - l = list(source.visit(rec=syncer.filter, - fil=syncer.filter)) - assert len(l) == 3 - basenames = [x.basename for x in l] - assert 'dir' in basenames - assert 'file.txt' in basenames - assert 'somedir' in basenames - - def test_hrsync_localhost_inplace(self, source, dest): - h1 = Host("localhost") - events = [] - rsync = HostRSync(source) - h1.initgateway() - rsync.add_target_host(h1, notify=events.append) - assert events - l = [x for x in events - if isinstance(x, event.HostRSyncing)] - assert len(l) == 1 - ev = l[0] - assert ev.host == h1 - assert ev.root == ev.remotepath - l = [x for x in events - if isinstance(x, event.HostRSyncRootReady)] - assert len(l) == 1 - ev = l[0] - assert ev.root == source - assert ev.host == h1 - - def test_hrsync_one_host(self, source, dest): - h1 = self._gethostinfo(dest) - finished = [] - rsync = HostRSync(source) - h1.initgateway() - rsync.add_target_host(h1) - source.join("hello.py").write("world") - rsync.send() - assert dest.join("hello.py").check() - - def test_hrsync_same_host_twice(self, source, dest): - h1 = self._gethostinfo(dest) - h2 = self._gethostinfo(dest) - finished = [] - rsync = HostRSync(source) - l = [] - h1.initgateway() - h2.initgateway() - res1 = rsync.add_target_host(h1) - assert res1 - res2 = rsync.add_target_host(h2) - assert not res2 + dest = py.test.ensuretemp(pyfuncitem.getmodpath()).mkdir("dest") + return dest class TestHostManager: def gethostmanager(self, source, dist_hosts, dist_rsync_roots=None): @@ -192,25 +22,37 @@ source.join("conftest.py").write("\n".join(l)) config = py.test.config._reparse([source]) assert config.topdir == source - session = config.initsession() - hm = HostManager(session) - assert hm.hosts + hm = HostManager(config) + assert hm.gwmanager.spec2gateway return hm - def test_hostmanager_custom_hosts(self, source, dest): + def xxtest_hostmanager_custom_hosts(self, source, dest): session = py.test.config._reparse([source]).initsession() - hm = HostManager(session, hosts=[1,2,3]) + hm = HostManager(session.config, hosts=[1,2,3]) assert hm.hosts == [1,2,3] - def test_hostmanager_init_rsync_topdir(self, source, dest): + def test_hostmanager_rsync_roots_no_roots(self, source, dest): + source.ensure("dir1", "file1").write("hello") + config = py.test.config._reparse([source]) + hm = HostManager(config, hosts=["localhost:%s" % dest]) + assert hm.config.topdir == source == config.topdir + hm.rsync_roots() + p, = hm.gwmanager.multi_exec("import os ; channel.send(os.getcwd())").receive() + p = py.path.local(p) + print "remote curdir", p + assert p == dest.join(config.topdir.basename) + assert p.join("dir1").check() + assert p.join("dir1", "file1").check() + + def test_hostmanager_rsync_roots_roots(self, source, dest): dir2 = source.ensure("dir1", "dir2", dir=1) dir2.ensure("hello") hm = self.gethostmanager(source, - dist_hosts = ["localhost:%s" % dest] + dist_hosts = ["localhost:%s" % dest], + dist_rsync_roots = ['dir1'] ) - assert hm.session.config.topdir == source - hm.init_rsync() - dest = dest.join(source.basename) + assert hm.config.topdir == source + hm.rsync_roots() assert dest.join("dir1").check() assert dest.join("dir1", "dir2").check() assert dest.join("dir1", "dir2", 'hello').check() @@ -222,8 +64,8 @@ dist_hosts = ["localhost:%s" % dest], dist_rsync_roots = [str(source)] ) - assert hm.session.config.topdir == source - hm.init_rsync() + assert hm.config.topdir == source + hm.rsync_roots() dest = dest.join(source.basename) assert dest.join("dir1").check() assert dest.join("dir1", "dir2").check() @@ -238,9 +80,9 @@ dist_rsync_roots = ['dir1/dir2'] """)) session = py.test.config._reparse([source]).initsession() - hm = HostManager(session, - hosts=[Host("localhost:" + str(dest))]) - hm.init_rsync() + hm = HostManager(session.config, + hosts=["localhost:" + str(dest)]) + hm.rsync_roots() assert dest.join("dir2").check() assert not dest.join("dir1").check() assert not dest.join("bogus").check() @@ -252,39 +94,43 @@ dir2.ensure("hello") source.join("conftest.py").write(py.code.Source(""" dist_rsync_ignore = ['dir1/dir2', 'dir5/dir6'] + dist_rsync_roots = ['dir1', 'dir5'] """)) session = py.test.config._reparse([source]).initsession() - hm = HostManager(session, - hosts=[Host("localhost:" + str(dest))]) - hm.init_rsync() + hm = HostManager(session.config, + hosts=["localhost:" + str(dest)]) + hm.rsync_roots() assert dest.join("dir1").check() assert not dest.join("dir1", "dir2").check() assert dest.join("dir5","file").check() assert not dest.join("dir6").check() def test_hostmanage_optimise_localhost(self, source, dest): - hosts = [Host("localhost") for i in range(3)] - session = py.test.config._reparse([source]).initsession() - hm = HostManager(session, hosts=hosts) - hm.init_rsync() - for host in hosts: - assert host.inplacelocal - assert host.gw_remotepath is None - assert not host.relpath + hosts = ["localhost"] * 3 + source.join("conftest.py").write("dist_rsync_roots = ['a']") + source.ensure('a', dir=1) + config = py.test.config._reparse([source]) + hm = HostManager(config, hosts=hosts) + hm.rsync_roots() + for gwspec in hm.gwmanager.spec2gateway: + assert gwspec.inplacelocal() + assert not gwspec.joinpath def test_hostmanage_setup_hosts(self, source): - hosts = [Host("localhost") for i in range(3)] - session = py.test.config._reparse([source]).initsession() - hm = HostManager(session, hosts=hosts) + hosts = ["localhost"] * 3 + source.join("conftest.py").write("dist_rsync_roots = ['a']") + source.ensure('a', dir=1) + config = py.test.config._reparse([source]) + hm = HostManager(config, hosts=hosts) queue = py.std.Queue.Queue() hm.setup_hosts(putevent=queue.put) - for host in hm.hosts: + for host in hm.gwmanager.spec2gateway: eventcall = queue.get(timeout=2.0) name, args, kwargs = eventcall assert name == "hostup" - for host in hm.hosts: + for host in hm.gwmanager.spec2gateway: host.node.shutdown() - for host in hm.hosts: + for host in hm.gwmanager.spec2gateway: eventcall = queue.get(timeout=2.0) name, args, kwargs = eventcall print name, args, kwargs @@ -304,24 +150,8 @@ assert 0 -def test_getpath_relto_home(): - x = getpath_relto_home("hello") - assert x == py.path.local._gethomedir().join("hello") - x = getpath_relto_home(".") - assert x == py.path.local._gethomedir() - -def test_sethomedir(): - old = py.path.local.get_temproot().chdir() - try: - sethomedir() - curdir = py.path.local() - finally: - old.chdir() - - assert py.path.local._gethomedir() == curdir - -def test_gethosts(): +def test_getconfighosts(): config = py.test.config._reparse(['-n3']) - hosts = gethosts(config, '') + hosts = getconfighosts(config) assert len(hosts) == 3 Modified: py/trunk/py/test/dsession/testing/test_masterslave.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_masterslave.py (original) +++ py/trunk/py/test/dsession/testing/test_masterslave.py Tue Mar 3 18:42:32 2009 @@ -1,7 +1,7 @@ import py from py.__.test.dsession.masterslave import MasterNode -from py.__.test.dsession.hostmanage import Host +from py.__.execnet.gwmanage import GatewaySpec class EventQueue: def __init__(self, bus, queue=None): @@ -43,22 +43,28 @@ config = py.test.config._reparse([]) self.config = config self.queue = py.std.Queue.Queue() - self.host = Host("localhost") - self.host.initgateway() - self.node = MasterNode(self.host, self.config, putevent=self.queue.put) + self.host = GatewaySpec("localhost") + self.gateway = self.host.makegateway() + self.node = MasterNode(self.host, self.gateway, self.config, putevent=self.queue.put) assert not self.node.channel.isclosed() return self.node def finalize(self): if hasattr(self, 'host'): - print "exiting:", self.host.gw - self.host.gw.exit() + print "exiting:", self.gateway + self.gateway.exit() def pytest_pyfuncarg_mysetup(pyfuncitem): mysetup = MySetup(pyfuncitem) pyfuncitem.addfinalizer(mysetup.finalize) return mysetup +def pytest_pyfuncarg_testdir(__call__, pyfuncitem): + # decorate to make us always change to testdir + testdir = __call__.execute(firstresult=True) + testdir.chdir() + return testdir + class TestMasterSlaveConnection: def test_crash_invalid_item(self, mysetup): Modified: py/trunk/py/test/looponfail/remote.py ============================================================================== --- py/trunk/py/test/looponfail/remote.py (original) +++ py/trunk/py/test/looponfail/remote.py Tue Mar 3 18:42:32 2009 @@ -11,7 +11,6 @@ from __future__ import generators import py from py.__.test.session import Session -from py.__.test.outcome import Failed, Passed, Skipped from py.__.test.dsession.mypickle import PickleChannel from py.__.test import event from py.__.test.looponfail import util @@ -67,27 +66,29 @@ msg = " ".join([str(x) for x in args]) print "RemoteControl:", msg + def initgateway(self): + return py.execnet.PopenGateway(self.executable) + def setup(self, out=None): - if hasattr(self, 'gateway'): - raise ValueError("already have gateway %r" % self.gateway) if out is None: out = py.io.TerminalWriter() - from py.__.test.dsession import masterslave + if hasattr(self, 'gateway'): + raise ValueError("already have gateway %r" % self.gateway) self.trace("setting up slave session") - self.gateway = py.execnet.PopenGateway(self.executable) + old = self.config.topdir.chdir() + try: + self.gateway = self.initgateway() + finally: + old.chdir() channel = self.gateway.remote_exec(source=""" from py.__.test.dsession.mypickle import PickleChannel - channel = PickleChannel(channel) from py.__.test.looponfail.remote import slave_runsession - from py.__.test.dsession import masterslave - config = masterslave.receive_and_send_pickled_config(channel) - fullwidth, hasmarkup = channel.receive() + channel = PickleChannel(channel) + config, fullwidth, hasmarkup = channel.receive() slave_runsession(channel, config, fullwidth, hasmarkup) """, stdout=out, stderr=out) channel = PickleChannel(channel) - masterslave.send_and_receive_pickled_config( - channel, self.config, remote_topdir=self.config.topdir) - channel.send((out.fullwidth, out.hasmarkup)) + channel.send((self.config, out.fullwidth, out.hasmarkup)) self.trace("set up of slave session complete") self.channel = channel Modified: py/trunk/py/test/plugin/pytest_default.py ============================================================================== --- py/trunk/py/test/plugin/pytest_default.py (original) +++ py/trunk/py/test/plugin/pytest_default.py Tue Mar 3 18:42:32 2009 @@ -3,6 +3,10 @@ class DefaultPlugin: """ Plugin implementing defaults and general options. """ + def pytest_pyfunc_call(self, pyfuncitem, args, kwargs): + pyfuncitem.obj(*args, **kwargs) + return + def pytest_collect_file(self, path, parent): ext = path.ext pb = path.purebasename Modified: py/trunk/py/test/plugin/pytest_monkeypatch.py ============================================================================== --- py/trunk/py/test/plugin/pytest_monkeypatch.py (original) +++ py/trunk/py/test/plugin/pytest_monkeypatch.py Tue Mar 3 18:42:32 2009 @@ -24,6 +24,8 @@ for obj, name, value in self._setattr: if value is not notset: setattr(obj, name, value) + else: + delattr(obj, name) for dictionary, name, value in self._setitem: if value is notset: del dictionary[name] @@ -42,6 +44,12 @@ monkeypatch.finalize() assert A.x == 1 + monkeypatch.setattr(A, 'y', 3) + assert A.y == 3 + monkeypatch.finalize() + assert not hasattr(A, 'y') + + def test_setitem(): d = {'x': 1} monkeypatch = MonkeyPatch() Modified: py/trunk/py/test/plugin/pytest_pytester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_pytester.py (original) +++ py/trunk/py/test/plugin/pytest_pytester.py Tue Mar 3 18:42:32 2009 @@ -204,6 +204,8 @@ p = self.tmpdir.join("conftest.py") if not p.check(): plugins = [x for x in self.plugins if isinstance(x, str)] + if not plugins: + return p.write("import py ; pytest_plugins = %r" % plugins) else: if self.plugins: Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Tue Mar 3 18:42:32 2009 @@ -103,9 +103,9 @@ def pyevent_hostup(self, event): d = event.platinfo.copy() - d['hostid'] = event.host.hostid + d['host'] = event.host.address d['version'] = repr_pythonversion(d['sys.version_info']) - self.write_line("HOSTUP: %(hostid)s %(sys.platform)s " + self.write_line("HOSTUP: %(host)s %(sys.platform)s " "%(sys.executable)s - Python %(version)s" % d) @@ -113,7 +113,7 @@ host = event.host error = event.error if error: - self.write_line("HostDown %s: %s" %(host.hostid, error)) + self.write_line("HostDown %s: %s" %(host, error)) def pyevent_itemstart(self, event): if self.config.option.verbose: @@ -311,16 +311,16 @@ from py.__.test import event from py.__.test.runner import basic_run_report -from py.__.test.dsession.hostmanage import Host, makehostup +from py.__.test.dsession.masterslave import makehostup class TestTerminal: def test_hostup(self, testdir, linecomp): + from py.__.execnet.gwmanage import GatewaySpec item = testdir.getitem("def test_func(): pass") rep = TerminalReporter(item._config, linecomp.stringio) - host = Host("localhost") - rep.pyevent_hostup(makehostup(host)) + rep.pyevent_hostup(makehostup()) linecomp.assert_contains_lines([ - "*%s %s %s - Python %s" %(host.hostid, sys.platform, + "*localhost %s %s - Python %s" %(sys.platform, sys.executable, repr_pythonversion(sys.version_info)) ]) @@ -409,11 +409,12 @@ ]) def test_hostready_crash(self, testdir, linecomp): + from py.__.execnet.gwmanage import GatewaySpec modcol = testdir.getmodulecol(""" def test_one(): pass """, configargs=("-v",)) - host1 = Host("localhost") + host1 = GatewaySpec("localhost") rep = TerminalReporter(modcol._config, file=linecomp.stringio) rep.pyevent_hostgatewayready(event.HostGatewayReady(host1, None)) linecomp.assert_contains_lines([ Modified: py/trunk/py/test/pycollect.py ============================================================================== --- py/trunk/py/test/pycollect.py (original) +++ py/trunk/py/test/pycollect.py Tue Mar 3 18:42:32 2009 @@ -348,11 +348,8 @@ """ execute the given test function. """ if not self._deprecated_testexecution(): kw = self.lookup_allargs() - pytest_pyfunc_call = self._config.pytestplugins.getfirst("pytest_pyfunc_call") - if pytest_pyfunc_call is not None: - if pytest_pyfunc_call(pyfuncitem=self, args=self._args, kwargs=kw): - return - self.obj(*self._args, **kw) + ret = self._config.pytestplugins.call_firstresult( + "pytest_pyfunc_call", pyfuncitem=self, args=self._args, kwargs=kw) def lookup_allargs(self): kwargs = {} Modified: py/trunk/py/test/pytestplugin.py ============================================================================== --- py/trunk/py/test/pytestplugin.py (original) +++ py/trunk/py/test/pytestplugin.py Tue Mar 3 18:42:32 2009 @@ -117,7 +117,11 @@ try: return __import__(importspec) except ImportError, e: + if str(e).find(importspec) == -1: + raise try: return __import__("py.__.test.plugin.%s" %(importspec), None, None, '__doc__') - except ImportError: + except ImportError, e: + if str(e).find(importspec) == -1: + raise return __import__(importspec) # show the original exception Modified: py/trunk/py/test/session.py ============================================================================== --- py/trunk/py/test/session.py (original) +++ py/trunk/py/test/session.py Tue Mar 3 18:42:32 2009 @@ -12,7 +12,7 @@ Item = py.test.collect.Item Collector = py.test.collect.Collector from runner import basic_collect_report -from py.__.test.dsession.hostmanage import makehostup +from py.__.test.dsession.masterslave import makehostup class Session(object): """ Deleted: /py/trunk/py/test/testing/test_collect_pickle.py ============================================================================== --- /py/trunk/py/test/testing/test_collect_pickle.py Tue Mar 3 18:42:32 2009 +++ (empty file) @@ -1,52 +0,0 @@ -import py - -def pytest_pyfuncarg_pickletransport(pyfuncitem): - return PickleTransport() - -class PickleTransport: - def __init__(self): - from py.__.test.dsession.mypickle import ImmutablePickler - self.p1 = ImmutablePickler(uneven=0) - self.p2 = ImmutablePickler(uneven=1) - - def p1_to_p2(self, obj): - return self.p2.loads(self.p1.dumps(obj)) - - def p2_to_p1(self, obj): - return self.p1.loads(self.p2.dumps(obj)) - - def unifyconfig(self, config): - p2config = self.p1_to_p2(config) - p2config._initafterpickle(config.topdir) - return p2config - -class TestPickling: - - def test_pickle_config(self, pickletransport): - config1 = py.test.config._reparse([]) - p2config = pickletransport.unifyconfig(config1) - assert p2config.topdir == config1.topdir - config_back = pickletransport.p2_to_p1(p2config) - assert config_back is config1 - - def test_pickle_module(self, testdir, pickletransport): - modcol1 = testdir.getmodulecol("def test_one(): pass") - pickletransport.unifyconfig(modcol1._config) - - modcol2a = pickletransport.p1_to_p2(modcol1) - modcol2b = pickletransport.p1_to_p2(modcol1) - assert modcol2a is modcol2b - - modcol1_back = pickletransport.p2_to_p1(modcol2a) - assert modcol1_back - - def test_pickle_func(self, testdir, pickletransport): - modcol1 = testdir.getmodulecol("def test_one(): pass") - pickletransport.unifyconfig(modcol1._config) - item = modcol1.collect_by_name("test_one") - item2a = pickletransport.p1_to_p2(item) - assert item is not item2a # of course - assert item2a.name == item.name - modback = pickletransport.p2_to_p1(item2a.parent) - assert modback is modcol1 - Modified: py/trunk/py/test/testing/test_config.py ============================================================================== --- py/trunk/py/test/testing/test_config.py (original) +++ py/trunk/py/test/testing/test_config.py Tue Mar 3 18:42:32 2009 @@ -290,150 +290,6 @@ assert gettopdir([c]) == a assert gettopdir([c, Z]) == tmp -class TestConfigPickling: - @py.test.mark(xfail=True, issue="config's pytestplugins/bus initialization") - def test_config_initafterpickle_plugin(self, testdir): - testdir.makepyfile(__init__="", conftest="x=1; y=2") - hello = testdir.makepyfile(hello="") - tmp = testdir.tmpdir - config = py.test.config._reparse([hello]) - config2 = py.test.config._reparse([tmp.dirpath()]) - config2._initialized = False # we have to do that from tests - config2._repr = config._makerepr() - config2._initafterpickle(topdir=tmp.dirpath()) - # we check that config "remote" config objects - # have correct plugin initialization - #XXX assert config2.pytestplugins.pm._plugins - #XXX assert config2.bus.isregistered(config2.pytestplugins.forward_event) - assert config2.bus == py._com.pyplugins - assert config2.pytestplugins.pm == py._com.pyplugins - - def test_config_initafterpickle_some(self, testdir): - tmp = testdir.tmpdir - tmp.ensure("__init__.py") - tmp.ensure("conftest.py").write("x=1 ; y=2") - hello = tmp.ensure("test_hello.py") - config = py.test.config._reparse([hello]) - config2 = testdir.Config() - config2._initialized = False # we have to do that from tests - config2._repr = config._makerepr() - config2._initafterpickle(tmp.dirpath()) - - for col1, col2 in zip(config.getcolitems(), config2.getcolitems()): - assert col1.fspath == col2.fspath - cols = config2.getcolitems() - assert len(cols) == 1 - col = cols[0] - assert col.name == 'test_hello.py' - assert col.parent.name == tmp.basename - assert col.parent.parent is None - - def test_config_make_and__mergerepr(self, testdir): - tmp = testdir.tmpdir - tmp.ensure("__init__.py") - tmp.ensure("conftest.py").write("x=1") - config = py.test.config._reparse([tmp]) - repr = config._makerepr() - - config.option.verbose = 42 - repr2 = config._makerepr() - - print "hello" - config = testdir.Config() - config._mergerepr(repr) - print config._conftest.getconftestmodules(None) - assert config.getvalue('x') == 1 - - config = testdir.Config() - config._preparse([]) - py.test.raises(KeyError, "config.getvalue('x')") - - config = testdir.Config() - config._mergerepr(repr2) - assert config.option.verbose == 42 - - def test_config_rconfig(self, testdir): - tmp = testdir.tmpdir - tmp.ensure("__init__.py") - tmp.ensure("conftest.py").write(py.code.Source(""" - import py - Option = py.test.config.Option - option = py.test.config.addoptions("testing group", - Option('-G', '--glong', action="store", default=42, - type="int", dest="gdest", help="g value.")) - """)) - config = py.test.config._reparse([tmp, "-G", "11"]) - assert config.option.gdest == 11 - repr = config._makerepr() - - config = testdir.Config() - py.test.raises(AttributeError, "config.option.gdest") - - config2 = testdir.Config() - config2._mergerepr(repr) - option = config2.addoptions("testing group", - config2.Option('-G', '--glong', action="store", default=42, - type="int", dest="gdest", help="g value.")) - assert config2.option.gdest == 11 - assert option.gdest == 11 - - def test_config_picklability(self, tmpdir): - import cPickle - config = py.test.config._reparse([tmpdir]) - s = cPickle.dumps(config) - newconfig = cPickle.loads(s) - assert not hasattr(newconfig, "topdir") - assert not newconfig._initialized - assert not hasattr(newconfig, 'args') - newconfig._initafterpickle(config.topdir) - assert newconfig.topdir == config.topdir - assert newconfig._initialized - assert newconfig.args == [tmpdir] - - def test_config_and_collector_pickling_missing_initafter(self, tmpdir): - from cPickle import Pickler, Unpickler - config = py.test.config._reparse([tmpdir]) - col = config.getfsnode(config.topdir) - io = py.std.cStringIO.StringIO() - pickler = Pickler(io) - pickler.dump(config) - pickler.dump(col) - io.seek(0) - unpickler = Unpickler(io) - newconfig = unpickler.load() - # we don't call _initafterpickle ... so - py.test.raises(ValueError, "unpickler.load()") - - def test_config_and_collector_pickling(self, tmpdir): - from cPickle import Pickler, Unpickler - dir1 = tmpdir.ensure("somedir", dir=1) - config = py.test.config._reparse([tmpdir]) - col = config.getfsnode(config.topdir) - col1 = col.join(dir1.basename) - assert col1.parent is col - io = py.std.cStringIO.StringIO() - pickler = Pickler(io) - pickler.dump(config) - pickler.dump(col) - pickler.dump(col1) - pickler.dump(col) - io.seek(0) - unpickler = Unpickler(io) - newconfig = unpickler.load() - topdir = tmpdir.ensure("newtopdir", dir=1) - newconfig._initafterpickle(topdir) - topdir.ensure("somedir", dir=1) - newcol = unpickler.load() - newcol2 = unpickler.load() - newcol3 = unpickler.load() - assert newcol2._config is newconfig - assert newcol2.parent == newcol - assert newcol._config is newconfig - assert newconfig.topdir == topdir - assert newcol3 is newcol - assert newcol.fspath == topdir - assert newcol2.fspath.basename == dir1.basename - assert newcol2.fspath.relto(topdir) def test_options_on_small_file_do_not_blow_up(testdir): def runfiletest(opts): From hpk at codespeak.net Tue Mar 3 19:05:13 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 3 Mar 2009 19:05:13 +0100 (CET) Subject: [py-svn] r62491 - py/trunk/py/test/plugin Message-ID: <20090303180513.514F51684B4@codespeak.net> Author: hpk Date: Tue Mar 3 19:05:08 2009 New Revision: 62491 Modified: py/trunk/py/test/plugin/pytest_terminal.py Log: fix a typo Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Tue Mar 3 19:05:08 2009 @@ -121,7 +121,7 @@ line = info.verboseline(basedir=self.curdir) + " " extra = "" if event.host: - extra = "-> " + event.host.hostid + extra = "-> " + str(event.host) self.write_ensure_prefix(line, extra) else: # ensure that the path is printed before the 1st test of From hpk at codespeak.net Tue Mar 3 19:06:17 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 3 Mar 2009 19:06:17 +0100 (CET) Subject: [py-svn] r62492 - in py/trunk/py: apigen bin doc Message-ID: <20090303180617.0F5021684B4@codespeak.net> Author: hpk Date: Tue Mar 3 19:06:16 2009 New Revision: 62492 Removed: py/trunk/py/apigen/ py/trunk/py/bin/gendoc.py py/trunk/py/doc/apigen.txt Modified: py/trunk/py/doc/confrest.py py/trunk/py/doc/index.txt Log: removing apigen bits from py lib, they are now in svn/apigen Deleted: /py/trunk/py/bin/gendoc.py ============================================================================== --- /py/trunk/py/bin/gendoc.py Tue Mar 3 19:06:16 2009 +++ (empty file) @@ -1,56 +0,0 @@ -#!/usr/bin/env python - -""" -build the 'py' documentation and api docs in a specified -directory, defaulting to 'html'. You need to be a directory -where your "py" package is. - -This script generates API documentation and static -documentation. The API documentation is generated by using -the "apigen" facility of the py lib which currently only works -on windows. -""" - -import sys -sys.path.insert(0, '.') - -import py -import os -try: - import subprocess -except ImportError: - from py.__.compat import subprocess - -def sysexec(cmd): - print "executing", cmd - p = subprocess.Popen(cmd, shell=True) - os.waitpid(p.pid, 0) - -if __name__ == '__main__': - pydir = py.path.local().join("py") - assert pydir.check(dir=1), "py directory not found" - pypath = py.path.local(py.__file__).dirpath() - assert pydir == pypath, "directory %s and %s differ" %(pydir, pypath) - - args = sys.argv[1:] - if not args: - htmldir = py.path.local('html') - else: - htmldir = py.path.local(sys.argv.pop(0)) - - print "generating docs into", htmldir - print "pypath", pypath - pytest = pypath.join("bin/py.test") - assert pytest.check() - - print - print "*" * 30, "apigen", "*"*30 - apigendir = htmldir.join("apigen") - env = 'DOCPATH="%s" APIGENPATH="%s"' %(htmldir, apigendir) - if apigendir.check(): - print apigendir, "exists, not re-generating - remove to trigger regeneration" - else: - sysexec('%(env)s %(pytest)s py' % locals()) - print - print "*" * 30, "static generation", "*" * 30 - sysexec('%(env)s %(pytest)s --forcegen %(pypath)s/doc' % locals()) Deleted: /py/trunk/py/doc/apigen.txt ============================================================================== --- /py/trunk/py/doc/apigen.txt Tue Mar 3 19:06:16 2009 +++ (empty file) @@ -1,284 +0,0 @@ -=========================================== -apigen - API documentation generation tool -=========================================== - -What is it? -=========== - -Apigen is a tool for automatically generating API reference documentation for -Python projects. It works by examining code at runtime rather than at compile -time. This way it is capable of displaying information about the code base -after initialization. A drawback is that you cannot easily document source code -that automatically starts server processes or has some other irreversible -effects upon getting imported. - -The apigen functionality is normally triggered from :api:`py.test`, and while -running the tests it gathers information such as code paths, arguments and -return values of callables, and exceptions that can be raised while the code -runs (XXX not yet!) to include in the documentation. It's also possible to -run the tracer (which collects the data) in other code if your project -does not use :api:`py.test` but still wants to collect the runtime information -and build the docs. - -Apigen is written for the :api:`py` lib, but can be used to build documentation -for any project: there are hooks in py.test to, by providing a simple script, -build api documentation for the tested project when running py.test. Of course -this does imply :api:`py.test` is actually used: if little or no tests are -actually ran, the additional information (code paths, arguments and return -values and exceptions) can not be gathered and thus there will be less of an -advantage of apigen compared to other solutions. - -Features -======== - -Some features were mentioned above already, but here's a complete list of all -the niceties apigen has to offer: - - * source documents - - Apigen not only builds the API documentation, but also a tree of - syntax-colored source files, with links from the API docs to the source - files. - - * abundance of information - - compared to other documentation generation tools, apigen produces an - abundant amount of information: it provides syntax-colored code snippets, - code path traces, etc. - - * linking - - besides links to the source files, apigen provides links all across the - documentation: callable arguments and return values link to their - definition (if part of the documented code), class definition to their - base classes (again, if they're part of the documented code), and - everywhere are links to the source files (including in traces) - - * (hopefully) improves testing - - because the documentation is built partially from test results, developers - may (especially if they're using the documentation themselves) be more - aware of untested parts of the code, or parts can use more tests or need - attention - -Using apigen -============ - -To trigger apigen, all you need to do is run the :source:`py/bin/py.test` tool -with an --apigen argument, as such:: - - $ py.test --apigen= - -where is a path to a script containing some special hooks to build -the documents (see below). The script to build the documents for the :api:`py` -lib can be found in :source:`py/apigen/apigen.py`, so building those documents -can be done by cd'ing to the 'py' directory, and executing:: - - $ py.test --apigen=apigen/apigen.py - -The documents will by default be built in the *parent directory* of the -*package dir* (in this case the 'py' directory). Be careful that you don't -overwrite anything! - -Other projects -============== - -To use apigen from another project, there are three things that you need to do: - -Use :api:`py.test` for unit tests ---------------------------------- - -This is a good idea anyway... ;) The more tests, the more tracing information -and such can be built, so it makes sense to have good test coverage when using -this tool. - -Provide :api:`py.test` hooks ----------------------------- - -To hook into the unit testing framework, you will need to write a script with -two functions. The first should be called 'get_documentable_items', gets a -package dir (the root of the project) as argument, and should return a tuple -with the package name as first element, and a dict as second. The dict should -contain, for all the to-be-documented items, a dotted name as key and a -reference to the item as value. - -The second function should be called 'build', and gets also the package dir as -argument, but also a reference to a DocStorageAcessor, which contains -information gathered by the tracer, and a reference to a -:api:`py.io.StdCaptureFD` instance that is used to capture stdout and stderr, -and allows writing to them, when the docs are built. - -This 'build' function is responsible for actually building the documentation, -and, depending on your needs, can be used to control each aspect of it. In most -situations you will just copy the code from :source:`py/apigen/apigen.py`'s -build() function, but if you want you can choose to build entirely different -output formats by directly accessing the DocStorageAccessor class. - -Provide layout --------------- - -For the :api:`py` lib tests, the 'LayoutPage' class found in -:source:`py/apigen/layout.py` is used, which produces HTML specific for that -particular library (with a menubar, etc.). To customize this, you will need to -provide a similar class, most probably using the Page base class from -:source:`py/doc/confrest.py`. Note that this step depends on how heavy the -customization in the previous step is done: if you decide to directly use the -DocStorageAccessor rather than let the code in :source:`py/apigen/htmlgen.py` -build HTML for you, this can be skipped. - -Using apigen from code -====================== - -If you want to avoid using :api:`py.test`, or have an other idea of how to best -collect information while running code, the apigen functionality can be -directly accessed. The most important classes are the Tracer class found in -:source:`py/apigen/tracer/tracer.py`, which holds the information gathered -during the tests, and the DocStorage and DocStorageAccessor classes from -:source:`py/apigen/tracer/docstorage.py`, which (respectively) store the data, -and make it accessible. - -Gathering information ---------------------- - -To gather information about documentation, you will first need to tell the tool -what objects it should investigate. Only information for registered objects -will be stored. An example:: - - >>> import py - >>> from py.__.apigen.tracer.docstorage import DocStorage, DocStorageAccessor - >>> from py.__.apigen.tracer.tracer import Tracer - >>> toregister = {'py.path.local': py.path.local, - ... 'py.path.svnwc': py.path.svnwc} - >>> ds = DocStorage().from_dict(toregister) - >>> t = Tracer(ds) - >>> t.start_tracing() - >>> p = py.path.local('.') - >>> p.check(dir=True) - True - >>> t.end_tracing() - -Now the 'ds' variable should contain all kinds of information about both the -:api:`py.path.local` and the :api:`py.path.svnwc` classes, and things like call -stacks, possible argument types, etc. as additional information about -:api:`py.path.local.check()` (since it was called from the traced code). - -Using the information ---------------------- - -To use the information, we need to get a DocStorageAccessor instance to -provide access to the data stored in the DocStorage object:: - - >>> dsa = DocStorageAccessor(ds) - -Currently there is no API reference available for this object, so you'll have -to read the source (:source:`py/apigen/tracer/docstorage.py`) to see what -functionality it offers. - -Comparison with other documentation generation tools -==================================================== - -Apigen is of course not the only documentation generation tool available for -Python. Although we knew in advance that our tool had certain features the -others do not offer, we decided to investigate a bit so that we could do a -proper comparison. - -Tools examined --------------- - -After some 'googling around', it turned out that the amount of documentation -generation tools available was surprisingly low. There were only 5 packages -I could find, of which 1 (called 'HappyDoc') seems dead (last release 2001), -one (called 'Pudge') not yet born (perhaps DOA even? most of the links on the -website are dead), and one (called 'Endo') specific to the Enthought suite. -The remaining two were Epydoc, which is widely used [1]_, and PyDoctor, which is -used only by (and written for) the Twisted project, but can be used seperately. - -Epydoc -~~~~~~ - -http://epydoc.sourceforge.net/ - -Epydoc is the best known, and most widely used, documentation generation tool -for Python. It builds a documentation tree by inspecting imported modules and -using Python's introspection features. This way it can display information like -containment, inheritance, and docstrings. - -The tool is relatively sophisticated, with support for generating HTML and PDF, -choosing different styles (CSS), generating graphs using Graphviz, etc. Also -it allows using markup (which can be ReST, JavaDoc, or their own 'epytext' -format) inside docstrings for displaying rich text in the result. - -Quick overview: - - * builds docs from object tree - * displays relatively little information, just inheritance trees, API and - docstrings - * supports some markup (ReST, 'epytext', JavaDoc) in docstrings - -PyDoctor -~~~~~~~~ - -http://codespeak.net/~mwh/pydoctor/ - -This tool is written by Michael Hudson for the Twisted project. The major -difference between this and Epydoc is that it browses the AST (Abstract Syntax -Tree) instead of using 'live' objects, which means that code that uses special -import mechanisms, or depends on other code that is not available can still be -inspected. On the other hand, code that, for example, puts bound methods into a -module namespace is not documented. - -The tool is relatively simple and doesn't support the more advanced features -that Epydoc offers. It was written for Twisted and there are no current plans to -promote its use for unrelated projects. - -Quick overview: - - * inspects AST rather than object tree - * again not a lot of information, the usual API docstrings, class inheritance - and module structure, but that's it - * rather heavy dependencies (depends on Twisted/Nevow (trunk version)) - * written for Twisted, but quite nice output with other applications - -Quick overview lists of the other tools ---------------------------------------- - -HappyDoc -~~~~~~~~ - -http://happydoc.sourceforge.net/ - - * dead - * inspects AST - * quite flexible, different output formats (HTML, XML, SGML, PDF) - * pluggable docstring parsers - -Pudge -~~~~~ - -http://pudge.lesscode.org/ - - * immature, dead? - * builds docs from live object tree (I think?) - * supports ReST - * uses Kid templates - -Endo -~~~~ - -https://svn.enthought.com/enthought/wiki/EndoHowTo - - * inspects object tree (I think?) - * 'traits' aware (see https://svn.enthought.com/enthought/wiki/Traits) - * customizable HTML output with custom templating engine - * little documentation, seems like it's written for Enthought's own use - mostly - * heavy dependencies - -.. [1] Epydoc doesn't seem to be developed anymore, either, but it's so - widely used it can not be ignored... - -Questions, remarks, etc. -======================== - -For more information, questions, remarks, etc. see http://codespeak.net/py. -This website also contains links to mailing list and IRC channel. Modified: py/trunk/py/doc/confrest.py ============================================================================== --- py/trunk/py/doc/confrest.py (original) +++ py/trunk/py/doc/confrest.py Tue Mar 3 19:06:16 2009 @@ -1,7 +1,6 @@ import py from py.__.misc.rest import convert_rest_html, strip_html_header from py.__.misc.difftime import worded_time -from py.__.apigen.linker import relpath html = py.xml.html @@ -187,3 +186,59 @@ page.contentspace.append(py.xml.raw(content)) outputpath.ensure().write(page.unicode().encode(encoding)) +# XXX this function comes from apigen/linker.py, put it +# somewhere in py lib +import os +def relpath(p1, p2, sep=os.path.sep, back='..', normalize=True): + """ create a relative path from p1 to p2 + + sep is the seperator used for input and (depending + on the setting of 'normalize', see below) output + + back is the string used to indicate the parent directory + + when 'normalize' is True, any backslashes (\) in the path + will be replaced with forward slashes, resulting in a consistent + output on Windows and the rest of the world + + paths to directories must end on a / (URL style) + """ + if normalize: + p1 = p1.replace(sep, '/') + p2 = p2.replace(sep, '/') + sep = '/' + # XXX would be cool to be able to do long filename + # expansion and drive + # letter fixes here, and such... iow: windows sucks :( + if (p1.startswith(sep) ^ p2.startswith(sep)): + raise ValueError("mixed absolute relative path: %r -> %r" %(p1, p2)) + fromlist = p1.split(sep) + tolist = p2.split(sep) + + # AA + # AA BB -> AA/BB + # + # AA BB + # AA CC -> CC + # + # AA BB + # AA -> ../AA + + diffindex = 0 + for x1, x2 in zip(fromlist, tolist): + if x1 != x2: + break + diffindex += 1 + commonindex = diffindex - 1 + + fromlist_diff = fromlist[diffindex:] + tolist_diff = tolist[diffindex:] + + if not fromlist_diff: + return sep.join(tolist[commonindex:]) + backcount = len(fromlist_diff) + if tolist_diff: + return sep.join([back,]*(backcount-1) + tolist_diff) + return sep.join([back,]*(backcount) + tolist[commonindex:]) + + Modified: py/trunk/py/doc/index.txt ============================================================================== --- py/trunk/py/doc/index.txt (original) +++ py/trunk/py/doc/index.txt Tue Mar 3 19:06:16 2009 @@ -26,7 +26,6 @@ `py lib scripts`_ describe the scripts contained in the ``py/bin`` directory. -`apigen`_: a new way to generate rich Python API documentation support functionality --------------------------------- @@ -50,7 +49,6 @@ .. _`py-dev at codespeak net`: http://codespeak.net/mailman/listinfo/py-dev .. _`py.execnet`: execnet.html .. _`py.magic.greenlet`: greenlet.html -.. _`apigen`: apigen.html .. _`py.log`: log.html .. _`py.io`: io.html .. _`py.path`: path.html From py-svn at codespeak.net Tue Mar 3 23:26:57 2009 From: py-svn at codespeak.net (py-svn at codespeak.net) Date: Tue, 3 Mar 2009 23:26:57 +0100 (CET) Subject: [py-svn] Please, send my data to him Message-ID: <20090303222657.CFDB116851A@codespeak.net> An HTML attachment was scrubbed... URL: From briandorsey at codespeak.net Wed Mar 4 07:12:46 2009 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Wed, 4 Mar 2009 07:12:46 +0100 (CET) Subject: [py-svn] r62503 - in py/extradoc/talk/pycon-us-2009/pytest-begin: . ui ui/default Message-ID: <20090304061246.32353168514@codespeak.net> Author: briandorsey Date: Wed Mar 4 07:12:45 2009 New Revision: 62503 Added: py/extradoc/talk/pycon-us-2009/pytest-begin/ py/extradoc/talk/pycon-us-2009/pytest-begin/TODO.txt py/extradoc/talk/pycon-us-2009/pytest-begin/build.sh (contents, props changed) py/extradoc/talk/pycon-us-2009/pytest-begin/pytest-begin.html py/extradoc/talk/pycon-us-2009/pytest-begin/pytest-begin.txt - copied, changed from r62444, py/extradoc/talk/pycon-us-2009/proposal-pytest-begin.txt py/extradoc/talk/pycon-us-2009/pytest-begin/ui/ py/extradoc/talk/pycon-us-2009/pytest-begin/ui/default/ py/extradoc/talk/pycon-us-2009/pytest-begin/ui/default/blank.gif (contents, props changed) py/extradoc/talk/pycon-us-2009/pytest-begin/ui/default/framing.css py/extradoc/talk/pycon-us-2009/pytest-begin/ui/default/iepngfix.htc py/extradoc/talk/pycon-us-2009/pytest-begin/ui/default/opera.css py/extradoc/talk/pycon-us-2009/pytest-begin/ui/default/outline.css py/extradoc/talk/pycon-us-2009/pytest-begin/ui/default/pretty.css py/extradoc/talk/pycon-us-2009/pytest-begin/ui/default/print.css py/extradoc/talk/pycon-us-2009/pytest-begin/ui/default/s5-core.css py/extradoc/talk/pycon-us-2009/pytest-begin/ui/default/slides.css py/extradoc/talk/pycon-us-2009/pytest-begin/ui/default/slides.js Log: first cut at a rst2s5 slide template Added: py/extradoc/talk/pycon-us-2009/pytest-begin/TODO.txt ============================================================================== --- (empty file) +++ py/extradoc/talk/pycon-us-2009/pytest-begin/TODO.txt Wed Mar 4 07:12:45 2009 @@ -0,0 +1,10 @@ +- setup template for rst2s5 +- experiment with bruce +- research question - any other plaintext presentation tools? +- figure out how s5 and bruce deal with different resolutions +- choose presentation tool + +- experiment with funcargs + +================= +2009/03/03 - install rst2s5 Added: py/extradoc/talk/pycon-us-2009/pytest-begin/build.sh ============================================================================== --- (empty file) +++ py/extradoc/talk/pycon-us-2009/pytest-begin/build.sh Wed Mar 4 07:12:45 2009 @@ -0,0 +1 @@ +rst2s5.py pytest-begin.txt pytest-begin.html Added: py/extradoc/talk/pycon-us-2009/pytest-begin/pytest-begin.html ============================================================================== --- (empty file) +++ py/extradoc/talk/pycon-us-2009/pytest-begin/pytest-begin.html Wed Mar 4 07:12:45 2009 @@ -0,0 +1,440 @@ + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+
+
+ +
+
+

py.test - rapid testing with minimal effort

+

Brian Dorsey <brian@dorseys.org> & Holger Krekel <holger@merlinux.eu>

+

http://pytest.org

+

TODO: Add URL to help people to prepare for the tutorial (install/config/etc)

+
+
+

Presenter bio

+

Brian Dorsey

+

Holger Krekel

+
+
+

Tutorial Summary

+

TODO: turn this into overview slide. +This tutorial introduces automated testing for Python using py.test +(http://codespeak.net/py/dist/test.html). We begin with a very short overview +of testing in Python and where unit testing py.test fit in. The rest of the +tutorial is hands on: a short introduction of something new followed by +exercises to experiment with it. First is basic usage of py.test and the +features which simplify your test writing experience. We walk through the +implementation of tests, setup and teardown of test state, debugging +facilities and point out similarities/differences to other test tools. We talk +about command line options which save time running and debugging your tests. +We then drop briefly into lecture mode and discuss additional features useful +in particular circumstances, such as running Javascript tests, testing the +format of text documents, or using py.test in a build system. We will close +with time to answer questions and discuss topics from the students.

+
+
+

Motivation

+

why automated testing? (10 minutes)

+
    +
  • what is unit testing and how does it compare to other types of testing
  • +
  • why do automated testing? benefits, etc
  • +
  • existing python testing tools
  • +
+
+
+

What you get

+

What you get with py.test (10 minutes)

+
    +
  • overview of all the basic benefits - automatic test discovery, simplicity, 'print' debugging (output redirection), function or class, assert introspection, etc
  • +
  • extra benefits, there if you need them, ignore if not - multiple python version, distributed testing, doctests, etc, etc
  • +
  • similarities and differences between nose and py.test
  • +
+
+
+

Installation

+

Installation, basic test functions. (30 minutes)

+
    +
  • installation
  • +
  • test functions
  • +
  • 20 minute work time
  • +
  • Basic setup and working through inevitable setup problems. Some people will finish very quickly - ask them to help others get setup.
  • +
+
+
+

Basic Usage

+

Basic usage of py.test (40 minutes)

+
    +
  • reinterpretation of asserts
  • +
  • working with failures - debug with print
  • +
  • exceptions
  • +
  • 10 minute work time - exercises
  • +
  • test classes
  • +
  • setup and teardown test state
  • +
  • skipping tests
  • +
  • 10 minute work time - exercises
  • +
+
+
+

Break

+
+
+

Options

+

Options (25 minutes)

+
    +
  • --exitfirst
  • +
  • --looponfailing - run large test set until all tests pass
  • +
  • -k to run tests matching name or keyword
  • +
  • --exec to use different Python interpreters
  • +
  • different options to control traceback generation
  • +
  • 10 minute work time - exercises
  • +
+
+
+

Branching out

+

Branching out (20 minutes)

+
    +
  • generative tests
  • +
  • skipping tests
  • +
  • --pdb
  • +
  • 10 minute work time - exercises
  • +
+
+
+

Using doctests

+

Using doctests (25 minutes)

+
    +
  • what are doctests
  • +
  • two usage scenarios - docstrings & stand alone files
  • +
  • how they work demo and examples.
  • +
  • 10 minute work time - exercises
  • +
+
+
+

Wrap up

+

Wrapping up and questions (20 minutes)

+
    +
  • where to go from here
  • +
  • quesitons and student topics
  • +
+
+
+ + Copied: py/extradoc/talk/pycon-us-2009/pytest-begin/pytest-begin.txt (from r62444, py/extradoc/talk/pycon-us-2009/proposal-pytest-begin.txt) ============================================================================== --- py/extradoc/talk/pycon-us-2009/proposal-pytest-begin.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-begin/pytest-begin.txt Wed Mar 4 07:12:45 2009 @@ -1,35 +1,26 @@ -Title: py.test - rapid testing with minimal effort -Presenter: Brian Dorsey , Holger Krekel -Tutorial format: interactive lecture -Recording: We give permission to record and publish my PyCon tutorial for free distribution. -Intended Audience: beginner programmers -Maximum number of students: maybe 30 -Perequisites/knowledge: basic knowledge of python programming -Requirements: Laptop with Python 2.4 or greater installed. Or pair with someone. :) - -Presenter bio: - -Brian Dorsey is a database and Python developer living in Seattle Washington, -USA. He mostly writes command line tools, windows services and more recntly -simple web apps. He is a long-time user of and occasional contributor to -py.test. He is a co-organizer of the Seattle Python Interest Group -(www.seapig.org), a member of www.saturdayhouse.org and co-founder of a -co-working space in Seattle (www.giraffelabs.com). He also loves lunch and -created www.noonhat.com to help feed that addiction. He doesn't like natto or -talking about himself in the third person. - -Holger Krekel is a co-founder of the PyPy project and -participates on many levels. He is the initiator and -maintainer of the popular py.test and a few other tools. In -2004 he founded merlinux, a company focusing on PyPy and py.test -developments. Holger has spoken at many EuroPython and -PyCon confereces and gave well received testing -tutorials at EuroPython 2008 and Pycon-UK 2008. +py.test - rapid testing with minimal effort +=========================================== -Tutorial Summary: +Brian Dorsey & Holger Krekel -XXX: this needs to be shorter: max 100 words. +http://pytest.org +TODO: Add URL to help people to prepare for the tutorial (install/config/etc) + + + +Presenter bio +============= + +Brian Dorsey + +Holger Krekel + + +Tutorial Summary +================ + +TODO: turn this into overview slide. This tutorial introduces automated testing for Python using py.test (http://codespeak.net/py/dist/test.html). We begin with a very short overview of testing in Python and where unit testing py.test fit in. The rest of the @@ -44,25 +35,40 @@ format of text documents, or using py.test in a build system. We will close with time to answer questions and discuss topics from the students. -Outline for review: -Motivation / why automated testing? (10 minutes) +Motivation +========== + +why automated testing? (10 minutes) + - what is unit testing and how does it compare to other types of testing - why do automated testing? benefits, etc - existing python testing tools +What you get +============ + What you get with py.test (10 minutes) + - overview of all the basic benefits - automatic test discovery, simplicity, 'print' debugging (output redirection), function or class, assert introspection, etc - extra benefits, there if you need them, ignore if not - multiple python version, distributed testing, doctests, etc, etc - similarities and differences between nose and py.test -Installation, basic test functions. (30 minutes) +Installation +============ + +*Installation, basic test functions. (30 minutes)* + - installation - test functions - 20 minute work time - - Basic setup and working through inevitable setup problems. Some people will finish very quickly - ask them to help others get setup. +- Basic setup and working through inevitable setup problems. Some people will finish very quickly - ask them to help others get setup. + +Basic Usage +=========== Basic usage of py.test (40 minutes) + - reinterpretation of asserts - working with failures - debug with print - exceptions @@ -73,8 +79,13 @@ - 10 minute work time - exercises Break +===== + +Options +======= Options (25 minutes) + - --exitfirst - --looponfailing - run large test set until all tests pass - -k to run tests matching name or keyword @@ -82,39 +93,31 @@ - different options to control traceback generation - 10 minute work time - exercises +Branching out +============= + Branching out (20 minutes) + - generative tests - skipping tests - --pdb - 10 minute work time - exercises +Using doctests +============== + Using doctests (25 minutes) + - what are doctests - two usage scenarios - docstrings & stand alone files - how they work demo and examples. - 10 minute work time - exercises +Wrap up +======= + Wrapping up and questions (20 minutes) + - where to go from here - quesitons and student topics - -Notes for reviewers: -We're happy to adjust the content of the talk to match attendees interests and would greatly value reviewer input into specific areas they expect people to be interested (or not) in. - -Previous speaking/teaching experience: -Brian: -I co-presented a talk at PyCon last year and gave an earlier version of this -talk at the Vancouver Python Workshop. Most months, I moderate our loose -disscussion oriented meetings for the Seattle Python Interest Group. I'm -comfortable in front of audiences speaking and teaching. I taught High School -English in Japan for two years. For an example of my speaking style in short -talks, see: http://www.youtube.com/watch?v=2f4c1dQW3vY and a regular talk format from PyCon last year: http://www.youtube.com/watch?v=OCZ19R0KD4o This tutorial would be a different speaking style - -Holger: -I have talked at numerous Pycon and EuroPython conferences about -PyPyand testing tools. In 2008 i gave a 1h non-hands on version -of the proposed tutorial which was received very well. I've -also co-ordinated many sprints, gave teaching lectures -at university about computer science and generally enjoy -interacting with an interested learning group. Added: py/extradoc/talk/pycon-us-2009/pytest-begin/ui/default/blank.gif ============================================================================== Binary file. No diff available. Added: py/extradoc/talk/pycon-us-2009/pytest-begin/ui/default/framing.css ============================================================================== --- (empty file) +++ py/extradoc/talk/pycon-us-2009/pytest-begin/ui/default/framing.css Wed Mar 4 07:12:45 2009 @@ -0,0 +1,25 @@ +/* This file has been placed in the public domain. */ +/* The following styles size, place, and layer the slide components. + Edit these if you want to change the overall slide layout. + The commented lines can be uncommented (and modified, if necessary) + to help you with the rearrangement process. */ + +/* target = 1024x768 */ + +div#header, div#footer, .slide {width: 100%; top: 0; left: 0;} +div#header {position: fixed; top: 0; height: 3em; z-index: 1;} +div#footer {top: auto; bottom: 0; height: 2.5em; z-index: 5;} +.slide {top: 0; width: 92%; padding: 2.5em 4% 4%; z-index: 2;} +div#controls {left: 50%; bottom: 0; width: 50%; z-index: 100;} +div#controls form {position: absolute; bottom: 0; right: 0; width: 100%; + margin: 0;} +#currentSlide {position: absolute; width: 10%; left: 45%; bottom: 1em; + z-index: 10;} +html>body #currentSlide {position: fixed;} + +/* +div#header {background: #FCC;} +div#footer {background: #CCF;} +div#controls {background: #BBD;} +div#currentSlide {background: #FFC;} +*/ Added: py/extradoc/talk/pycon-us-2009/pytest-begin/ui/default/iepngfix.htc ============================================================================== --- (empty file) +++ py/extradoc/talk/pycon-us-2009/pytest-begin/ui/default/iepngfix.htc Wed Mar 4 07:12:45 2009 @@ -0,0 +1,42 @@ + + + + + \ No newline at end of file Added: py/extradoc/talk/pycon-us-2009/pytest-begin/ui/default/opera.css ============================================================================== --- (empty file) +++ py/extradoc/talk/pycon-us-2009/pytest-begin/ui/default/opera.css Wed Mar 4 07:12:45 2009 @@ -0,0 +1,8 @@ +/* This file has been placed in the public domain. */ +/* DO NOT CHANGE THESE unless you really want to break Opera Show */ +.slide { + visibility: visible !important; + position: static !important; + page-break-before: always; +} +#slide0 {page-break-before: avoid;} Added: py/extradoc/talk/pycon-us-2009/pytest-begin/ui/default/outline.css ============================================================================== --- (empty file) +++ py/extradoc/talk/pycon-us-2009/pytest-begin/ui/default/outline.css Wed Mar 4 07:12:45 2009 @@ -0,0 +1,16 @@ +/* This file has been placed in the public domain. */ +/* Don't change this unless you want the layout stuff to show up in the + outline view! */ + +.layout div, #footer *, #controlForm * {display: none;} +#footer, #controls, #controlForm, #navLinks, #toggle { + display: block; visibility: visible; margin: 0; padding: 0;} +#toggle {float: right; padding: 0.5em;} +html>body #toggle {position: fixed; top: 0; right: 0;} + +/* making the outline look pretty-ish */ + +#slide0 h1, #slide0 h2, #slide0 h3, #slide0 h4 {border: none; margin: 0;} +#toggle {border: 1px solid; border-width: 0 0 1px 1px; background: #FFF;} + +.outline {display: inline ! important;} Added: py/extradoc/talk/pycon-us-2009/pytest-begin/ui/default/pretty.css ============================================================================== --- (empty file) +++ py/extradoc/talk/pycon-us-2009/pytest-begin/ui/default/pretty.css Wed Mar 4 07:12:45 2009 @@ -0,0 +1,120 @@ +/* This file has been placed in the public domain. */ +/* Following are the presentation styles -- edit away! */ + +html, body {margin: 0; padding: 0;} +body {background: white; color: black;} +/* Replace the background style above with the style below (and again for + div#header) for a graphic: */ +/* background: white url(bodybg.gif) -16px 0 no-repeat; */ +:link, :visited {text-decoration: none; color: #00C;} +#controls :active {color: #88A !important;} +#controls :focus {outline: 1px dotted #227;} +h1, h2, h3, h4 {font-size: 100%; margin: 0; padding: 0; font-weight: inherit;} + +blockquote {padding: 0 2em 0.5em; margin: 0 1.5em 0.5em;} +blockquote p {margin: 0;} + +kbd {font-weight: bold; font-size: 1em;} +sup {font-size: smaller; line-height: 1px;} + +.slide pre {padding: 0; margin-left: 0; margin-right: 0; font-size: 90%;} +.slide ul ul li {list-style: square;} +.slide img.leader {display: block; margin: 0 auto;} +.slide tt {font-size: 90%;} + +div#header, div#footer {background: #005; color: #AAB; font-family: sans-serif;} +/* background: #005 url(bodybg.gif) -16px 0 no-repeat; */ +div#footer {font-size: 0.5em; font-weight: bold; padding: 1em 0;} +#footer h1 {display: block; padding: 0 1em;} +#footer h2 {display: block; padding: 0.8em 1em 0;} + +.slide {font-size: 1.2em;} +.slide h1 {position: absolute; top: 0.45em; z-index: 1; + margin: 0; padding-left: 0.7em; white-space: nowrap; + font: bold 150% sans-serif; color: #DDE; background: #005;} +.slide h2 {font: bold 120%/1em sans-serif; padding-top: 0.5em;} +.slide h3 {font: bold 100% sans-serif; padding-top: 0.5em;} +h1 abbr {font-variant: small-caps;} + +div#controls {position: absolute; left: 50%; bottom: 0; + width: 50%; text-align: right; font: bold 0.9em sans-serif;} +html>body div#controls {position: fixed; padding: 0 0 1em 0; top: auto;} +div#controls form {position: absolute; bottom: 0; right: 0; width: 100%; + margin: 0; padding: 0;} +#controls #navLinks a {padding: 0; margin: 0 0.5em; + background: #005; border: none; color: #779; cursor: pointer;} +#controls #navList {height: 1em;} +#controls #navList #jumplist {position: absolute; bottom: 0; right: 0; + background: #DDD; color: #227;} + +#currentSlide {text-align: center; font-size: 0.5em; color: #449; + font-family: sans-serif; font-weight: bold;} + +#slide0 {padding-top: 1.5em} +#slide0 h1 {position: static; margin: 1em 0 0; padding: 0; color: #000; + font: bold 2em sans-serif; white-space: normal; background: transparent;} +#slide0 h2 {font: bold italic 1em sans-serif; margin: 0.25em;} +#slide0 h3 {margin-top: 1.5em; font-size: 1.5em;} +#slide0 h4 {margin-top: 0; font-size: 1em;} + +ul.urls {list-style: none; display: inline; margin: 0;} +.urls li {display: inline; margin: 0;} +.external {border-bottom: 1px dotted gray;} +html>body .external {border-bottom: none;} +.external:after {content: " \274F"; font-size: smaller; color: #77B;} + +.incremental, .incremental *, .incremental *:after {visibility: visible; + color: white; border: 0;} +img.incremental {visibility: hidden;} +.slide .current {color: green;} + +.slide-display {display: inline ! important;} + +.huge {font-family: sans-serif; font-weight: bold; font-size: 150%;} +.big {font-family: sans-serif; font-weight: bold; font-size: 120%;} +.small {font-size: 75%;} +.tiny {font-size: 50%;} +.huge tt, .big tt, .small tt, .tiny tt {font-size: 115%;} +.huge pre, .big pre, .small pre, .tiny pre {font-size: 115%;} + +.maroon {color: maroon;} +.red {color: red;} +.magenta {color: magenta;} +.fuchsia {color: fuchsia;} +.pink {color: #FAA;} +.orange {color: orange;} +.yellow {color: yellow;} +.lime {color: lime;} +.green {color: green;} +.olive {color: olive;} +.teal {color: teal;} +.cyan {color: cyan;} +.aqua {color: aqua;} +.blue {color: blue;} +.navy {color: navy;} +.purple {color: purple;} +.black {color: black;} +.gray {color: gray;} +.silver {color: silver;} +.white {color: white;} + +.left {text-align: left ! important;} +.center {text-align: center ! important;} +.right {text-align: right ! important;} + +.animation {position: relative; margin: 1em 0; padding: 0;} +.animation img {position: absolute;} + +/* Docutils-specific overrides */ + +.slide table.docinfo {margin: 1em 0 0.5em 2em;} + +pre.literal-block, pre.doctest-block {background-color: white;} + +tt.docutils {background-color: white;} + +/* diagnostics */ +/* +li:after {content: " [" attr(class) "]"; color: #F88;} +div:before {content: "[" attr(class) "]"; color: #F88;} +*/ Added: py/extradoc/talk/pycon-us-2009/pytest-begin/ui/default/print.css ============================================================================== --- (empty file) +++ py/extradoc/talk/pycon-us-2009/pytest-begin/ui/default/print.css Wed Mar 4 07:12:45 2009 @@ -0,0 +1,24 @@ +/* This file has been placed in the public domain. */ +/* The following rule is necessary to have all slides appear in print! + DO NOT REMOVE IT! */ +.slide, ul {page-break-inside: avoid; visibility: visible !important;} +h1 {page-break-after: avoid;} + +body {font-size: 12pt; background: white;} +* {color: black;} + +#slide0 h1 {font-size: 200%; border: none; margin: 0.5em 0 0.25em;} +#slide0 h3 {margin: 0; padding: 0;} +#slide0 h4 {margin: 0 0 0.5em; padding: 0;} +#slide0 {margin-bottom: 3em;} + +#header {display: none;} +#footer h1 {margin: 0; border-bottom: 1px solid; color: gray; + font-style: italic;} +#footer h2, #controls {display: none;} + +.print {display: inline ! important;} + +/* The following rule keeps the layout stuff out of print. + Remove at your own risk! */ +.layout, .layout * {display: none !important;} Added: py/extradoc/talk/pycon-us-2009/pytest-begin/ui/default/s5-core.css ============================================================================== --- (empty file) +++ py/extradoc/talk/pycon-us-2009/pytest-begin/ui/default/s5-core.css Wed Mar 4 07:12:45 2009 @@ -0,0 +1,11 @@ +/* This file has been placed in the public domain. */ +/* Do not edit or override these styles! + The system will likely break if you do. */ + +div#header, div#footer, div#controls, .slide {position: absolute;} +html>body div#header, html>body div#footer, + html>body div#controls, html>body .slide {position: fixed;} +.handout {display: none;} +.layout {display: block;} +.slide, .hideme, .incremental {visibility: hidden;} +#slide0 {visibility: visible;} Added: py/extradoc/talk/pycon-us-2009/pytest-begin/ui/default/slides.css ============================================================================== --- (empty file) +++ py/extradoc/talk/pycon-us-2009/pytest-begin/ui/default/slides.css Wed Mar 4 07:12:45 2009 @@ -0,0 +1,10 @@ +/* This file has been placed in the public domain. */ + +/* required to make the slide show run at all */ + at import url(s5-core.css); + +/* sets basic placement and size of slide components */ + at import url(framing.css); + +/* styles that make the slides look good */ + at import url(pretty.css); Added: py/extradoc/talk/pycon-us-2009/pytest-begin/ui/default/slides.js ============================================================================== --- (empty file) +++ py/extradoc/talk/pycon-us-2009/pytest-begin/ui/default/slides.js Wed Mar 4 07:12:45 2009 @@ -0,0 +1,558 @@ +// S5 v1.1 slides.js -- released into the Public Domain +// Modified for Docutils (http://docutils.sf.net) by David Goodger +// +// Please see http://www.meyerweb.com/eric/tools/s5/credits.html for +// information about all the wonderful and talented contributors to this code! + +var undef; +var slideCSS = ''; +var snum = 0; +var smax = 1; +var slideIDs = new Array(); +var incpos = 0; +var number = undef; +var s5mode = true; +var defaultView = 'slideshow'; +var controlVis = 'visible'; + +var isIE = navigator.appName == 'Microsoft Internet Explorer' ? 1 : 0; +var isOp = navigator.userAgent.indexOf('Opera') > -1 ? 1 : 0; +var isGe = navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('Safari') < 1 ? 1 : 0; + +function hasClass(object, className) { + if (!object.className) return false; + return (object.className.search('(^|\\s)' + className + '(\\s|$)') != -1); +} + +function hasValue(object, value) { + if (!object) return false; + return (object.search('(^|\\s)' + value + '(\\s|$)') != -1); +} + +function removeClass(object,className) { + if (!object) return; + object.className = object.className.replace(new RegExp('(^|\\s)'+className+'(\\s|$)'), RegExp.$1+RegExp.$2); +} + +function addClass(object,className) { + if (!object || hasClass(object, className)) return; + if (object.className) { + object.className += ' '+className; + } else { + object.className = className; + } +} + +function GetElementsWithClassName(elementName,className) { + var allElements = document.getElementsByTagName(elementName); + var elemColl = new Array(); + for (var i = 0; i< allElements.length; i++) { + if (hasClass(allElements[i], className)) { + elemColl[elemColl.length] = allElements[i]; + } + } + return elemColl; +} + +function isParentOrSelf(element, id) { + if (element == null || element.nodeName=='BODY') return false; + else if (element.id == id) return true; + else return isParentOrSelf(element.parentNode, id); +} + +function nodeValue(node) { + var result = ""; + if (node.nodeType == 1) { + var children = node.childNodes; + for (var i = 0; i < children.length; ++i) { + result += nodeValue(children[i]); + } + } + else if (node.nodeType == 3) { + result = node.nodeValue; + } + return(result); +} + +function slideLabel() { + var slideColl = GetElementsWithClassName('*','slide'); + var list = document.getElementById('jumplist'); + smax = slideColl.length; + for (var n = 0; n < smax; n++) { + var obj = slideColl[n]; + + var did = 'slide' + n.toString(); + if (obj.getAttribute('id')) { + slideIDs[n] = obj.getAttribute('id'); + } + else { + obj.setAttribute('id',did); + slideIDs[n] = did; + } + if (isOp) continue; + + var otext = ''; + var menu = obj.firstChild; + if (!menu) continue; // to cope with empty slides + while (menu && menu.nodeType == 3) { + menu = menu.nextSibling; + } + if (!menu) continue; // to cope with slides with only text nodes + + var menunodes = menu.childNodes; + for (var o = 0; o < menunodes.length; o++) { + otext += nodeValue(menunodes[o]); + } + list.options[list.length] = new Option(n + ' : ' + otext, n); + } +} + +function currentSlide() { + var cs; + var footer_nodes; + var vis = 'visible'; + if (document.getElementById) { + cs = document.getElementById('currentSlide'); + footer_nodes = document.getElementById('footer').childNodes; + } else { + cs = document.currentSlide; + footer = document.footer.childNodes; + } + cs.innerHTML = '' + snum + '<\/span> ' + + '\/<\/span> ' + + '' + (smax-1) + '<\/span>'; + if (snum == 0) { + vis = 'hidden'; + } + cs.style.visibility = vis; + for (var i = 0; i < footer_nodes.length; i++) { + if (footer_nodes[i].nodeType == 1) { + footer_nodes[i].style.visibility = vis; + } + } +} + +function go(step) { + if (document.getElementById('slideProj').disabled || step == 0) return; + var jl = document.getElementById('jumplist'); + var cid = slideIDs[snum]; + var ce = document.getElementById(cid); + if (incrementals[snum].length > 0) { + for (var i = 0; i < incrementals[snum].length; i++) { + removeClass(incrementals[snum][i], 'current'); + removeClass(incrementals[snum][i], 'incremental'); + } + } + if (step != 'j') { + snum += step; + lmax = smax - 1; + if (snum > lmax) snum = lmax; + if (snum < 0) snum = 0; + } else + snum = parseInt(jl.value); + var nid = slideIDs[snum]; + var ne = document.getElementById(nid); + if (!ne) { + ne = document.getElementById(slideIDs[0]); + snum = 0; + } + if (step < 0) {incpos = incrementals[snum].length} else {incpos = 0;} + if (incrementals[snum].length > 0 && incpos == 0) { + for (var i = 0; i < incrementals[snum].length; i++) { + if (hasClass(incrementals[snum][i], 'current')) + incpos = i + 1; + else + addClass(incrementals[snum][i], 'incremental'); + } + } + if (incrementals[snum].length > 0 && incpos > 0) + addClass(incrementals[snum][incpos - 1], 'current'); + ce.style.visibility = 'hidden'; + ne.style.visibility = 'visible'; + jl.selectedIndex = snum; + currentSlide(); + number = 0; +} + +function goTo(target) { + if (target >= smax || target == snum) return; + go(target - snum); +} + +function subgo(step) { + if (step > 0) { + removeClass(incrementals[snum][incpos - 1],'current'); + removeClass(incrementals[snum][incpos], 'incremental'); + addClass(incrementals[snum][incpos],'current'); + incpos++; + } else { + incpos--; + removeClass(incrementals[snum][incpos],'current'); + addClass(incrementals[snum][incpos], 'incremental'); + addClass(incrementals[snum][incpos - 1],'current'); + } +} + +function toggle() { + var slideColl = GetElementsWithClassName('*','slide'); + var slides = document.getElementById('slideProj'); + var outline = document.getElementById('outlineStyle'); + if (!slides.disabled) { + slides.disabled = true; + outline.disabled = false; + s5mode = false; + fontSize('1em'); + for (var n = 0; n < smax; n++) { + var slide = slideColl[n]; + slide.style.visibility = 'visible'; + } + } else { + slides.disabled = false; + outline.disabled = true; + s5mode = true; + fontScale(); + for (var n = 0; n < smax; n++) { + var slide = slideColl[n]; + slide.style.visibility = 'hidden'; + } + slideColl[snum].style.visibility = 'visible'; + } +} + +function showHide(action) { + var obj = GetElementsWithClassName('*','hideme')[0]; + switch (action) { + case 's': obj.style.visibility = 'visible'; break; + case 'h': obj.style.visibility = 'hidden'; break; + case 'k': + if (obj.style.visibility != 'visible') { + obj.style.visibility = 'visible'; + } else { + obj.style.visibility = 'hidden'; + } + break; + } +} + +// 'keys' code adapted from MozPoint (http://mozpoint.mozdev.org/) +function keys(key) { + if (!key) { + key = event; + key.which = key.keyCode; + } + if (key.which == 84) { + toggle(); + return; + } + if (s5mode) { + switch (key.which) { + case 10: // return + case 13: // enter + if (window.event && isParentOrSelf(window.event.srcElement, 'controls')) return; + if (key.target && isParentOrSelf(key.target, 'controls')) return; + if(number != undef) { + goTo(number); + break; + } + case 32: // spacebar + case 34: // page down + case 39: // rightkey + case 40: // downkey + if(number != undef) { + go(number); + } else if (!incrementals[snum] || incpos >= incrementals[snum].length) { + go(1); + } else { + subgo(1); + } + break; + case 33: // page up + case 37: // leftkey + case 38: // upkey + if(number != undef) { + go(-1 * number); + } else if (!incrementals[snum] || incpos <= 0) { + go(-1); + } else { + subgo(-1); + } + break; + case 36: // home + goTo(0); + break; + case 35: // end + goTo(smax-1); + break; + case 67: // c + showHide('k'); + break; + } + if (key.which < 48 || key.which > 57) { + number = undef; + } else { + if (window.event && isParentOrSelf(window.event.srcElement, 'controls')) return; + if (key.target && isParentOrSelf(key.target, 'controls')) return; + number = (((number != undef) ? number : 0) * 10) + (key.which - 48); + } + } + return false; +} + +function clicker(e) { + number = undef; + var target; + if (window.event) { + target = window.event.srcElement; + e = window.event; + } else target = e.target; + if (target.href != null || hasValue(target.rel, 'external') || isParentOrSelf(target, 'controls') || isParentOrSelf(target,'embed') || isParentOrSelf(target, 'object')) return true; + if (!e.which || e.which == 1) { + if (!incrementals[snum] || incpos >= incrementals[snum].length) { + go(1); + } else { + subgo(1); + } + } +} + +function findSlide(hash) { + var target = document.getElementById(hash); + if (target) { + for (var i = 0; i < slideIDs.length; i++) { + if (target.id == slideIDs[i]) return i; + } + } + return null; +} + +function slideJump() { + if (window.location.hash == null || window.location.hash == '') { + currentSlide(); + return; + } + if (window.location.hash == null) return; + var dest = null; + dest = findSlide(window.location.hash.slice(1)); + if (dest == null) { + dest = 0; + } + go(dest - snum); +} + +function fixLinks() { + var thisUri = window.location.href; + thisUri = thisUri.slice(0, thisUri.length - window.location.hash.length); + var aelements = document.getElementsByTagName('A'); + for (var i = 0; i < aelements.length; i++) { + var a = aelements[i].href; + var slideID = a.match('\#.+'); + if ((slideID) && (slideID[0].slice(0,1) == '#')) { + var dest = findSlide(slideID[0].slice(1)); + if (dest != null) { + if (aelements[i].addEventListener) { + aelements[i].addEventListener("click", new Function("e", + "if (document.getElementById('slideProj').disabled) return;" + + "go("+dest+" - snum); " + + "if (e.preventDefault) e.preventDefault();"), true); + } else if (aelements[i].attachEvent) { + aelements[i].attachEvent("onclick", new Function("", + "if (document.getElementById('slideProj').disabled) return;" + + "go("+dest+" - snum); " + + "event.returnValue = false;")); + } + } + } + } +} + +function externalLinks() { + if (!document.getElementsByTagName) return; + var anchors = document.getElementsByTagName('a'); + for (var i=0; i' + + '

what you get with py.test

@@ -399,8 +400,12 @@

assert introspection

 def test_function():
-    assert True
-    # TODO: add other assertFail, etc
+    assert True     # assertTrue()
+    assert 1 == 1   # assertEqual()
+    assert 1 == 2   # assertNotEqual()
+    assert False    # assertFalse()
+
+    assert should_return_true()
 

This allows you to write test code which looks just like application code. For example, no need to pass functions and parameters separately into @@ -437,14 +442,15 @@

nose and py.test

+

"nose provides an alternate test discovery and running process for unittest, one that is intended to mimic the behavior of py.test as much as is reasonably possible without resorting to too much magic." -- from nose home page

    -
  • None of the above features are in xUnit frameworks (like stdlib unittest)
  • -
  • <breif history of nose>
  • nosetest and py.test share basic philosophy and features.
  • -
  • ...
  • -
  • ...
  • +
  • py.test used to be more difficult to distribute and had a perception of too +much magic.
  • +
  • opinion: nose is basically a subset of py.test
  • +
  • for basic use, can be difficult to choose - luckily you already have. :)
-

TODO: fill in comparison

+

None of the above features are in xUnit frameworks (like stdlib unittest)

Nose doesn't do re-interpretation of failed statements, so it prints less context information when tests fail. The implementers of Nose prefer to drop into pdb and debug when detailed context information is needed. (TODO: Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt Mon Mar 9 11:25:40 2009 @@ -50,6 +50,7 @@ TODO: split +(steal some from Titus: http://ivory.idyll.org/articles/nose-intro.html) what you get with py.test ========================= @@ -107,8 +108,12 @@ :: def test_function(): - assert True - # TODO: add other assertFail, etc + assert True # assertTrue() + assert 1 == 1 # assertEqual() + assert 1 == 2 # assertNotEqual() + assert False # assertFalse() + + assert should_return_true() .. class:: handout @@ -156,22 +161,25 @@ nose and py.test ================ -- None of the above features are in xUnit frameworks (like stdlib unittest) -- -- nosetest and py.test share basic philosophy and features. -- ... -- ... +*"nose provides an alternate test discovery and running process for unittest, one that is intended to mimic the behavior of py.test as much as is reasonably possible without resorting to too much magic."* -- from nose home page -TODO: fill in comparison +- nosetest and py.test share basic philosophy and features. +- py.test used to be more difficult to distribute and had a perception of too + much magic. +- opinion: nose is basically a subset of py.test +- for basic use, can be difficult to choose - luckily you already have. :) .. class:: handout + None of the above features are in xUnit frameworks (like stdlib unittest) + Nose doesn't do re-interpretation of failed statements, so it prints less context information when tests fail. The implementers of Nose prefer to drop into pdb and debug when detailed context information is needed. (TODO: confirm with Jason P that this is still true) + installation ============ From guido at codespeak.net Mon Mar 9 13:55:01 2009 From: guido at codespeak.net (guido at codespeak.net) Date: Mon, 9 Mar 2009 13:55:01 +0100 (CET) Subject: [py-svn] r62754 - py/trunk/py/test/web Message-ID: <20090309125501.65A77169E2A@codespeak.net> Author: guido Date: Mon Mar 9 13:55:00 2009 New Revision: 62754 Modified: py/trunk/py/test/web/webcheck.py Log: Seems w3 have updated their HTML. Modified: py/trunk/py/test/web/webcheck.py ============================================================================== --- py/trunk/py/test/web/webcheck.py (original) +++ py/trunk/py/test/web/webcheck.py Mon Mar 9 13:55:00 2009 @@ -15,8 +15,8 @@ is_valid = get_validation_result_from_w3_html(ret) return is_valid -reg_validation_result = re.compile(']*class="(in)?valid"[^>]*>([^<]*)<', - re.M | re.S) +reg_validation_result = re.compile( + '<(h2|td)[^>]*class="(in)?valid"[^>]*>([^<]*)<', re.M | re.S) def get_validation_result_from_w3_html(html): match = reg_validation_result.search(html) valid = match.group(1) is None From py-svn at codespeak.net Mon Mar 9 17:03:25 2009 From: py-svn at codespeak.net (py-svn at codespeak.net) Date: Mon, 9 Mar 2009 17:03:25 +0100 (CET) Subject: [py-svn] Maximal endurance - many girls Message-ID: <20090309160325.4A5CB169E2D@codespeak.net> An HTML attachment was scrubbed... URL: From rxe at codespeak.net Tue Mar 10 22:39:42 2009 From: rxe at codespeak.net (rxe at codespeak.net) Date: Tue, 10 Mar 2009 22:39:42 +0100 (CET) Subject: [py-svn] r62832 - py/trunk/py/test Message-ID: <20090310213942.91EFB168501@codespeak.net> Author: rxe Date: Tue Mar 10 22:39:39 2009 New Revision: 62832 Modified: py/trunk/py/test/collect.py Log: unbound res on special files Modified: py/trunk/py/test/collect.py ============================================================================== --- py/trunk/py/test/collect.py (original) +++ py/trunk/py/test/collect.py Tue Mar 10 22:39:39 2009 @@ -432,6 +432,8 @@ res = self.consider_file(path) elif path.check(dir=1): res = self.consider_dir(path) + else: + res = None if isinstance(res, list): # throw out identical modules l = [] From afa at codespeak.net Wed Mar 11 02:40:11 2009 From: afa at codespeak.net (afa at codespeak.net) Date: Wed, 11 Mar 2009 02:40:11 +0100 (CET) Subject: [py-svn] r62835 - in py/trunk/py: code io io/testing test/plugin Message-ID: <20090311014011.41DD9168509@codespeak.net> Author: afa Date: Wed Mar 11 02:40:08 2009 New Revision: 62835 Modified: py/trunk/py/code/excinfo.py py/trunk/py/io/terminalwriter.py py/trunk/py/io/testing/test_terminalwriter.py py/trunk/py/test/plugin/pytest_terminal.py Log: Add ANSI colouring to the Win32 console. This gives a nice display for py.test, and during pypy translation. the "markup" function should not be used any more. Modified: py/trunk/py/code/excinfo.py ============================================================================== --- py/trunk/py/code/excinfo.py (original) +++ py/trunk/py/code/excinfo.py Wed Mar 11 02:40:08 2009 @@ -275,7 +275,7 @@ self.reprfuncargs.toterminal(tw) for line in self.lines: red = line.startswith("E ") - tw.line(tw.markup(bold=True, red=red, text=line)) + tw.line(line, bold=True, red=red) if self.reprlocals: #tw.sep(self.localssep, "Locals") tw.line("") Modified: py/trunk/py/io/terminalwriter.py ============================================================================== --- py/trunk/py/io/terminalwriter.py (original) +++ py/trunk/py/io/terminalwriter.py Wed Mar 11 02:40:08 2009 @@ -14,6 +14,57 @@ height,width = struct.unpack( "hhhh", call ) [:2] return height, width +if sys.platform == 'win32': + # ctypes access to the Windows console + + STD_OUTPUT_HANDLE = -11 + STD_ERROR_HANDLE = -12 + FOREGROUND_BLUE = 0x0001 # text color contains blue. + FOREGROUND_GREEN = 0x0002 # text color contains green. + FOREGROUND_RED = 0x0004 # text color contains red. + FOREGROUND_WHITE = 0x0007 + FOREGROUND_INTENSITY = 0x0008 # text color is intensified. + BACKGROUND_BLUE = 0x0010 # background color contains blue. + BACKGROUND_GREEN = 0x0020 # background color contains green. + BACKGROUND_RED = 0x0040 # background color contains red. + BACKGROUND_WHITE = 0x0007 + BACKGROUND_INTENSITY = 0x0080 # background color is intensified. + + def GetStdHandle(kind): + import ctypes + return ctypes.windll.kernel32.GetStdHandle(kind) + + def SetConsoleTextAttribute(handle, attr): + import ctypes + ctypes.windll.kernel32.SetConsoleTextAttribute( + handle, attr) + + def _getdimensions(): + import ctypes + from ctypes import wintypes + + SHORT = ctypes.c_short + class COORD(ctypes.Structure): + _fields_ = [('X', SHORT), + ('Y', SHORT)] + class SMALL_RECT(ctypes.Structure): + _fields_ = [('Left', SHORT), + ('Top', SHORT), + ('Right', SHORT), + ('Bottom', SHORT)] + class CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure): + _fields_ = [('dwSize', COORD), + ('dwCursorPosition', COORD), + ('wAttributes', wintypes.WORD), + ('srWindow', SMALL_RECT), + ('dwMaximumWindowSize', COORD)] + STD_OUTPUT_HANDLE = -11 + handle = GetStdHandle(STD_OUTPUT_HANDLE) + info = CONSOLE_SCREEN_BUFFER_INFO() + ctypes.windll.kernel32.GetConsoleScreenBufferInfo( + handle, ctypes.byref(info)) + return info.dwSize.Y, info.dwSize.X + def get_terminal_width(): try: height, width = _getdimensions() @@ -31,15 +82,46 @@ if file is None: file = sys.stderr text = text.rstrip() + if esc and not isinstance(esc, tuple): + esc = (esc,) if esc and sys.platform != "win32" and file.isatty(): - if not isinstance(esc, tuple): - esc = (esc,) text = (''.join(['\x1b[%sm' % cod for cod in esc]) + text + '\x1b[0m') # ANSI color code "reset" if newline: text += '\n' - file.write(text) + + if esc and sys.platform == "win32" and file.isatty(): + if 1 in esc: + bold = True + esc = tuple(x for x in esc if x != 1) + else: + bold = False + esctable = {() : FOREGROUND_WHITE, # normal + (31,): FOREGROUND_RED, # red + (32,): FOREGROUND_GREEN, # green + (33,): FOREGROUND_GREEN|FOREGROUND_RED, # yellow + (34,): FOREGROUND_BLUE, # blue + (35,): FOREGROUND_BLUE|FOREGROUND_RED, # purple + (36,): FOREGROUND_BLUE|FOREGROUND_GREEN, # cyan + (37,): FOREGROUND_WHITE, # white + (39,): FOREGROUND_WHITE, # reset + } + attr = esctable.get(esc, FOREGROUND_WHITE) + if bold: + attr |= FOREGROUND_INTENSITY + STD_OUTPUT_HANDLE = -11 + STD_ERROR_HANDLE = -12 + if file is sys.stderr: + handle = GetStdHandle(STD_ERROR_HANDLE) + else: + handle = GetStdHandle(STD_OUTPUT_HANDLE) + SetConsoleTextAttribute(handle, attr) + file.write(text) + SetConsoleTextAttribute(handle, FOREGROUND_WHITE) + else: + file.write(text) + if flush: file.flush() @@ -60,8 +142,7 @@ file = WriteFile(file) self._file = file self.fullwidth = get_terminal_width() - self.hasmarkup = sys.platform != "win32" and \ - hasattr(file, 'isatty') and file.isatty() + self.hasmarkup = hasattr(file, 'isatty') and file.isatty() def _escaped(self, text, esc): if esc and self.hasmarkup: @@ -119,6 +200,81 @@ self._file.write('\n') self._file.flush() + +class Win32ConsoleWriter(object): + + def __init__(self, file=None, stringio=False): + if file is None: + if stringio: + self.stringio = file = py.std.cStringIO.StringIO() + else: + file = py.std.sys.stdout + elif callable(file): + file = WriteFile(file) + self._file = file + self.fullwidth = get_terminal_width() + self.hasmarkup = hasattr(file, 'isatty') and file.isatty() + + def sep(self, sepchar, title=None, fullwidth=None, **kw): + if fullwidth is None: + fullwidth = self.fullwidth + # On a Windows console, writing in the last column + # causes a line feed. + fullwidth -= 1 + # the goal is to have the line be as long as possible + # under the condition that len(line) <= fullwidth + if title is not None: + # we want 2 + 2*len(fill) + len(title) <= fullwidth + # i.e. 2 + 2*len(sepchar)*N + len(title) <= fullwidth + # 2*len(sepchar)*N <= fullwidth - len(title) - 2 + # N <= (fullwidth - len(title) - 2) // (2*len(sepchar)) + N = (fullwidth - len(title) - 2) // (2*len(sepchar)) + fill = sepchar * N + line = "%s %s %s" % (fill, title, fill) + else: + # we want len(sepchar)*N <= fullwidth + # i.e. N <= fullwidth // len(sepchar) + line = sepchar * (fullwidth // len(sepchar)) + # in some situations there is room for an extra sepchar at the right, + # in particular if we consider that with a sepchar like "_ " the + # trailing space is not important at the end of the line + if len(line) + len(sepchar.rstrip()) <= fullwidth: + line += sepchar.rstrip() + + self.line(line, **kw) + + def write(self, s, **kw): + if s: + s = str(s) + if self.hasmarkup: + handle = GetStdHandle(STD_OUTPUT_HANDLE) + + if self.hasmarkup and kw: + attr = 0 + if kw.pop('bold', False): + attr |= FOREGROUND_INTENSITY + + if kw.pop('red', False): + attr |= FOREGROUND_RED + elif kw.pop('blue', False): + attr |= FOREGROUND_BLUE + elif kw.pop('green', False): + attr |= FOREGROUND_GREEN + else: + attr |= FOREGROUND_WHITE + + SetConsoleTextAttribute(handle, attr) + self._file.write(s) + self._file.flush() + if self.hasmarkup: + SetConsoleTextAttribute(handle, FOREGROUND_WHITE) + + def line(self, s='', **kw): + self.write(s + '\n', **kw) + +if sys.platform == 'win32': + TerminalWriter = Win32ConsoleWriter + class WriteFile(object): def __init__(self, writemethod): self.write = writemethod Modified: py/trunk/py/io/testing/test_terminalwriter.py ============================================================================== --- py/trunk/py/io/testing/test_terminalwriter.py (original) +++ py/trunk/py/io/testing/test_terminalwriter.py Wed Mar 11 02:40:08 2009 @@ -1,7 +1,11 @@ import py -import os +import os, sys from py.__.io import terminalwriter +def skip_win32(): + if sys.platform == 'win32': + py.test.skip('Not relevant on win32') + def test_terminalwriter_computes_width(): py.magic.patch(terminalwriter, 'get_terminal_width', lambda: 42) try: @@ -36,6 +40,7 @@ tw.sep("-", fullwidth=60) l = self.getlines() assert len(l) == 1 + skip_win32() assert l[0] == "-" * 60 + "\n" def test_sep_with_title(self): @@ -43,14 +48,17 @@ tw.sep("-", "hello", fullwidth=60) l = self.getlines() assert len(l) == 1 + skip_win32() assert l[0] == "-" * 26 + " hello " + "-" * 27 + "\n" def test__escaped(self): + skip_win32() tw = self.getwriter() text2 = tw._escaped("hello", (31)) assert text2.find("hello") != -1 def test_markup(self): + skip_win32() tw = self.getwriter() for bold in (True, False): for color in ("red", "green"): @@ -65,6 +73,7 @@ tw.line("x", bold=True) tw.write("x\n", red=True) l = self.getlines() + skip_win32() assert len(l[0]) > 2, l assert len(l[1]) > 2, l Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Wed Mar 11 02:40:08 2009 @@ -40,13 +40,13 @@ self.currentfspath = fspath self._tw.write(res) - def write_ensure_prefix(self, prefix, extra=""): + def write_ensure_prefix(self, prefix, extra="", **kwargs): if self.currentfspath != prefix: self._tw.line() self.currentfspath = prefix self._tw.write(prefix) if extra: - self._tw.write(extra) + self._tw.write(extra, **kwargs) self.currentfspath = -2 def ensure_newline(self): @@ -77,13 +77,13 @@ def getoutcomeword(self, event): if event.passed: - return self._tw.markup("PASS", green=True) + return "PASS", dict(green=True) elif event.failed: - return self._tw.markup("FAIL", red=True) + return "FAIL", dict(red=True) elif event.skipped: return "SKIP" else: - return self._tw.markup("???", red=True) + return "???", dict(red=True) def pyevent_internalerror(self, event): for line in str(event.repr).split("\n"): @@ -139,13 +139,17 @@ def pyevent_itemtestreport(self, event): fspath = event.colitem.fspath cat, letter, word = self.getcategoryletterword(event) + if isinstance(word, tuple): + word, markup = word + else: + markup = {} self.stats.setdefault(cat, []).append(event) if not self.config.option.verbose: self.write_fspath_result(fspath, letter) else: info = event.colitem.repr_metainfo() line = info.verboseline(basedir=self.curdir) + " " - self.write_ensure_prefix(line, word) + self.write_ensure_prefix(line, word, **markup) def pyevent_collectionreport(self, event): if not event.passed: From afa at codespeak.net Wed Mar 11 13:55:45 2009 From: afa at codespeak.net (afa at codespeak.net) Date: Wed, 11 Mar 2009 13:55:45 +0100 (CET) Subject: [py-svn] r62857 - in py/trunk/py/io: . testing Message-ID: <20090311125545.C007C1684D6@codespeak.net> Author: afa Date: Wed Mar 11 13:55:44 2009 New Revision: 62857 Modified: py/trunk/py/io/terminalwriter.py py/trunk/py/io/testing/test_terminalwriter.py Log: Always substract the last column from the Win32 terminal console: otherwise the last char wraps and the \n causes an empty line. This allows more tests to pass. Modified: py/trunk/py/io/terminalwriter.py ============================================================================== --- py/trunk/py/io/terminalwriter.py (original) +++ py/trunk/py/io/terminalwriter.py Wed Mar 11 13:55:44 2009 @@ -63,7 +63,9 @@ info = CONSOLE_SCREEN_BUFFER_INFO() ctypes.windll.kernel32.GetConsoleScreenBufferInfo( handle, ctypes.byref(info)) - return info.dwSize.Y, info.dwSize.X + # Substract one from the width, otherwise the cursor wraps + # and the ending \n causes an empty line to display. + return info.dwSize.Y, info.dwSize.X - 1 def get_terminal_width(): try: @@ -218,9 +220,6 @@ def sep(self, sepchar, title=None, fullwidth=None, **kw): if fullwidth is None: fullwidth = self.fullwidth - # On a Windows console, writing in the last column - # causes a line feed. - fullwidth -= 1 # the goal is to have the line be as long as possible # under the condition that len(line) <= fullwidth if title is not None: Modified: py/trunk/py/io/testing/test_terminalwriter.py ============================================================================== --- py/trunk/py/io/testing/test_terminalwriter.py (original) +++ py/trunk/py/io/testing/test_terminalwriter.py Wed Mar 11 13:55:44 2009 @@ -40,7 +40,6 @@ tw.sep("-", fullwidth=60) l = self.getlines() assert len(l) == 1 - skip_win32() assert l[0] == "-" * 60 + "\n" def test_sep_with_title(self): @@ -48,7 +47,6 @@ tw.sep("-", "hello", fullwidth=60) l = self.getlines() assert len(l) == 1 - skip_win32() assert l[0] == "-" * 26 + " hello " + "-" * 27 + "\n" def test__escaped(self): From py-svn at codespeak.net Fri Mar 13 22:04:54 2009 From: py-svn at codespeak.net (Dr. Danaka Hurney) Date: Fri, 13 Mar 2009 22:04:54 +0100 (CET) Subject: [py-svn] Found your papers downstairs Message-ID: <20090313210454.9C48E1683FF@codespeak.net> An HTML attachment was scrubbed... URL: From py-svn at codespeak.net Fri Mar 13 22:12:56 2009 From: py-svn at codespeak.net (Dr. Muguette Kemppinen) Date: Fri, 13 Mar 2009 22:12:56 +0100 (CET) Subject: [py-svn] Note for everybody here Message-ID: <20090313211256.E8EAF1684CE@codespeak.net> An HTML attachment was scrubbed... URL: From py-svn at codespeak.net Sun Mar 15 08:02:52 2009 From: py-svn at codespeak.net (py-svn at codespeak.net) Date: Sun, 15 Mar 2009 08:02:52 +0100 (CET) Subject: [py-svn] Order discount Message-ID: <20090315070252.D979D168421@codespeak.net> An HTML attachment was scrubbed... URL: From py-svn at codespeak.net Sun Mar 15 13:32:05 2009 From: py-svn at codespeak.net (Eaeaag) Date: Sun, 15 Mar 2009 13:32:05 +0100 (CET) Subject: [py-svn] Can't call you right now Message-ID: <20090315123205.9BD06168065@codespeak.net> An HTML attachment was scrubbed... URL: From py-svn at codespeak.net Sun Mar 15 16:40:28 2009 From: py-svn at codespeak.net (Paqalyefoz) Date: Sun, 15 Mar 2009 16:40:28 +0100 (CET) Subject: [py-svn] The list of cancelled ones. Need other Message-ID: <20090315154028.E7A02168432@codespeak.net> An HTML attachment was scrubbed... URL: From py-svn at codespeak.net Sun Mar 15 16:51:01 2009 From: py-svn at codespeak.net (Afabq) Date: Sun, 15 Mar 2009 16:51:01 +0100 (CET) Subject: [py-svn] Let's change our meeting Message-ID: <20090315155101.5EB1316803A@codespeak.net> An HTML attachment was scrubbed... URL: From hpk at codespeak.net Mon Mar 16 13:42:22 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 16 Mar 2009 13:42:22 +0100 (CET) Subject: [py-svn] r62967 - py/trunk/py/code/testing Message-ID: <20090316124222.7585F1683F0@codespeak.net> Author: hpk Date: Mon Mar 16 13:42:20 2009 New Revision: 62967 Modified: py/trunk/py/code/testing/test_excinfo.py Log: fix mock object Modified: py/trunk/py/code/testing/test_excinfo.py ============================================================================== --- py/trunk/py/code/testing/test_excinfo.py (original) +++ py/trunk/py/code/testing/test_excinfo.py Mon Mar 16 13:42:20 2009 @@ -6,7 +6,7 @@ self.lines = [] def sep(self, sep, line=None): self.lines.append((sep, line)) - def line(self, line): + def line(self, line, **kw): self.lines.append(line) def markup(self, text, **kw): return text From guido at codespeak.net Mon Mar 16 16:16:10 2009 From: guido at codespeak.net (guido at codespeak.net) Date: Mon, 16 Mar 2009 16:16:10 +0100 (CET) Subject: [py-svn] r62971 - in py/trunk/py: . bin Message-ID: <20090316151610.9A121168415@codespeak.net> Author: guido Date: Mon Mar 16 16:16:08 2009 New Revision: 62971 Added: py/trunk/py/bin/gendoc.py (contents, props changed) Modified: py/trunk/py/conftest.py Log: Re-added 'gendoc.py' script to build the py lib documentation, now using the externalized apigen package. The package should be on the PYTHONPATH, if not the script warns. Added: py/trunk/py/bin/gendoc.py ============================================================================== --- (empty file) +++ py/trunk/py/bin/gendoc.py Mon Mar 16 16:16:08 2009 @@ -0,0 +1,22 @@ +#!/usr/bin/env python + +import sys +import os +from _findpy import py +try: + import apigen +except ImportError: + print 'Can not find apigen - make sure PYTHONPATH is set correctly!' + py.std.sys.exit() +else: + args = list(sys.argv[1:]) + argkeys = [a.split('=')[0] for a in args] + if '--apigen' not in argkeys: + args.append('--apigen') + if '--apigenscript' not in argkeys: + fpath = os.path.join( + os.path.dirname(apigen.__file__), 'tool', 'py_build', 'build.py') + args.append('--apigenscript=%s' % (fpath,)) + if '--apigenpath' not in argkeys: + args.append('--apigenpath=/tmp/pylib-api') + py.test.cmdline.main(args) Modified: py/trunk/py/conftest.py ============================================================================== --- py/trunk/py/conftest.py (original) +++ py/trunk/py/conftest.py Mon Mar 16 16:16:08 2009 @@ -1,6 +1,6 @@ dist_rsync_roots = ['.'] # XXX -pytest_plugins = 'pytest_doctest', 'pytest_pytester', 'pytest_restdoc' +pytest_plugins = 'pytest_doctest', 'pytest_pytester', 'pytest_restdoc', 'pytest_apigen' import py class PylibTestPlugin: From guido at codespeak.net Mon Mar 16 16:17:30 2009 From: guido at codespeak.net (guido at codespeak.net) Date: Mon, 16 Mar 2009 16:17:30 +0100 (CET) Subject: [py-svn] r62972 - py/trunk/py Message-ID: <20090316151730.64C03168415@codespeak.net> Author: guido Date: Mon Mar 16 16:17:29 2009 New Revision: 62972 Modified: py/trunk/py/conftest.py Log: Oops, accidentally checked in a change, now rolling back (added 'apigen' to enabled plugins, which fails if the apigen package is not on the PYTHONPATH). Modified: py/trunk/py/conftest.py ============================================================================== --- py/trunk/py/conftest.py (original) +++ py/trunk/py/conftest.py Mon Mar 16 16:17:29 2009 @@ -1,6 +1,6 @@ dist_rsync_roots = ['.'] # XXX -pytest_plugins = 'pytest_doctest', 'pytest_pytester', 'pytest_restdoc', 'pytest_apigen' +pytest_plugins = 'pytest_doctest', 'pytest_pytester', 'pytest_restdoc' import py class PylibTestPlugin: From hpk at codespeak.net Mon Mar 16 17:04:21 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 16 Mar 2009 17:04:21 +0100 (CET) Subject: [py-svn] r62974 - in py/trunk/py/test: . testing Message-ID: <20090316160421.B66F61683FA@codespeak.net> Author: hpk Date: Mon Mar 16 17:04:18 2009 New Revision: 62974 Modified: py/trunk/py/test/outcome.py py/trunk/py/test/testing/test_outcome.py Log: don't allow "_" in py.test.mark attributes Modified: py/trunk/py/test/outcome.py ============================================================================== --- py/trunk/py/test/outcome.py (original) +++ py/trunk/py/test/outcome.py Mon Mar 16 17:04:18 2009 @@ -161,6 +161,8 @@ return func def __getattr__(self, name): + if name[0] == "_": + raise AttributeError(name) kw = self._keywords.copy() kw[name] = True return self.__class__(kw, lastname=name) Modified: py/trunk/py/test/testing/test_outcome.py ============================================================================== --- py/trunk/py/test/testing/test_outcome.py (original) +++ py/trunk/py/test/testing/test_outcome.py Mon Mar 16 17:04:18 2009 @@ -76,8 +76,20 @@ print py.code.ExceptionInfo() py.test.fail("spurious skip") +def test_pytest_mark_getattr(): + from py.__.test.outcome import mark + def f(): pass + + mark.hello(f) + assert f.hello == True + + mark.hello("test")(f) + assert f.hello == "test" + + py.test.raises(AttributeError, "mark._hello") + py.test.raises(AttributeError, "mark.__str__") -def test_pytest_mark(): +def test_pytest_mark_call(): from py.__.test.outcome import mark def f(): pass mark(x=3)(f) From hpk at codespeak.net Mon Mar 16 17:53:54 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 16 Mar 2009 17:53:54 +0100 (CET) Subject: [py-svn] r62975 - in py/trunk/py/execnet: . testing Message-ID: <20090316165354.B713B168421@codespeak.net> Author: hpk Date: Mon Mar 16 17:53:52 2009 New Revision: 62975 Modified: py/trunk/py/execnet/gwmanage.py py/trunk/py/execnet/testing/test_gwmanage.py Log: - use "popen" instead of localhost if you want popen-gateways - have a default joinpath Modified: py/trunk/py/execnet/gwmanage.py ============================================================================== --- py/trunk/py/execnet/gwmanage.py (original) +++ py/trunk/py/execnet/gwmanage.py Mon Mar 16 17:53:52 2009 @@ -4,7 +4,7 @@ Host specification strings and implied gateways: socket:hostname:port:path SocketGateway - localhost[:path] PopenGateway + popen[-executable][:path] PopenGateway [ssh:]spec:path SshGateway * [SshGateway] @@ -23,9 +23,9 @@ class GatewaySpec(object): type = "ssh" - def __init__(self, spec): - if spec == "localhost" or spec.startswith("localhost:"): - self.address = "localhost" + def __init__(self, spec, defaultjoinpath="pyexecnetcache"): + if spec == "popen" or spec.startswith("popen:"): + self.address = "popen" self.joinpath = spec[len(self.address)+1:] self.type = "popen" elif spec.startswith("socket:"): @@ -42,6 +42,8 @@ parts = spec.split(":", 1) self.address = parts.pop(0) self.joinpath = parts and parts.pop(0) or "" + if not self.joinpath and not self.inplacelocal(): + self.joinpath = defaultjoinpath def inplacelocal(self): return bool(self.type == "popen" and not self.joinpath) Modified: py/trunk/py/execnet/testing/test_gwmanage.py ============================================================================== --- py/trunk/py/execnet/testing/test_gwmanage.py (original) +++ py/trunk/py/execnet/testing/test_gwmanage.py Mon Mar 16 17:53:52 2009 @@ -13,17 +13,17 @@ class TestGatewaySpec: """ socket:hostname:port:path SocketGateway - localhost[:path] PopenGateway + popen[-executable][:path] PopenGateway [ssh:]spec:path SshGateway * [SshGateway] """ - def test_localhost_nopath(self): + def test_popen_nopath(self): for joinpath in ('', ':abc', ':ab:cd', ':/x/y'): - spec = GatewaySpec("localhost" + joinpath) - assert spec.address == "localhost" + spec = GatewaySpec("popen" + joinpath) + assert spec.address == "popen" assert spec.joinpath == joinpath[1:] assert spec.type == "popen" - spec2 = GatewaySpec("localhost" + joinpath) + spec2 = GatewaySpec("popen" + joinpath) self._equality(spec, spec2) if joinpath == "": assert spec.inplacelocal() @@ -37,19 +37,25 @@ specstring = prefix + hostpart + joinpath spec = GatewaySpec(specstring) assert spec.address == hostpart - assert spec.joinpath == joinpath[1:] + if joinpath[1:]: + assert spec.joinpath == joinpath[1:] + else: + assert spec.joinpath == "pyexecnetcache" assert spec.type == "ssh" spec2 = GatewaySpec(specstring) self._equality(spec, spec2) assert not spec.inplacelocal() - + def test_socket(self): - for hostpart in ('x.y', 'x', 'localhost'): + for hostpart in ('x.y', 'x', 'popen'): for port in ":80", ":1000": for joinpath in ('', ':abc', ':abc:de'): spec = GatewaySpec("socket:" + hostpart + port + joinpath) assert spec.address == (hostpart, int(port[1:])) - assert spec.joinpath == joinpath[1:] + if joinpath[1:]: + assert spec.joinpath == joinpath[1:] + else: + assert spec.joinpath == "pyexecnetcache" assert spec.type == "socket" spec2 = GatewaySpec("socket:" + hostpart + port + joinpath) self._equality(spec, spec2) @@ -62,23 +68,23 @@ class TestGatewaySpecAPI: - def test_localhost_nopath_makegateway(self, testdir): - spec = GatewaySpec("localhost") + def test_popen_nopath_makegateway(self, testdir): + spec = GatewaySpec("popen") gw = spec.makegateway() p = gw.remote_exec("import os; channel.send(os.getcwd())").receive() curdir = py.std.os.getcwd() assert curdir == p gw.exit() - def test_localhost_makegateway(self, testdir): - spec = GatewaySpec("localhost:" + str(testdir.tmpdir)) + def test_popen_makegateway(self, testdir): + spec = GatewaySpec("popen:" + str(testdir.tmpdir)) gw = spec.makegateway() p = gw.remote_exec("import os; channel.send(os.getcwd())").receive() assert spec.joinpath == p gw.exit() - def test_localhost_makegateway_python(self, testdir): - spec = GatewaySpec("localhost") + def test_popen_makegateway_python(self, testdir): + spec = GatewaySpec("popen") gw = spec.makegateway(python=py.std.sys.executable) res = gw.remote_exec("import sys ; channel.send(sys.executable)").receive() assert py.std.sys.executable == res @@ -96,16 +102,16 @@ gw = py.execnet.PopenGateway() spec = GatewaySpec("ssh:" + sshhost) -class TestGatewayManagerLocalhost: - def test_hostmanager_localhosts_makegateway(self): - hm = GatewayManager(["localhost"] * 2) +class TestGatewayManagerPopen: + def test_hostmanager_popen_makegateway(self): + hm = GatewayManager(["popen"] * 2) hm.makegateways() assert len(hm.spec2gateway) == 2 hm.exit() assert not len(hm.spec2gateway) - def test_hostmanager_localhosts_rsync(self, source): - hm = GatewayManager(["localhost"] * 2) + def test_hostmanager_popens_rsync(self, source): + hm = GatewayManager(["popen"] * 2) hm.makegateways() assert len(hm.spec2gateway) == 2 for gw in hm.spec2gateway.values(): @@ -116,8 +122,8 @@ hm.exit() assert not len(hm.spec2gateway) - def test_hostmanager_rsync_localhost_with_path(self, source, dest): - hm = GatewayManager(["localhost:%s" %dest] * 1) + def test_hostmanager_rsync_popen_with_path(self, source, dest): + hm = GatewayManager(["popen:%s" %dest] * 1) hm.makegateways() source.ensure("dir1", "dir2", "hello") l = [] @@ -143,9 +149,9 @@ print events assert 0 - def test_multi_chdir_localhost_with_path(self, testdir): + def test_multi_chdir_popen_with_path(self, testdir): import os - hm = GatewayManager(["localhost:hello"] * 2) + hm = GatewayManager(["popen:hello"] * 2) testdir.tmpdir.chdir() hellopath = testdir.tmpdir.mkdir("hello") hm.makegateways() @@ -161,9 +167,9 @@ assert l[0].startswith(curwd) assert l[0].endswith("world") - def test_multi_chdir_localhost(self, testdir): + def test_multi_chdir_popen(self, testdir): import os - hm = GatewayManager(["localhost"] * 2) + hm = GatewayManager(["popen"] * 2) testdir.tmpdir.chdir() hellopath = testdir.tmpdir.mkdir("hello") hm.makegateways() @@ -222,7 +228,7 @@ assert 'somedir' in basenames def test_hrsync_one_host(self, source, dest): - spec = GatewaySpec("localhost:%s" % dest) + spec = GatewaySpec("popen:%s" % dest) gw = spec.makegateway() finished = [] rsync = HostRSync(source) From hpk at codespeak.net Mon Mar 16 22:15:54 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 16 Mar 2009 22:15:54 +0100 (CET) Subject: [py-svn] r62977 - py/trunk/py/test Message-ID: <20090316211554.3BD49168439@codespeak.net> Author: hpk Date: Mon Mar 16 22:15:52 2009 New Revision: 62977 Modified: py/trunk/py/test/parseopt.py Log: actually make use of option group description Modified: py/trunk/py/test/parseopt.py ============================================================================== --- py/trunk/py/test/parseopt.py (original) +++ py/trunk/py/test/parseopt.py Mon Mar 16 22:15:52 2009 @@ -52,7 +52,8 @@ optparser = optparse.OptionParser(usage=self._usage) for group in self._groups: if group.options: - optgroup = optparse.OptionGroup(optparser, group.name) + desc = group.description or group.name + optgroup = optparse.OptionGroup(optparser, desc) optgroup.add_options(group.options) optparser.add_option_group(optgroup) return optparser.parse_args([str(x) for x in args]) From hpk at codespeak.net Mon Mar 16 22:17:14 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 16 Mar 2009 22:17:14 +0100 (CET) Subject: [py-svn] r62978 - in py/trunk/py: execnet execnet/testing test test/dsession test/dsession/testing test/plugin test/testing Message-ID: <20090316211714.DCA40168439@codespeak.net> Author: hpk Date: Mon Mar 16 22:17:14 2009 New Revision: 62978 Modified: py/trunk/py/execnet/gwmanage.py py/trunk/py/execnet/testing/test_gwmanage.py py/trunk/py/test/config.py py/trunk/py/test/dsession/dsession.py py/trunk/py/test/dsession/hostmanage.py py/trunk/py/test/dsession/testing/test_functional_dsession.py py/trunk/py/test/dsession/testing/test_hostmanage.py py/trunk/py/test/dsession/testing/test_masterslave.py py/trunk/py/test/plugin/pytest_default.py py/trunk/py/test/plugin/pytest_plugintester.py py/trunk/py/test/plugin/pytest_pytester.py py/trunk/py/test/plugin/pytest_terminal.py py/trunk/py/test/session.py py/trunk/py/test/testing/acceptance_test.py py/trunk/py/test/testing/test_config.py py/trunk/py/test/testing/test_pytestplugin.py Log: * introduce --hosts and --rsyncdirs optiosn * re-sort option groups, disable some options for now * add docstrings to execnet gatewaymanage * streamline tests a bit * unify debugging and tracing Modified: py/trunk/py/execnet/gwmanage.py ============================================================================== --- py/trunk/py/execnet/gwmanage.py (original) +++ py/trunk/py/execnet/gwmanage.py Mon Mar 16 22:17:14 2009 @@ -22,7 +22,6 @@ class GatewaySpec(object): - type = "ssh" def __init__(self, spec, defaultjoinpath="pyexecnetcache"): if spec == "popen" or spec.startswith("popen:"): self.address = "popen" @@ -42,6 +41,7 @@ parts = spec.split(":", 1) self.address = parts.pop(0) self.joinpath = parts and parts.pop(0) or "" + self.type = "ssh" if not self.joinpath and not self.inplacelocal(): self.joinpath = defaultjoinpath @@ -60,8 +60,15 @@ elif self.type == "ssh": gw = py.execnet.SshGateway(self.address, remotepython=python) if self.joinpath: - channel = gw.remote_exec("import os ; os.chdir(channel.receive())") - channel.send(self.joinpath) + channel = gw.remote_exec(""" + import os + path = %r + try: + os.chdir(path) + except OSError: + os.mkdir(path) + os.chdir(path) + """ % self.joinpath) if waitclose: channel.waitclose() else: @@ -74,13 +81,16 @@ def __init__(self, channels): self._channels = channels - def receive(self): - values = [] + def receive_items(self): + items = [] for ch in self._channels: - values.append(ch.receive()) - return values + items.append((ch, ch.receive())) + return items + + def receive(self): + return [x[1] for x in self.receive_items()] - def wait(self): + def waitclose(self): for ch in self._channels: ch.waitclose() @@ -91,8 +101,7 @@ self.spec2gateway[GatewaySpec(spec)] = None def trace(self, msg): - py._com.pyplugins.notify("trace_gatewaymanage", msg) - #print "trace", msg + py._com.pyplugins.notify("trace", "gatewaymanage", msg) def makegateways(self): for spec, value in self.spec2gateway.items(): @@ -101,6 +110,9 @@ self.spec2gateway[spec] = spec.makegateway() def multi_exec(self, source, inplacelocal=True): + """ remote execute code on all gateways. + @param inplacelocal=False: don't send code to inplacelocal hosts. + """ source = py.code.Source(source) channels = [] for spec, gw in self.spec2gateway.items(): @@ -109,10 +121,15 @@ return MultiChannel(channels) def multi_chdir(self, basename, inplacelocal=True): + """ perform a remote chdir to the given path, may be relative. + @param inplacelocal=False: don't send code to inplacelocal hosts. + """ self.multi_exec("import os ; os.chdir(%r)" % basename, - inplacelocal=inplacelocal).wait() + inplacelocal=inplacelocal).waitclose() def rsync(self, source, notify=None, verbose=False, ignores=None): + """ perform rsync to all remote hosts. + """ rsync = HostRSync(source, verbose=verbose, ignores=ignores) added = False for spec, gateway in self.spec2gateway.items(): Modified: py/trunk/py/execnet/testing/test_gwmanage.py ============================================================================== --- py/trunk/py/execnet/testing/test_gwmanage.py (original) +++ py/trunk/py/execnet/testing/test_gwmanage.py Mon Mar 16 22:17:14 2009 @@ -155,7 +155,11 @@ testdir.tmpdir.chdir() hellopath = testdir.tmpdir.mkdir("hello") hm.makegateways() - l = hm.multi_exec("import os ; channel.send(os.getcwd())").receive() + l = [x[1] for x in hm.multi_exec( + "import os ; channel.send(os.getcwd())" + ).receive_items() + ] + paths = [x[1] for x in l] assert l == [str(hellopath)] * 2 py.test.raises(Exception, 'hm.multi_chdir("world", inplacelocal=False)') worldpath = hellopath.mkdir("world") @@ -188,22 +192,25 @@ from py.__.execnet.gwmanage import MultiChannel class TestMultiChannel: - def test_multichannel_receive(self): + def test_multichannel_receive_items(self): class pseudochannel: def receive(self): return 12 - multichannel = MultiChannel([pseudochannel(), pseudochannel()]) - l = multichannel.receive() + + pc1 = pseudochannel() + pc2 = pseudochannel() + multichannel = MultiChannel([pc1, pc2]) + l = multichannel.receive_items() assert len(l) == 2 - assert l == [12, 12] + assert l == [(pc1, 12), (pc2, 12)] - def test_multichannel_wait(self): + def test_multichannel_waitclose(self): l = [] class pseudochannel: def waitclose(self): l.append(0) multichannel = MultiChannel([pseudochannel(), pseudochannel()]) - multichannel.wait() + multichannel.waitclose() assert len(l) == 2 Modified: py/trunk/py/test/config.py ============================================================================== --- py/trunk/py/test/config.py (original) +++ py/trunk/py/test/config.py Mon Mar 16 22:17:14 2009 @@ -39,9 +39,17 @@ assert isinstance(pytestplugins, py.test._PytestPlugins) self.bus = pytestplugins.pyplugins self.pytestplugins = pytestplugins - self._conftest = Conftest(onimport=self.pytestplugins.consider_conftest) + self._conftest = Conftest(onimport=self._onimportconftest) self._setupstate = SetupState() + def _onimportconftest(self, conftestmodule): + self.trace("loaded conftestmodule %r" %(conftestmodule,)) + self.pytestplugins.consider_conftest(conftestmodule) + + def trace(self, msg): + if getattr(self.option, 'traceconfig', None): + self.bus.notify("trace", "config", msg) + def _processopt(self, opt): if hasattr(opt, 'default') and opt.dest: val = os.environ.get("PYTEST_OPTION_" + opt.dest.upper(), None) @@ -119,21 +127,18 @@ col = Dir(pkgpath, config=self) return col._getfsnode(path) - def getvalue_pathlist(self, name, path=None): + def getconftest_pathlist(self, name, path=None): """ return a matching value, which needs to be sequence of filenames that will be returned as a list of Path objects (they can be relative to the location where they were found). """ try: - return getattr(self.option, name) - except AttributeError: - try: - mod, relroots = self._conftest.rget_with_confmod(name, path) - except KeyError: - return None - modpath = py.path.local(mod.__file__).dirpath() - return [modpath.join(x, abs=True) for x in relroots] + mod, relroots = self._conftest.rget_with_confmod(name, path) + except KeyError: + return None + modpath = py.path.local(mod.__file__).dirpath() + return [modpath.join(x, abs=True) for x in relroots] def addoptions(self, groupname, *specs): """ add a named group of options to the current testing session. Modified: py/trunk/py/test/dsession/dsession.py ============================================================================== --- py/trunk/py/test/dsession/dsession.py (original) +++ py/trunk/py/test/dsession/dsession.py Mon Mar 16 22:17:14 2009 @@ -44,9 +44,9 @@ def fixoptions(self): """ check, fix and determine conflicting options. """ option = self.config.option - if option.runbrowser and not option.startserver: - #print "--runbrowser implies --startserver" - option.startserver = True + #if option.runbrowser and not option.startserver: + # #print "--runbrowser implies --startserver" + # option.startserver = True if self.config.getvalue("dist_boxed") and option.dist: option.boxed = True # conflicting options Modified: py/trunk/py/test/dsession/hostmanage.py ============================================================================== --- py/trunk/py/test/dsession/hostmanage.py (original) +++ py/trunk/py/test/dsession/hostmanage.py Mon Mar 16 22:17:14 2009 @@ -8,19 +8,34 @@ if config.option.numprocesses: hosts = ['localhost'] * config.option.numprocesses else: - hosts = config.getvalue("dist_hosts") - assert hosts is not None + hosts = config.option.hosts + if not hosts: + hosts = config.getvalue("hosts") + else: + hosts = hosts.split(",") + assert hosts is not None return hosts + +def getconfigroots(config): + roots = config.option.rsyncdirs + if roots: + roots = [py.path.local(x) for x in roots.split(',')] + else: + roots = [] + conftestroots = config.getconftest_pathlist("rsyncdirs") + if conftestroots: + roots.extend(conftestroots) + for root in roots: + if not root.check(): + raise ValueError("rsyncdir doesn't exist: %r" %(root,)) + return roots class HostManager(object): def __init__(self, config, hosts=None): self.config = config - roots = self.config.getvalue_pathlist("rsyncroots") - if not roots: - roots = self.config.getvalue_pathlist("dist_rsync_roots") - self.roots = roots if hosts is None: hosts = getconfighosts(self.config) + self.roots = getconfigroots(config) self.gwmanager = GatewayManager(hosts) def makegateways(self): @@ -29,6 +44,19 @@ self.gwmanager.makegateways() finally: old.chdir() + self.trace_hoststatus() + + def trace_hoststatus(self): + if self.config.option.debug: + for ch, result in self.gwmanager.multi_exec(""" + import sys, os + channel.send((sys.executable, os.getcwd(), sys.path)) + """).receive_items(): + self.trace("spec %r, execuable %r, cwd %r, syspath %r" %( + ch.gateway.spec, result[0], result[1], result[2])) + + def config_getignores(self): + return self.config.getconftest_pathlist("rsyncignore") def rsync_roots(self): """ make sure that all remote gateways @@ -42,20 +70,23 @@ # (for other gateways this chdir is irrelevant) self.makegateways() options = { - 'ignores': self.config.getvalue_pathlist("dist_rsync_ignore"), - 'verbose': self.config.option.verbose + 'ignores': self.config_getignores(), + 'verbose': 1, # self.config.option.verbose } if self.roots: # send each rsync root for root in self.roots: self.gwmanager.rsync(root, **options) else: - # we transfer our topdir as the root - # but need to be careful regarding + # we transfer our topdir as the root self.gwmanager.rsync(self.config.topdir, **options) + # and cd into it self.gwmanager.multi_chdir(self.config.topdir.basename, inplacelocal=False) self.config.bus.notify("rsyncfinished", event.RsyncFinished()) + def trace(self, msg): + self.config.bus.notify("trace", "testhostmanage", msg) + def setup_hosts(self, putevent): self.rsync_roots() nice = self.config.getvalue("dist_nicelevel") @@ -64,8 +95,10 @@ import os if hasattr(os, 'nice'): os.nice(%r) - """ % nice).wait() - + """ % nice).waitclose() + + self.trace_hoststatus() + for host, gateway in self.gwmanager.spec2gateway.items(): host.node = MasterNode(host, gateway, Modified: py/trunk/py/test/dsession/testing/test_functional_dsession.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_functional_dsession.py (original) +++ py/trunk/py/test/dsession/testing/test_functional_dsession.py Mon Mar 16 22:17:14 2009 @@ -39,7 +39,6 @@ ]) def test_dist_some_tests(self, testdir): - testdir.makepyfile(conftest="dist_hosts=['localhost']\n") p1 = testdir.makepyfile(test_one=""" def test_1(): pass @@ -49,7 +48,7 @@ def test_fail(): assert 0 """) - config = testdir.parseconfig('-d', p1) + config = testdir.parseconfig('-d', p1, '--hosts=popen') dsession = DSession(config) eq = EventQueue(config.bus) dsession.main([config.getfsnode(p1)]) @@ -61,7 +60,7 @@ assert ev.failed # see that the host is really down ev, = eq.geteventargs("hostdown") - assert ev.host.address == "localhost" + assert ev.host.address == "popen" ev, = eq.geteventargs("testrunfinish") def test_distribution_rsync_roots_example(self, testdir): @@ -70,8 +69,8 @@ subdir = "sub_example_dist" sourcedir = self.tmpdir.mkdir("source") sourcedir.ensure(subdir, "conftest.py").write(py.code.Source(""" - dist_hosts = ["localhost:%s"] - dist_rsync_roots = ["%s", "../py"] + hosts = ["popen:%s"] + rsyncdirs = ["%s", "../py"] """ % (destdir, tmpdir.join(subdir), ))) tmpdir.ensure(subdir, "__init__.py") tmpdir.ensure(subdir, "test_one.py").write(py.code.Source(""" @@ -102,7 +101,6 @@ if not hasattr(os, 'nice'): py.test.skip("no os.nice() available") testdir.makepyfile(conftest=""" - dist_hosts=['localhost'] dist_nicelevel = 10 """) p1 = testdir.makepyfile(""" @@ -110,7 +108,7 @@ import os assert os.nice(0) == 10 """) - evrec = testdir.inline_run('-d', p1) + evrec = testdir.inline_run('-d', p1, '--hosts=popen') ev = evrec.getreport('test_nice') assert ev.passed Modified: py/trunk/py/test/dsession/testing/test_hostmanage.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_hostmanage.py (original) +++ py/trunk/py/test/dsession/testing/test_hostmanage.py Mon Mar 16 22:17:14 2009 @@ -3,7 +3,7 @@ """ import py -from py.__.test.dsession.hostmanage import HostManager, getconfighosts +from py.__.test.dsession.hostmanage import HostManager, getconfighosts, getconfigroots from py.__.execnet.gwmanage import GatewaySpec as Host from py.__.test import event @@ -15,12 +15,14 @@ return dest class TestHostManager: - def gethostmanager(self, source, dist_hosts, dist_rsync_roots=None): - l = ["dist_hosts = %r" % dist_hosts] - if dist_rsync_roots: - l.append("dist_rsync_roots = %r" % dist_rsync_roots) - source.join("conftest.py").write("\n".join(l)) - config = py.test.config._reparse([source]) + def gethostmanager(self, source, hosts, rsyncdirs=None): + def opt(optname, l): + return '%s=%s' % (optname, ",".join(map(str, l))) + args = [opt('--hosts', hosts)] + if rsyncdirs: + args.append(opt('--rsyncdir', [source.join(x, abs=True) for x in rsyncdirs])) + args.append(source) + config = py.test.config._reparse(args) assert config.topdir == source hm = HostManager(config) assert hm.gwmanager.spec2gateway @@ -34,7 +36,7 @@ def test_hostmanager_rsync_roots_no_roots(self, source, dest): source.ensure("dir1", "file1").write("hello") config = py.test.config._reparse([source]) - hm = HostManager(config, hosts=["localhost:%s" % dest]) + hm = HostManager(config, hosts=["popen:%s" % dest]) assert hm.config.topdir == source == config.topdir hm.rsync_roots() p, = hm.gwmanager.multi_exec("import os ; channel.send(os.getcwd())").receive() @@ -48,8 +50,8 @@ dir2 = source.ensure("dir1", "dir2", dir=1) dir2.ensure("hello") hm = self.gethostmanager(source, - dist_hosts = ["localhost:%s" % dest], - dist_rsync_roots = ['dir1'] + hosts = ["popen:%s" % dest], + rsyncdirs = ['dir1'] ) assert hm.config.topdir == source hm.rsync_roots() @@ -61,8 +63,8 @@ dir2 = source.ensure("dir1", "dir2", dir=1) dir2.ensure("hello") hm = self.gethostmanager(source, - dist_hosts = ["localhost:%s" % dest], - dist_rsync_roots = [str(source)] + hosts = ["popen:%s" % dest], + rsyncdirs = [str(source)] ) assert hm.config.topdir == source hm.rsync_roots() @@ -77,37 +79,37 @@ dir2.ensure("hello") source.ensure("bogusdir", "file") source.join("conftest.py").write(py.code.Source(""" - dist_rsync_roots = ['dir1/dir2'] + rsyncdirs = ['dir1/dir2'] """)) session = py.test.config._reparse([source]).initsession() hm = HostManager(session.config, - hosts=["localhost:" + str(dest)]) + hosts=["popen:" + str(dest)]) hm.rsync_roots() assert dest.join("dir2").check() assert not dest.join("dir1").check() assert not dest.join("bogus").check() - def test_hostmanager_rsync_ignore(self, source, dest): + def test_hostmanager_rsyncignore(self, source, dest): dir2 = source.ensure("dir1", "dir2", dir=1) dir5 = source.ensure("dir5", "dir6", "bogus") dirf = source.ensure("dir5", "file") dir2.ensure("hello") source.join("conftest.py").write(py.code.Source(""" - dist_rsync_ignore = ['dir1/dir2', 'dir5/dir6'] - dist_rsync_roots = ['dir1', 'dir5'] + rsyncdirs = ['dir1', 'dir5'] + rsyncignore = ['dir1/dir2', 'dir5/dir6'] """)) session = py.test.config._reparse([source]).initsession() hm = HostManager(session.config, - hosts=["localhost:" + str(dest)]) + hosts=["popen:" + str(dest)]) hm.rsync_roots() assert dest.join("dir1").check() assert not dest.join("dir1", "dir2").check() assert dest.join("dir5","file").check() assert not dest.join("dir6").check() - def test_hostmanage_optimise_localhost(self, source, dest): - hosts = ["localhost"] * 3 - source.join("conftest.py").write("dist_rsync_roots = ['a']") + def test_hostmanage_optimise_popen(self, source, dest): + hosts = ["popen"] * 3 + source.join("conftest.py").write("rsyncdirs = ['a']") source.ensure('a', dir=1) config = py.test.config._reparse([source]) hm = HostManager(config, hosts=hosts) @@ -116,31 +118,35 @@ assert gwspec.inplacelocal() assert not gwspec.joinpath - def test_hostmanage_setup_hosts(self, source): - hosts = ["localhost"] * 3 - source.join("conftest.py").write("dist_rsync_roots = ['a']") + def test_hostmanage_setup_hosts_DEBUG(self, source, EventRecorder): + hosts = ["popen"] * 2 + source.join("conftest.py").write("rsyncdirs = ['a']") source.ensure('a', dir=1) - config = py.test.config._reparse([source]) + config = py.test.config._reparse([source, '--debug']) + assert config.option.debug hm = HostManager(config, hosts=hosts) - queue = py.std.Queue.Queue() - hm.setup_hosts(putevent=queue.put) - for host in hm.gwmanager.spec2gateway: - eventcall = queue.get(timeout=2.0) - name, args, kwargs = eventcall - assert name == "hostup" + evrec = EventRecorder(config.bus, debug=True) + hm.setup_hosts(putevent=[].append) for host in hm.gwmanager.spec2gateway: - host.node.shutdown() - for host in hm.gwmanager.spec2gateway: - eventcall = queue.get(timeout=2.0) - name, args, kwargs = eventcall - print name, args, kwargs - assert name == "hostdown" + l = evrec.getnamed("trace") + print evrec.events + assert l + hm.teardown_hosts() + + def test_hostmanage_simple_ssh_test(self, testdir): + rp = testdir.mkdir('xyz123') + rp.ensure("__init__.py") + p = testdir.makepyfile("def test_123(): import xyz123") + result = testdir.runpytest(p, '-d', "--hosts=popen", '--rsyncdirs=' + str(rp)) + assert result.ret == 0 + assert result.stdout.str().find("1 passed") != -1 - def XXXtest_ssh_rsync_samehost_twice(self): - #XXX we have no easy way to have a temp directory remotely! + @py.test.mark.xfail("implement double-rsync test") + def test_ssh_rsync_samehost_twice(self): option = py.test.config.option if option.sshhost is None: py.test.skip("no known ssh target, use -S to set one") + host1 = Host("%s" % (option.sshhost, )) host2 = Host("%s" % (option.sshhost, )) hm = HostManager(config, hosts=[host1, host2]) @@ -150,8 +156,35 @@ assert 0 -def test_getconfighosts(): +def test_getconfighosts_numprocesses(): config = py.test.config._reparse(['-n3']) hosts = getconfighosts(config) assert len(hosts) == 3 +def test_getconfighosts_disthosts(): + config = py.test.config._reparse(['--hosts=a,b,c']) + hosts = getconfighosts(config) + assert len(hosts) == 3 + assert hosts == ['a', 'b', 'c'] + +def test_getconfigroots(testdir): + config = testdir.parseconfig('--rsyncdirs=' + str(testdir.tmpdir)) + roots = getconfigroots(config) + assert len(roots) == 1 + assert roots == [testdir.tmpdir] + +def test_getconfigroots_with_conftest(testdir): + testdir.chdir() + p = py.path.local() + for bn in 'x y z'.split(): + p.mkdir(bn) + testdir.makeconftest(""" + rsyncdirs= 'x', + """) + config = testdir.parseconfig(testdir.tmpdir, '--rsyncdirs=y,z') + roots = getconfigroots(config) + assert len(roots) == 3 + assert py.path.local('y') in roots + assert py.path.local('z') in roots + assert testdir.tmpdir.join('x') in roots + Modified: py/trunk/py/test/dsession/testing/test_masterslave.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_masterslave.py (original) +++ py/trunk/py/test/dsession/testing/test_masterslave.py Mon Mar 16 22:17:14 2009 @@ -43,7 +43,7 @@ config = py.test.config._reparse([]) self.config = config self.queue = py.std.Queue.Queue() - self.host = GatewaySpec("localhost") + self.host = GatewaySpec("popen") self.gateway = self.host.makegateway() self.node = MasterNode(self.host, self.gateway, self.config, putevent=self.queue.put) assert not self.node.channel.isclosed() Modified: py/trunk/py/test/plugin/pytest_default.py ============================================================================== --- py/trunk/py/test/plugin/pytest_default.py (original) +++ py/trunk/py/test/plugin/pytest_default.py Mon Mar 16 22:17:14 2009 @@ -36,9 +36,6 @@ group._addoption('-x', '--exitfirst', action="store_true", dest="exitfirst", default=False, help="exit instantly on first error or failed test."), - group._addoption('-s', '--nocapture', - action="store_true", dest="nocapture", default=False, - help="disable catching of sys.stdout/stderr output."), group._addoption('-k', action="store", dest="keyword", default='', help="only run test items matching the given " @@ -47,57 +44,70 @@ "to run all subsequent tests. ") group._addoption('-l', '--showlocals', action="store_true", dest="showlocals", default=False, - help="show locals in tracebacks (disabled by default)."), + help="show locals in tracebacks (disabled by default).") group._addoption('--showskipsummary', action="store_true", dest="showskipsummary", default=False, - help="always show summary of skipped tests"), - group._addoption('', '--pdb', + help="always show summary of skipped tests") + group._addoption('--pdb', action="store_true", dest="usepdb", default=False, - help="start pdb (the Python debugger) on errors."), - group._addoption('', '--tb', + help="start pdb (the Python debugger) on errors.") + group._addoption('--tb', action="store", dest="tbstyle", default='long', type="choice", choices=['long', 'short', 'no'], - help="traceback verboseness (long/short/no)."), - group._addoption('', '--fulltrace', + help="traceback verboseness (long/short/no).") + group._addoption('--fulltrace', action="store_true", dest="fulltrace", default=False, - help="don't cut any tracebacks (default is to cut)."), - group._addoption('', '--nomagic', - action="store_true", dest="nomagic", default=False, - help="refrain from using magic as much as possible."), - group._addoption('', '--traceconfig', - action="store_true", dest="traceconfig", default=False, - help="trace considerations of conftest.py files."), + help="don't cut any tracebacks (default is to cut).") + group._addoption('-s', '--nocapture', + action="store_true", dest="nocapture", default=False, + help="disable catching of sys.stdout/stderr output."), + group._addoption('--boxed', + action="store_true", dest="boxed", default=False, + help="box each test run in a separate process"), group._addoption('-f', '--looponfailing', action="store_true", dest="looponfailing", default=False, - help="loop on failing test set."), - group._addoption('', '--exec', - action="store", dest="executable", default=None, - help="python executable to run the tests with."), - group._addoption('-n', '--numprocesses', dest="numprocesses", default=0, - action="store", type="int", - help="number of local test processes."), - group._addoption('', '--debug', + help="loop on failing test set.") + + group = parser.addgroup("test process debugging") + group.addoption('--collectonly', + action="store_true", dest="collectonly", + help="only collect tests, don't execute them."), + group.addoption('--traceconfig', + action="store_true", dest="traceconfig", default=False, + help="trace considerations of conftest.py files."), + group._addoption('--nomagic', + action="store_true", dest="nomagic", default=False, + help="don't use assert reinterpretation and python traceback cutting. ") + group.addoption('--debug', action="store_true", dest="debug", default=False, - help="turn on debugging information."), + help="generate and show debugging information.") - group = parser.addgroup("experimental", "experimental options") + group = parser.addgroup("xplatform", "distributed/cross platform testing") group._addoption('-d', '--dist', action="store_true", dest="dist", default=False, - help="ad-hoc distribute tests across machines (requires conftest settings)"), - group._addoption('-w', '--startserver', - action="store_true", dest="startserver", default=False, - help="starts local web server for displaying test progress.", - ), - group._addoption('-r', '--runbrowser', - action="store_true", dest="runbrowser", default=False, - help="run browser (implies --startserver)." - ), - group._addoption('--boxed', - action="store_true", dest="boxed", default=False, - help="box each test run in a separate process"), - group._addoption('--rest', - action='store_true', dest="restreport", default=False, - help="restructured text output reporting."), + help="ad-hoc distribute tests across machines (requires conftest settings)") + group._addoption('-n', '--numprocesses', dest="numprocesses", default=0, metavar="num", + action="store", type="int", + help="number of local test processes. conflicts with --dist.") + group.addoption('--rsyncdirs', dest="rsyncdirs", default=None, metavar="dir1,dir2,...", + help="comma-separated list of directories to rsync. All those roots will be rsynced " + "into a corresponding subdir on the remote sides. ") + group.addoption('--hosts', dest="hosts", default=None, metavar="host1,host2,...", + help="comma-separated list of host specs to send tests to.") + group._addoption('--exec', + action="store", dest="executable", default=None, + help="python executable to run the tests with.") + #group._addoption('-w', '--startserver', + # action="store_true", dest="startserver", default=False, + # help="starts local web server for displaying test progress.", + # ), + #group._addoption('-r', '--runbrowser', + # action="store_true", dest="runbrowser", default=False, + # help="run browser (implies --startserver)." + # ), + #group._addoption('--rest', + # action='store_true', dest="restreport", default=False, + # help="restructured text output reporting."), def pytest_configure(self, config): self.setsession(config) Modified: py/trunk/py/test/plugin/pytest_plugintester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_plugintester.py (original) +++ py/trunk/py/test/plugin/pytest_plugintester.py Mon Mar 16 22:17:14 2009 @@ -155,6 +155,9 @@ def pyevent(self, eventname, *args, **kwargs): """ called for each testing event. """ + def pyevent_trace(self, category, msg): + """ called for tracing events events. """ + def pyevent_internalerror(self, event): """ called for internal errors. """ Modified: py/trunk/py/test/plugin/pytest_pytester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_pytester.py (original) +++ py/trunk/py/test/plugin/pytest_pytester.py Mon Mar 16 22:17:14 2009 @@ -248,10 +248,8 @@ if name == "plugin_registered" and args == (self,): return if self.debug: - print "[event] %s: %s **%s" %(name, ", ".join(map(str, args)), kwargs,) - if len(args) == 1: - event, = args - self.events.append((name, event)) + print "[event: %s]: %s **%s" %(name, ", ".join(map(str, args)), kwargs,) + self.events.append((name,) + tuple(args)) def get(self, cls): l = [] Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Mon Mar 16 22:17:14 2009 @@ -3,11 +3,6 @@ class TerminalPlugin(object): """ Report a test run to a terminal. """ - def pytest_addoption(self, parser): - parser.addoption('--collectonly', - action="store_true", dest="collectonly", - help="only collect tests, don't execute them."), - def pytest_configure(self, config): if config.option.collectonly: self.reporter = CollectonlyReporter(config) @@ -115,6 +110,11 @@ if error: self.write_line("HostDown %s: %s" %(host, error)) + def pyevent_trace(self, category, msg): + if self.config.option.debug or \ + self.config.option.traceconfig and category.find("config") != -1: + self.write_line("[%s] %s" %(category, msg)) + def pyevent_itemstart(self, event): if self.config.option.verbose: info = event.item.repr_metainfo() @@ -167,14 +167,15 @@ rev = py.__pkg__.getrev() self.write_line("using py lib: %s " % ( py.path.local(py.__file__).dirpath(), rev)) - plugins = [] - for x in self.config.pytestplugins._plugins: - if isinstance(x, str) and x.startswith("pytest_"): - plugins.append(x[7:]) - else: - plugins.append(str(x)) # XXX display conftest plugins more nicely - plugins = ", ".join(plugins) - self.write_line("active plugins: %s" %(plugins,)) + if self.config.option.traceconfig: + plugins = [] + for x in self.config.pytestplugins._plugins: + if isinstance(x, str) and x.startswith("pytest_"): + plugins.append(x[7:]) + else: + plugins.append(str(x)) # XXX display conftest plugins more nicely + plugins = ", ".join(plugins) + self.write_line("active plugins: %s" %(plugins,)) for i, testarg in py.builtin.enumerate(self.config.args): self.write_line("test object %d: %s" %(i+1, testarg)) Modified: py/trunk/py/test/session.py ============================================================================== --- py/trunk/py/test/session.py (original) +++ py/trunk/py/test/session.py Mon Mar 16 22:17:14 2009 @@ -29,9 +29,9 @@ def fixoptions(self): """ check, fix and determine conflicting options. """ option = self.config.option - if option.runbrowser and not option.startserver: - #print "--runbrowser implies --startserver" - option.startserver = True + #if option.runbrowser and not option.startserver: + # #print "--runbrowser implies --startserver" + # option.startserver = True # conflicting options if option.looponfailing and option.usepdb: raise ValueError, "--looponfailing together with --pdb not supported." Modified: py/trunk/py/test/testing/acceptance_test.py ============================================================================== --- py/trunk/py/test/testing/acceptance_test.py (original) +++ py/trunk/py/test/testing/acceptance_test.py Mon Mar 16 22:17:14 2009 @@ -241,13 +241,11 @@ def test_skip(): py.test.skip("hello") """, - conftest=""" - dist_hosts = ['localhost'] * 3 - """ ) - result = testdir.runpytest(p1, '-d') + #result = testdir.runpytest(p1, '-d') + result = testdir.runpytest(p1, '-d', '--hosts=popen,popen,popen') result.stdout.fnmatch_lines([ - "HOSTUP: localhost*Python*", + "HOSTUP: popen*Python*", #"HOSTUP: localhost*Python*", #"HOSTUP: localhost*Python*", "*2 failed, 1 passed, 1 skipped*", @@ -273,17 +271,14 @@ import os time.sleep(0.5) os.kill(os.getpid(), 15) - """, - conftest=""" - dist_hosts = ['localhost'] * 3 """ ) - result = testdir.runpytest(p1, '-d') + result = testdir.runpytest(p1, '-d', '--hosts=popen,popen,popen') result.stdout.fnmatch_lines([ - "*localhost*Python*", - "*localhost*Python*", - "*localhost*Python*", - "HostDown*localhost*TERMINATED*", + "*popen*Python*", + "*popen*Python*", + "*popen*Python*", + "HostDown*TERMINATED*", "*3 failed, 1 passed, 1 skipped*" ]) assert result.ret == 1 Modified: py/trunk/py/test/testing/test_config.py ============================================================================== --- py/trunk/py/test/testing/test_config.py (original) +++ py/trunk/py/test/testing/test_config.py Mon Mar 16 22:17:14 2009 @@ -1,5 +1,6 @@ import py + class TestConfigCmdlineParsing: def test_config_cmdline_options(self, testdir): testdir.makepyfile(conftest=""" @@ -118,22 +119,18 @@ config = py.test.config._reparse([str(o)]) assert config.getvalue('x') == 1 - def test_getvalue_pathlist(self, tmpdir): + def test_getconftest_pathlist(self, tmpdir): somepath = tmpdir.join("x", "y", "z") p = tmpdir.join("conftest.py") p.write("pathlist = ['.', %r]" % str(somepath)) config = py.test.config._reparse([p]) - assert config.getvalue_pathlist('notexist') is None - pl = config.getvalue_pathlist('pathlist') + assert config.getconftest_pathlist('notexist') is None + pl = config.getconftest_pathlist('pathlist') print pl assert len(pl) == 2 assert pl[0] == tmpdir assert pl[1] == somepath - config.option.mypathlist = [py.path.local()] - pl = config.getvalue_pathlist('mypathlist') - assert pl == [py.path.local()] - def test_setsessionclass_and_initsession(self, testdir): from py.__.test.config import Config config = Config() Modified: py/trunk/py/test/testing/test_pytestplugin.py ============================================================================== --- py/trunk/py/test/testing/test_pytestplugin.py (original) +++ py/trunk/py/test/testing/test_pytestplugin.py Mon Mar 16 22:17:14 2009 @@ -100,7 +100,7 @@ def test_config_sets_conftesthandle_onimport(self, testdir): config = testdir.parseconfig([]) - assert config._conftest._onimport == config.pytestplugins.consider_conftest + assert config._conftest._onimport == config._onimportconftest def test_consider_conftest_deps(self, testdir): mod = testdir.makepyfile("pytest_plugins='xyz'").pyimport() From hpk at codespeak.net Tue Mar 17 07:10:40 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 17 Mar 2009 07:10:40 +0100 (CET) Subject: [py-svn] r62981 - in py/trunk/py/test: . dsession dsession/testing plugin testing Message-ID: <20090317061040.E0F6316802B@codespeak.net> Author: hpk Date: Tue Mar 17 07:10:40 2009 New Revision: 62981 Modified: py/trunk/py/test/config.py py/trunk/py/test/dsession/dsession.py py/trunk/py/test/dsession/hostmanage.py py/trunk/py/test/dsession/testing/test_dsession.py py/trunk/py/test/plugin/pytest_pytester.py py/trunk/py/test/testing/acceptance_test.py py/trunk/py/test/testing/test_config.py Log: allowing conftest to set default values for options Modified: py/trunk/py/test/config.py ============================================================================== --- py/trunk/py/test/config.py (original) +++ py/trunk/py/test/config.py Tue Mar 17 07:10:40 2009 @@ -63,6 +63,12 @@ elif not opt.type and opt.action in ("store_true", "store_false"): val = eval(val) opt.default = val + else: + name = "pytest_option_" + opt.dest + try: + opt.default = self._conftest.rget(name) + except (ValueError, KeyError): + pass if not hasattr(self.option, opt.dest): setattr(self.option, opt.dest, opt.default) Modified: py/trunk/py/test/dsession/dsession.py ============================================================================== --- py/trunk/py/test/dsession/dsession.py (original) +++ py/trunk/py/test/dsession/dsession.py Tue Mar 17 07:10:40 2009 @@ -60,16 +60,12 @@ if config.option.numprocesses: return try: - config.getvalue('dist_hosts') + config.getvalue('hosts') except KeyError: - print "Don't know where to distribute tests to. You may want" - print "to specify either a number of local processes to start" - print "with '--numprocesses=NUM' or specify 'dist_hosts' in a local" - print "conftest.py file, for example:" - print - print " dist_hosts = ['localhost'] * 4 # for 3 processors" - print " dist_hosts = ['you at remote.com', '...'] # for testing on ssh accounts" - print " # with your remote ssh accounts" + print "Please specify hosts for distribution of tests:" + print "cmdline: --hosts=host1,host2,..." + print "conftest.py: pytest_option_hosts=['host1','host2',]" + print "environment: PYTEST_OPTION_HOSTS=host1,host2,host3" print print "see also: http://codespeak.net/py/current/doc/test.html#automated-distributed-testing" raise SystemExit Modified: py/trunk/py/test/dsession/hostmanage.py ============================================================================== --- py/trunk/py/test/dsession/hostmanage.py (original) +++ py/trunk/py/test/dsession/hostmanage.py Tue Mar 17 07:10:40 2009 @@ -6,7 +6,7 @@ def getconfighosts(config): if config.option.numprocesses: - hosts = ['localhost'] * config.option.numprocesses + hosts = ['popen'] * config.option.numprocesses else: hosts = config.option.hosts if not hosts: Modified: py/trunk/py/test/dsession/testing/test_dsession.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_dsession.py (original) +++ py/trunk/py/test/dsession/testing/test_dsession.py Tue Mar 17 07:10:40 2009 @@ -300,17 +300,17 @@ remaining = session.filteritems(items) assert remaining == [] - evname, ev = evrec.events[-1] - assert evname == "deselected" - assert ev.items == items + event = evrec.events[-1] + assert event.name == "deselected" + assert event.args[0].items == items modcol._config.option.keyword = "test_fail" remaining = session.filteritems(items) assert remaining == [items[0]] - evname, ev = evrec.events[-1] - assert evname == "deselected" - assert ev.items == [items[1]] + event = evrec.events[-1] + assert event.name == "deselected" + assert event.args[0].items == [items[1]] def test_hostdown_shutdown_after_completion(self, testdir): item = testdir.getitem("def test_func(): pass") Modified: py/trunk/py/test/plugin/pytest_pytester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_pytester.py (original) +++ py/trunk/py/test/plugin/pytest_pytester.py Tue Mar 17 07:10:40 2009 @@ -236,6 +236,11 @@ def runpytest(self, *args): return self.runpybin("py.test", *args) +class Event: + def __init__(self, name, args, kwargs): + self.name = name + self.args = args + self.kwargs = kwargs class EventRecorder(object): def __init__(self, pyplugins, debug=False): # True): @@ -249,26 +254,27 @@ return if self.debug: print "[event: %s]: %s **%s" %(name, ", ".join(map(str, args)), kwargs,) - self.events.append((name,) + tuple(args)) + self.events.append(Event(name, args, kwargs)) def get(self, cls): l = [] - for name, value in self.events: + for event in self.events: + value = event.args[0] if isinstance(value, cls): l.append(value) return l def getnamed(self, *names): l = [] - for evname, event in self.events: - if evname in names: - l.append(event) + for event in self.events: + if event.name in names: + l.append(event.args[0]) return l def getfirstnamed(self, name): - for evname, event in self.events: - if evname == name: - return event + for event in self.events: + if event.name == name: + return event.args[0] def getfailures(self, names='itemtestreport collectionreport'): l = [] Modified: py/trunk/py/test/testing/acceptance_test.py ============================================================================== --- py/trunk/py/test/testing/acceptance_test.py (original) +++ py/trunk/py/test/testing/acceptance_test.py Tue Mar 17 07:10:40 2009 @@ -252,6 +252,31 @@ ]) assert result.ret == 1 + def test_dist_testing_conftest_specified(self, testdir): + p1 = testdir.makepyfile(""" + import py + def test_fail0(): + assert 0 + def test_fail1(): + raise ValueError() + def test_ok(): + pass + def test_skip(): + py.test.skip("hello") + """, + ) + testdir.makeconftest(""" + pytest_option_hosts='popen,popen,popen' + """) + result = testdir.runpytest(p1, '-d') + result.stdout.fnmatch_lines([ + "HOSTUP: popen*Python*", + #"HOSTUP: localhost*Python*", + #"HOSTUP: localhost*Python*", + "*2 failed, 1 passed, 1 skipped*", + ]) + assert result.ret == 1 + def test_dist_tests_with_crash(self, testdir): if not hasattr(py.std.os, 'kill'): py.test.skip("no os.kill") Modified: py/trunk/py/test/testing/test_config.py ============================================================================== --- py/trunk/py/test/testing/test_config.py (original) +++ py/trunk/py/test/testing/test_config.py Tue Mar 17 07:10:40 2009 @@ -40,6 +40,12 @@ group.addoption("--option4", action="store", type="int") assert group.options[3].default == ("NO", "DEFAULT") + def test_parser_addoption_default_conftest(self, testdir, monkeypatch): + import os + testdir.makeconftest("pytest_option_verbose=True") + config = testdir.parseconfig() + assert config.option.verbose + def test_config_cmdline_options_only_lowercase(self, testdir): testdir.makepyfile(conftest=""" import py From hpk at codespeak.net Tue Mar 17 08:03:50 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 17 Mar 2009 08:03:50 +0100 (CET) Subject: [py-svn] r62982 - in py/trunk/py/test: . testing Message-ID: <20090317070350.18FFF16840C@codespeak.net> Author: hpk Date: Tue Mar 17 08:03:49 2009 New Revision: 62982 Modified: py/trunk/py/test/cmdline.py py/trunk/py/test/config.py py/trunk/py/test/testing/acceptance_test.py Log: report basic configuration errors more gracefully to the user Modified: py/trunk/py/test/cmdline.py ============================================================================== --- py/trunk/py/test/cmdline.py (original) +++ py/trunk/py/test/cmdline.py Tue Mar 17 08:03:49 2009 @@ -9,12 +9,16 @@ if args is None: args = py.std.sys.argv[1:] config = py.test.config - config.parse(args) - config.pytestplugins.do_configure(config) - session = config.initsession() - exitstatus = session.main() - config.pytestplugins.do_unconfigure(config) - raise SystemExit(exitstatus) + try: + config.parse(args) + config.pytestplugins.do_configure(config) + session = config.initsession() + exitstatus = session.main() + config.pytestplugins.do_unconfigure(config) + raise SystemExit(exitstatus) + except config.Error, e: + py.std.sys.stderr.write("config ERROR: %s\n" %(e.args[0],)) + raise SystemExit(3) def warn_about_missing_assertion(): try: Modified: py/trunk/py/test/config.py ============================================================================== --- py/trunk/py/test/config.py (original) +++ py/trunk/py/test/config.py Tue Mar 17 08:03:49 2009 @@ -22,9 +22,13 @@ def __repr__(self): return "" %(self.__dict__,) +class Error(Exception): + """ Test Configuration Error. """ + class Config(object): """ central bus for dealing with configuration/initialization data. """ Option = py.compat.optparse.Option # deprecated + Error = Error _sessionclass = None def __init__(self, pytestplugins=None, topdir=None): @@ -124,7 +128,8 @@ def getfsnode(self, path): path = py.path.local(path) - assert path.check(), "%s: path does not exist" %(path,) + if not path.check(): + raise self.Error("file not found: %s" %(path,)) # we want our possibly custom collection tree to start at pkgroot pkgpath = path.pypkgpath() if pkgpath is None: Modified: py/trunk/py/test/testing/acceptance_test.py ============================================================================== --- py/trunk/py/test/testing/acceptance_test.py (original) +++ py/trunk/py/test/testing/acceptance_test.py Tue Mar 17 08:03:49 2009 @@ -5,6 +5,18 @@ EXPECTTIMEOUT=10.0 class TestPyTest: + def test_config_error(self, testdir): + testdir.makeconftest(""" + class ConftestPlugin: + def pytest_configure(self, config): + raise config.Error("hello") + """) + result = testdir.runpytest(testdir.tmpdir) + assert result.ret != 0 + assert result.stderr.fnmatch_lines([ + 'config ERROR: hello' + ]) + def test_assertion_magic(self, testdir): p = testdir.makepyfile(""" def test_this(): @@ -17,7 +29,7 @@ "E assert 0", ]) assert result.ret == 1 - + def test_collectonly_simple(self, testdir): p = testdir.makepyfile(""" def test_func1(): From hpk at codespeak.net Tue Mar 17 08:11:19 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 17 Mar 2009 08:11:19 +0100 (CET) Subject: [py-svn] r62983 - py/trunk/py/test/testing Message-ID: <20090317071119.1960116840C@codespeak.net> Author: hpk Date: Tue Mar 17 08:11:18 2009 New Revision: 62983 Added: py/trunk/py/test/testing/test_traceback.py Log: forgot to add traceback cutting tests long time ago, i guess Added: py/trunk/py/test/testing/test_traceback.py ============================================================================== --- (empty file) +++ py/trunk/py/test/testing/test_traceback.py Tue Mar 17 08:11:18 2009 @@ -0,0 +1,29 @@ +import py + +class TestTracebackCutting: + def test_skip_simple(self): + from py.__.test.outcome import Skipped + excinfo = py.test.raises(Skipped, 'py.test.skip("xxx")') + assert excinfo.traceback[-1].frame.code.name == "skip" + assert excinfo.traceback[-1].ishidden() + + def test_traceback_argsetup(self, testdir): + testdir.makeconftest(""" + class ConftestPlugin: + def pytest_pyfuncarg_hello(self, pyfuncitem): + raise ValueError("xyz") + """) + p = testdir.makepyfile("def test(hello): pass") + result = testdir.runpytest(p) + assert result.ret != 0 + out = result.stdout.str() + assert out.find("xyz") != -1 + assert out.find("conftest.py:3: ValueError") != -1 + numentries = out.count("_ _ _") # separator for traceback entries + assert numentries == 0 + + result = testdir.runpytest("--fulltrace", p) + out = result.stdout.str() + assert out.find("conftest.py:3: ValueError") != -1 + numentries = out.count("_ _ _ _") # separator for traceback entries + assert numentries >3 From hpk at codespeak.net Tue Mar 17 08:19:24 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 17 Mar 2009 08:19:24 +0100 (CET) Subject: [py-svn] r62984 - in py/trunk/py/test/dsession: . testing Message-ID: <20090317071924.9D10A16843D@codespeak.net> Author: hpk Date: Tue Mar 17 08:19:23 2009 New Revision: 62984 Modified: py/trunk/py/test/dsession/dsession.py py/trunk/py/test/dsession/testing/test_dsession.py Log: allow for host to go down if it didn't go up yet Modified: py/trunk/py/test/dsession/dsession.py ============================================================================== --- py/trunk/py/test/dsession/dsession.py (original) +++ py/trunk/py/test/dsession/dsession.py Tue Mar 17 08:19:23 2009 @@ -170,7 +170,11 @@ self.host2pending[host] = [] def removehost(self, host): - pending = self.host2pending.pop(host) + try: + pending = self.host2pending.pop(host) + except KeyError: + # this happens if we didn't receive a hostup event yet + return [] for item in pending: del self.item2host[item] return pending Modified: py/trunk/py/test/dsession/testing/test_dsession.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_dsession.py (original) +++ py/trunk/py/test/dsession/testing/test_dsession.py Tue Mar 17 08:19:23 2009 @@ -47,7 +47,8 @@ pending = session.removehost(host) assert pending == [item] assert item not in session.item2host - py.test.raises(Exception, "session.removehost(host)") + l = session.removehost(host) + assert not l def test_senditems_removeitems(self, testdir): item = testdir.getitem("def test_func(): pass") From hpk at codespeak.net Tue Mar 17 08:36:01 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 17 Mar 2009 08:36:01 +0100 (CET) Subject: [py-svn] r62985 - in py/trunk/py/test: . testing Message-ID: <20090317073601.D1E6516843D@codespeak.net> Author: hpk Date: Tue Mar 17 08:35:58 2009 New Revision: 62985 Modified: py/trunk/py/test/session.py py/trunk/py/test/testing/test_session.py Log: rewrite and fix a skipped test Modified: py/trunk/py/test/session.py ============================================================================== --- py/trunk/py/test/session.py (original) +++ py/trunk/py/test/session.py Tue Mar 17 08:35:58 2009 @@ -60,6 +60,8 @@ for x in self.genitems(ev.result, keywordexpr): yield x notify("collectionreport", ev) + if self.shouldstop: + break def filteritems(self, colitems): """ return items to process (some may be deselected)""" Modified: py/trunk/py/test/testing/test_session.py ============================================================================== --- py/trunk/py/test/testing/test_session.py (original) +++ py/trunk/py/test/testing/test_session.py Tue Mar 17 08:35:58 2009 @@ -213,17 +213,10 @@ assert len(colfail) == 1 assert len(colskipped) == 1 - def test_minus_x_import_error(self): - py.test.skip("fails") - o = self.tmpdir - tfile = o.join('test_one.py').write(py.code.Source(""" - xxxx - """)) - tfile2 = o.join('test_two.py').write(py.code.Source(""" - yyyyy - """)) - sorter = self.events_from_cmdline('-x') - finished = sorter.get(event.CollectionReport) + def test_minus_x_import_error(self, testdir): + testdir.makepyfile(test_one="xxxx", test_two="yyyy") + sorter = testdir.inline_run("-x", testdir.tmpdir) + finished = sorter.getnamed("collectionreport") colfail = [x for x in finished if x.failed] assert len(colfail) == 1 From hpk at codespeak.net Tue Mar 17 08:40:40 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 17 Mar 2009 08:40:40 +0100 (CET) Subject: [py-svn] r62986 - py/trunk/py/test Message-ID: <20090317074040.229C4168442@codespeak.net> Author: hpk Date: Tue Mar 17 08:40:39 2009 New Revision: 62986 Modified: py/trunk/py/test/session.py Log: fix tests that got broken Modified: py/trunk/py/test/session.py ============================================================================== --- py/trunk/py/test/session.py (original) +++ py/trunk/py/test/session.py Tue Mar 17 08:40:39 2009 @@ -25,6 +25,7 @@ self.bus.register(self) self._testsfailed = False self._nomatch = False + self.shouldstop = False def fixoptions(self): """ check, fix and determine conflicting options. """ From hpk at codespeak.net Tue Mar 17 10:18:41 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 17 Mar 2009 10:18:41 +0100 (CET) Subject: [py-svn] r62987 - py/trunk/py/test/plugin Message-ID: <20090317091841.9FCD916844B@codespeak.net> Author: hpk Date: Tue Mar 17 10:18:38 2009 New Revision: 62987 Modified: py/trunk/py/test/plugin/pytest_monkeypatch.py Log: add a "setenv" helper for setting a value in the environment Modified: py/trunk/py/test/plugin/pytest_monkeypatch.py ============================================================================== --- py/trunk/py/test/plugin/pytest_monkeypatch.py (original) +++ py/trunk/py/test/plugin/pytest_monkeypatch.py Tue Mar 17 10:18:38 2009 @@ -1,3 +1,5 @@ +import os + class MonkeypatchPlugin: """ setattr-monkeypatching with automatical reversal after test. """ def pytest_pyfuncarg_monkeypatch(self, pyfuncitem): @@ -20,6 +22,9 @@ self._setitem.insert(0, (dictionary, name, dictionary.get(name, notset))) dictionary[name] = value + def setenv(self, name, value): + self.setitem(os.environ, name, str(value)) + def finalize(self): for obj, name, value in self._setattr: if value is not notset: @@ -63,6 +68,14 @@ assert d['x'] == 1 assert 'y' not in d +def test_setenv(): + monkeypatch = MonkeyPatch() + monkeypatch.setenv('XYZ123', 2) + import os + assert os.environ['XYZ123'] == "2" + monkeypatch.finalize() + assert 'XYZ123' not in os.environ + def test_monkeypatch_plugin(testdir): sorter = testdir.inline_runsource(""" pytest_plugins = 'pytest_monkeypatch', From hpk at codespeak.net Tue Mar 17 11:29:45 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 17 Mar 2009 11:29:45 +0100 (CET) Subject: [py-svn] r62993 - in py/trunk/py/test: . dsession/testing plugin testing Message-ID: <20090317102945.E0035168445@codespeak.net> Author: hpk Date: Tue Mar 17 11:29:45 2009 New Revision: 62993 Modified: py/trunk/py/test/config.py py/trunk/py/test/dsession/testing/test_functional_dsession.py py/trunk/py/test/plugin/pytest_default.py py/trunk/py/test/plugin/pytest_pytester.py py/trunk/py/test/testing/acceptance_test.py py/trunk/py/test/testing/test_config.py py/trunk/py/test/testing/test_session.py Log: * moving ensuretemp to config object * adding --basetemp option * added/rewrote some tests Modified: py/trunk/py/test/config.py ============================================================================== --- py/trunk/py/test/config.py (original) +++ py/trunk/py/test/config.py Tue Mar 17 11:29:45 2009 @@ -4,16 +4,11 @@ from py.__.test import parseopt from py.__.misc.warn import APIWARN -# XXX move to Config class -basetemp = None def ensuretemp(string, dir=1): """ return temporary directory path with the given string as the trailing part. """ - global basetemp - if basetemp is None: - basetemp = py.path.local.make_numbered_dir(prefix='pytest-') - return basetemp.ensure(string, dir=dir) + return py.test.config.ensuretemp(string, dir=dir) class CmdOptions(object): """ pure container instance for holding cmdline options @@ -29,6 +24,7 @@ """ central bus for dealing with configuration/initialization data. """ Option = py.compat.optparse.Option # deprecated Error = Error + basetemp = None _sessionclass = None def __init__(self, pytestplugins=None, topdir=None): @@ -123,6 +119,18 @@ self._preparse(args) self.args = args + def ensuretemp(self, string, dir=True): + if self.basetemp is None: + basetemp = self.option.basetemp + if basetemp: + basetemp = py.path.local(basetemp) + if not basetemp.check(dir=1): + basetemp.mkdir() + else: + basetemp = py.path.local.make_numbered_dir(prefix='pytest-') + self.basetemp = basetemp + return self.basetemp.ensure(string, dir=dir) + def getcolitems(self): return [self.getfsnode(arg) for arg in self.args] Modified: py/trunk/py/test/dsession/testing/test_functional_dsession.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_functional_dsession.py (original) +++ py/trunk/py/test/dsession/testing/test_functional_dsession.py Tue Mar 17 11:29:45 2009 @@ -63,38 +63,21 @@ assert ev.host.address == "popen" ev, = eq.geteventargs("testrunfinish") - def test_distribution_rsync_roots_example(self, testdir): - py.test.skip("testing for root rsync needs rework") - destdir = py.test.ensuretemp("example_dist_destdir") - subdir = "sub_example_dist" - sourcedir = self.tmpdir.mkdir("source") - sourcedir.ensure(subdir, "conftest.py").write(py.code.Source(""" - hosts = ["popen:%s"] - rsyncdirs = ["%s", "../py"] - """ % (destdir, tmpdir.join(subdir), ))) - tmpdir.ensure(subdir, "__init__.py") - tmpdir.ensure(subdir, "test_one.py").write(py.code.Source(""" - def test_1(): - pass - def test_2(): - assert 0 - def test_3(): - raise ValueError(23) - def test_4(someargs): - pass - def test_5(): - assert __file__ != '%s' - #def test_6(): - # import py - # assert py.__file__ != '%s' - """ % (tmpdir.join(subdir), py.__file__))) - destdir.join("py").mksymlinkto(py.path.local(py.__file__).dirpath()) - - sorter = testdir.inline_run(tmpdir.join(subdir)) - testevents = sorter.getnamed('itemtestreport') - assert len([x for x in testevents if x.passed]) == 2 - assert len([x for x in testevents if x.failed]) == 3 - assert len([x for x in testevents if x.skipped]) == 0 + @py.test.mark.xfail("XXX") + def test_distribution_rsyncdirs_example(self, testdir): + source = testdir.mkdir("source") + dest = testdir.mkdir("dest") + subdir = source.mkdir("example_pkg") + subdir.ensure("__init__.py") + p = subdir.join("test_one.py") + p.write("def test_5(): assert not __file__.startswith(%r)" % str(p)) + result = testdir.runpytest("-d", "--rsyncdirs=%(subdir)s" % locals(), + "--hosts=popen:%(dest)s" % locals()) + assert result.ret == 0 + result.stdout.fnmatch_lines([ + "*1 passed*" + ]) + assert dest.join(subdir.basename).check(dir=1) def test_nice_level(self, testdir): """ Tests if nice level behaviour is ok """ Modified: py/trunk/py/test/plugin/pytest_default.py ============================================================================== --- py/trunk/py/test/plugin/pytest_default.py (original) +++ py/trunk/py/test/plugin/pytest_default.py Tue Mar 17 11:29:45 2009 @@ -61,7 +61,9 @@ group._addoption('-s', '--nocapture', action="store_true", dest="nocapture", default=False, help="disable catching of sys.stdout/stderr output."), - group._addoption('--boxed', + group.addoption('--basetemp', dest="basetemp", default=None, + help="directory to use for this test run.") + group.addoption('--boxed', action="store_true", dest="boxed", default=False, help="box each test run in a separate process"), group._addoption('-f', '--looponfailing', Modified: py/trunk/py/test/plugin/pytest_pytester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_pytester.py (original) +++ py/trunk/py/test/plugin/pytest_pytester.py Tue Mar 17 11:29:45 2009 @@ -234,6 +234,9 @@ return self.run(script, *args) def runpytest(self, *args): + p = py.path.local.make_numbered_dir(prefix="runpytest-", + keep=None, rootdir=self.tmpdir) + args = ('--basetemp=%s' % p, ) + args return self.runpybin("py.test", *args) class Event: Modified: py/trunk/py/test/testing/acceptance_test.py ============================================================================== --- py/trunk/py/test/testing/acceptance_test.py (original) +++ py/trunk/py/test/testing/acceptance_test.py Tue Mar 17 11:29:45 2009 @@ -16,6 +16,17 @@ assert result.stderr.fnmatch_lines([ 'config ERROR: hello' ]) + + def test_basetemp(self, testdir): + mytemp = testdir.tmpdir.mkdir("mytemp") + p = testdir.makepyfile(""" + import py + def test_1(): + py.test.ensuretemp('xyz') + """) + result = testdir.runpytest(p, '--basetemp=%s' %mytemp) + assert result.ret == 0 + assert mytemp.join('xyz').check(dir=1) def test_assertion_magic(self, testdir): p = testdir.makepyfile(""" Modified: py/trunk/py/test/testing/test_config.py ============================================================================== --- py/trunk/py/test/testing/test_config.py (original) +++ py/trunk/py/test/testing/test_config.py Tue Mar 17 11:29:45 2009 @@ -85,8 +85,9 @@ yield check_conflict_option, opts class TestConfigAPI: + @py.test.mark.issue("ensuretemp should call config.maketemp(basename)") - def test_tmpdir(self): + def test_ensuretemp(self): d1 = py.test.ensuretemp('hello') d2 = py.test.ensuretemp('hello') assert d1 == d2 Modified: py/trunk/py/test/testing/test_session.py ============================================================================== --- py/trunk/py/test/testing/test_session.py (original) +++ py/trunk/py/test/testing/test_session.py Tue Mar 17 11:29:45 2009 @@ -214,6 +214,7 @@ assert len(colskipped) == 1 def test_minus_x_import_error(self, testdir): + testdir.makepyfile(__init__="") testdir.makepyfile(test_one="xxxx", test_two="yyyy") sorter = testdir.inline_run("-x", testdir.tmpdir) finished = sorter.getnamed("collectionreport") From hpk at codespeak.net Tue Mar 17 12:53:11 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 17 Mar 2009 12:53:11 +0100 (CET) Subject: [py-svn] r62994 - in py/trunk/py: execnet execnet/testing test/dsession test/dsession/testing test/plugin Message-ID: <20090317115311.02817168417@codespeak.net> Author: hpk Date: Tue Mar 17 12:53:09 2009 New Revision: 62994 Modified: py/trunk/py/execnet/gwmanage.py py/trunk/py/execnet/testing/test_gwmanage.py py/trunk/py/test/dsession/hostmanage.py py/trunk/py/test/dsession/testing/test_hostmanage.py py/trunk/py/test/plugin/pytest_pytester.py Log: introducing internal MultiGateway class Modified: py/trunk/py/execnet/gwmanage.py ============================================================================== --- py/trunk/py/execnet/gwmanage.py (original) +++ py/trunk/py/execnet/gwmanage.py Tue Mar 17 12:53:09 2009 @@ -94,31 +94,46 @@ for ch in self._channels: ch.waitclose() +class MultiGateway: + def __init__(self, gateways): + self.gateways = gateways + def remote_exec(self, source): + channels = [] + for gw in self.gateways: + channels.append(gw.remote_exec(source)) + return MultiChannel(channels) + class GatewayManager: def __init__(self, specs): - self.spec2gateway = {} - for spec in specs: - self.spec2gateway[GatewaySpec(spec)] = None + self.specs = [GatewaySpec(spec) for spec in specs] + self.gateways = [] def trace(self, msg): py._com.pyplugins.notify("trace", "gatewaymanage", msg) def makegateways(self): - for spec, value in self.spec2gateway.items(): - assert value is None + assert not self.gateways + for spec in self.specs: self.trace("makegateway %s" %(spec)) - self.spec2gateway[spec] = spec.makegateway() + self.gateways.append(spec.makegateway()) + + def getgateways(self, remote=True, inplacelocal=True): + l = [] + for gw in self.gateways: + if gw.spec.inplacelocal(): + if inplacelocal: + l.append(gw) + else: + if remote: + l.append(gw) + return MultiGateway(gateways=l) def multi_exec(self, source, inplacelocal=True): """ remote execute code on all gateways. @param inplacelocal=False: don't send code to inplacelocal hosts. """ - source = py.code.Source(source) - channels = [] - for spec, gw in self.spec2gateway.items(): - if inplacelocal or not spec.inplacelocal(): - channels.append(gw.remote_exec(source)) - return MultiChannel(channels) + multigw = self.getgateways(inplacelocal=inplacelocal) + return multigw.remote_exec(source) def multi_chdir(self, basename, inplacelocal=True): """ perform a remote chdir to the given path, may be relative. @@ -132,7 +147,8 @@ """ rsync = HostRSync(source, verbose=verbose, ignores=ignores) added = False - for spec, gateway in self.spec2gateway.items(): + for gateway in self.gateways: + spec = gateway.spec if not spec.inplacelocal(): self.trace("add_target_host %r" %(gateway,)) def finished(): @@ -148,8 +164,8 @@ self.trace("rsync: nothing to do.") def exit(self): - while self.spec2gateway: - spec, gw = self.spec2gateway.popitem() + while self.gateways: + gw = self.gateways.pop() self.trace("exiting gateway %s" % gw) gw.exit() Modified: py/trunk/py/execnet/testing/test_gwmanage.py ============================================================================== --- py/trunk/py/execnet/testing/test_gwmanage.py (original) +++ py/trunk/py/execnet/testing/test_gwmanage.py Tue Mar 17 12:53:09 2009 @@ -106,21 +106,21 @@ def test_hostmanager_popen_makegateway(self): hm = GatewayManager(["popen"] * 2) hm.makegateways() - assert len(hm.spec2gateway) == 2 + assert len(hm.gateways) == 2 hm.exit() - assert not len(hm.spec2gateway) + assert not len(hm.gateways) def test_hostmanager_popens_rsync(self, source): hm = GatewayManager(["popen"] * 2) hm.makegateways() - assert len(hm.spec2gateway) == 2 - for gw in hm.spec2gateway.values(): + assert len(hm.gateways) == 2 + for gw in hm.gateways: gw.remote_exec = None l = [] hm.rsync(source, notify=lambda *args: l.append(args)) assert not l hm.exit() - assert not len(hm.spec2gateway) + assert not len(hm.gateways) def test_hostmanager_rsync_popen_with_path(self, source, dest): hm = GatewayManager(["popen:%s" %dest] * 1) @@ -129,7 +129,7 @@ l = [] hm.rsync(source, notify=lambda *args: l.append(args)) assert len(l) == 1 - assert l[0] == ("rsyncrootready", hm.spec2gateway.keys()[0], source) + assert l[0] == ("rsyncrootready", hm.gateways[0].spec, source) hm.exit() dest = dest.join(source.basename) assert dest.join("dir1").check() Modified: py/trunk/py/test/dsession/hostmanage.py ============================================================================== --- py/trunk/py/test/dsession/hostmanage.py (original) +++ py/trunk/py/test/dsession/hostmanage.py Tue Mar 17 12:53:09 2009 @@ -99,7 +99,8 @@ self.trace_hoststatus() - for host, gateway in self.gwmanager.spec2gateway.items(): + for gateway in self.gwmanager.gateways: + host = gateway.spec host.node = MasterNode(host, gateway, self.config, Modified: py/trunk/py/test/dsession/testing/test_hostmanage.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_hostmanage.py (original) +++ py/trunk/py/test/dsession/testing/test_hostmanage.py Tue Mar 17 12:53:09 2009 @@ -25,7 +25,7 @@ config = py.test.config._reparse(args) assert config.topdir == source hm = HostManager(config) - assert hm.gwmanager.spec2gateway + assert hm.gwmanager.specs return hm def xxtest_hostmanager_custom_hosts(self, source, dest): @@ -114,7 +114,7 @@ config = py.test.config._reparse([source]) hm = HostManager(config, hosts=hosts) hm.rsync_roots() - for gwspec in hm.gwmanager.spec2gateway: + for gwspec in hm.gwmanager.specs: assert gwspec.inplacelocal() assert not gwspec.joinpath @@ -127,20 +127,12 @@ hm = HostManager(config, hosts=hosts) evrec = EventRecorder(config.bus, debug=True) hm.setup_hosts(putevent=[].append) - for host in hm.gwmanager.spec2gateway: + for host in hm.gwmanager.specs: l = evrec.getnamed("trace") print evrec.events assert l hm.teardown_hosts() - def test_hostmanage_simple_ssh_test(self, testdir): - rp = testdir.mkdir('xyz123') - rp.ensure("__init__.py") - p = testdir.makepyfile("def test_123(): import xyz123") - result = testdir.runpytest(p, '-d', "--hosts=popen", '--rsyncdirs=' + str(rp)) - assert result.ret == 0 - assert result.stdout.str().find("1 passed") != -1 - @py.test.mark.xfail("implement double-rsync test") def test_ssh_rsync_samehost_twice(self): option = py.test.config.option Modified: py/trunk/py/test/plugin/pytest_pytester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_pytester.py (original) +++ py/trunk/py/test/plugin/pytest_pytester.py Tue Mar 17 12:53:09 2009 @@ -36,7 +36,7 @@ def __init__(self, pyfuncitem): self.pyfuncitem = pyfuncitem # XXX remove duplication with tmpdir plugin - basetmp = py.test.ensuretemp("testdir") + basetmp = pyfuncitem._config.ensuretemp("testdir") name = pyfuncitem.name for i in range(100): try: From hpk at codespeak.net Tue Mar 17 13:22:16 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 17 Mar 2009 13:22:16 +0100 (CET) Subject: [py-svn] r62995 - in py/trunk/py/test: dsession plugin Message-ID: <20090317122216.9753C16843D@codespeak.net> Author: hpk Date: Tue Mar 17 13:22:14 2009 New Revision: 62995 Modified: py/trunk/py/test/dsession/masterslave.py py/trunk/py/test/plugin/pytest_pytester.py Log: try to contain session test tempdirs in one parent Modified: py/trunk/py/test/dsession/masterslave.py ============================================================================== --- py/trunk/py/test/dsession/masterslave.py (original) +++ py/trunk/py/test/dsession/masterslave.py Tue Mar 17 13:22:14 2009 @@ -70,7 +70,13 @@ slavenode.run() """) channel = PickleChannel(channel) - channel.send((host, config)) + basetemp = None + if host.type == "popen": + popenbase = config.ensuretemp("popen") + basetemp = py.path.local.make_numbered_dir(prefix="slave-", + keep=0, rootdir=popenbase) + basetemp = str(basetemp) + channel.send((host, config, basetemp)) return channel class SlaveNode(object): @@ -85,7 +91,9 @@ def run(self): channel = self.channel - host, self.config = channel.receive() + host, self.config, basetemp = channel.receive() + if basetemp: + self.config.basetemp = py.path.local(basetemp) self.config.pytestplugins.do_configure(self.config) self.sendevent("hostup", makehostup(host)) try: Modified: py/trunk/py/test/plugin/pytest_pytester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_pytester.py (original) +++ py/trunk/py/test/plugin/pytest_pytester.py Tue Mar 17 13:22:14 2009 @@ -154,7 +154,8 @@ if not args: args = (self.tmpdir,) config = self.config_preparse() - config.parse(list(args)) + args = list(args) + ["--basetemp=%s" % self.tmpdir.dirpath('basetemp')] + config.parse(args) return config def getitem(self, source, funcname="test_func"): From hpk at codespeak.net Tue Mar 17 13:42:40 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 17 Mar 2009 13:42:40 +0100 (CET) Subject: [py-svn] r62997 - in py/trunk/py/test: . testing Message-ID: <20090317124240.B7CA9168449@codespeak.net> Author: hpk Date: Tue Mar 17 13:42:40 2009 New Revision: 62997 Modified: py/trunk/py/test/config.py py/trunk/py/test/testing/test_config.py Log: some more tests, seems like temp test dirs are now more contained when doing distributed testing Modified: py/trunk/py/test/config.py ============================================================================== --- py/trunk/py/test/config.py (original) +++ py/trunk/py/test/config.py Tue Mar 17 13:42:40 2009 @@ -120,7 +120,10 @@ self.args = args def ensuretemp(self, string, dir=True): - if self.basetemp is None: + return self.getbasetemp().ensure(string, dir=dir) + + def getbasetemp(self): + if self.basetemp is None: basetemp = self.option.basetemp if basetemp: basetemp = py.path.local(basetemp) @@ -128,8 +131,16 @@ basetemp.mkdir() else: basetemp = py.path.local.make_numbered_dir(prefix='pytest-') - self.basetemp = basetemp - return self.basetemp.ensure(string, dir=dir) + self.basetemp = basetemp + return self.basetemp + + def mktemp(self, basename, numbered=False): + basetemp = self.getbasetemp() + if not numbered: + return basetemp.mkdir(basename) + else: + return py.path.local.make_numbered_dir(prefix=basename + "-", + keep=0, rootdir=basetemp) def getcolitems(self): return [self.getfsnode(arg) for arg in self.args] @@ -215,6 +226,7 @@ oldconfig = py.test.config try: config_per_process = py.test.config = Config() + config_per_process.basetemp = self.mktemp("reparse", numbered=True) config_per_process.parse(args) return config_per_process finally: Modified: py/trunk/py/test/testing/test_config.py ============================================================================== --- py/trunk/py/test/testing/test_config.py (original) +++ py/trunk/py/test/testing/test_config.py Tue Mar 17 13:42:40 2009 @@ -84,14 +84,34 @@ opts = spec.split() yield check_conflict_option, opts +class TestConfigTmpdir: + def test_getbasetemp(self, testdir): + config = testdir.Config() + config.basetemp = "hello" + config.getbasetemp() == "hello" + + def test_mktemp(self, testdir): + config = testdir.Config() + config.basetemp = testdir.mkdir("hello") + tmp = config.mktemp("world") + assert tmp.relto(config.basetemp) == "world" + tmp = config.mktemp("this", numbered=True) + assert tmp.relto(config.basetemp).startswith("this") + tmp2 = config.mktemp("this", numbered=True) + assert tmp2.relto(config.basetemp).startswith("this") + assert tmp2 != tmp + + def test_reparse(self, testdir): + config = testdir.Config() + config.basetemp = testdir.mkdir("my") + config2 = config._reparse([]) + assert config2.getbasetemp().relto(config.basetemp) + config3 = config._reparse([]) + assert config3.getbasetemp().relto(config.basetemp) + assert config2.basetemp != config3.basetemp + class TestConfigAPI: - @py.test.mark.issue("ensuretemp should call config.maketemp(basename)") - def test_ensuretemp(self): - d1 = py.test.ensuretemp('hello') - d2 = py.test.ensuretemp('hello') - assert d1 == d2 - assert d1.check(dir=1) def test_config_getvalue_honours_conftest(self, testdir): testdir.makepyfile(conftest="x=1") @@ -313,4 +333,10 @@ def test_default_bus(): assert py.test.config.bus is py._com.pyplugins - + + at py.test.mark.todo("test for deprecation") +def test_ensuretemp(): + d1 = py.test.ensuretemp('hello') + d2 = py.test.ensuretemp('hello') + assert d1 == d2 + assert d1.check(dir=1) From hpk at codespeak.net Tue Mar 17 14:10:19 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 17 Mar 2009 14:10:19 +0100 (CET) Subject: [py-svn] r62998 - in py/trunk/py/test: plugin testing Message-ID: <20090317131019.4D54D168451@codespeak.net> Author: hpk Date: Tue Mar 17 14:10:17 2009 New Revision: 62998 Modified: py/trunk/py/test/plugin/pytest_tmpdir.py py/trunk/py/test/testing/acceptance_test.py Log: tweak another place to not create random subdirs Modified: py/trunk/py/test/plugin/pytest_tmpdir.py ============================================================================== --- py/trunk/py/test/plugin/pytest_tmpdir.py (original) +++ py/trunk/py/test/plugin/pytest_tmpdir.py Tue Mar 17 14:10:17 2009 @@ -13,19 +13,9 @@ """ provide temporary directories to test functions and methods. """ - def pytest_configure(self, config): - # XXX make ensuretemp live on config - self.basetmp = py.test.ensuretemp("tmpdir") - def pytest_pyfuncarg_tmpdir(self, pyfuncitem): name = pyfuncitem.name - for i in range(10000): - try: - tmpdir = self.basetmp.mkdir(name + (i and str(i) or '')) - except py.error.EEXIST: - continue - break - return tmpdir + return pyfuncitem._config.mktemp(name, numbered=True) # =============================================================================== # @@ -39,8 +29,7 @@ def test_pyfuncarg(testdir): item = testdir.getitem("def test_func(tmpdir): pass") plugin = TmpdirPlugin() - plugin.pytest_configure(item._config) p = plugin.pytest_pyfuncarg_tmpdir(item) assert p.check() - bn = p.basename.strip("0123456789") + bn = p.basename.strip("0123456789-") assert bn.endswith("test_func") Modified: py/trunk/py/test/testing/acceptance_test.py ============================================================================== --- py/trunk/py/test/testing/acceptance_test.py (original) +++ py/trunk/py/test/testing/acceptance_test.py Tue Mar 17 14:10:17 2009 @@ -373,7 +373,9 @@ class TestInteractive: def getspawn(self, tmpdir): pexpect = py.test.importorskip("pexpect") + basetemp = tmpdir.mkdir("basetemp") def spawn(cmd): + cmd = cmd + " --basetemp=" + str(basetemp) return pexpect.spawn(cmd, logfile=tmpdir.join("spawn.out").open("w")) return spawn @@ -391,7 +393,6 @@ i = 0 assert i == 1 """) - child = spawn("%s %s --pdb %s" % (py.std.sys.executable, pytestpath, p1)) child.timeout = EXPECTTIMEOUT #child.expect(".*def test_1.*") From hpk at codespeak.net Tue Mar 17 14:58:15 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 17 Mar 2009 14:58:15 +0100 (CET) Subject: [py-svn] r62999 - py/trunk/py/test/dsession/testing Message-ID: <20090317135815.CFE161683EE@codespeak.net> Author: hpk Date: Tue Mar 17 14:58:13 2009 New Revision: 62999 Modified: py/trunk/py/test/dsession/testing/test_functional_dsession.py Log: this test can easily pass. Modified: py/trunk/py/test/dsession/testing/test_functional_dsession.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_functional_dsession.py (original) +++ py/trunk/py/test/dsession/testing/test_functional_dsession.py Tue Mar 17 14:58:13 2009 @@ -63,7 +63,6 @@ assert ev.host.address == "popen" ev, = eq.geteventargs("testrunfinish") - @py.test.mark.xfail("XXX") def test_distribution_rsyncdirs_example(self, testdir): source = testdir.mkdir("source") dest = testdir.mkdir("dest") @@ -72,7 +71,7 @@ p = subdir.join("test_one.py") p.write("def test_5(): assert not __file__.startswith(%r)" % str(p)) result = testdir.runpytest("-d", "--rsyncdirs=%(subdir)s" % locals(), - "--hosts=popen:%(dest)s" % locals()) + "--hosts=popen:%(dest)s" % locals(), p) assert result.ret == 0 result.stdout.fnmatch_lines([ "*1 passed*" From hpk at codespeak.net Tue Mar 17 15:12:13 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 17 Mar 2009 15:12:13 +0100 (CET) Subject: [py-svn] r63000 - in py/trunk/py: path/local path/local/testing test test/dsession/testing Message-ID: <20090317141213.D7CCE168416@codespeak.net> Author: hpk Date: Tue Mar 17 15:12:10 2009 New Revision: 63000 Modified: py/trunk/py/path/local/local.py py/trunk/py/path/local/testing/test_local.py py/trunk/py/test/config.py py/trunk/py/test/dsession/testing/test_functional_dsession.py Log: * do an as lightweight config.mktemp() as possible * avoid writing lock files if lock_timeout is 0 or None Modified: py/trunk/py/path/local/local.py ============================================================================== --- py/trunk/py/path/local/local.py (original) +++ py/trunk/py/path/local/local.py Tue Mar 17 15:12:10 2009 @@ -618,25 +618,26 @@ # put a .lock file in the new directory that will be removed at # process exit - lockfile = udir.join('.lock') - mypid = os.getpid() - if hasattr(lockfile, 'mksymlinkto'): - lockfile.mksymlinkto(str(mypid)) - else: - lockfile.write(str(mypid)) - def try_remove_lockfile(): - # in a fork() situation, only the last process should - # remove the .lock, otherwise the other processes run the - # risk of seeing their temporary dir disappear. For now - # we remove the .lock in the parent only (i.e. we assume - # that the children finish before the parent). - if os.getpid() != mypid: - return - try: - lockfile.remove() - except py.error.Error: - pass - atexit.register(try_remove_lockfile) + if lock_timeout: + lockfile = udir.join('.lock') + mypid = os.getpid() + if hasattr(lockfile, 'mksymlinkto'): + lockfile.mksymlinkto(str(mypid)) + else: + lockfile.write(str(mypid)) + def try_remove_lockfile(): + # in a fork() situation, only the last process should + # remove the .lock, otherwise the other processes run the + # risk of seeing their temporary dir disappear. For now + # we remove the .lock in the parent only (i.e. we assume + # that the children finish before the parent). + if os.getpid() != mypid: + return + try: + lockfile.remove() + except py.error.Error: + pass + atexit.register(try_remove_lockfile) # prune old directories if keep: @@ -647,7 +648,7 @@ try: t1 = lf.lstat().mtime t2 = lockfile.lstat().mtime - if abs(t2-t1) < lock_timeout: + if not lock_timeout or abs(t2-t1) < lock_timeout: continue # skip directories still locked except py.error.Error: pass # assume that it means that there is no 'lf' Modified: py/trunk/py/path/local/testing/test_local.py ============================================================================== --- py/trunk/py/path/local/testing/test_local.py (original) +++ py/trunk/py/path/local/testing/test_local.py Tue Mar 17 15:12:10 2009 @@ -275,8 +275,6 @@ assert not numdir.new(ext=str(i-3)).check() def test_locked_make_numbered_dir(self): - if py.test.config.option.boxed: - py.test.skip("Fails when run as boxed tests") root = self.tmpdir for i in range(10): numdir = local.make_numbered_dir(prefix='base2.', rootdir=root, Modified: py/trunk/py/test/config.py ============================================================================== --- py/trunk/py/test/config.py (original) +++ py/trunk/py/test/config.py Tue Mar 17 15:12:10 2009 @@ -140,7 +140,7 @@ return basetemp.mkdir(basename) else: return py.path.local.make_numbered_dir(prefix=basename + "-", - keep=0, rootdir=basetemp) + keep=0, rootdir=basetemp, lock_timeout=None) def getcolitems(self): return [self.getfsnode(arg) for arg in self.args] Modified: py/trunk/py/test/dsession/testing/test_functional_dsession.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_functional_dsession.py (original) +++ py/trunk/py/test/dsession/testing/test_functional_dsession.py Tue Mar 17 15:12:10 2009 @@ -1,12 +1,6 @@ - -""" Tests various aspects of dist, like ssh hosts setup/teardown -""" - import py from py.__.test.dsession.dsession import DSession from test_masterslave import EventQueue -import os - class TestAsyncFunctional: def test_conftest_options(self, testdir): @@ -80,6 +74,7 @@ def test_nice_level(self, testdir): """ Tests if nice level behaviour is ok """ + import os if not hasattr(os, 'nice'): py.test.skip("no os.nice() available") testdir.makepyfile(conftest=""" From hpk at codespeak.net Tue Mar 17 22:11:26 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 17 Mar 2009 22:11:26 +0100 (CET) Subject: [py-svn] r63013 - py/trunk/py/test/testing Message-ID: <20090317211126.AE28F168426@codespeak.net> Author: hpk Date: Tue Mar 17 22:11:23 2009 New Revision: 63013 Modified: py/trunk/py/test/testing/acceptance_test.py Log: adding a failing test for --dist-each Modified: py/trunk/py/test/testing/acceptance_test.py ============================================================================== --- py/trunk/py/test/testing/acceptance_test.py (original) +++ py/trunk/py/test/testing/acceptance_test.py Tue Mar 17 22:11:23 2009 @@ -265,8 +265,7 @@ py.test.skip("hello") """, ) - #result = testdir.runpytest(p1, '-d') - result = testdir.runpytest(p1, '-d', '--hosts=popen,popen,popen') + result = testdir.runpytest(p1, '-d', '--hosts=popen,popen') result.stdout.fnmatch_lines([ "HOSTUP: popen*Python*", #"HOSTUP: localhost*Python*", @@ -423,4 +422,23 @@ child.expect("MODIFIED.*test_simple_looponfailing_interaction.py", timeout=4.0) child.expect("1 passed", timeout=5.0) child.kill(15) - + + @py.test.mark.xfail("need new cmdline option") + def test_dist_each(self, testdir): + for name in ("python2.4", "python2.5"): + if not py.path.local.sysfind(name): + py.test.skip("%s not found" % name) + testdir.makepyfile(__init__="", test_one=""" + import sys + def test_hello(): + print sys.version_info[:2] + assert 0 + """) + result = testdir.runpytest("--dist-each", "--gateways=popen-python2.5,popen-python2.4") + assert result.ret == 1 + result.stdout.fnmatch_lines([ + "*popen-python2.5*FAIL*", + "*popen-python2.4*FAIL*", + "*2 failed*" + ]) + From hpk at codespeak.net Tue Mar 17 23:42:00 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 17 Mar 2009 23:42:00 +0100 (CET) Subject: [py-svn] r63014 - in py/trunk/py: execnet execnet/testing test/dsession test/dsession/testing Message-ID: <20090317224200.78E2416842D@codespeak.net> Author: hpk Date: Tue Mar 17 23:41:56 2009 New Revision: 63014 Modified: py/trunk/py/execnet/gwmanage.py py/trunk/py/execnet/testing/test_gwmanage.py py/trunk/py/test/dsession/hostmanage.py py/trunk/py/test/dsession/testing/test_hostmanage.py Log: streamlining multichannel interface, fixing test work with -n 3 Modified: py/trunk/py/execnet/gwmanage.py ============================================================================== --- py/trunk/py/execnet/gwmanage.py (original) +++ py/trunk/py/execnet/gwmanage.py Tue Mar 17 23:41:56 2009 @@ -19,7 +19,9 @@ import sys, os from py.__.test.dsession.masterslave import MasterNode from py.__.test import event +from py.__.execnet.channel import RemoteError +NO_ENDMARKER_WANTED = object() class GatewaySpec(object): def __init__(self, spec, defaultjoinpath="pyexecnetcache"): @@ -81,20 +83,49 @@ def __init__(self, channels): self._channels = channels - def receive_items(self): - items = [] + def send_each(self, item): for ch in self._channels: - items.append((ch, ch.receive())) - return items + ch.send(item) + + def receive_each(self, withchannel=False): + assert not hasattr(self, '_queue') + l = [] + for ch in self._channels: + obj = ch.receive() + if withchannel: + l.append((ch, obj)) + else: + l.append(obj) + return l + + def make_receive_queue(self, endmarker=NO_ENDMARKER_WANTED): + try: + return self._queue + except AttributeError: + self._queue = py.std.Queue.Queue() + for ch in self._channels: + def putreceived(obj, channel=ch): + self._queue.put((channel, obj)) + if endmarker is NO_ENDMARKER_WANTED: + ch.setcallback(putreceived) + else: + ch.setcallback(putreceived, endmarker=endmarker) + return self._queue - def receive(self): - return [x[1] for x in self.receive_items()] def waitclose(self): + first = None for ch in self._channels: - ch.waitclose() + try: + ch.waitclose() + except ch.RemoteError: + if first is None: + first = py.std.sys.exc_info() + if first: + raise first[0], first[1], first[2] class MultiGateway: + RemoteError = RemoteError def __init__(self, gateways): self.gateways = gateways def remote_exec(self, source): @@ -104,6 +135,8 @@ return MultiChannel(channels) class GatewayManager: + RemoteError = RemoteError + def __init__(self, specs): self.specs = [GatewaySpec(spec) for spec in specs] self.gateways = [] @@ -118,6 +151,8 @@ self.gateways.append(spec.makegateway()) def getgateways(self, remote=True, inplacelocal=True): + if not self.gateways and self.specs: + self.makegateways() l = [] for gw in self.gateways: if gw.spec.inplacelocal(): Modified: py/trunk/py/execnet/testing/test_gwmanage.py ============================================================================== --- py/trunk/py/execnet/testing/test_gwmanage.py (original) +++ py/trunk/py/execnet/testing/test_gwmanage.py Tue Mar 17 23:41:56 2009 @@ -1,7 +1,8 @@ """ tests for - - host specifications - - managing hosts + - gateway specifications + - multi channels and multi gateways + - gateway management - manage rsyncing of hosts """ @@ -25,10 +26,6 @@ assert spec.type == "popen" spec2 = GatewaySpec("popen" + joinpath) self._equality(spec, spec2) - if joinpath == "": - assert spec.inplacelocal() - else: - assert not spec.inplacelocal() def test_ssh(self): for prefix in ('ssh:', ''): # ssh is default @@ -44,7 +41,6 @@ assert spec.type == "ssh" spec2 = GatewaySpec(specstring) self._equality(spec, spec2) - assert not spec.inplacelocal() def test_socket(self): for hostpart in ('x.y', 'x', 'popen'): @@ -59,7 +55,6 @@ assert spec.type == "socket" spec2 = GatewaySpec("socket:" + hostpart + port + joinpath) self._equality(spec, spec2) - assert not spec.inplacelocal() def _equality(self, spec1, spec2): assert spec1 != spec2 @@ -155,16 +150,13 @@ testdir.tmpdir.chdir() hellopath = testdir.tmpdir.mkdir("hello") hm.makegateways() - l = [x[1] for x in hm.multi_exec( - "import os ; channel.send(os.getcwd())" - ).receive_items() - ] + l = hm.multi_exec("import os ; channel.send(os.getcwd())").receive_each() paths = [x[1] for x in l] assert l == [str(hellopath)] * 2 - py.test.raises(Exception, 'hm.multi_chdir("world", inplacelocal=False)') + py.test.raises(hm.RemoteError, 'hm.multi_chdir("world", inplacelocal=False)') worldpath = hellopath.mkdir("world") hm.multi_chdir("world", inplacelocal=False) - l = hm.multi_exec("import os ; channel.send(os.getcwd())").receive() + l = hm.multi_exec("import os ; channel.send(os.getcwd())").receive_each() assert len(l) == 2 assert l[0] == l[1] curwd = os.getcwd() @@ -178,12 +170,12 @@ hellopath = testdir.tmpdir.mkdir("hello") hm.makegateways() hm.multi_chdir("hello", inplacelocal=False) - l = hm.multi_exec("import os ; channel.send(os.getcwd())").receive() + l = hm.multi_exec("import os ; channel.send(os.getcwd())").receive_each() assert len(l) == 2 assert l == [os.getcwd()] * 2 hm.multi_chdir("hello") - l = hm.multi_exec("import os ; channel.send(os.getcwd())").receive() + l = hm.multi_exec("import os ; channel.send(os.getcwd())").receive_each() assert len(l) == 2 assert l[0] == l[1] curwd = os.getcwd() @@ -192,7 +184,7 @@ from py.__.execnet.gwmanage import MultiChannel class TestMultiChannel: - def test_multichannel_receive_items(self): + def test_multichannel_receive_each(self): class pseudochannel: def receive(self): return 12 @@ -200,9 +192,35 @@ pc1 = pseudochannel() pc2 = pseudochannel() multichannel = MultiChannel([pc1, pc2]) - l = multichannel.receive_items() + l = multichannel.receive_each(withchannel=True) assert len(l) == 2 assert l == [(pc1, 12), (pc2, 12)] + l = multichannel.receive_each(withchannel=False) + assert l == [12,12] + + def test_multichannel_send_each(self): + gm = GatewayManager(['popen'] * 2) + mc = gm.multi_exec(""" + import os + channel.send(channel.receive() + 1) + """) + mc.send_each(41) + l = mc.receive_each() + assert l == [42,42] + + def test_multichannel_receive_queue(self): + gm = GatewayManager(['popen'] * 2) + mc = gm.multi_exec(""" + import os + channel.send(os.getpid()) + """) + queue = mc.make_receive_queue() + ch, item = queue.get(timeout=0.5) + ch2, item2 = queue.get(timeout=0.5) + assert ch != ch2 + assert ch.gateway != ch2.gateway + assert item != item2 + mc.waitclose() def test_multichannel_waitclose(self): l = [] Modified: py/trunk/py/test/dsession/hostmanage.py ============================================================================== --- py/trunk/py/test/dsession/hostmanage.py (original) +++ py/trunk/py/test/dsession/hostmanage.py Tue Mar 17 23:41:56 2009 @@ -51,7 +51,7 @@ for ch, result in self.gwmanager.multi_exec(""" import sys, os channel.send((sys.executable, os.getcwd(), sys.path)) - """).receive_items(): + """).receive_each(withchannel=True): self.trace("spec %r, execuable %r, cwd %r, syspath %r" %( ch.gateway.spec, result[0], result[1], result[2])) Modified: py/trunk/py/test/dsession/testing/test_hostmanage.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_hostmanage.py (original) +++ py/trunk/py/test/dsession/testing/test_hostmanage.py Tue Mar 17 23:41:56 2009 @@ -39,7 +39,7 @@ hm = HostManager(config, hosts=["popen:%s" % dest]) assert hm.config.topdir == source == config.topdir hm.rsync_roots() - p, = hm.gwmanager.multi_exec("import os ; channel.send(os.getcwd())").receive() + p, = hm.gwmanager.multi_exec("import os ; channel.send(os.getcwd())").receive_each() p = py.path.local(p) print "remote curdir", p assert p == dest.join(config.topdir.basename) From hpk at codespeak.net Wed Mar 18 00:35:04 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 18 Mar 2009 00:35:04 +0100 (CET) Subject: [py-svn] r63016 - in py/trunk/py: . execnet/testing Message-ID: <20090317233504.72FF9168418@codespeak.net> Author: hpk Date: Wed Mar 18 00:34:59 2009 New Revision: 63016 Modified: py/trunk/py/execnet/testing/test_gwmanage.py py/trunk/py/initpkg.py Log: * fix setattr on apimodules * higher timeout Modified: py/trunk/py/execnet/testing/test_gwmanage.py ============================================================================== --- py/trunk/py/execnet/testing/test_gwmanage.py (original) +++ py/trunk/py/execnet/testing/test_gwmanage.py Wed Mar 18 00:34:59 2009 @@ -215,8 +215,8 @@ channel.send(os.getpid()) """) queue = mc.make_receive_queue() - ch, item = queue.get(timeout=0.5) - ch2, item2 = queue.get(timeout=0.5) + ch, item = queue.get(timeout=10) + ch2, item2 = queue.get(timeout=10) assert ch != ch2 assert ch.gateway != ch2.gateway assert item != item2 Modified: py/trunk/py/initpkg.py ============================================================================== --- py/trunk/py/initpkg.py (original) +++ py/trunk/py/initpkg.py Wed Mar 18 00:34:59 2009 @@ -165,9 +165,9 @@ class ApiModule(ModuleType): def __init__(self, pkg, name): + self.__map__ = {} self.__pkg__ = pkg self.__name__ = name - self.__map__ = {} def __repr__(self): return '' % (self.__name__,) @@ -178,17 +178,24 @@ result = self.__pkg__._resolve(extpy) else: try: - extpy = self.__map__[name] + extpy = self.__map__.pop(name) except KeyError: __tracebackhide__ = True raise AttributeError(name) else: result = self.__pkg__._resolve(extpy) - del self.__map__[name] + setattr(self, name, result) #self._fixinspection(result, name) return result + def __setattr__(self, name, value): + super(ApiModule, self).__setattr__(name, value) + try: + del self.__map__[name] + except KeyError: + pass + def _deprecated_fixinspection(self, result, name): # modify some attrs to make a class appear at export level if hasattr(result, '__module__'): From hpk at codespeak.net Wed Mar 18 00:48:08 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 18 Mar 2009 00:48:08 +0100 (CET) Subject: [py-svn] r63017 - in py/trunk/py/test: . dsession/testing looponfail/testing plugin testing Message-ID: <20090317234808.C94C516842D@codespeak.net> Author: hpk Date: Wed Mar 18 00:48:07 2009 New Revision: 63017 Modified: py/trunk/py/test/collect.py py/trunk/py/test/dsession/testing/test_dsession.py py/trunk/py/test/dsession/testing/test_masterslave.py py/trunk/py/test/looponfail/testing/test_remote.py py/trunk/py/test/plugin/pytest_default.py py/trunk/py/test/plugin/pytest_doctest.py py/trunk/py/test/plugin/pytest_plugintester.py py/trunk/py/test/plugin/pytest_pytester.py py/trunk/py/test/plugin/pytest_restdoc.py py/trunk/py/test/plugin/pytest_terminal.py py/trunk/py/test/plugin/pytest_tmpdir.py py/trunk/py/test/pycollect.py py/trunk/py/test/runner.py py/trunk/py/test/session.py py/trunk/py/test/testing/test_collect.py py/trunk/py/test/testing/test_config.py py/trunk/py/test/testing/test_pickling.py py/trunk/py/test/testing/test_pycollect.py py/trunk/py/test/testing/test_pytestplugin.py Log: rename colitem._config to colitem.config - it's an official attribute Modified: py/trunk/py/test/collect.py ============================================================================== --- py/trunk/py/test/collect.py (original) +++ py/trunk/py/test/collect.py Wed Mar 18 00:48:07 2009 @@ -24,7 +24,7 @@ def configproperty(name): def fget(self): #print "retrieving %r property from %s" %(name, self.fspath) - return self._config.getvalue(name, self.fspath) + return self.config.getvalue(name, self.fspath) return property(fget) class ReprMetaInfo(object): @@ -70,8 +70,8 @@ self.name = name self.parent = parent if config is None: - config = getattr(parent, '_config') - self._config = config + config = parent.config + self.config = config self.fspath = getattr(parent, 'fspath', None) @@ -88,7 +88,7 @@ #self.__init__(name=name, parent=parent) def __repr__(self): - if getattr(self._config.option, 'debug', False): + if getattr(self.config.option, 'debug', False): return "<%s %r %0x>" %(self.__class__.__name__, getattr(self, 'name', None), id(self)) else: @@ -265,7 +265,7 @@ starting from a different topdir). """ chain = self.listchain() - topdir = self._config.topdir + topdir = self.config.topdir relpath = chain[0].fspath.relto(topdir) if not relpath: if chain[0].fspath == topdir: @@ -287,12 +287,12 @@ # XXX temporary hack: getrepr() should not take a 'style' argument # at all; it should record all data in all cases, and the style # should be parametrized in toterminal(). - if self._config.option.tbstyle == "short": + if self.config.option.tbstyle == "short": style = "short" else: style = "long" repr = excinfo.getrepr(funcargs=True, - showlocals=self._config.option.showlocals, + showlocals=self.config.option.showlocals, style=style) for secname, content in zip(["out", "err"], outerr): if content: @@ -382,7 +382,7 @@ def __getstate__(self): if self.parent is None: # the root node needs to pickle more context info - topdir = self._config.topdir + topdir = self.config.topdir relpath = self.fspath.relto(topdir) if not relpath: if self.fspath == topdir: @@ -390,7 +390,7 @@ else: raise ValueError("%r not relative to topdir %s" %(self.fspath, topdir)) - return (self.name, self._config, relpath) + return (self.name, self.config, relpath) else: return (self.name, self.parent) @@ -444,23 +444,23 @@ return res def consider_file(self, path): - return self._config.pytestplugins.call_each( + return self.config.pytestplugins.call_each( 'pytest_collect_file', path=path, parent=self) def consider_dir(self, path, usefilters=None): if usefilters is not None: APIWARN("0.99", "usefilters argument not needed") - res = self._config.pytestplugins.call_firstresult( + res = self.config.pytestplugins.call_firstresult( 'pytest_collect_recurse', path=path, parent=self) if res is None or res: - return self._config.pytestplugins.call_each( + return self.config.pytestplugins.call_each( 'pytest_collect_directory', path=path, parent=self) from py.__.test.runner import basic_run_report, forked_run_report class Item(Node): """ a basic test item. """ def _getrunner(self): - if self._config.option.boxed: + if self.config.option.boxed: return forked_run_report return basic_run_report Modified: py/trunk/py/test/dsession/testing/test_dsession.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_dsession.py (original) +++ py/trunk/py/test/dsession/testing/test_dsession.py Wed Mar 18 00:48:07 2009 @@ -37,7 +37,7 @@ def test_add_remove_host(self, testdir): item = testdir.getitem("def test_func(): pass") rep = run(item) - session = DSession(item._config) + session = DSession(item.config) host = GatewaySpec("localhost") host.node = MockNode() assert not session.host2pending @@ -53,7 +53,7 @@ def test_senditems_removeitems(self, testdir): item = testdir.getitem("def test_func(): pass") rep = run(item) - session = DSession(item._config) + session = DSession(item.config) host = GatewaySpec("localhost") host.node = MockNode() session.addhost(host) @@ -69,7 +69,7 @@ def test_func(): pass """) - session = DSession(modcol._config) + session = DSession(modcol.config) session.triggertesting([modcol]) name, args, kwargs = session.queue.get(block=False) assert name == 'collectionreport' @@ -78,7 +78,7 @@ def test_triggertesting_item(self, testdir): item = testdir.getitem("def test_func(): pass") - session = DSession(item._config) + session = DSession(item.config) host1 = GatewaySpec("localhost") host1.node = MockNode() host2 = GatewaySpec("localhost") @@ -99,7 +99,7 @@ def test_keyboardinterrupt(self, testdir): item = testdir.getitem("def test_func(): pass") - session = DSession(item._config) + session = DSession(item.config) def raise_(timeout=None): raise KeyboardInterrupt() session.queue.get = raise_ exitstatus = session.loop([]) @@ -107,7 +107,7 @@ def test_internalerror(self, testdir): item = testdir.getitem("def test_func(): pass") - session = DSession(item._config) + session = DSession(item.config) def raise_(): raise ValueError() session.queue.get = raise_ exitstatus = session.loop([]) @@ -115,7 +115,7 @@ def test_rescheduleevent(self, testdir): item = testdir.getitem("def test_func(): pass") - session = DSession(item._config) + session = DSession(item.config) host1 = GatewaySpec("localhost") host1.node = MockNode() session.addhost(host1) @@ -139,7 +139,7 @@ def test_no_hosts_remaining_for_tests(self, testdir): item = testdir.getitem("def test_func(): pass") # setup a session with one host - session = DSession(item._config) + session = DSession(item.config) host1 = GatewaySpec("localhost") host1.node = MockNode() session.addhost(host1) @@ -164,7 +164,7 @@ item1, item2 = modcol.collect() # setup a session with two hosts - session = DSession(item1._config) + session = DSession(item1.config) host1 = GatewaySpec("localhost") host1.node = MockNode() session.addhost(host1) @@ -191,7 +191,7 @@ def test_hostup_adds_to_available(self, testdir): item = testdir.getitem("def test_func(): pass") # setup a session with two hosts - session = DSession(item._config) + session = DSession(item.config) host1 = GatewaySpec("localhost") hostup = makehostup(host1) session.queueevent("hostup", hostup) @@ -203,7 +203,7 @@ def test_event_propagation(self, testdir, EventRecorder): item = testdir.getitem("def test_func(): pass") - session = DSession(item._config) + session = DSession(item.config) evrec = EventRecorder(session.bus) session.queueevent("NOPevent", 42) @@ -211,7 +211,7 @@ assert evrec.getfirstnamed('NOPevent') def runthrough(self, item): - session = DSession(item._config) + session = DSession(item.config) host1 = GatewaySpec("localhost") host1.node = MockNode() session.addhost(host1) @@ -247,8 +247,8 @@ def test_pass(): pass """) - modcol._config.option.exitfirst = True - session = DSession(modcol._config) + modcol.config.option.exitfirst = True + session = DSession(modcol.config) host1 = GatewaySpec("localhost") host1.node = MockNode() session.addhost(host1) @@ -270,7 +270,7 @@ def test_shuttingdown_filters_events(self, testdir, EventRecorder): item = testdir.getitem("def test_func(): pass") - session = DSession(item._config) + session = DSession(item.config) host = GatewaySpec("localhost") session.addhost(host) loopstate = LoopState([]) @@ -291,9 +291,9 @@ def test_pass(): pass """) - session = DSession(modcol._config) + session = DSession(modcol.config) - modcol._config.option.keyword = "nothing" + modcol.config.option.keyword = "nothing" dsel = session.filteritems([modcol]) assert dsel == [modcol] items = modcol.collect() @@ -305,7 +305,7 @@ assert event.name == "deselected" assert event.args[0].items == items - modcol._config.option.keyword = "test_fail" + modcol.config.option.keyword = "test_fail" remaining = session.filteritems(items) assert remaining == [items[0]] @@ -315,7 +315,7 @@ def test_hostdown_shutdown_after_completion(self, testdir): item = testdir.getitem("def test_func(): pass") - session = DSession(item._config) + session = DSession(item.config) host = GatewaySpec("localhost") host.node = MockNode() @@ -338,7 +338,7 @@ def test_pass(): pass """) - session = DSession(modcol._config) + session = DSession(modcol.config) host = GatewaySpec("localhost") host.node = MockNode() session.addhost(host) Modified: py/trunk/py/test/dsession/testing/test_masterslave.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_masterslave.py (original) +++ py/trunk/py/test/dsession/testing/test_masterslave.py Wed Mar 18 00:48:07 2009 @@ -82,7 +82,7 @@ import os os.kill(os.getpid(), 15) """) - node = mysetup.makenode(item._config) + node = mysetup.makenode(item.config) node.send(item) ev, = mysetup.geteventargs("hostdown") assert ev.host == mysetup.host @@ -100,7 +100,7 @@ def test_send_on_closed_channel(self, testdir, mysetup): item = testdir.getitem("def test_func(): pass") - node = mysetup.makenode(item._config) + node = mysetup.makenode(item.config) node.channel.close() py.test.raises(IOError, "node.send(item)") #ev = self.geteventargs(event.InternalException) @@ -108,7 +108,7 @@ def test_send_one(self, testdir, mysetup): item = testdir.getitem("def test_func(): pass") - node = mysetup.makenode(item._config) + node = mysetup.makenode(item.config) node.send(item) ev, = mysetup.geteventargs("itemtestreport") assert ev.passed @@ -126,7 +126,7 @@ import py py.test.skip("x") """) - node = mysetup.makenode(items[0]._config) + node = mysetup.makenode(items[0].config) for item in items: node.send(item) for outcome in "passed failed skipped".split(): Modified: py/trunk/py/test/looponfail/testing/test_remote.py ============================================================================== --- py/trunk/py/test/looponfail/testing/test_remote.py (original) +++ py/trunk/py/test/looponfail/testing/test_remote.py Wed Mar 18 00:48:07 2009 @@ -4,14 +4,14 @@ class TestRemoteControl: def test_nofailures(self, testdir): item = testdir.getitem("def test_func(): pass\n") - control = RemoteControl(item._config) + control = RemoteControl(item.config) control.setup() failures = control.runsession() assert not failures def test_failures_somewhere(self, testdir): item = testdir.getitem("def test_func(): assert 0\n") - control = RemoteControl(item._config) + control = RemoteControl(item.config) control.setup() failures = control.runsession() assert failures @@ -26,7 +26,7 @@ def test_func(): assert 0 """) - control = RemoteControl(modcol._config) + control = RemoteControl(modcol.config) control.setup() failures = control.runsession() assert failures @@ -54,7 +54,7 @@ def test_two(): assert 1 """) - session = LooponfailingSession(modcol._config) + session = LooponfailingSession(modcol.config) loopstate = LoopState() session.remotecontrol.setup() session.loop_once(loopstate) @@ -76,7 +76,7 @@ def test_one(): assert 0 """) - session = LooponfailingSession(modcol._config) + session = LooponfailingSession(modcol.config) loopstate = LoopState() session.remotecontrol.setup() loopstate.colitems = [] @@ -103,7 +103,7 @@ def test_two(): assert 0 """) - session = LooponfailingSession(modcol._config) + session = LooponfailingSession(modcol.config) loopstate = LoopState() session.remotecontrol.setup() loopstate.colitems = [] Modified: py/trunk/py/test/plugin/pytest_default.py ============================================================================== --- py/trunk/py/test/plugin/pytest_default.py (original) +++ py/trunk/py/test/plugin/pytest_default.py Wed Mar 18 00:48:07 2009 @@ -11,14 +11,14 @@ ext = path.ext pb = path.purebasename if pb.startswith("test_") or pb.endswith("_test") or \ - path in parent._config.args: + path in parent.config.args: if ext == ".py": return parent.Module(path, parent=parent) def pytest_collect_directory(self, path, parent): if not parent.recfilter(path): # check if cmdline specified this dir or a subdir - for arg in parent._config.args: + for arg in parent.config.args: if path == arg or arg.relto(path): break else: @@ -26,7 +26,7 @@ # not use parent.Directory here as we want # dir/conftest.py to be able to # define Directory(dir) already - Directory = parent._config.getvalue('Directory', path) + Directory = parent.config.getvalue('Directory', path) return Directory(path, parent=parent) def pytest_addoption(self, parser): Modified: py/trunk/py/test/plugin/pytest_doctest.py ============================================================================== --- py/trunk/py/test/plugin/pytest_doctest.py (original) +++ py/trunk/py/test/plugin/pytest_doctest.py Wed Mar 18 00:48:07 2009 @@ -8,7 +8,7 @@ def pytest_collect_file(self, path, parent): if path.ext == ".py": - if parent._config.getvalue("doctestmodules"): + if parent.config.getvalue("doctestmodules"): return DoctestModule(path, parent) if path.check(fnmatch="test_*.txt"): return DoctestTextfile(path, parent) Modified: py/trunk/py/test/plugin/pytest_plugintester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_plugintester.py (original) +++ py/trunk/py/test/plugin/pytest_plugintester.py Wed Mar 18 00:48:07 2009 @@ -26,7 +26,7 @@ class PluginTester(Support): def testdir(self): # XXX import differently, eg. - # FSTester = self.pyfuncitem._config.pytestplugins.getpluginattr("pytester", "FSTester") + # FSTester = self.pyfuncitem.config.pytestplugins.getpluginattr("pytester", "FSTester") from pytest_pytester import TmpTestdir crunner = TmpTestdir(self.pyfuncitem) # Modified: py/trunk/py/test/plugin/pytest_pytester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_pytester.py (original) +++ py/trunk/py/test/plugin/pytest_pytester.py Wed Mar 18 00:48:07 2009 @@ -36,7 +36,7 @@ def __init__(self, pyfuncitem): self.pyfuncitem = pyfuncitem # XXX remove duplication with tmpdir plugin - basetmp = pyfuncitem._config.ensuretemp("testdir") + basetmp = pyfuncitem.config.ensuretemp("testdir") name = pyfuncitem.name for i in range(100): try: @@ -166,7 +166,7 @@ def getitems(self, source): modcol = self.getmodulecol(source) - return list(modcol._config.initsession().genitems([modcol])) + return list(modcol.config.initsession().genitems([modcol])) #assert item is not None, "%r item not found in module:\n%s" %(funcname, source) #return item Modified: py/trunk/py/test/plugin/pytest_restdoc.py ============================================================================== --- py/trunk/py/test/plugin/pytest_restdoc.py (original) +++ py/trunk/py/test/plugin/pytest_restdoc.py Wed Mar 18 00:48:07 2009 @@ -127,7 +127,7 @@ return (text, apigen_relpath + 'source/%s' % (relpath,)) def _checkskip(self, lpath, htmlpath=None): - if not self._config.getvalue("forcegen"): + if not self.config.getvalue("forcegen"): lpath = py.path.local(lpath) if htmlpath is not None: htmlpath = py.path.local(htmlpath) @@ -140,7 +140,7 @@ class DoctestText(py.test.collect.Item): def runtest(self): content = self._normalize_linesep() - newcontent = self._config.pytestplugins.call_firstresult( + newcontent = self.config.pytestplugins.call_firstresult( 'pytest_doctest_prepare_content', content=content) if newcontent is not None: content = newcontent @@ -192,7 +192,7 @@ def genlinkchecks(self): path = self.fspath # generating functions + args as single tests - timeout = self._config.getvalue("urlcheck_timeout") + timeout = self.config.getvalue("urlcheck_timeout") for lineno, line in py.builtin.enumerate(path.readlines()): line = line.strip() if line.startswith('.. _'): @@ -206,7 +206,7 @@ tryfn = l[1].strip() name = "%s:%d" %(tryfn, lineno) if tryfn.startswith('http:') or tryfn.startswith('https'): - if self._config.getvalue("urlcheck"): + if self.config.getvalue("urlcheck"): yield CheckLink(name, parent=self, args=(tryfn, path, lineno, timeout), callobj=urlcheck) elif tryfn.startswith('webcal:'): Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Wed Mar 18 00:48:07 2009 @@ -322,7 +322,7 @@ def test_hostup(self, testdir, linecomp): from py.__.execnet.gwmanage import GatewaySpec item = testdir.getitem("def test_func(): pass") - rep = TerminalReporter(item._config, linecomp.stringio) + rep = TerminalReporter(item.config, linecomp.stringio) rep.pyevent_hostup(makehostup()) linecomp.assert_contains_lines([ "*localhost %s %s - Python %s" %(sys.platform, @@ -339,7 +339,7 @@ def test_func(): assert 0 """) - rep = TerminalReporter(modcol._config, file=linecomp.stringio) + rep = TerminalReporter(modcol.config, file=linecomp.stringio) rep.config.bus.register(rep) rep.config.bus.notify("testrunstart", event.TestrunStart()) @@ -366,7 +366,7 @@ def test_func(): assert 0 """, configargs=("-v",)) - rep = TerminalReporter(modcol._config, file=linecomp.stringio) + rep = TerminalReporter(modcol.config, file=linecomp.stringio) rep.config.bus.register(rep) rep.config.bus.notify("testrunstart", event.TestrunStart()) items = modcol.collect() @@ -390,7 +390,7 @@ def test_collect_fail(self, testdir, linecomp): modcol = testdir.getmodulecol("import xyz") - rep = TerminalReporter(modcol._config, file=linecomp.stringio) + rep = TerminalReporter(modcol.config, file=linecomp.stringio) rep.config.bus.register(rep) rep.config.bus.notify("testrunstart", event.TestrunStart()) l = list(testdir.genitems([modcol])) @@ -406,7 +406,7 @@ def test_internalerror(self, testdir, linecomp): modcol = testdir.getmodulecol("def test_one(): pass") - rep = TerminalReporter(modcol._config, file=linecomp.stringio) + rep = TerminalReporter(modcol.config, file=linecomp.stringio) excinfo = py.test.raises(ValueError, "raise ValueError('hello')") rep.pyevent_internalerror(event.InternalException(excinfo)) linecomp.assert_contains_lines([ @@ -420,7 +420,7 @@ pass """, configargs=("-v",)) host1 = GatewaySpec("localhost") - rep = TerminalReporter(modcol._config, file=linecomp.stringio) + rep = TerminalReporter(modcol.config, file=linecomp.stringio) rep.pyevent_hostgatewayready(event.HostGatewayReady(host1, None)) linecomp.assert_contains_lines([ "*HostGatewayReady*" @@ -433,7 +433,7 @@ def test_writeline(self, testdir, linecomp): modcol = testdir.getmodulecol("def test_one(): pass") stringio = py.std.cStringIO.StringIO() - rep = TerminalReporter(modcol._config, file=linecomp.stringio) + rep = TerminalReporter(modcol.config, file=linecomp.stringio) rep.write_fspath_result(py.path.local("xy.py"), '.') rep.write_line("hello world") lines = linecomp.stringio.getvalue().split('\n') @@ -448,14 +448,14 @@ def test_fail2(): raise ValueError() """) - rep = TerminalReporter(modcol._config, file=linecomp.stringio) + rep = TerminalReporter(modcol.config, file=linecomp.stringio) reports = [basic_run_report(x) for x in modcol.collect()] - rep.pyevent_looponfailinginfo(event.LooponfailingInfo(reports, [modcol._config.topdir])) + rep.pyevent_looponfailinginfo(event.LooponfailingInfo(reports, [modcol.config.topdir])) linecomp.assert_contains_lines([ "*test_looponfailingreport.py:2: assert 0", "*test_looponfailingreport.py:4: ValueError*", "*waiting*", - "*%s*" % (modcol._config.topdir), + "*%s*" % (modcol.config.topdir), ]) def test_tb_option(self, testdir, linecomp): @@ -470,7 +470,7 @@ print 6*7 g() # --calling-- """, configargs=("--tb=%s" % tbopt,)) - rep = TerminalReporter(modcol._config, file=linecomp.stringio) + rep = TerminalReporter(modcol.config, file=linecomp.stringio) rep.config.bus.register(rep) rep.config.bus.notify("testrunstart", event.TestrunStart()) rep.config.bus.notify("testrunstart", event.TestrunStart()) @@ -497,8 +497,8 @@ def test_foobar(): pass """) - rep = TerminalReporter(modcol._config, file=linecomp.stringio) - modcol._config.bus.register(rep) + rep = TerminalReporter(modcol.config, file=linecomp.stringio) + modcol.config.bus.register(rep) l = list(testdir.genitems([modcol])) assert len(l) == 1 rep.config.bus.notify("itemstart", event.ItemStart(l[0])) @@ -515,9 +515,9 @@ def test_interrupt_me(): raise KeyboardInterrupt # simulating the user """, configargs=("--showskipsummary",) + ("-v",)*verbose) - rep = TerminalReporter(modcol._config, file=linecomp.stringio) - modcol._config.bus.register(rep) - bus = modcol._config.bus + rep = TerminalReporter(modcol.config, file=linecomp.stringio) + modcol.config.bus.register(rep) + bus = modcol.config.bus bus.notify("testrunstart", event.TestrunStart()) try: for item in testdir.genitems([modcol]): @@ -576,8 +576,8 @@ def test_func(): pass """) - rep = CollectonlyReporter(modcol._config, out=linecomp.stringio) - modcol._config.bus.register(rep) + rep = CollectonlyReporter(modcol.config, out=linecomp.stringio) + modcol.config.bus.register(rep) indent = rep.indent rep.config.bus.notify("collectionstart", event.CollectionStart(modcol)) linecomp.assert_contains_lines([ @@ -597,8 +597,8 @@ import py py.test.skip("nomod") """) - rep = CollectonlyReporter(modcol._config, out=linecomp.stringio) - modcol._config.bus.register(rep) + rep = CollectonlyReporter(modcol.config, out=linecomp.stringio) + modcol.config.bus.register(rep) cols = list(testdir.genitems([modcol])) assert len(cols) == 0 linecomp.assert_contains_lines(""" @@ -610,8 +610,8 @@ modcol = testdir.getmodulecol(configargs=['--collectonly'], source=""" raise ValueError(0) """) - rep = CollectonlyReporter(modcol._config, out=linecomp.stringio) - modcol._config.bus.register(rep) + rep = CollectonlyReporter(modcol.config, out=linecomp.stringio) + modcol.config.bus.register(rep) cols = list(testdir.genitems([modcol])) assert len(cols) == 0 linecomp.assert_contains_lines(""" Modified: py/trunk/py/test/plugin/pytest_tmpdir.py ============================================================================== --- py/trunk/py/test/plugin/pytest_tmpdir.py (original) +++ py/trunk/py/test/plugin/pytest_tmpdir.py Wed Mar 18 00:48:07 2009 @@ -15,7 +15,7 @@ def pytest_pyfuncarg_tmpdir(self, pyfuncitem): name = pyfuncitem.name - return pyfuncitem._config.mktemp(name, numbered=True) + return pyfuncitem.config.mktemp(name, numbered=True) # =============================================================================== # Modified: py/trunk/py/test/pycollect.py ============================================================================== --- py/trunk/py/test/pycollect.py (original) +++ py/trunk/py/test/pycollect.py Wed Mar 18 00:48:07 2009 @@ -140,7 +140,7 @@ return self.join(name) def makeitem(self, name, obj): - res = self._config.pytestplugins.call_firstresult( + res = self.config.pytestplugins.call_firstresult( "pytest_pymodule_makeitem", modcol=self, name=name, obj=obj) if res: return res @@ -172,25 +172,25 @@ # we assume we are only called once per module mod = self.fspath.pyimport() #print "imported test module", mod - self._config.pytestplugins.consider_module(mod) + self.config.pytestplugins.consider_module(mod) return mod def setup(self): - if not self._config.option.nomagic: + if not self.config.option.nomagic: #print "*" * 20, "INVOKE assertion", self py.magic.invoke(assertion=1) mod = self.obj - self._config.pytestplugins.register(mod) + self.config.pytestplugins.register(mod) if hasattr(mod, 'setup_module'): self.obj.setup_module(mod) def teardown(self): if hasattr(self.obj, 'teardown_module'): self.obj.teardown_module(self.obj) - if not self._config.option.nomagic: + if not self.config.option.nomagic: #print "*" * 20, "revoke assertion", self py.magic.revoke(assertion=1) - self._config.pytestplugins.unregister(self.obj) + self.config.pytestplugins.unregister(self.obj) class Class(PyCollectorMixin, py.test.collect.Collector): @@ -268,7 +268,7 @@ teardown_func_or_meth(self.obj) def _prunetraceback(self, traceback): - if not self._config.option.fulltrace: + if not self.config.option.fulltrace: code = py.code.Code(self.obj) path, firstlineno = code.path, code.firstlineno ntraceback = traceback.cut(path=path, firstlineno=firstlineno) @@ -289,7 +289,7 @@ # test generators are collectors yet participate in # the test-item setup and teardown protocol. # otherwise we could avoid global setupstate - self._config._setupstate.prepare(self) + self.config._setupstate.prepare(self) l = [] for i, x in py.builtin.enumerate(self.obj()): name, call, args = self.getcallargs(x) @@ -348,7 +348,7 @@ """ execute the given test function. """ if not self._deprecated_testexecution(): kw = self.lookup_allargs() - ret = self._config.pytestplugins.call_firstresult( + ret = self.config.pytestplugins.call_firstresult( "pytest_pyfunc_call", pyfuncitem=self, args=self._args, kwargs=kw) def lookup_allargs(self): @@ -374,13 +374,13 @@ return kwargs def lookup_onearg(self, argname): - value = self._config.pytestplugins.call_firstresult( + value = self.config.pytestplugins.call_firstresult( "pytest_pyfuncarg_" + argname, pyfuncitem=self) if value is not None: return value else: metainfo = self.repr_metainfo() - #self._config.bus.notify("pyfuncarg_lookuperror", argname) + #self.config.bus.notify("pyfuncarg_lookuperror", argname) raise LookupError("funcargument %r not found for: %s" %(argname,metainfo.verboseline())) def __eq__(self, other): Modified: py/trunk/py/test/runner.py ============================================================================== --- py/trunk/py/test/runner.py (original) +++ py/trunk/py/test/runner.py Wed Mar 18 00:48:07 2009 @@ -19,7 +19,7 @@ """ def __init__(self, colitem, pdb=None): self.colitem = colitem - self.getcapture = colitem._config._getcapture + self.getcapture = colitem.config._getcapture self.pdb = pdb def __repr__(self): @@ -50,16 +50,16 @@ class ItemRunner(RobustRun): def setup(self): - self.colitem._config._setupstate.prepare(self.colitem) + self.colitem.config._setupstate.prepare(self.colitem) def teardown(self): - self.colitem._config._setupstate.teardown_exact(self.colitem) + self.colitem.config._setupstate.teardown_exact(self.colitem) def execute(self): #self.colitem.config.pytestplugins.pre_execute(self.colitem) self.colitem.runtest() #self.colitem.config.pytestplugins.post_execute(self.colitem) def makereport(self, res, when, excinfo, outerr): - testrep = self.colitem._config.pytestplugins.call_firstresult( + testrep = self.colitem.config.pytestplugins.call_firstresult( "pytest_item_makereport", item=self.colitem, excinfo=excinfo, when=when, outerr=outerr) if self.pdb and testrep.failed: @@ -96,7 +96,7 @@ EXITSTATUS_TESTEXIT = 4 ipickle = ImmutablePickler(uneven=0) - ipickle.selfmemoize(item._config) + ipickle.selfmemoize(item.config) def runforked(): try: testrep = basic_run_report(item) Modified: py/trunk/py/test/session.py ============================================================================== --- py/trunk/py/test/session.py (original) +++ py/trunk/py/test/session.py Wed Mar 18 00:48:07 2009 @@ -46,7 +46,7 @@ if isinstance(next, (tuple, list)): colitems[:] = list(next) + colitems continue - assert self.bus is next._config.bus + assert self.bus is next.config.bus notify = self.bus.notify if isinstance(next, Item): remaining = self.filteritems([next]) Modified: py/trunk/py/test/testing/test_collect.py ============================================================================== --- py/trunk/py/test/testing/test_collect.py (original) +++ py/trunk/py/test/testing/test_collect.py Wed Mar 18 00:48:07 2009 @@ -67,10 +67,10 @@ def test_listnames_and__getitembynames(self, testdir): modcol = testdir.getmodulecol("pass", withinit=True) - print modcol._config.pytestplugins.getplugins() + print modcol.config.pytestplugins.getplugins() names = modcol.listnames() print names - dircol = modcol._config.getfsnode(modcol._config.topdir) + dircol = modcol.config.getfsnode(modcol.config.topdir) x = dircol._getitembynames(names) assert modcol.name == x.name @@ -203,7 +203,7 @@ def pytest_addoption(self, parser): parser.addoption("--XX", action="store_true", default=False) def pytest_collect_recurse(self, path, parent): - return parent._config.getvalue("XX") + return parent.config.getvalue("XX") """) testdir.mkdir("hello") evrec = testdir.inline_run(testdir.tmpdir) Modified: py/trunk/py/test/testing/test_config.py ============================================================================== --- py/trunk/py/test/testing/test_config.py (original) +++ py/trunk/py/test/testing/test_config.py Wed Mar 18 00:48:07 2009 @@ -180,7 +180,7 @@ col = colitems[0] assert isinstance(col, py.test.collect.Directory) for col in col.listchain(): - assert col._config is config + assert col.config is config def test_getcolitems_twodirs(self, tmpdir): config = py.test.config._reparse([tmpdir, tmpdir]) @@ -200,7 +200,7 @@ assert col2.name == 'a' for col in colitems: for subcol in col.listchain(): - assert col._config is config + assert col.config is config def test__getcol_global_file(self, tmpdir): x = tmpdir.ensure("x.py") @@ -211,7 +211,7 @@ assert col.parent.name == tmpdir.basename assert col.parent.parent is None for col in col.listchain(): - assert col._config is config + assert col.config is config def test__getcol_global_dir(self, tmpdir): x = tmpdir.ensure("a", dir=1) @@ -221,7 +221,7 @@ print col.listchain() assert col.name == 'a' assert col.parent is None - assert col._config is config + assert col.config is config def test__getcol_pkgfile(self, tmpdir): x = tmpdir.ensure("x.py") @@ -233,7 +233,7 @@ assert col.parent.name == x.dirpath().basename assert col.parent.parent is None for col in col.listchain(): - assert col._config is config + assert col.config is config Modified: py/trunk/py/test/testing/test_pickling.py ============================================================================== --- py/trunk/py/test/testing/test_pickling.py (original) +++ py/trunk/py/test/testing/test_pickling.py Wed Mar 18 00:48:07 2009 @@ -158,9 +158,9 @@ newcol = unpickler.load() newcol2 = unpickler.load() newcol3 = unpickler.load() - assert newcol2._config is newcol._config + assert newcol2.config is newcol.config assert newcol2.parent == newcol - assert newcol2._config.topdir == topdir + assert newcol2.config.topdir == topdir assert newcol.fspath == topdir assert newcol2.fspath.basename == dir1.basename assert newcol2.fspath.relto(topdir) Modified: py/trunk/py/test/testing/test_pycollect.py ============================================================================== --- py/trunk/py/test/testing/test_pycollect.py (original) +++ py/trunk/py/test/testing/test_pycollect.py Wed Mar 18 00:48:07 2009 @@ -50,9 +50,9 @@ def test_module_participates_as_plugin(self, testdir): modcol = testdir.getmodulecol("") modcol.setup() - assert modcol._config.pytestplugins.isregistered(modcol.obj) + assert modcol.config.pytestplugins.isregistered(modcol.obj) modcol.teardown() - assert not modcol._config.pytestplugins.isregistered(modcol.obj) + assert not modcol.config.pytestplugins.isregistered(modcol.obj) def test_module_considers_pytestplugins_at_import(self, testdir): modcol = testdir.getmodulecol("pytest_plugins='xasdlkj',") @@ -237,7 +237,7 @@ class Provider: def pytest_pyfuncarg_some(self, pyfuncitem): return pyfuncitem.name - item._config.pytestplugins.register(Provider()) + item.config.pytestplugins.register(Provider()) kw = item.lookup_allargs() assert len(kw) == 1 @@ -246,7 +246,7 @@ class Provider: def pytest_pyfuncarg_other(self, pyfuncitem): return pyfuncitem.name - item._config.pytestplugins.register(Provider()) + item.config.pytestplugins.register(Provider()) kw = item.lookup_allargs() assert len(kw) == 1 name, value = kw.popitem() @@ -260,7 +260,7 @@ return pyfuncitem.name def pytest_pyfuncarg_other(self, pyfuncitem): return 42 - item._config.pytestplugins.register(Provider()) + item.config.pytestplugins.register(Provider()) kw = item.lookup_allargs() assert len(kw) == 2 assert kw['some'] == "test_func" @@ -273,7 +273,7 @@ def pytest_pyfuncarg_some(self, pyfuncitem): pyfuncitem.addfinalizer(lambda: l.append(42)) return 3 - item._config.pytestplugins.register(Provider()) + item.config.pytestplugins.register(Provider()) kw = item.lookup_allargs() assert len(kw) == 1 assert kw['some'] == 3 @@ -295,13 +295,13 @@ """) item1, item2 = testdir.genitems([modcol]) modcol.setup() - assert modcol._config.pytestplugins.isregistered(modcol.obj) + assert modcol.config.pytestplugins.isregistered(modcol.obj) kwargs = item1.lookup_allargs() assert kwargs['something'] == "test_method" kwargs = item2.lookup_allargs() assert kwargs['something'] == "test_func" modcol.teardown() - assert not modcol._config.pytestplugins.isregistered(modcol.obj) + assert not modcol.config.pytestplugins.isregistered(modcol.obj) class TestSorting: def test_check_equality_and_cmp_basic(self, testdir): Modified: py/trunk/py/test/testing/test_pytestplugin.py ============================================================================== --- py/trunk/py/test/testing/test_pytestplugin.py (original) +++ py/trunk/py/test/testing/test_pytestplugin.py Wed Mar 18 00:48:07 2009 @@ -267,7 +267,7 @@ return x+0 """) l = [] - call = modcol._config.pytestplugins.setupcall(modcol, "pytest_method", 1) + call = modcol.config.pytestplugins.setupcall(modcol, "pytest_method", 1) assert len(call.methods) == 3 results = call.execute() assert results == [1,2,2] From hpk at codespeak.net Wed Mar 18 00:58:07 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 18 Mar 2009 00:58:07 +0100 (CET) Subject: [py-svn] r63018 - in py/trunk/py/test: dsession dsession/testing plugin testing Message-ID: <20090317235807.9DDF2168416@codespeak.net> Author: hpk Date: Wed Mar 18 00:58:06 2009 New Revision: 63018 Modified: py/trunk/py/test/dsession/dsession.py py/trunk/py/test/dsession/hostmanage.py py/trunk/py/test/dsession/testing/test_functional_dsession.py py/trunk/py/test/dsession/testing/test_hostmanage.py py/trunk/py/test/plugin/pytest_default.py py/trunk/py/test/testing/acceptance_test.py Log: rename "--hosts" to "--gateways" to make naming more consistent with py.execnet Modified: py/trunk/py/test/dsession/dsession.py ============================================================================== --- py/trunk/py/test/dsession/dsession.py (original) +++ py/trunk/py/test/dsession/dsession.py Wed Mar 18 00:58:06 2009 @@ -63,7 +63,7 @@ config.getvalue('hosts') except KeyError: print "Please specify hosts for distribution of tests:" - print "cmdline: --hosts=host1,host2,..." + print "cmdline: --gateways=host1,host2,..." print "conftest.py: pytest_option_hosts=['host1','host2',]" print "environment: PYTEST_OPTION_HOSTS=host1,host2,host3" print Modified: py/trunk/py/test/dsession/hostmanage.py ============================================================================== --- py/trunk/py/test/dsession/hostmanage.py (original) +++ py/trunk/py/test/dsession/hostmanage.py Wed Mar 18 00:58:06 2009 @@ -4,17 +4,17 @@ from py.__.execnet.gwmanage import GatewayManager from py.__.test import event -def getconfighosts(config): +def getconfiggwspecs(config): if config.option.numprocesses: - hosts = ['popen'] * config.option.numprocesses + gwspecs = ['popen'] * config.option.numprocesses else: - hosts = config.option.hosts - if not hosts: - hosts = config.getvalue("hosts") + gwspecs = config.option.gateways + if not gwspecs: + gwspecs = config.getvalue("gateways") else: - hosts = hosts.split(",") - assert hosts is not None - return hosts + gwspecs = gwspecs.split(",") + assert gwspecs is not None + return gwspecs def getconfigroots(config): roots = config.option.rsyncdirs @@ -34,7 +34,7 @@ def __init__(self, config, hosts=None): self.config = config if hosts is None: - hosts = getconfighosts(self.config) + hosts = getconfiggwspecs(self.config) self.roots = getconfigroots(config) self.gwmanager = GatewayManager(hosts) Modified: py/trunk/py/test/dsession/testing/test_functional_dsession.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_functional_dsession.py (original) +++ py/trunk/py/test/dsession/testing/test_functional_dsession.py Wed Mar 18 00:58:06 2009 @@ -42,7 +42,7 @@ def test_fail(): assert 0 """) - config = testdir.parseconfig('-d', p1, '--hosts=popen') + config = testdir.parseconfig('-d', p1, '--gateways=popen') dsession = DSession(config) eq = EventQueue(config.bus) dsession.main([config.getfsnode(p1)]) @@ -65,7 +65,7 @@ p = subdir.join("test_one.py") p.write("def test_5(): assert not __file__.startswith(%r)" % str(p)) result = testdir.runpytest("-d", "--rsyncdirs=%(subdir)s" % locals(), - "--hosts=popen:%(dest)s" % locals(), p) + "--gateways=popen:%(dest)s" % locals(), p) assert result.ret == 0 result.stdout.fnmatch_lines([ "*1 passed*" @@ -85,7 +85,7 @@ import os assert os.nice(0) == 10 """) - evrec = testdir.inline_run('-d', p1, '--hosts=popen') + evrec = testdir.inline_run('-d', p1, '--gateways=popen') ev = evrec.getreport('test_nice') assert ev.passed Modified: py/trunk/py/test/dsession/testing/test_hostmanage.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_hostmanage.py (original) +++ py/trunk/py/test/dsession/testing/test_hostmanage.py Wed Mar 18 00:58:06 2009 @@ -3,7 +3,7 @@ """ import py -from py.__.test.dsession.hostmanage import HostManager, getconfighosts, getconfigroots +from py.__.test.dsession.hostmanage import HostManager, getconfiggwspecs, getconfigroots from py.__.execnet.gwmanage import GatewaySpec as Host from py.__.test import event @@ -18,7 +18,7 @@ def gethostmanager(self, source, hosts, rsyncdirs=None): def opt(optname, l): return '%s=%s' % (optname, ",".join(map(str, l))) - args = [opt('--hosts', hosts)] + args = [opt('--gateways', hosts)] if rsyncdirs: args.append(opt('--rsyncdir', [source.join(x, abs=True) for x in rsyncdirs])) args.append(source) @@ -148,14 +148,14 @@ assert 0 -def test_getconfighosts_numprocesses(): +def test_getconfiggwspecs_numprocesses(): config = py.test.config._reparse(['-n3']) - hosts = getconfighosts(config) + hosts = getconfiggwspecs(config) assert len(hosts) == 3 -def test_getconfighosts_disthosts(): - config = py.test.config._reparse(['--hosts=a,b,c']) - hosts = getconfighosts(config) +def test_getconfiggwspecs_disthosts(): + config = py.test.config._reparse(['--gateways=a,b,c']) + hosts = getconfiggwspecs(config) assert len(hosts) == 3 assert hosts == ['a', 'b', 'c'] Modified: py/trunk/py/test/plugin/pytest_default.py ============================================================================== --- py/trunk/py/test/plugin/pytest_default.py (original) +++ py/trunk/py/test/plugin/pytest_default.py Wed Mar 18 00:58:06 2009 @@ -94,8 +94,8 @@ group.addoption('--rsyncdirs', dest="rsyncdirs", default=None, metavar="dir1,dir2,...", help="comma-separated list of directories to rsync. All those roots will be rsynced " "into a corresponding subdir on the remote sides. ") - group.addoption('--hosts', dest="hosts", default=None, metavar="host1,host2,...", - help="comma-separated list of host specs to send tests to.") + group.addoption('--gateways', dest="gateways", default=None, metavar="spec1,spec2,...", + help="comma-separated list of gateway specs, used by test distribution modes") group._addoption('--exec', action="store", dest="executable", default=None, help="python executable to run the tests with.") Modified: py/trunk/py/test/testing/acceptance_test.py ============================================================================== --- py/trunk/py/test/testing/acceptance_test.py (original) +++ py/trunk/py/test/testing/acceptance_test.py Wed Mar 18 00:58:06 2009 @@ -265,7 +265,7 @@ py.test.skip("hello") """, ) - result = testdir.runpytest(p1, '-d', '--hosts=popen,popen') + result = testdir.runpytest(p1, '-d', '--gateways=popen,popen') result.stdout.fnmatch_lines([ "HOSTUP: popen*Python*", #"HOSTUP: localhost*Python*", @@ -288,7 +288,7 @@ """, ) testdir.makeconftest(""" - pytest_option_hosts='popen,popen,popen' + pytest_option_gateways='popen,popen,popen' """) result = testdir.runpytest(p1, '-d') result.stdout.fnmatch_lines([ @@ -320,7 +320,7 @@ os.kill(os.getpid(), 15) """ ) - result = testdir.runpytest(p1, '-d', '--hosts=popen,popen,popen') + result = testdir.runpytest(p1, '-d', '--gateways=popen,popen,popen') result.stdout.fnmatch_lines([ "*popen*Python*", "*popen*Python*", From hpk at codespeak.net Wed Mar 18 01:38:06 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 18 Mar 2009 01:38:06 +0100 (CET) Subject: [py-svn] r63019 - in py/trunk/py: io test/dsession test/dsession/testing Message-ID: <20090318003806.90150168417@codespeak.net> Author: hpk Date: Wed Mar 18 01:38:02 2009 New Revision: 63019 Modified: py/trunk/py/io/terminalwriter.py py/trunk/py/test/dsession/hostmanage.py py/trunk/py/test/dsession/testing/test_hostmanage.py Log: some simple ssh tests, always transfer py lib as rsyncroot Modified: py/trunk/py/io/terminalwriter.py ============================================================================== --- py/trunk/py/io/terminalwriter.py (original) +++ py/trunk/py/io/terminalwriter.py Wed Mar 18 01:38:02 2009 @@ -96,7 +96,7 @@ if esc and sys.platform == "win32" and file.isatty(): if 1 in esc: bold = True - esc = tuple(x for x in esc if x != 1) + esc = tuple([x for x in esc if x != 1]) else: bold = False esctable = {() : FOREGROUND_WHITE, # normal Modified: py/trunk/py/test/dsession/hostmanage.py ============================================================================== --- py/trunk/py/test/dsession/hostmanage.py (original) +++ py/trunk/py/test/dsession/hostmanage.py Wed Mar 18 01:38:02 2009 @@ -25,9 +25,16 @@ conftestroots = config.getconftest_pathlist("rsyncdirs") if conftestroots: roots.extend(conftestroots) + pydir = py.path.local(py.__file__).dirpath() for root in roots: if not root.check(): raise ValueError("rsyncdir doesn't exist: %r" %(root,)) + if pydir is not None and root.basename == "py": + if root != pydir: + raise ValueError("root %r conflicts with current %r" %(root, pydir)) + pydir = None + if pydir is not None: + roots.append(pydir) return roots class HostManager(object): @@ -98,6 +105,11 @@ """ % nice).waitclose() self.trace_hoststatus() + multigw = self.gwmanager.getgateways(inplacelocal=False, remote=True) + multigw.remote_exec(""" + import os, sys + sys.path.insert(0, os.getcwd()) + """).waitclose() for gateway in self.gwmanager.gateways: host = gateway.spec Modified: py/trunk/py/test/dsession/testing/test_hostmanage.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_hostmanage.py (original) +++ py/trunk/py/test/dsession/testing/test_hostmanage.py Wed Mar 18 01:38:02 2009 @@ -33,6 +33,7 @@ hm = HostManager(session.config, hosts=[1,2,3]) assert hm.hosts == [1,2,3] + @py.test.mark.xfail("consider / forbid implicit rsyncdirs?") def test_hostmanager_rsync_roots_no_roots(self, source, dest): source.ensure("dir1", "file1").write("hello") config = py.test.config._reparse([source]) @@ -133,6 +134,18 @@ assert l hm.teardown_hosts() + def test_hostmanage_ssh_setup_hosts(self, testdir): + sshhost = py.test.config.getvalueorskip("sshhost") + testdir.makepyfile(__init__="", test_x=""" + def test_one(): + pass + """) + + sorter = testdir.inline_run("-d", "--rsyncdirs=%s" % testdir.tmpdir, + "--gateways=%s" % sshhost, testdir.tmpdir) + ev = sorter.getfirstnamed("itemtestreport") + assert ev.passed + @py.test.mark.xfail("implement double-rsync test") def test_ssh_rsync_samehost_twice(self): option = py.test.config.option @@ -162,8 +175,8 @@ def test_getconfigroots(testdir): config = testdir.parseconfig('--rsyncdirs=' + str(testdir.tmpdir)) roots = getconfigroots(config) - assert len(roots) == 1 - assert roots == [testdir.tmpdir] + assert len(roots) == 1 + 1 + assert testdir.tmpdir in roots def test_getconfigroots_with_conftest(testdir): testdir.chdir() @@ -175,7 +188,7 @@ """) config = testdir.parseconfig(testdir.tmpdir, '--rsyncdirs=y,z') roots = getconfigroots(config) - assert len(roots) == 3 + assert len(roots) == 3 + 1 assert py.path.local('y') in roots assert py.path.local('z') in roots assert testdir.tmpdir.join('x') in roots From hpk at codespeak.net Wed Mar 18 02:13:10 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 18 Mar 2009 02:13:10 +0100 (CET) Subject: [py-svn] r63020 - in py/trunk/py: execnet execnet/testing io/testing test/dsession/testing Message-ID: <20090318011310.B39F1168413@codespeak.net> Author: hpk Date: Wed Mar 18 02:13:07 2009 New Revision: 63020 Modified: py/trunk/py/execnet/gwmanage.py py/trunk/py/execnet/register.py py/trunk/py/execnet/testing/test_gateway.py py/trunk/py/execnet/testing/test_gwmanage.py py/trunk/py/io/testing/test_terminalwriter.py py/trunk/py/test/dsession/testing/test_functional_dsession.py py/trunk/py/test/dsession/testing/test_hostmanage.py Log: allow to specify python executable in gatewayspecs, fix a few tests Modified: py/trunk/py/execnet/gwmanage.py ============================================================================== --- py/trunk/py/execnet/gwmanage.py (original) +++ py/trunk/py/execnet/gwmanage.py Wed Mar 18 02:13:07 2009 @@ -24,11 +24,17 @@ NO_ENDMARKER_WANTED = object() class GatewaySpec(object): + python = "python" def __init__(self, spec, defaultjoinpath="pyexecnetcache"): if spec == "popen" or spec.startswith("popen:"): - self.address = "popen" - self.joinpath = spec[len(self.address)+1:] - self.type = "popen" + parts = spec.split(":", 2) + self.type = self.address = parts.pop(0) + if parts: + self.python = parts.pop(0) + if parts: + self.joinpath = parts.pop(0) + else: + self.joinpath = "" elif spec.startswith("socket:"): parts = spec[7:].split(":", 2) self.address = parts.pop(0) @@ -40,8 +46,9 @@ else: if spec.startswith("ssh:"): spec = spec[4:] - parts = spec.split(":", 1) + parts = spec.split(":", 2) self.address = parts.pop(0) + self.python = parts and parts.pop(0) or "python" self.joinpath = parts and parts.pop(0) or "" self.type = "ssh" if not self.joinpath and not self.inplacelocal(): @@ -54,13 +61,13 @@ return "" % (self.address, self.joinpath) __repr__ = __str__ - def makegateway(self, python=None, waitclose=True): + def makegateway(self, waitclose=True): if self.type == "popen": - gw = py.execnet.PopenGateway(python=python) + gw = py.execnet.PopenGateway(python=self.python) elif self.type == "socket": gw = py.execnet.SocketGateway(*self.address) elif self.type == "ssh": - gw = py.execnet.SshGateway(self.address, remotepython=python) + gw = py.execnet.SshGateway(self.address, remotepython=self.python) if self.joinpath: channel = gw.remote_exec(""" import os Modified: py/trunk/py/execnet/register.py ============================================================================== --- py/trunk/py/execnet/register.py (original) +++ py/trunk/py/execnet/register.py Wed Mar 18 02:13:07 2009 @@ -76,7 +76,7 @@ """ instantiate a gateway to a subprocess started with the given 'python' executable. """ - if python is None: + if not python: python = sys.executable cmd = '%s -u -c "exec input()"' % python super(PopenGateway, self).__init__(cmd) Modified: py/trunk/py/execnet/testing/test_gateway.py ============================================================================== --- py/trunk/py/execnet/testing/test_gateway.py (original) +++ py/trunk/py/execnet/testing/test_gateway.py Wed Mar 18 02:13:07 2009 @@ -570,9 +570,8 @@ class TestSshGateway(BasicRemoteExecution): def setup_class(cls): - if py.test.config.option.sshhost is None: - py.test.skip("no known ssh target, use --sshhost to set one") - cls.gw = py.execnet.SshGateway(py.test.config.option.sshhost) + sshhost = py.test.config.getvalueorskip("sshhost") + cls.gw = py.execnet.SshGateway(sshhost) def test_sshconfig_functional(self): tmpdir = py.test.ensuretemp("test_sshconfig") Modified: py/trunk/py/execnet/testing/test_gwmanage.py ============================================================================== --- py/trunk/py/execnet/testing/test_gwmanage.py (original) +++ py/trunk/py/execnet/testing/test_gwmanage.py Wed Mar 18 02:13:07 2009 @@ -18,29 +18,38 @@ [ssh:]spec:path SshGateway * [SshGateway] """ - def test_popen_nopath(self): - for joinpath in ('', ':abc', ':ab:cd', ':/x/y'): - spec = GatewaySpec("popen" + joinpath) - assert spec.address == "popen" - assert spec.joinpath == joinpath[1:] - assert spec.type == "popen" - spec2 = GatewaySpec("popen" + joinpath) - self._equality(spec, spec2) + def test_popen(self): + for python in ('', 'python2.4'): + for joinpath in ('', 'abc', 'ab:cd', '/x/y'): + s = ":".join(["popen", python, joinpath]) + print s + spec = GatewaySpec(s) + assert spec.address == "popen" + assert spec.python == python + assert spec.joinpath == joinpath + assert spec.type == "popen" + spec2 = GatewaySpec("popen" + joinpath) + self._equality(spec, spec2) def test_ssh(self): - for prefix in ('ssh:', ''): # ssh is default + for prefix in ('ssh', ''): # ssh is default for hostpart in ('x.y', 'xyz at x.y'): - for joinpath in ('', ':abc', ':ab:cd', ':/tmp'): - specstring = prefix + hostpart + joinpath - spec = GatewaySpec(specstring) - assert spec.address == hostpart - if joinpath[1:]: - assert spec.joinpath == joinpath[1:] - else: - assert spec.joinpath == "pyexecnetcache" - assert spec.type == "ssh" - spec2 = GatewaySpec(specstring) - self._equality(spec, spec2) + for python in ('python', 'python2.5'): + for joinpath in ('', 'abc', 'ab:cd', '/tmp'): + specstring = ":".join([prefix, hostpart, python, joinpath]) + if specstring[0] == ":": + specstring = specstring[1:] + print specstring + spec = GatewaySpec(specstring) + assert spec.address == hostpart + assert spec.python == python + if joinpath: + assert spec.joinpath == joinpath + else: + assert spec.joinpath == "pyexecnetcache" + assert spec.type == "ssh" + spec2 = GatewaySpec(specstring) + self._equality(spec, spec2) def test_socket(self): for hostpart in ('x.y', 'x', 'popen'): @@ -72,17 +81,17 @@ gw.exit() def test_popen_makegateway(self, testdir): - spec = GatewaySpec("popen:" + str(testdir.tmpdir)) + spec = GatewaySpec("popen::" + str(testdir.tmpdir)) gw = spec.makegateway() p = gw.remote_exec("import os; channel.send(os.getcwd())").receive() assert spec.joinpath == p gw.exit() def test_popen_makegateway_python(self, testdir): - spec = GatewaySpec("popen") - gw = spec.makegateway(python=py.std.sys.executable) + spec = GatewaySpec("popen:%s" % py.std.sys.executable) + gw = spec.makegateway() res = gw.remote_exec("import sys ; channel.send(sys.executable)").receive() - assert py.std.sys.executable == res + assert py.std.sys.executable == py.std.sys.executable gw.exit() def test_ssh(self): @@ -118,7 +127,7 @@ assert not len(hm.gateways) def test_hostmanager_rsync_popen_with_path(self, source, dest): - hm = GatewayManager(["popen:%s" %dest] * 1) + hm = GatewayManager(["popen::%s" %dest] * 1) hm.makegateways() source.ensure("dir1", "dir2", "hello") l = [] @@ -146,7 +155,7 @@ def test_multi_chdir_popen_with_path(self, testdir): import os - hm = GatewayManager(["popen:hello"] * 2) + hm = GatewayManager(["popen::hello"] * 2) testdir.tmpdir.chdir() hellopath = testdir.tmpdir.mkdir("hello") hm.makegateways() @@ -253,7 +262,7 @@ assert 'somedir' in basenames def test_hrsync_one_host(self, source, dest): - spec = GatewaySpec("popen:%s" % dest) + spec = GatewaySpec("popen::%s" % dest) gw = spec.makegateway() finished = [] rsync = HostRSync(source) Modified: py/trunk/py/io/testing/test_terminalwriter.py ============================================================================== --- py/trunk/py/io/testing/test_terminalwriter.py (original) +++ py/trunk/py/io/testing/test_terminalwriter.py Wed Mar 18 02:13:07 2009 @@ -18,7 +18,7 @@ py.magic.patch(terminalwriter, '_getdimensions', lambda: 0/0) try: tw = py.io.TerminalWriter() - assert tw.fullwidth == os.environ.get('COLUMNS', 80)-1 + assert tw.fullwidth == int(os.environ.get('COLUMNS', 80)) -1 finally: py.magic.revert(terminalwriter, '_getdimensions') Modified: py/trunk/py/test/dsession/testing/test_functional_dsession.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_functional_dsession.py (original) +++ py/trunk/py/test/dsession/testing/test_functional_dsession.py Wed Mar 18 02:13:07 2009 @@ -65,7 +65,7 @@ p = subdir.join("test_one.py") p.write("def test_5(): assert not __file__.startswith(%r)" % str(p)) result = testdir.runpytest("-d", "--rsyncdirs=%(subdir)s" % locals(), - "--gateways=popen:%(dest)s" % locals(), p) + "--gateways=popen::%(dest)s" % locals(), p) assert result.ret == 0 result.stdout.fnmatch_lines([ "*1 passed*" Modified: py/trunk/py/test/dsession/testing/test_hostmanage.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_hostmanage.py (original) +++ py/trunk/py/test/dsession/testing/test_hostmanage.py Wed Mar 18 02:13:07 2009 @@ -37,7 +37,7 @@ def test_hostmanager_rsync_roots_no_roots(self, source, dest): source.ensure("dir1", "file1").write("hello") config = py.test.config._reparse([source]) - hm = HostManager(config, hosts=["popen:%s" % dest]) + hm = HostManager(config, hosts=["popen::%s" % dest]) assert hm.config.topdir == source == config.topdir hm.rsync_roots() p, = hm.gwmanager.multi_exec("import os ; channel.send(os.getcwd())").receive_each() @@ -51,7 +51,7 @@ dir2 = source.ensure("dir1", "dir2", dir=1) dir2.ensure("hello") hm = self.gethostmanager(source, - hosts = ["popen:%s" % dest], + hosts = ["popen::%s" % dest], rsyncdirs = ['dir1'] ) assert hm.config.topdir == source @@ -64,7 +64,7 @@ dir2 = source.ensure("dir1", "dir2", dir=1) dir2.ensure("hello") hm = self.gethostmanager(source, - hosts = ["popen:%s" % dest], + hosts = ["popen::%s" % dest], rsyncdirs = [str(source)] ) assert hm.config.topdir == source @@ -84,7 +84,7 @@ """)) session = py.test.config._reparse([source]).initsession() hm = HostManager(session.config, - hosts=["popen:" + str(dest)]) + hosts=["popen::" + str(dest)]) hm.rsync_roots() assert dest.join("dir2").check() assert not dest.join("dir1").check() @@ -101,7 +101,7 @@ """)) session = py.test.config._reparse([source]).initsession() hm = HostManager(session.config, - hosts=["popen:" + str(dest)]) + hosts=["popen::" + str(dest)]) hm.rsync_roots() assert dest.join("dir1").check() assert not dest.join("dir1", "dir2").check() From hpk at codespeak.net Wed Mar 18 09:10:08 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 18 Mar 2009 09:10:08 +0100 (CET) Subject: [py-svn] r63024 - py/trunk/py/execnet Message-ID: <20090318081008.5EAC0168416@codespeak.net> Author: hpk Date: Wed Mar 18 09:10:06 2009 New Revision: 63024 Modified: py/trunk/py/execnet/register.py Log: don't close_fds by default Modified: py/trunk/py/execnet/register.py ============================================================================== --- py/trunk/py/execnet/register.py (original) +++ py/trunk/py/execnet/register.py Wed Mar 18 09:10:06 2009 @@ -61,7 +61,9 @@ class PopenCmdGateway(InstallableGateway): def __init__(self, cmd): - p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, close_fds=True) + # on win close_fds=True does not work, not sure it'd needed + #p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, close_fds=True) + p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE) infile, outfile = p.stdin, p.stdout self._cmd = cmd io = inputoutput.Popen2IO(infile, outfile) From hpk at codespeak.net Wed Mar 18 12:18:42 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 18 Mar 2009 12:18:42 +0100 (CET) Subject: [py-svn] r63027 - in py/trunk/py: . doc execnet execnet/testing Message-ID: <20090318111842.5DF7D168453@codespeak.net> Author: hpk Date: Wed Mar 18 12:18:39 2009 New Revision: 63027 Added: py/trunk/py/execnet/testing/test_gwspec.py - copied, changed from r63024, py/trunk/py/execnet/testing/test_gwmanage.py py/trunk/py/execnet/testing/test_multi.py - copied, changed from r63024, py/trunk/py/execnet/testing/test_gwmanage.py Modified: py/trunk/py/__init__.py py/trunk/py/doc/execnet.txt py/trunk/py/execnet/gwmanage.py py/trunk/py/execnet/testing/test_gwmanage.py Log: new API: py.execnet.MultiGateway py.execnet.MultiChannel py.execnet.GatewaySpec with some docs and streamlined tests. Modified: py/trunk/py/__init__.py ============================================================================== --- py/trunk/py/__init__.py (original) +++ py/trunk/py/__init__.py Wed Mar 18 12:18:39 2009 @@ -151,6 +151,9 @@ 'execnet.SocketGateway' : ('./execnet/register.py', 'SocketGateway'), 'execnet.PopenGateway' : ('./execnet/register.py', 'PopenGateway'), 'execnet.SshGateway' : ('./execnet/register.py', 'SshGateway'), + 'execnet.GatewaySpec' : ('./execnet/gwmanage.py', 'GatewaySpec'), + 'execnet.MultiGateway' : ('./execnet/gwmanage.py', 'MultiGateway'), + 'execnet.MultiChannel' : ('./execnet/gwmanage.py', 'MultiChannel'), # execnet scripts 'execnet.RSync' : ('./execnet/rsync.py', 'RSync'), Modified: py/trunk/py/doc/execnet.txt ============================================================================== --- py/trunk/py/doc/execnet.txt (original) +++ py/trunk/py/doc/execnet.txt Wed Mar 18 12:18:39 2009 @@ -38,18 +38,11 @@ With ''py.execnet'' you get the means -- to navigate through the network with Process, Thread, SSH - and Socket- gateways that allow you ... +- to execute python code fragements in remote processes and +- to interchange data between asynchronously executing code fragments -- to distribute your program across a network and define - 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 - even that is virtualized. - -Available Gateways/Connection methods +Available Gateways ----------------------------------------- You may use one of the following connection methods: @@ -68,10 +61,11 @@ script. You can run this "server script" without having the py lib installed on that remote system. -Remote execution approach + +executing code remotely ------------------------------------- -All gateways offer one main high level function: +All gateways offer remote code execution via this high level function: def remote_exec(source): """return channel object for communicating with the asynchronously @@ -94,29 +88,20 @@ >>> remote_pid != py.std.os.getpid() True -`remote_exec` implements the idea to ``determine -protocol and remote code from the client/local side``. -This makes distributing a program run in an ad-hoc -manner (using e.g. :api:`py.execnet.SshGateway`) very easy. - -You should not need to maintain software on the other sides -you are running your code at, other than the Python -executable itself. - .. _`Channel`: .. _`channel-api`: .. _`exchange data`: -The **Channel** interface for exchanging data across gateways +Bidirectionally exchange data between hosts ------------------------------------------------------------- -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 current interface:: +A channel object allows to send and receive data between +two asynchronously running programs. When calling +`remote_exec` you will get a channel object back and +the code fragement running on the other side will +see a channel object in its global namespace. + +Here is the interface of channel objects:: # # API for sending and receiving anonymous values @@ -125,7 +110,7 @@ sends the given item to the other side of the channel, possibly blocking if the sender queue is full. Note that items need to be marshallable (all basic - python types are): + python types are). channel.receive(): receives an item that was sent from the other side, @@ -146,25 +131,93 @@ A remote side blocking on receive() on this channel will get woken up and see an EOFError exception. +Instantiating a gateway from a string-specification +--------------------------------------------------------- + +To specify Gateways with a String:: + + >>> import py + >>> gwspec = py.execnet.GatewaySpec("popen") + >>> gw = gwspec.makegateway() + >>> ex = gw.remote_exec("import sys ; channel.send(sys.executable)").receive() + >>> assert ex == py.std.sys.executable + >>> + +current gateway types and specifications ++++++++++++++++++++++++++++++++++++++++++++++++ + ++------------------------+-------------------------------------------+ +| ssh host | ssh:host:pythonexecutable:path | ++------------------------+-------------------------------------------+ +| local subprocess | popen:python_executable:path | ++------------------------+-------------------------------------------+ +| remote socket process | socket:host:port:python_executable:path | ++------------------------+-------------------------------------------+ + +examples of valid specifications +++++++++++++++++++++++++++++++++++++++ + +``ssh:wyvern:python2.4`` signifies a connection to a Python process on the machine reached via "ssh wyvern", current dir will be the 'pyexecnet-cache' subdirectory. + +``socket:192.168.1.4`` signifies a connection to a Python Socket server process to the given IP on the default port 8888; current dir will be the 'pyexecnet-cache' directory. + +``popen:python2.5`` signifies a connection to a python2.5 subprocess; current dir will be the current dir of the instantiator. + +``popen::pytest-cache1`` signifies a connection to a subprocess using ``sys.executable``; current dir will be the `pytest-cache1`. -The complete Fileserver example -........................................ +Examples for execnet usage +------------------------------------------- + +Example: compare cwd() of Popen Gateways +++++++++++++++++++++++++++++++++++++++++ + +A PopenGateway has the same working directory as the instantiatior:: + + >>> import py, os + >>> gw = py.execnet.PopenGateway() + >>> ch = gw.remote_exec("import os; channel.send(os.getcwd())") + >>> res = ch.receive() + >>> assert res == os.getcwd() + >>> gw.exit() + +Example: multichannels +++++++++++++++++++++++++++++++++++++++++ + +MultiChannels manage 1-n execution and communication: + + >>> import py + >>> ch1 = py.execnet.PopenGateway().remote_exec("channel.send(1)") + >>> ch2 = py.execnet.PopenGateway().remote_exec("channel.send(2)") + >>> mch = py.execnet.MultiChannel([ch1, ch2]) + >>> l = mch.receive_each() + >>> assert len(l) == 2 + >>> assert 1 in l + >>> assert 2 in l + +MultiGateways help with sending code to multiple remote places: + + >>> import py + >>> mgw = py.execnet.MultiGateway([py.execnet.PopenGateway() for x in range(2)]) + >>> mch = mgw.remote_exec("import os; channel.send(os.getcwd())") + >>> res = mch.receive_each() + >>> assert res == [os.getcwd()] * 2, res + >>> mgw.exit() + + +Example: receiving file contents from remote places +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ problem: retrieving contents of remote files:: import py - contentserverbootstrap = py.code.Source( - """ + # open a gateway to a fresh child process + gw = py.execnet.SshGateway('codespeak.net') + channel = gw.remote_exec(""" for fn in channel: f = open(fn, 'rb') - try: - channel.send(f.read()) - finally: - f.close() - """) - # open a gateway to a fresh child process - contentgateway = py.execnet.SshGateway('codespeak.net') - channel = contentgateway.remote_exec(contentserverbootstrap) + channel.send(f.read()) + f.close() + """) for fn in somefilelist: channel.send(fn) @@ -218,7 +271,4 @@ gw = py.execnet.SocketGateway('localhost', cls.port) print "initialized socket gateway to port", cls.port -.. [#] 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 Modified: py/trunk/py/execnet/gwmanage.py ============================================================================== --- py/trunk/py/execnet/gwmanage.py (original) +++ py/trunk/py/execnet/gwmanage.py Wed Mar 18 12:18:39 2009 @@ -140,6 +140,9 @@ for gw in self.gateways: channels.append(gw.remote_exec(source)) return MultiChannel(channels) + def exit(self): + for gw in self.gateways: + gw.exit() class GatewayManager: RemoteError = RemoteError Modified: py/trunk/py/execnet/testing/test_gwmanage.py ============================================================================== --- py/trunk/py/execnet/testing/test_gwmanage.py (original) +++ py/trunk/py/execnet/testing/test_gwmanage.py Wed Mar 18 12:18:39 2009 @@ -8,103 +8,7 @@ """ import py -from py.__.execnet.gwmanage import GatewaySpec, GatewayManager -from py.__.execnet.gwmanage import HostRSync - -class TestGatewaySpec: - """ - socket:hostname:port:path SocketGateway - popen[-executable][:path] PopenGateway - [ssh:]spec:path SshGateway - * [SshGateway] - """ - def test_popen(self): - for python in ('', 'python2.4'): - for joinpath in ('', 'abc', 'ab:cd', '/x/y'): - s = ":".join(["popen", python, joinpath]) - print s - spec = GatewaySpec(s) - assert spec.address == "popen" - assert spec.python == python - assert spec.joinpath == joinpath - assert spec.type == "popen" - spec2 = GatewaySpec("popen" + joinpath) - self._equality(spec, spec2) - - def test_ssh(self): - for prefix in ('ssh', ''): # ssh is default - for hostpart in ('x.y', 'xyz at x.y'): - for python in ('python', 'python2.5'): - for joinpath in ('', 'abc', 'ab:cd', '/tmp'): - specstring = ":".join([prefix, hostpart, python, joinpath]) - if specstring[0] == ":": - specstring = specstring[1:] - print specstring - spec = GatewaySpec(specstring) - assert spec.address == hostpart - assert spec.python == python - if joinpath: - assert spec.joinpath == joinpath - else: - assert spec.joinpath == "pyexecnetcache" - assert spec.type == "ssh" - spec2 = GatewaySpec(specstring) - self._equality(spec, spec2) - - def test_socket(self): - for hostpart in ('x.y', 'x', 'popen'): - for port in ":80", ":1000": - for joinpath in ('', ':abc', ':abc:de'): - spec = GatewaySpec("socket:" + hostpart + port + joinpath) - assert spec.address == (hostpart, int(port[1:])) - if joinpath[1:]: - assert spec.joinpath == joinpath[1:] - else: - assert spec.joinpath == "pyexecnetcache" - assert spec.type == "socket" - spec2 = GatewaySpec("socket:" + hostpart + port + joinpath) - self._equality(spec, spec2) - - def _equality(self, spec1, spec2): - assert spec1 != spec2 - assert hash(spec1) != hash(spec2) - assert not (spec1 == spec2) - - -class TestGatewaySpecAPI: - def test_popen_nopath_makegateway(self, testdir): - spec = GatewaySpec("popen") - gw = spec.makegateway() - p = gw.remote_exec("import os; channel.send(os.getcwd())").receive() - curdir = py.std.os.getcwd() - assert curdir == p - gw.exit() - - def test_popen_makegateway(self, testdir): - spec = GatewaySpec("popen::" + str(testdir.tmpdir)) - gw = spec.makegateway() - p = gw.remote_exec("import os; channel.send(os.getcwd())").receive() - assert spec.joinpath == p - gw.exit() - - def test_popen_makegateway_python(self, testdir): - spec = GatewaySpec("popen:%s" % py.std.sys.executable) - gw = spec.makegateway() - res = gw.remote_exec("import sys ; channel.send(sys.executable)").receive() - assert py.std.sys.executable == py.std.sys.executable - gw.exit() - - def test_ssh(self): - sshhost = py.test.config.getvalueorskip("sshhost") - spec = GatewaySpec("ssh:" + sshhost) - gw = spec.makegateway() - p = gw.remote_exec("import os ; channel.send(os.getcwd())").receive() - gw.exit() - - @py.test.mark.xfail("implement socketserver test scenario") - def test_socketgateway(self): - gw = py.execnet.PopenGateway() - spec = GatewaySpec("ssh:" + sshhost) +from py.__.execnet.gwmanage import GatewayManager, HostRSync class TestGatewayManagerPopen: def test_hostmanager_popen_makegateway(self): @@ -191,56 +95,6 @@ assert l[0].startswith(curwd) assert l[0].endswith("hello") -from py.__.execnet.gwmanage import MultiChannel -class TestMultiChannel: - def test_multichannel_receive_each(self): - class pseudochannel: - def receive(self): - return 12 - - pc1 = pseudochannel() - pc2 = pseudochannel() - multichannel = MultiChannel([pc1, pc2]) - l = multichannel.receive_each(withchannel=True) - assert len(l) == 2 - assert l == [(pc1, 12), (pc2, 12)] - l = multichannel.receive_each(withchannel=False) - assert l == [12,12] - - def test_multichannel_send_each(self): - gm = GatewayManager(['popen'] * 2) - mc = gm.multi_exec(""" - import os - channel.send(channel.receive() + 1) - """) - mc.send_each(41) - l = mc.receive_each() - assert l == [42,42] - - def test_multichannel_receive_queue(self): - gm = GatewayManager(['popen'] * 2) - mc = gm.multi_exec(""" - import os - channel.send(os.getpid()) - """) - queue = mc.make_receive_queue() - ch, item = queue.get(timeout=10) - ch2, item2 = queue.get(timeout=10) - assert ch != ch2 - assert ch.gateway != ch2.gateway - assert item != item2 - mc.waitclose() - - def test_multichannel_waitclose(self): - l = [] - class pseudochannel: - def waitclose(self): - l.append(0) - multichannel = MultiChannel([pseudochannel(), pseudochannel()]) - multichannel.waitclose() - assert len(l) == 2 - - def pytest_pyfuncarg_source(pyfuncitem): return py.test.ensuretemp(pyfuncitem.getmodpath()).mkdir("source") def pytest_pyfuncarg_dest(pyfuncitem): @@ -262,7 +116,7 @@ assert 'somedir' in basenames def test_hrsync_one_host(self, source, dest): - spec = GatewaySpec("popen::%s" % dest) + spec = py.execnet.GatewaySpec("popen::%s" % dest) gw = spec.makegateway() finished = [] rsync = HostRSync(source) Copied: py/trunk/py/execnet/testing/test_gwspec.py (from r63024, py/trunk/py/execnet/testing/test_gwmanage.py) ============================================================================== --- py/trunk/py/execnet/testing/test_gwmanage.py (original) +++ py/trunk/py/execnet/testing/test_gwspec.py Wed Mar 18 12:18:39 2009 @@ -1,15 +1,8 @@ """ - tests for - - gateway specifications - - multi channels and multi gateways - - gateway management - - manage rsyncing of hosts - + tests for py.execnet.GatewaySpec """ import py -from py.__.execnet.gwmanage import GatewaySpec, GatewayManager -from py.__.execnet.gwmanage import HostRSync class TestGatewaySpec: """ @@ -23,12 +16,12 @@ for joinpath in ('', 'abc', 'ab:cd', '/x/y'): s = ":".join(["popen", python, joinpath]) print s - spec = GatewaySpec(s) + spec = py.execnet.GatewaySpec(s) assert spec.address == "popen" assert spec.python == python assert spec.joinpath == joinpath assert spec.type == "popen" - spec2 = GatewaySpec("popen" + joinpath) + spec2 = py.execnet.GatewaySpec("popen" + joinpath) self._equality(spec, spec2) def test_ssh(self): @@ -40,7 +33,7 @@ if specstring[0] == ":": specstring = specstring[1:] print specstring - spec = GatewaySpec(specstring) + spec = py.execnet.GatewaySpec(specstring) assert spec.address == hostpart assert spec.python == python if joinpath: @@ -48,21 +41,21 @@ else: assert spec.joinpath == "pyexecnetcache" assert spec.type == "ssh" - spec2 = GatewaySpec(specstring) + spec2 = py.execnet.GatewaySpec(specstring) self._equality(spec, spec2) def test_socket(self): for hostpart in ('x.y', 'x', 'popen'): for port in ":80", ":1000": for joinpath in ('', ':abc', ':abc:de'): - spec = GatewaySpec("socket:" + hostpart + port + joinpath) + spec = py.execnet.GatewaySpec("socket:" + hostpart + port + joinpath) assert spec.address == (hostpart, int(port[1:])) if joinpath[1:]: assert spec.joinpath == joinpath[1:] else: assert spec.joinpath == "pyexecnetcache" assert spec.type == "socket" - spec2 = GatewaySpec("socket:" + hostpart + port + joinpath) + spec2 = py.execnet.GatewaySpec("socket:" + hostpart + port + joinpath) self._equality(spec, spec2) def _equality(self, spec1, spec2): @@ -73,7 +66,7 @@ class TestGatewaySpecAPI: def test_popen_nopath_makegateway(self, testdir): - spec = GatewaySpec("popen") + spec = py.execnet.GatewaySpec("popen") gw = spec.makegateway() p = gw.remote_exec("import os; channel.send(os.getcwd())").receive() curdir = py.std.os.getcwd() @@ -81,14 +74,14 @@ gw.exit() def test_popen_makegateway(self, testdir): - spec = GatewaySpec("popen::" + str(testdir.tmpdir)) + spec = py.execnet.GatewaySpec("popen::" + str(testdir.tmpdir)) gw = spec.makegateway() p = gw.remote_exec("import os; channel.send(os.getcwd())").receive() assert spec.joinpath == p gw.exit() def test_popen_makegateway_python(self, testdir): - spec = GatewaySpec("popen:%s" % py.std.sys.executable) + spec = py.execnet.GatewaySpec("popen:%s" % py.std.sys.executable) gw = spec.makegateway() res = gw.remote_exec("import sys ; channel.send(sys.executable)").receive() assert py.std.sys.executable == py.std.sys.executable @@ -96,7 +89,7 @@ def test_ssh(self): sshhost = py.test.config.getvalueorskip("sshhost") - spec = GatewaySpec("ssh:" + sshhost) + spec = py.execnet.GatewaySpec("ssh:" + sshhost) gw = spec.makegateway() p = gw.remote_exec("import os ; channel.send(os.getcwd())").receive() gw.exit() @@ -104,172 +97,5 @@ @py.test.mark.xfail("implement socketserver test scenario") def test_socketgateway(self): gw = py.execnet.PopenGateway() - spec = GatewaySpec("ssh:" + sshhost) - -class TestGatewayManagerPopen: - def test_hostmanager_popen_makegateway(self): - hm = GatewayManager(["popen"] * 2) - hm.makegateways() - assert len(hm.gateways) == 2 - hm.exit() - assert not len(hm.gateways) - - def test_hostmanager_popens_rsync(self, source): - hm = GatewayManager(["popen"] * 2) - hm.makegateways() - assert len(hm.gateways) == 2 - for gw in hm.gateways: - gw.remote_exec = None - l = [] - hm.rsync(source, notify=lambda *args: l.append(args)) - assert not l - hm.exit() - assert not len(hm.gateways) - - def test_hostmanager_rsync_popen_with_path(self, source, dest): - hm = GatewayManager(["popen::%s" %dest] * 1) - hm.makegateways() - source.ensure("dir1", "dir2", "hello") - l = [] - hm.rsync(source, notify=lambda *args: l.append(args)) - assert len(l) == 1 - assert l[0] == ("rsyncrootready", hm.gateways[0].spec, source) - hm.exit() - dest = dest.join(source.basename) - assert dest.join("dir1").check() - assert dest.join("dir1", "dir2").check() - assert dest.join("dir1", "dir2", 'hello').check() - - def XXXtest_ssh_rsync_samehost_twice(self): - #XXX we have no easy way to have a temp directory remotely! - option = py.test.config.option - if option.sshhost is None: - py.test.skip("no known ssh target, use -S to set one") - host1 = Host("%s" % (option.sshhost, )) - host2 = Host("%s" % (option.sshhost, )) - hm = HostManager(config, hosts=[host1, host2]) - events = [] - hm.init_rsync(events.append) - print events - assert 0 - - def test_multi_chdir_popen_with_path(self, testdir): - import os - hm = GatewayManager(["popen::hello"] * 2) - testdir.tmpdir.chdir() - hellopath = testdir.tmpdir.mkdir("hello") - hm.makegateways() - l = hm.multi_exec("import os ; channel.send(os.getcwd())").receive_each() - paths = [x[1] for x in l] - assert l == [str(hellopath)] * 2 - py.test.raises(hm.RemoteError, 'hm.multi_chdir("world", inplacelocal=False)') - worldpath = hellopath.mkdir("world") - hm.multi_chdir("world", inplacelocal=False) - l = hm.multi_exec("import os ; channel.send(os.getcwd())").receive_each() - assert len(l) == 2 - assert l[0] == l[1] - curwd = os.getcwd() - assert l[0].startswith(curwd) - assert l[0].endswith("world") - - def test_multi_chdir_popen(self, testdir): - import os - hm = GatewayManager(["popen"] * 2) - testdir.tmpdir.chdir() - hellopath = testdir.tmpdir.mkdir("hello") - hm.makegateways() - hm.multi_chdir("hello", inplacelocal=False) - l = hm.multi_exec("import os ; channel.send(os.getcwd())").receive_each() - assert len(l) == 2 - assert l == [os.getcwd()] * 2 - - hm.multi_chdir("hello") - l = hm.multi_exec("import os ; channel.send(os.getcwd())").receive_each() - assert len(l) == 2 - assert l[0] == l[1] - curwd = os.getcwd() - assert l[0].startswith(curwd) - assert l[0].endswith("hello") - -from py.__.execnet.gwmanage import MultiChannel -class TestMultiChannel: - def test_multichannel_receive_each(self): - class pseudochannel: - def receive(self): - return 12 - - pc1 = pseudochannel() - pc2 = pseudochannel() - multichannel = MultiChannel([pc1, pc2]) - l = multichannel.receive_each(withchannel=True) - assert len(l) == 2 - assert l == [(pc1, 12), (pc2, 12)] - l = multichannel.receive_each(withchannel=False) - assert l == [12,12] - - def test_multichannel_send_each(self): - gm = GatewayManager(['popen'] * 2) - mc = gm.multi_exec(""" - import os - channel.send(channel.receive() + 1) - """) - mc.send_each(41) - l = mc.receive_each() - assert l == [42,42] - - def test_multichannel_receive_queue(self): - gm = GatewayManager(['popen'] * 2) - mc = gm.multi_exec(""" - import os - channel.send(os.getpid()) - """) - queue = mc.make_receive_queue() - ch, item = queue.get(timeout=10) - ch2, item2 = queue.get(timeout=10) - assert ch != ch2 - assert ch.gateway != ch2.gateway - assert item != item2 - mc.waitclose() - - def test_multichannel_waitclose(self): - l = [] - class pseudochannel: - def waitclose(self): - l.append(0) - multichannel = MultiChannel([pseudochannel(), pseudochannel()]) - multichannel.waitclose() - assert len(l) == 2 - - -def pytest_pyfuncarg_source(pyfuncitem): - return py.test.ensuretemp(pyfuncitem.getmodpath()).mkdir("source") -def pytest_pyfuncarg_dest(pyfuncitem): - return py.test.ensuretemp(pyfuncitem.getmodpath()).mkdir("dest") - -class TestHRSync: - def test_hrsync_filter(self, source, dest): - source.ensure("dir", "file.txt") - source.ensure(".svn", "entries") - source.ensure(".somedotfile", "moreentries") - source.ensure("somedir", "editfile~") - syncer = HostRSync(source) - l = list(source.visit(rec=syncer.filter, - fil=syncer.filter)) - assert len(l) == 3 - basenames = [x.basename for x in l] - assert 'dir' in basenames - assert 'file.txt' in basenames - assert 'somedir' in basenames - - def test_hrsync_one_host(self, source, dest): - spec = GatewaySpec("popen::%s" % dest) - gw = spec.makegateway() - finished = [] - rsync = HostRSync(source) - rsync.add_target_host(gw, finished=lambda: finished.append(1)) - source.join("hello.py").write("world") - rsync.send() - gw.exit() - assert dest.join(source.basename, "hello.py").check() - assert len(finished) == 1 + spec = py.execnet.GatewaySpec("ssh:" + sshhost) Copied: py/trunk/py/execnet/testing/test_multi.py (from r63024, py/trunk/py/execnet/testing/test_gwmanage.py) ============================================================================== --- py/trunk/py/execnet/testing/test_gwmanage.py (original) +++ py/trunk/py/execnet/testing/test_multi.py Wed Mar 18 12:18:39 2009 @@ -1,198 +1,12 @@ """ tests for - - gateway specifications - multi channels and multi gateways - - gateway management - - manage rsyncing of hosts """ import py -from py.__.execnet.gwmanage import GatewaySpec, GatewayManager -from py.__.execnet.gwmanage import HostRSync -class TestGatewaySpec: - """ - socket:hostname:port:path SocketGateway - popen[-executable][:path] PopenGateway - [ssh:]spec:path SshGateway - * [SshGateway] - """ - def test_popen(self): - for python in ('', 'python2.4'): - for joinpath in ('', 'abc', 'ab:cd', '/x/y'): - s = ":".join(["popen", python, joinpath]) - print s - spec = GatewaySpec(s) - assert spec.address == "popen" - assert spec.python == python - assert spec.joinpath == joinpath - assert spec.type == "popen" - spec2 = GatewaySpec("popen" + joinpath) - self._equality(spec, spec2) - - def test_ssh(self): - for prefix in ('ssh', ''): # ssh is default - for hostpart in ('x.y', 'xyz at x.y'): - for python in ('python', 'python2.5'): - for joinpath in ('', 'abc', 'ab:cd', '/tmp'): - specstring = ":".join([prefix, hostpart, python, joinpath]) - if specstring[0] == ":": - specstring = specstring[1:] - print specstring - spec = GatewaySpec(specstring) - assert spec.address == hostpart - assert spec.python == python - if joinpath: - assert spec.joinpath == joinpath - else: - assert spec.joinpath == "pyexecnetcache" - assert spec.type == "ssh" - spec2 = GatewaySpec(specstring) - self._equality(spec, spec2) - - def test_socket(self): - for hostpart in ('x.y', 'x', 'popen'): - for port in ":80", ":1000": - for joinpath in ('', ':abc', ':abc:de'): - spec = GatewaySpec("socket:" + hostpart + port + joinpath) - assert spec.address == (hostpart, int(port[1:])) - if joinpath[1:]: - assert spec.joinpath == joinpath[1:] - else: - assert spec.joinpath == "pyexecnetcache" - assert spec.type == "socket" - spec2 = GatewaySpec("socket:" + hostpart + port + joinpath) - self._equality(spec, spec2) - - def _equality(self, spec1, spec2): - assert spec1 != spec2 - assert hash(spec1) != hash(spec2) - assert not (spec1 == spec2) - - -class TestGatewaySpecAPI: - def test_popen_nopath_makegateway(self, testdir): - spec = GatewaySpec("popen") - gw = spec.makegateway() - p = gw.remote_exec("import os; channel.send(os.getcwd())").receive() - curdir = py.std.os.getcwd() - assert curdir == p - gw.exit() - - def test_popen_makegateway(self, testdir): - spec = GatewaySpec("popen::" + str(testdir.tmpdir)) - gw = spec.makegateway() - p = gw.remote_exec("import os; channel.send(os.getcwd())").receive() - assert spec.joinpath == p - gw.exit() - - def test_popen_makegateway_python(self, testdir): - spec = GatewaySpec("popen:%s" % py.std.sys.executable) - gw = spec.makegateway() - res = gw.remote_exec("import sys ; channel.send(sys.executable)").receive() - assert py.std.sys.executable == py.std.sys.executable - gw.exit() - - def test_ssh(self): - sshhost = py.test.config.getvalueorskip("sshhost") - spec = GatewaySpec("ssh:" + sshhost) - gw = spec.makegateway() - p = gw.remote_exec("import os ; channel.send(os.getcwd())").receive() - gw.exit() - - @py.test.mark.xfail("implement socketserver test scenario") - def test_socketgateway(self): - gw = py.execnet.PopenGateway() - spec = GatewaySpec("ssh:" + sshhost) - -class TestGatewayManagerPopen: - def test_hostmanager_popen_makegateway(self): - hm = GatewayManager(["popen"] * 2) - hm.makegateways() - assert len(hm.gateways) == 2 - hm.exit() - assert not len(hm.gateways) - - def test_hostmanager_popens_rsync(self, source): - hm = GatewayManager(["popen"] * 2) - hm.makegateways() - assert len(hm.gateways) == 2 - for gw in hm.gateways: - gw.remote_exec = None - l = [] - hm.rsync(source, notify=lambda *args: l.append(args)) - assert not l - hm.exit() - assert not len(hm.gateways) - - def test_hostmanager_rsync_popen_with_path(self, source, dest): - hm = GatewayManager(["popen::%s" %dest] * 1) - hm.makegateways() - source.ensure("dir1", "dir2", "hello") - l = [] - hm.rsync(source, notify=lambda *args: l.append(args)) - assert len(l) == 1 - assert l[0] == ("rsyncrootready", hm.gateways[0].spec, source) - hm.exit() - dest = dest.join(source.basename) - assert dest.join("dir1").check() - assert dest.join("dir1", "dir2").check() - assert dest.join("dir1", "dir2", 'hello').check() - - def XXXtest_ssh_rsync_samehost_twice(self): - #XXX we have no easy way to have a temp directory remotely! - option = py.test.config.option - if option.sshhost is None: - py.test.skip("no known ssh target, use -S to set one") - host1 = Host("%s" % (option.sshhost, )) - host2 = Host("%s" % (option.sshhost, )) - hm = HostManager(config, hosts=[host1, host2]) - events = [] - hm.init_rsync(events.append) - print events - assert 0 - - def test_multi_chdir_popen_with_path(self, testdir): - import os - hm = GatewayManager(["popen::hello"] * 2) - testdir.tmpdir.chdir() - hellopath = testdir.tmpdir.mkdir("hello") - hm.makegateways() - l = hm.multi_exec("import os ; channel.send(os.getcwd())").receive_each() - paths = [x[1] for x in l] - assert l == [str(hellopath)] * 2 - py.test.raises(hm.RemoteError, 'hm.multi_chdir("world", inplacelocal=False)') - worldpath = hellopath.mkdir("world") - hm.multi_chdir("world", inplacelocal=False) - l = hm.multi_exec("import os ; channel.send(os.getcwd())").receive_each() - assert len(l) == 2 - assert l[0] == l[1] - curwd = os.getcwd() - assert l[0].startswith(curwd) - assert l[0].endswith("world") - - def test_multi_chdir_popen(self, testdir): - import os - hm = GatewayManager(["popen"] * 2) - testdir.tmpdir.chdir() - hellopath = testdir.tmpdir.mkdir("hello") - hm.makegateways() - hm.multi_chdir("hello", inplacelocal=False) - l = hm.multi_exec("import os ; channel.send(os.getcwd())").receive_each() - assert len(l) == 2 - assert l == [os.getcwd()] * 2 - - hm.multi_chdir("hello") - l = hm.multi_exec("import os ; channel.send(os.getcwd())").receive_each() - assert len(l) == 2 - assert l[0] == l[1] - curwd = os.getcwd() - assert l[0].startswith(curwd) - assert l[0].endswith("hello") - -from py.__.execnet.gwmanage import MultiChannel -class TestMultiChannel: +class TestMultiChannelAndGateway: def test_multichannel_receive_each(self): class pseudochannel: def receive(self): @@ -200,7 +14,7 @@ pc1 = pseudochannel() pc2 = pseudochannel() - multichannel = MultiChannel([pc1, pc2]) + multichannel = py.execnet.MultiChannel([pc1, pc2]) l = multichannel.receive_each(withchannel=True) assert len(l) == 2 assert l == [(pc1, 12), (pc2, 12)] @@ -208,8 +22,9 @@ assert l == [12,12] def test_multichannel_send_each(self): - gm = GatewayManager(['popen'] * 2) - mc = gm.multi_exec(""" + l = [py.execnet.PopenGateway() for x in range(2)] + gm = py.execnet.MultiGateway(l) + mc = gm.remote_exec(""" import os channel.send(channel.receive() + 1) """) @@ -217,9 +32,10 @@ l = mc.receive_each() assert l == [42,42] - def test_multichannel_receive_queue(self): - gm = GatewayManager(['popen'] * 2) - mc = gm.multi_exec(""" + def test_multichannel_receive_queue_for_two_subprocesses(self): + l = [py.execnet.PopenGateway() for x in range(2)] + gm = py.execnet.MultiGateway(l) + mc = gm.remote_exec(""" import os channel.send(os.getpid()) """) @@ -236,40 +52,7 @@ class pseudochannel: def waitclose(self): l.append(0) - multichannel = MultiChannel([pseudochannel(), pseudochannel()]) + multichannel = py.execnet.MultiChannel([pseudochannel(), pseudochannel()]) multichannel.waitclose() assert len(l) == 2 - -def pytest_pyfuncarg_source(pyfuncitem): - return py.test.ensuretemp(pyfuncitem.getmodpath()).mkdir("source") -def pytest_pyfuncarg_dest(pyfuncitem): - return py.test.ensuretemp(pyfuncitem.getmodpath()).mkdir("dest") - -class TestHRSync: - def test_hrsync_filter(self, source, dest): - source.ensure("dir", "file.txt") - source.ensure(".svn", "entries") - source.ensure(".somedotfile", "moreentries") - source.ensure("somedir", "editfile~") - syncer = HostRSync(source) - l = list(source.visit(rec=syncer.filter, - fil=syncer.filter)) - assert len(l) == 3 - basenames = [x.basename for x in l] - assert 'dir' in basenames - assert 'file.txt' in basenames - assert 'somedir' in basenames - - def test_hrsync_one_host(self, source, dest): - spec = GatewaySpec("popen::%s" % dest) - gw = spec.makegateway() - finished = [] - rsync = HostRSync(source) - rsync.add_target_host(gw, finished=lambda: finished.append(1)) - source.join("hello.py").write("world") - rsync.send() - gw.exit() - assert dest.join(source.basename, "hello.py").check() - assert len(finished) == 1 - From hpk at codespeak.net Wed Mar 18 13:05:20 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 18 Mar 2009 13:05:20 +0100 (CET) Subject: [py-svn] r63028 - in py/trunk/py: doc execnet test/dsession test/plugin test/testing Message-ID: <20090318120520.5D2931683F3@codespeak.net> Author: hpk Date: Wed Mar 18 13:05:18 2009 New Revision: 63028 Modified: py/trunk/py/doc/execnet.txt py/trunk/py/execnet/gwmanage.py py/trunk/py/test/dsession/hostmanage.py py/trunk/py/test/dsession/masterslave.py py/trunk/py/test/plugin/pytest_pytester.py py/trunk/py/test/plugin/pytest_terminal.py py/trunk/py/test/testing/acceptance_test.py Log: allow py.test --exec=python2.4 -n 3 to work and fix some bugs from doing so. Modified: py/trunk/py/doc/execnet.txt ============================================================================== --- py/trunk/py/doc/execnet.txt (original) +++ py/trunk/py/doc/execnet.txt Wed Mar 18 13:05:18 2009 @@ -140,7 +140,7 @@ >>> gwspec = py.execnet.GatewaySpec("popen") >>> gw = gwspec.makegateway() >>> ex = gw.remote_exec("import sys ; channel.send(sys.executable)").receive() - >>> assert ex == py.std.sys.executable + >>> assert ex == py.std.sys.executable, (ex, py.std.sys.executable) >>> current gateway types and specifications Modified: py/trunk/py/execnet/gwmanage.py ============================================================================== --- py/trunk/py/execnet/gwmanage.py (original) +++ py/trunk/py/execnet/gwmanage.py Wed Mar 18 13:05:18 2009 @@ -24,7 +24,7 @@ NO_ENDMARKER_WANTED = object() class GatewaySpec(object): - python = "python" + python = None def __init__(self, spec, defaultjoinpath="pyexecnetcache"): if spec == "popen" or spec.startswith("popen:"): parts = spec.split(":", 2) @@ -35,6 +35,9 @@ self.joinpath = parts.pop(0) else: self.joinpath = "" + if not self.python: + self.python = py.std.sys.executable + elif spec.startswith("socket:"): parts = spec[7:].split(":", 2) self.address = parts.pop(0) Modified: py/trunk/py/test/dsession/hostmanage.py ============================================================================== --- py/trunk/py/test/dsession/hostmanage.py (original) +++ py/trunk/py/test/dsession/hostmanage.py Wed Mar 18 13:05:18 2009 @@ -6,7 +6,11 @@ def getconfiggwspecs(config): if config.option.numprocesses: - gwspecs = ['popen'] * config.option.numprocesses + if config.option.executable: + s = 'popen:%s' % config.option.executable + else: + s = 'popen' + gwspecs = [s] * config.option.numprocesses else: gwspecs = config.option.gateways if not gwspecs: Modified: py/trunk/py/test/dsession/masterslave.py ============================================================================== --- py/trunk/py/test/dsession/masterslave.py (original) +++ py/trunk/py/test/dsession/masterslave.py Wed Mar 18 13:05:18 2009 @@ -120,11 +120,8 @@ self.sendevent("itemtestreport", testrep) -def makehostup(host=None): - from py.__.execnet.gwmanage import GatewaySpec +def makehostup(host="INPROCESS"): import sys - if host is None: - host = GatewaySpec("localhost") platinfo = {} for name in 'platform', 'executable', 'version_info': platinfo["sys."+name] = getattr(sys, name) Modified: py/trunk/py/test/plugin/pytest_pytester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_pytester.py (original) +++ py/trunk/py/test/plugin/pytest_pytester.py Wed Mar 18 13:05:18 2009 @@ -232,7 +232,7 @@ else: script = bindir.join(scriptname) assert script.check() - return self.run(script, *args) + return self.run(py.std.sys.executable, script, *args) def runpytest(self, *args): p = py.path.local.make_numbered_dir(prefix="runpytest-", Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Wed Mar 18 13:05:18 2009 @@ -98,7 +98,7 @@ def pyevent_hostup(self, event): d = event.platinfo.copy() - d['host'] = event.host.address + d['host'] = getattr(event.host, 'address', event.host) d['version'] = repr_pythonversion(d['sys.version_info']) self.write_line("HOSTUP: %(host)s %(sys.platform)s " "%(sys.executable)s - Python %(version)s" % @@ -325,7 +325,7 @@ rep = TerminalReporter(item.config, linecomp.stringio) rep.pyevent_hostup(makehostup()) linecomp.assert_contains_lines([ - "*localhost %s %s - Python %s" %(sys.platform, + "*inline %s %s - Python %s" %(sys.platform, sys.executable, repr_pythonversion(sys.version_info)) ]) Modified: py/trunk/py/test/testing/acceptance_test.py ============================================================================== --- py/trunk/py/test/testing/acceptance_test.py (original) +++ py/trunk/py/test/testing/acceptance_test.py Wed Mar 18 13:05:18 2009 @@ -179,7 +179,7 @@ verinfo = ".".join(map(str, py.std.sys.version_info[:3])) extra = result.stdout.fnmatch_lines([ "*===== test session starts ====*", - "*localhost* %s %s - Python %s*" %( + "HOSTUP*INPROCESS* %s %s - Python %s*" %( py.std.sys.platform, py.std.sys.executable, verinfo), "*test_header_trailer_info.py .", "=* 1 passed in *.[0-9][0-9] seconds *=", @@ -379,7 +379,7 @@ return spawn def requirespexpect(self, version_needed): - import pexpect + pexpect = py.test.importorskip("pexpect") ver = tuple(map(int, pexpect.__version__.split("."))) if ver < version_needed: py.test.skip("pexpect version %s needed" %(".".join(map(str, version_needed)))) From hpk at codespeak.net Wed Mar 18 15:35:54 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 18 Mar 2009 15:35:54 +0100 (CET) Subject: [py-svn] r63034 - in py/trunk/py: code/testing execnet/testing test/plugin Message-ID: <20090318143554.531A7168453@codespeak.net> Author: hpk Date: Wed Mar 18 15:35:51 2009 New Revision: 63034 Modified: py/trunk/py/code/testing/test_source.py py/trunk/py/execnet/testing/test_gwspec.py py/trunk/py/test/plugin/pytest_terminal.py Log: fixing some tests to work across python revisions Modified: py/trunk/py/code/testing/test_source.py ============================================================================== --- py/trunk/py/code/testing/test_source.py (original) +++ py/trunk/py/code/testing/test_source.py Wed Mar 18 15:35:51 2009 @@ -326,16 +326,11 @@ src = getsource(x) assert src == expected -def test_getsource___source__(): +def test_idem_compile_and_getsource(): from py.__.code.source import getsource - x = py.code.compile("""if 1: - def x(): - pass -""") - - expected = """def x(): - pass""" - src = getsource(x) + expected = "def x(): pass" + co = py.code.compile(expected) + src = getsource(co) assert src == expected def test_findsource_fallback(): @@ -346,11 +341,17 @@ def test_findsource___source__(): from py.__.code.source import findsource - x = py.code.compile("""if 1: + co = py.code.compile("""if 1: def x(): pass """) - src, lineno = findsource(x) + src, lineno = findsource(co) assert 'if 1:' in str(src) - assert src[lineno] == ' def x():' + + d = {} + eval(co, d) + src, lineno = findsource(d['x']) + assert 'if 1:' in str(src) + assert src[lineno] == " def x():" + Modified: py/trunk/py/execnet/testing/test_gwspec.py ============================================================================== --- py/trunk/py/execnet/testing/test_gwspec.py (original) +++ py/trunk/py/execnet/testing/test_gwspec.py Wed Mar 18 15:35:51 2009 @@ -18,7 +18,7 @@ print s spec = py.execnet.GatewaySpec(s) assert spec.address == "popen" - assert spec.python == python + assert spec.python == (python or py.std.sys.executable) assert spec.joinpath == joinpath assert spec.type == "popen" spec2 = py.execnet.GatewaySpec("popen" + joinpath) Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Wed Mar 18 15:35:51 2009 @@ -325,7 +325,7 @@ rep = TerminalReporter(item.config, linecomp.stringio) rep.pyevent_hostup(makehostup()) linecomp.assert_contains_lines([ - "*inline %s %s - Python %s" %(sys.platform, + "*INPROCESS* %s %s - Python %s" %(sys.platform, sys.executable, repr_pythonversion(sys.version_info)) ]) From hpk at codespeak.net Wed Mar 18 16:51:57 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 18 Mar 2009 16:51:57 +0100 (CET) Subject: [py-svn] r63040 - in py/trunk/py: execnet execnet/testing test test/plugin Message-ID: <20090318155157.1427F168466@codespeak.net> Author: hpk Date: Wed Mar 18 16:51:55 2009 New Revision: 63040 Added: py/trunk/py/execnet/testing/test_event.py py/trunk/py/test/plugin/pytest_execnetcleanup.py Modified: py/trunk/py/execnet/gateway.py py/trunk/py/execnet/register.py py/trunk/py/execnet/testing/test_gateway.py py/trunk/py/test/defaultconftest.py py/trunk/py/test/plugin/pytest_plugintester.py py/trunk/py/test/plugin/pytest_pytester.py py/trunk/py/test/pytestplugin.py Log: try harder to record and auto-exit gateways after test runs Modified: py/trunk/py/execnet/gateway.py ============================================================================== --- py/trunk/py/execnet/gateway.py (original) +++ py/trunk/py/execnet/gateway.py Wed Mar 18 16:51:55 2009 @@ -330,6 +330,7 @@ self._cleanup.unregister(self) self._stopexec() self._stopsend() + py._com.pyplugins.notify("gateway_exit", self) def _stopsend(self): self._send(None) Modified: py/trunk/py/execnet/register.py ============================================================================== --- py/trunk/py/execnet/register.py (original) +++ py/trunk/py/execnet/register.py Wed Mar 18 16:51:55 2009 @@ -41,6 +41,7 @@ super(InstallableGateway, self).__init__(io=io, _startcount=1) # XXX we dissallow execution form the other side self._initreceive(requestqueue=False) + py._com.pyplugins.notify("gateway_init", self) def _remote_bootstrap_gateway(self, io, extra=''): """ return Gateway with a asynchronously remotely Added: py/trunk/py/execnet/testing/test_event.py ============================================================================== --- (empty file) +++ py/trunk/py/execnet/testing/test_event.py Wed Mar 18 16:51:55 2009 @@ -0,0 +1,11 @@ +import py +pytest_plugins = "pytester" + +class TestExecnetEvents: + def test_popengateway(self, eventrecorder): + gw = py.execnet.PopenGateway() + event = eventrecorder.popevent("gateway_init") + assert event.args[0] == gw + gw.exit() + event = eventrecorder.popevent("gateway_exit") + assert event.args[0] == gw Modified: py/trunk/py/execnet/testing/test_gateway.py ============================================================================== --- py/trunk/py/execnet/testing/test_gateway.py (original) +++ py/trunk/py/execnet/testing/test_gateway.py Wed Mar 18 16:51:55 2009 @@ -571,6 +571,9 @@ class TestSshGateway(BasicRemoteExecution): def setup_class(cls): sshhost = py.test.config.getvalueorskip("sshhost") + if sshhost.find(":") != -1: + sshhost = sshhost.split(":")[0] + cls.sshhost = sshhost cls.gw = py.execnet.SshGateway(sshhost) def test_sshconfig_functional(self): @@ -578,7 +581,7 @@ ssh_config = tmpdir.join("ssh_config") ssh_config.write( "Host alias123\n" - " HostName %s\n" % (py.test.config.option.sshhost,)) + " HostName %s\n" % self.sshhost) gw = py.execnet.SshGateway("alias123", ssh_config=ssh_config) assert gw._cmd.find("-F") != -1 assert gw._cmd.find(str(ssh_config)) != -1 @@ -586,7 +589,7 @@ gw.exit() def test_sshaddress(self): - assert self.gw.remoteaddress == py.test.config.option.sshhost + assert self.gw.remoteaddress == self.sshhost @py.test.mark.xfail("XXX ssh-gateway error handling") def test_connexion_failes_on_non_existing_hosts(self): Modified: py/trunk/py/test/defaultconftest.py ============================================================================== --- py/trunk/py/test/defaultconftest.py (original) +++ py/trunk/py/test/defaultconftest.py Wed Mar 18 16:51:55 2009 @@ -11,7 +11,7 @@ conf_iocapture = "fd" # overridable from conftest.py # XXX resultlog should go, pypy's nightrun depends on it -pytest_plugins = "default terminal xfail tmpdir resultlog monkeypatch".split() +pytest_plugins = "default terminal xfail tmpdir execnetcleanup resultlog monkeypatch".split() # =================================================== # Distributed testing specific options Added: py/trunk/py/test/plugin/pytest_execnetcleanup.py ============================================================================== --- (empty file) +++ py/trunk/py/test/plugin/pytest_execnetcleanup.py Wed Mar 18 16:51:55 2009 @@ -0,0 +1,53 @@ +import py + +class ExecnetcleanupPlugin: + _gateways = None + _debug = None + + def pytest_configure(self, config): + self._debug = config.option.debug + + def trace(self, msg, *args): + if self._debug: + print "[execnetcleanup %0x] %s %s" %(id(self), msg, args) + + def pyevent_gateway_init(self, gateway): + self.trace("init", gateway) + if self._gateways is not None: + self._gateways.append(gateway) + + def pyevent_gateway_exit(self, gateway): + self.trace("exit", gateway) + if self._gateways is not None: + self._gateways.remove(gateway) + + def pyevent_testrunstart(self, event): + self.trace("testrunstart", event) + self._gateways = [] + + def pyevent_testrunfinish(self, event): + self.trace("testrunfinish", event) + l = [] + for gw in self._gateways: + gw.exit() + l.append(gw) + for gw in l: + gw.join() + +def test_generic(plugintester): + plugintester.apicheck(ExecnetcleanupPlugin) + + at py.test.mark.xfail("clarify plugin registration/unregistration") +def test_execnetplugin(testdir): + p = ExecnetcleanupPlugin() + testdir.plugins.append(p) + testdir.inline_runsource(""" + import py + import sys + def test_hello(): + sys._gw = py.execnet.PopenGateway() + """, "-s", "--debug") + assert not p._gateways + assert py.std.sys._gw + py.test.raises(KeyError, "py.std.sys._gw.exit()") # already closed + Modified: py/trunk/py/test/plugin/pytest_plugintester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_plugintester.py (original) +++ py/trunk/py/test/plugin/pytest_plugintester.py Wed Mar 18 16:51:55 2009 @@ -155,8 +155,14 @@ def pyevent(self, eventname, *args, **kwargs): """ called for each testing event. """ + def pyevent_gateway_init(self, gateway): + """ called a gateway has been initialized. """ + + def pyevent_gateway_exit(self, gateway): + """ called when gateway is being exited. """ + def pyevent_trace(self, category, msg): - """ called for tracing events events. """ + """ called for tracing events. """ def pyevent_internalerror(self, event): """ called for internal errors. """ Modified: py/trunk/py/test/plugin/pytest_pytester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_pytester.py (original) +++ py/trunk/py/test/plugin/pytest_pytester.py Wed Mar 18 16:51:55 2009 @@ -21,6 +21,11 @@ def pytest_pyfuncarg_EventRecorder(self, pyfuncitem): return EventRecorder + def pytest_pyfuncarg_eventrecorder(self, pyfuncitem): + evrec = EventRecorder(py._com.pyplugins) + pyfuncitem.addfinalizer(lambda: evrec.pyplugins.unregister(evrec)) + return evrec + def test_generic(plugintester): plugintester.apicheck(PytesterPlugin) @@ -245,6 +250,8 @@ self.name = name self.args = args self.kwargs = kwargs + def __repr__(self): + return "" %(self.name, self.args) class EventRecorder(object): def __init__(self, pyplugins, debug=False): # True): @@ -260,6 +267,13 @@ print "[event: %s]: %s **%s" %(name, ", ".join(map(str, args)), kwargs,) self.events.append(Event(name, args, kwargs)) + def popevent(self, name): + for i, event in py.builtin.enumerate(self.events): + if event.name == name: + del self.events[i] + return event + raise KeyError("popevent: %r not found in %r" %(name, self.events)) + def get(self, cls): l = [] for event in self.events: Modified: py/trunk/py/test/pytestplugin.py ============================================================================== --- py/trunk/py/test/pytestplugin.py (original) +++ py/trunk/py/test/pytestplugin.py Wed Mar 18 16:51:55 2009 @@ -95,6 +95,7 @@ config = self._config del self._config self.pyplugins.call_each("pytest_unconfigure", config=config) + config.bus.unregister(self) # # XXX old code to automatically load classes From hpk at codespeak.net Wed Mar 18 17:25:59 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 18 Mar 2009 17:25:59 +0100 (CET) Subject: [py-svn] r63046 - py/trunk/py/test/plugin Message-ID: <20090318162559.4E208168472@codespeak.net> Author: hpk Date: Wed Mar 18 17:25:58 2009 New Revision: 63046 Modified: py/trunk/py/test/plugin/pytest_default.py Log: change a few comments Modified: py/trunk/py/test/plugin/pytest_default.py ============================================================================== --- py/trunk/py/test/plugin/pytest_default.py (original) +++ py/trunk/py/test/plugin/pytest_default.py Wed Mar 18 17:25:58 2009 @@ -16,15 +16,18 @@ return parent.Module(path, parent=parent) def pytest_collect_directory(self, path, parent): + #excludelist = parent._config.getvalue_pathlist('dir_exclude', path) + #if excludelist and path in excludelist: + # return if not parent.recfilter(path): - # check if cmdline specified this dir or a subdir + # check if cmdline specified this dir or a subdir directly for arg in parent.config.args: if path == arg or arg.relto(path): break else: return - # not use parent.Directory here as we want - # dir/conftest.py to be able to + # not use parent.Directory here as we generally + # want dir/conftest.py to be able to # define Directory(dir) already Directory = parent.config.getvalue('Directory', path) return Directory(path, parent=parent) From hpk at codespeak.net Wed Mar 18 18:05:37 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 18 Mar 2009 18:05:37 +0100 (CET) Subject: [py-svn] r63048 - in py/trunk/py: code/testing misc/testing Message-ID: <20090318170537.ACD31168480@codespeak.net> Author: hpk Date: Wed Mar 18 18:05:37 2009 New Revision: 63048 Modified: py/trunk/py/code/testing/test_excinfo.py py/trunk/py/misc/testing/test_com.py Log: fix windows issues Modified: py/trunk/py/code/testing/test_excinfo.py ============================================================================== --- py/trunk/py/code/testing/test_excinfo.py (original) +++ py/trunk/py/code/testing/test_excinfo.py Wed Mar 18 18:05:37 2009 @@ -212,7 +212,7 @@ print s assert s.startswith(__file__[:-1]) # pyc file assert s.endswith("ValueError") - assert len(s.split(":")) == 3 + assert len(s.split(":")) >= 3 # on windows it's 4 def test_excinfo_errisinstance(): excinfo = py.test.raises(ValueError, h) Modified: py/trunk/py/misc/testing/test_com.py ============================================================================== --- py/trunk/py/misc/testing/test_com.py (original) +++ py/trunk/py/misc/testing/test_com.py Wed Mar 18 18:05:37 2009 @@ -207,8 +207,8 @@ try: monkeypatch.setitem(os.environ, "PYLIB", 'unknownconsider') excinfo = py.test.raises(py.process.cmdexec.Error, """ - py.process.cmdexec("python -c 'import py'") - """) + py.process.cmdexec('%s -c "import py"') + """ % py.std.sys.executable) assert str(excinfo.value).find("ImportError") != -1 assert str(excinfo.value).find("unknownconsider") != -1 finally: From hpk at codespeak.net Wed Mar 18 18:54:46 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 18 Mar 2009 18:54:46 +0100 (CET) Subject: [py-svn] r63050 - in py/trunk/py: execnet/testing test test/dsession test/plugin test/testing Message-ID: <20090318175446.A2097168498@codespeak.net> Author: hpk Date: Wed Mar 18 18:54:45 2009 New Revision: 63050 Modified: py/trunk/py/execnet/testing/test_gateway.py py/trunk/py/execnet/testing/test_gwspec.py py/trunk/py/test/dsession/hostmanage.py py/trunk/py/test/plugin/pytest_execnetcleanup.py py/trunk/py/test/pycollect.py py/trunk/py/test/testing/test_pycollect.py Log: * disabled classes or modules will now lead to a skip during setup of the colitem * more graceful handling when "ssh" is not present Modified: py/trunk/py/execnet/testing/test_gateway.py ============================================================================== --- py/trunk/py/execnet/testing/test_gateway.py (original) +++ py/trunk/py/execnet/testing/test_gateway.py Wed Mar 18 18:54:45 2009 @@ -564,17 +564,23 @@ ## def teardown_class(cls): ## cls.gw.exit() ## cls.proxygw.exit() + +def getsshhost(): + sshhost = py.test.config.getvalueorskip("sshhost") + if sshhost.find(":") != -1: + sshhost = sshhost.split(":")[0] + ssh = py.path.local.sysfind("ssh") + if not ssh: + py.test.skip("command not found: ssh") + return sshhost class TestSocketGateway(SocketGatewaySetup, BasicRemoteExecution): pass class TestSshGateway(BasicRemoteExecution): def setup_class(cls): - sshhost = py.test.config.getvalueorskip("sshhost") - if sshhost.find(":") != -1: - sshhost = sshhost.split(":")[0] - cls.sshhost = sshhost - cls.gw = py.execnet.SshGateway(sshhost) + cls.sshhost = getsshhost() + cls.gw = py.execnet.SshGateway(cls.sshhost) def test_sshconfig_functional(self): tmpdir = py.test.ensuretemp("test_sshconfig") Modified: py/trunk/py/execnet/testing/test_gwspec.py ============================================================================== --- py/trunk/py/execnet/testing/test_gwspec.py (original) +++ py/trunk/py/execnet/testing/test_gwspec.py Wed Mar 18 18:54:45 2009 @@ -3,6 +3,7 @@ """ import py +from test_gateway import getsshhost class TestGatewaySpec: """ @@ -88,7 +89,7 @@ gw.exit() def test_ssh(self): - sshhost = py.test.config.getvalueorskip("sshhost") + sshhost = getsshhost() spec = py.execnet.GatewaySpec("ssh:" + sshhost) gw = spec.makegateway() p = gw.remote_exec("import os ; channel.send(os.getcwd())").receive() Modified: py/trunk/py/test/dsession/hostmanage.py ============================================================================== --- py/trunk/py/test/dsession/hostmanage.py (original) +++ py/trunk/py/test/dsession/hostmanage.py Wed Mar 18 18:54:45 2009 @@ -89,6 +89,7 @@ for root in self.roots: self.gwmanager.rsync(root, **options) else: + XXX # do we want to care for situations without explicit rsyncdirs? # we transfer our topdir as the root self.gwmanager.rsync(self.config.topdir, **options) # and cd into it Modified: py/trunk/py/test/plugin/pytest_execnetcleanup.py ============================================================================== --- py/trunk/py/test/plugin/pytest_execnetcleanup.py (original) +++ py/trunk/py/test/plugin/pytest_execnetcleanup.py Wed Mar 18 18:54:45 2009 @@ -31,8 +31,8 @@ for gw in self._gateways: gw.exit() l.append(gw) - for gw in l: - gw.join() + #for gw in l: + # gw.join() def test_generic(plugintester): plugintester.apicheck(ExecnetcleanupPlugin) Modified: py/trunk/py/test/pycollect.py ============================================================================== --- py/trunk/py/test/pycollect.py (original) +++ py/trunk/py/test/pycollect.py Wed Mar 18 18:54:45 2009 @@ -160,11 +160,6 @@ return self.Function(name, parent=self) class Module(py.test.collect.File, PyCollectorMixin): - def collect(self): - if self.fspath.ext == ".py" and getattr(self.obj, 'disabled', 0): - return [] - return super(Module, self).collect() - def _getobj(self): return self._memoizedcall('_obj', self._importtestmodule) @@ -176,6 +171,8 @@ return mod def setup(self): + if getattr(self.obj, 'disabled', 0): + py.test.skip("%r is disabled" %(self.obj,)) if not self.config.option.nomagic: #print "*" * 20, "INVOKE assertion", self py.magic.invoke(assertion=1) @@ -195,14 +192,14 @@ class Class(PyCollectorMixin, py.test.collect.Collector): def collect(self): - if getattr(self.obj, 'disabled', 0): - return [] l = self._deprecated_collect() if l is not None: return l return [self.Instance(name="()", parent=self)] def setup(self): + if getattr(self.obj, 'disabled', 0): + py.test.skip("%r is disabled" %(self.obj,)) setup_class = getattr(self.obj, 'setup_class', None) if setup_class is not None: setup_class = getattr(setup_class, 'im_func', setup_class) Modified: py/trunk/py/test/testing/test_pycollect.py ============================================================================== --- py/trunk/py/test/testing/test_pycollect.py (original) +++ py/trunk/py/test/testing/test_pycollect.py Wed Mar 18 18:54:45 2009 @@ -1,5 +1,7 @@ import py +from py.__.test.outcome import Skipped + class TestModule: def test_module_file_not_found(self, testdir): tmpdir = testdir.tmpdir @@ -38,15 +40,6 @@ x = l.pop() assert x is None - def test_disabled_module(self, testdir): - modcol = testdir.getmodulecol(""" - disabled = True - def setup_module(mod): - raise ValueError - """) - assert not modcol.collect() - assert not modcol.run() - def test_module_participates_as_plugin(self, testdir): modcol = testdir.getmodulecol("") modcol.setup() @@ -58,6 +51,18 @@ modcol = testdir.getmodulecol("pytest_plugins='xasdlkj',") py.test.raises(ImportError, "modcol.obj") + def test_disabled_module(self, testdir): + modcol = testdir.getmodulecol(""" + disabled = True + def setup_module(mod): + raise ValueError + def test_method(): + pass + """) + l = modcol.collect() + assert len(l) == 1 + py.test.raises(Skipped, "modcol.setup()") + class TestClass: def test_disabled_class(self, testdir): modcol = testdir.getmodulecol(""" @@ -70,7 +75,9 @@ assert len(l) == 1 modcol = l[0] assert isinstance(modcol, py.test.collect.Class) - assert not modcol.collect() + l = modcol.collect() + assert len(l) == 1 + py.test.raises(Skipped, "modcol.setup()") class TestGenerator: def test_generative_functions(self, testdir): From hpk at codespeak.net Wed Mar 18 20:23:40 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 18 Mar 2009 20:23:40 +0100 (CET) Subject: [py-svn] r63051 - in py/trunk/py/test: . plugin Message-ID: <20090318192340.5E4D116847F@codespeak.net> Author: hpk Date: Wed Mar 18 20:23:38 2009 New Revision: 63051 Modified: py/trunk/py/test/collect.py py/trunk/py/test/event.py py/trunk/py/test/plugin/pytest_pytester.py py/trunk/py/test/pycollect.py Log: be very careful when we cannot unpickle an colitem because its parent can't collect the same way as on the sending side. (due to platform skips etc.) Modified: py/trunk/py/test/collect.py ============================================================================== --- py/trunk/py/test/collect.py (original) +++ py/trunk/py/test/collect.py Wed Mar 18 20:23:38 2009 @@ -20,6 +20,7 @@ """ import py from py.__.misc.warn import APIWARN +from py.__.test.outcome import Skipped def configproperty(name): def fget(self): @@ -81,11 +82,24 @@ def __getstate__(self): return (self.name, self.parent) def __setstate__(self, (name, parent)): - newnode = parent.join(name) - if newnode is None: - raise AssertionError(self, name, parent, parent.__dict__) - self.__dict__.update(newnode.__dict__) - #self.__init__(name=name, parent=parent) + try: + colitems = parent._memocollect() + except KeyboardInterrupt: + raise + except Exception: + # seems our parent can't collect us + # so let's be somewhat operable + self.name = name + self.parent = parent + self.config = parent.config + self._obj = "could not unpickle" + else: + for colitem in colitems: + if colitem.name == name: + # we are a copy that will not be returned + # by our parent + self.__dict__ = colitem.__dict__ + break def __repr__(self): if getattr(self.config.option, 'debug', False): @@ -368,11 +382,6 @@ warnoldcollect() return self.collect_by_name(name) - def multijoin(self, namelist): - """ DEPRECATED: return a list of child items matching the given namelist. """ - warnoldcollect() - return [self.join(name) for name in namelist] - class FSCollector(Collector): def __init__(self, fspath, parent=None, config=None): fspath = py.path.local(fspath) Modified: py/trunk/py/test/event.py ============================================================================== --- py/trunk/py/test/event.py (original) +++ py/trunk/py/test/event.py Wed Mar 18 20:23:38 2009 @@ -70,7 +70,15 @@ def __init__(self, colitem, excinfo=None, when=None, outerr=None): self.colitem = colitem - self.keywords = colitem and colitem.readkeywords() + if colitem and when != "setup": + self.keywords = colitem.readkeywords() + else: + # if we fail during setup it might mean + # we are not able to access the underlying object + # this might e.g. happen if we are unpickled + # and our parent collector did not collect us + # (because it e.g. skipped for platform reasons) + self.keywords = {} if not excinfo: self.passed = True self.shortrepr = "." Modified: py/trunk/py/test/plugin/pytest_pytester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_pytester.py (original) +++ py/trunk/py/test/plugin/pytest_pytester.py Wed Mar 18 20:23:38 2009 @@ -234,10 +234,12 @@ bindir = py.path.local(py.__file__).dirpath("bin") if py.std.sys.platform == "win32": script = bindir.join("win32", scriptname + ".cmd") + assert script.check() + return self.run(script, *args) else: script = bindir.join(scriptname) - assert script.check() - return self.run(py.std.sys.executable, script, *args) + assert script.check() + return self.run(py.std.sys.executable, script, *args) def runpytest(self, *args): p = py.path.local.make_numbered_dir(prefix="runpytest-", Modified: py/trunk/py/test/pycollect.py ============================================================================== --- py/trunk/py/test/pycollect.py (original) +++ py/trunk/py/test/pycollect.py Wed Mar 18 20:23:38 2009 @@ -265,7 +265,7 @@ teardown_func_or_meth(self.obj) def _prunetraceback(self, traceback): - if not self.config.option.fulltrace: + if hasattr(self, '_obj') and not self.config.option.fulltrace: code = py.code.Code(self.obj) path, firstlineno = code.path, code.firstlineno ntraceback = traceback.cut(path=path, firstlineno=firstlineno) From hpk at codespeak.net Wed Mar 18 20:23:53 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 18 Mar 2009 20:23:53 +0100 (CET) Subject: [py-svn] r63052 - in py/trunk/py: cmdline/testing rest/testing Message-ID: <20090318192353.7369316847F@codespeak.net> Author: hpk Date: Wed Mar 18 20:23:52 2009 New Revision: 63052 Modified: py/trunk/py/cmdline/testing/test_generic.py py/trunk/py/rest/testing/test_rst.py Log: some fixes for win32 Modified: py/trunk/py/cmdline/testing/test_generic.py ============================================================================== --- py/trunk/py/cmdline/testing/test_generic.py (original) +++ py/trunk/py/cmdline/testing/test_generic.py Wed Mar 18 20:23:52 2009 @@ -30,7 +30,11 @@ cmd += " sys" print "executing", script try: - py.process.cmdexec(cmd) + old = script.dirpath().chdir() + try: + py.process.cmdexec(cmd) + finally: + old.chdir() except py.process.cmdexec.Error, e: if cmd.find("py.rest") != -1 and \ e.out.find("module named") != -1: Modified: py/trunk/py/rest/testing/test_rst.py ============================================================================== --- py/trunk/py/rest/testing/test_rst.py (original) +++ py/trunk/py/rest/testing/test_rst.py Wed Mar 18 20:23:52 2009 @@ -1,7 +1,8 @@ """ rst generation tests """ - +import py +py.test.importorskip("docutils") from py.__.rest.rst import * from py.__.misc.rest import process as restcheck import traceback From hpk at codespeak.net Wed Mar 18 20:39:35 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 18 Mar 2009 20:39:35 +0100 (CET) Subject: [py-svn] r63053 - py/trunk/py/io Message-ID: <20090318193935.2ACCE168455@codespeak.net> Author: hpk Date: Wed Mar 18 20:39:33 2009 New Revision: 63053 Modified: py/trunk/py/io/terminalwriter.py Log: forbid too small windows width for now Modified: py/trunk/py/io/terminalwriter.py ============================================================================== --- py/trunk/py/io/terminalwriter.py (original) +++ py/trunk/py/io/terminalwriter.py Wed Mar 18 20:39:33 2009 @@ -75,6 +75,8 @@ except: # FALLBACK width = int(os.environ.get('COLUMNS', 80))-1 + # XXX the windows getdimensions may be bogus, let's sanify a bit + width = max(width, 40) # we alaways need 40 chars return width terminal_width = get_terminal_width() From hpk at codespeak.net Wed Mar 18 21:49:40 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 18 Mar 2009 21:49:40 +0100 (CET) Subject: [py-svn] r63054 - in py/trunk/py: doc execnet process process/testing test test/dsession/testing test/plugin test/testing Message-ID: <20090318204940.415681683E4@codespeak.net> Author: hpk Date: Wed Mar 18 21:49:38 2009 New Revision: 63054 Modified: py/trunk/py/doc/path.txt py/trunk/py/execnet/gwmanage.py py/trunk/py/process/killproc.py py/trunk/py/process/testing/test_killproc.py py/trunk/py/test/collect.py py/trunk/py/test/dsession/testing/test_hostmanage.py py/trunk/py/test/plugin/pytest_pytester.py py/trunk/py/test/plugin/pytest_restdoc.py py/trunk/py/test/pytestplugin.py py/trunk/py/test/testing/test_pytestplugin.py Log: several windows fixes, test suite passes now remotely. Modified: py/trunk/py/doc/path.txt ============================================================================== --- py/trunk/py/doc/path.txt (original) +++ py/trunk/py/doc/path.txt Wed Mar 18 21:49:38 2009 @@ -132,7 +132,7 @@ >>> sep = py.path.local.sep >>> p2.relto(p1).replace(sep, '/') # os-specific path sep in the string 'baz/qux' - >>> p2.bestrelpath(p1) + >>> p2.bestrelpath(p1).replace(sep, '/') '../..' >>> p2.join(p2.bestrelpath(p1)) == p1 True Modified: py/trunk/py/execnet/gwmanage.py ============================================================================== --- py/trunk/py/execnet/gwmanage.py (original) +++ py/trunk/py/execnet/gwmanage.py Wed Mar 18 21:49:38 2009 @@ -30,7 +30,12 @@ parts = spec.split(":", 2) self.type = self.address = parts.pop(0) if parts: - self.python = parts.pop(0) + python = parts.pop(0) + # XXX XXX XXX do better GWSPEC that can deal + # with "C:" + if py.std.sys.platform == "win32" and len(python) == 1: + python = "%s:%s" %(python, parts.pop(0)) + self.python = python if parts: self.joinpath = parts.pop(0) else: Modified: py/trunk/py/process/killproc.py ============================================================================== --- py/trunk/py/process/killproc.py (original) +++ py/trunk/py/process/killproc.py Wed Mar 18 21:49:38 2009 @@ -11,7 +11,7 @@ def dokill(pid): PROCESS_TERMINATE = 1 handle = ctypes.windll.kernel32.OpenProcess( - PROCESS_TERMINATE, False, process.pid) + PROCESS_TERMINATE, False, pid) ctypes.windll.kernel32.TerminateProcess(handle, -1) ctypes.windll.kernel32.CloseHandle(handle) else: Modified: py/trunk/py/process/testing/test_killproc.py ============================================================================== --- py/trunk/py/process/testing/test_killproc.py (original) +++ py/trunk/py/process/testing/test_killproc.py Wed Mar 18 21:49:38 2009 @@ -10,4 +10,8 @@ assert proc.poll() is None # no return value yet py.process.kill(proc.pid) ret = proc.wait() + if sys.platform == "win32" and ret == 0: + py.test.skip("XXX on win32, subprocess.Popen().wait() on a killed " + "process does not yield return value != 0") + assert ret != 0 Modified: py/trunk/py/test/collect.py ============================================================================== --- py/trunk/py/test/collect.py (original) +++ py/trunk/py/test/collect.py Wed Mar 18 21:49:38 2009 @@ -92,7 +92,7 @@ self.name = name self.parent = parent self.config = parent.config - self._obj = "could not unpickle" + #self._obj = "could not unpickle" else: for colitem in colitems: if colitem.name == name: Modified: py/trunk/py/test/dsession/testing/test_hostmanage.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_hostmanage.py (original) +++ py/trunk/py/test/dsession/testing/test_hostmanage.py Wed Mar 18 21:49:38 2009 @@ -5,6 +5,7 @@ import py from py.__.test.dsession.hostmanage import HostManager, getconfiggwspecs, getconfigroots from py.__.execnet.gwmanage import GatewaySpec as Host +from py.__.execnet.testing.test_gateway import getsshhost from py.__.test import event @@ -135,7 +136,7 @@ hm.teardown_hosts() def test_hostmanage_ssh_setup_hosts(self, testdir): - sshhost = py.test.config.getvalueorskip("sshhost") + sshhost = getsshhost() testdir.makepyfile(__init__="", test_x=""" def test_one(): pass @@ -148,12 +149,9 @@ @py.test.mark.xfail("implement double-rsync test") def test_ssh_rsync_samehost_twice(self): - option = py.test.config.option - if option.sshhost is None: - py.test.skip("no known ssh target, use -S to set one") - - host1 = Host("%s" % (option.sshhost, )) - host2 = Host("%s" % (option.sshhost, )) + sshhost = getsshhost() + host1 = Host("%s" % (sshhost, )) + host2 = Host("%s" % (sshhost, )) hm = HostManager(config, hosts=[host1, host2]) events = [] hm.init_rsync(events.append) Modified: py/trunk/py/test/plugin/pytest_pytester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_pytester.py (original) +++ py/trunk/py/test/plugin/pytest_pytester.py Wed Mar 18 21:49:38 2009 @@ -232,14 +232,9 @@ def runpybin(self, scriptname, *args): bindir = py.path.local(py.__file__).dirpath("bin") - if py.std.sys.platform == "win32": - script = bindir.join("win32", scriptname + ".cmd") - assert script.check() - return self.run(script, *args) - else: - script = bindir.join(scriptname) - assert script.check() - return self.run(py.std.sys.executable, script, *args) + script = bindir.join(scriptname) + assert script.check() + return self.run(py.std.sys.executable, script, *args) def runpytest(self, *args): p = py.path.local.make_numbered_dir(prefix="runpytest-", Modified: py/trunk/py/test/plugin/pytest_restdoc.py ============================================================================== --- py/trunk/py/test/plugin/pytest_restdoc.py (original) +++ py/trunk/py/test/plugin/pytest_restdoc.py Wed Mar 18 21:49:38 2009 @@ -414,5 +414,6 @@ sorter = testdir.inline_run(xtxt) assert len(l) == 1 passed, skipped, failed = sorter.countoutcomes() - assert not failed and not skipped assert passed >= 1 + assert not failed + assert skipped <= 1 Modified: py/trunk/py/test/pytestplugin.py ============================================================================== --- py/trunk/py/test/pytestplugin.py (original) +++ py/trunk/py/test/pytestplugin.py Wed Mar 18 21:49:38 2009 @@ -125,4 +125,6 @@ except ImportError, e: if str(e).find(importspec) == -1: raise + #print "syspath:", py.std.sys.path + #print "curdir:", py.std.os.getcwd() return __import__(importspec) # show the original exception Modified: py/trunk/py/test/testing/test_pytestplugin.py ============================================================================== --- py/trunk/py/test/testing/test_pytestplugin.py (original) +++ py/trunk/py/test/testing/test_pytestplugin.py Wed Mar 18 21:49:38 2009 @@ -23,14 +23,15 @@ assert l2 == l3 def test_pytestplugin_ENV_startup(self, testdir, monkeypatch): - testdir.makepyfile(pytest_x500="class X500Plugin: pass") + x500 = testdir.makepyfile(pytest_x500="class X500Plugin: pass") p = testdir.makepyfile(""" import py def test_hello(): plugin = py.test.config.pytestplugins.getplugin('x500') assert plugin is not None """) - testdir.syspathinsert() + new = str(x500.dirpath()) # "%s:%s" %(x500.dirpath(), os.environ.get('PYTHONPATH', '')) + monkeypatch.setitem(os.environ, 'PYTHONPATH', new) monkeypatch.setitem(os.environ, 'PYTEST_PLUGINS', 'pytest_x500') result = testdir.runpytest(p) assert result.ret == 0 From hpk at codespeak.net Wed Mar 18 22:24:44 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 18 Mar 2009 22:24:44 +0100 (CET) Subject: [py-svn] r63056 - in py/trunk/py: . test Message-ID: <20090318212444.9D88316846A@codespeak.net> Author: hpk Date: Wed Mar 18 22:24:42 2009 New Revision: 63056 Modified: py/trunk/py/conftest.py py/trunk/py/test/config.py Log: try to ignore build directory (which shouldn't be there at all) for rsyncing Modified: py/trunk/py/conftest.py ============================================================================== --- py/trunk/py/conftest.py (original) +++ py/trunk/py/conftest.py Wed Mar 18 22:24:42 2009 @@ -2,6 +2,8 @@ pytest_plugins = 'pytest_doctest', 'pytest_pytester', 'pytest_restdoc' +rsyncignore = ['c-extension/greenlet/build'] + import py class PylibTestPlugin: def pytest_addoption(self, parser): Modified: py/trunk/py/test/config.py ============================================================================== --- py/trunk/py/test/config.py (original) +++ py/trunk/py/test/config.py Wed Mar 18 22:24:42 2009 @@ -168,7 +168,11 @@ except KeyError: return None modpath = py.path.local(mod.__file__).dirpath() - return [modpath.join(x, abs=True) for x in relroots] + l = [] + for relroot in relroots: + relroot = relroot.replace("/", py.path.local.sep) + l.append(modpath.join(relroot, abs=True)) + return l def addoptions(self, groupname, *specs): """ add a named group of options to the current testing session. From hpk at codespeak.net Thu Mar 19 10:56:41 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 19 Mar 2009 10:56:41 +0100 (CET) Subject: [py-svn] r63060 - py/trunk/py/execnet Message-ID: <20090319095641.B936716843E@codespeak.net> Author: hpk Date: Thu Mar 19 10:56:40 2009 New Revision: 63060 Modified: py/trunk/py/execnet/gateway.py Log: execnet really needs to improve its bootstrapping Modified: py/trunk/py/execnet/gateway.py ============================================================================== --- py/trunk/py/execnet/gateway.py (original) +++ py/trunk/py/execnet/gateway.py Thu Mar 19 10:56:40 2009 @@ -330,7 +330,12 @@ self._cleanup.unregister(self) self._stopexec() self._stopsend() - py._com.pyplugins.notify("gateway_exit", self) + try: + py._com.pyplugins.notify("gateway_exit", self) + except NameError: + # on the remote side 'py' is not imported + # and so we can't notify (XXX: make execnet synchronous) + pass def _stopsend(self): self._send(None) From hpk at codespeak.net Thu Mar 19 10:57:20 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 19 Mar 2009 10:57:20 +0100 (CET) Subject: [py-svn] r63061 - py/trunk/py/execnet/script Message-ID: <20090319095720.2AE3C168453@codespeak.net> Author: hpk Date: Thu Mar 19 10:57:19 2009 New Revision: 63061 Modified: py/trunk/py/execnet/script/socketserver.py Log: dont print too much info on non-debug Modified: py/trunk/py/execnet/script/socketserver.py ============================================================================== --- py/trunk/py/execnet/script/socketserver.py (original) +++ py/trunk/py/execnet/script/socketserver.py Thu Mar 19 10:57:19 2009 @@ -68,8 +68,12 @@ except (KeyboardInterrupt, SystemExit): raise except: - import traceback - traceback.print_exc() + if debug: + import traceback + traceback.print_exc() + else: + excinfo = sys.exc_info() + print "got exception", excinfo[1] if not loop: break finally: From hpk at codespeak.net Thu Mar 19 15:34:34 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 19 Mar 2009 15:34:34 +0100 (CET) Subject: [py-svn] r63073 - in py/trunk/py/test: . testing Message-ID: <20090319143434.0D1DE1683E4@codespeak.net> Author: hpk Date: Thu Mar 19 15:34:33 2009 New Revision: 63073 Modified: py/trunk/py/test/pycollect.py py/trunk/py/test/testing/test_pycollect.py Log: make sure that generated test names are always unique Modified: py/trunk/py/test/pycollect.py ============================================================================== --- py/trunk/py/test/pycollect.py (original) +++ py/trunk/py/test/pycollect.py Thu Mar 19 15:34:33 2009 @@ -288,6 +288,7 @@ # otherwise we could avoid global setupstate self.config._setupstate.prepare(self) l = [] + seen = {} for i, x in py.builtin.enumerate(self.obj()): name, call, args = self.getcallargs(x) if not callable(call): @@ -296,6 +297,9 @@ name = "[%d]" % i else: name = "['%s']" % name + if name in seen: + raise ValueError("%r generated tests with non-unique name %r" %(self, name)) + seen[name] = True l.append(self.Function(name, self, args=args, callobj=call)) return l Modified: py/trunk/py/test/testing/test_pycollect.py ============================================================================== --- py/trunk/py/test/testing/test_pycollect.py (original) +++ py/trunk/py/test/testing/test_pycollect.py Thu Mar 19 15:34:33 2009 @@ -140,6 +140,20 @@ assert gencolitems[1].name == "['fortytwo']" assert gencolitems[1].obj.func_name == 'func1' + def test_generative_functions_unique_explicit_names(self, testdir): + # generative + modcol = testdir.getmodulecol(""" + def func(): pass + def test_gen(): + yield "name", func + yield "name", func + """) + colitems = modcol.collect() + assert len(colitems) == 1 + gencol = colitems[0] + assert isinstance(gencol, py.test.collect.Generator) + py.test.raises(ValueError, "gencol.collect()") + def test_generative_methods_with_explicit_names(self, testdir): modcol = testdir.getmodulecol(""" def func1(arg, arg2): From hpk at codespeak.net Thu Mar 19 16:23:00 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 19 Mar 2009 16:23:00 +0100 (CET) Subject: [py-svn] r63083 - in py/extradoc/talk/pycon-us-2009/pytest-advanced: . img Message-ID: <20090319152300.A2B34168464@codespeak.net> Author: hpk Date: Thu Mar 19 16:22:59 2009 New Revision: 63083 Added: py/extradoc/talk/pycon-us-2009/pytest-advanced/author.latex - copied, changed from r62841, pypy/extradoc/talk/openbossa2009/pypy-mobile/author.latex py/extradoc/talk/pycon-us-2009/pytest-advanced/img/ py/extradoc/talk/pycon-us-2009/pytest-advanced/img/end_of_a_age_by_marikaz.jpg (props changed) - copied unchanged from r62841, pypy/extradoc/talk/openbossa2009/pypy-mobile/end_of_a_age_by_marikaz.jpg py/extradoc/talk/pycon-us-2009/pytest-advanced/img/flying_lady_by_marikaz.jpg (props changed) - copied unchanged from r62841, pypy/extradoc/talk/openbossa2009/pypy-mobile/flying_lady_by_marikaz.jpg py/extradoc/talk/pycon-us-2009/pytest-advanced/img/in_a_long_time_by_marikaz.jpg (contents, props changed) py/extradoc/talk/pycon-us-2009/pytest-advanced/img/large.graffle (contents, props changed) py/extradoc/talk/pycon-us-2009/pytest-advanced/img/large.png (contents, props changed) py/extradoc/talk/pycon-us-2009/pytest-advanced/img/little_red_riding_hood_by_marikaz.jpg (props changed) - copied unchanged from r62841, pypy/extradoc/talk/openbossa2009/pypy-mobile/little_red_riding_hood_by_marikaz.jpg py/extradoc/talk/pycon-us-2009/pytest-advanced/img/medium.graffle (contents, props changed) py/extradoc/talk/pycon-us-2009/pytest-advanced/img/medium.png (contents, props changed) py/extradoc/talk/pycon-us-2009/pytest-advanced/img/merlinux-logo.jpg - copied unchanged from r62841, pypy/extradoc/talk/openbossa2009/pypy-mobile/merlinux-logo.jpg py/extradoc/talk/pycon-us-2009/pytest-advanced/img/mystical_color_statue_by_marikaz.jpg (props changed) - copied unchanged from r62841, pypy/extradoc/talk/openbossa2009/pypy-mobile/mystical_color_statue_by_marikaz.jpg py/extradoc/talk/pycon-us-2009/pytest-advanced/img/new_color_in_dark_old_city_by_marikaz.jpg (props changed) - copied unchanged from r62841, pypy/extradoc/talk/openbossa2009/pypy-mobile/new_color_in_dark_old_city_by_marikaz.jpg py/extradoc/talk/pycon-us-2009/pytest-advanced/img/py-web.png (props changed) - copied unchanged from r62662, pypy/extradoc/talk/img/py-web.png py/extradoc/talk/pycon-us-2009/pytest-advanced/img/rails_in_the_city_by_marikaz.jpg (contents, props changed) py/extradoc/talk/pycon-us-2009/pytest-advanced/img/small.graffle (contents, props changed) py/extradoc/talk/pycon-us-2009/pytest-advanced/img/small.png (contents, props changed) py/extradoc/talk/pycon-us-2009/pytest-advanced/makepdf - copied, changed from r62841, pypy/extradoc/talk/openbossa2009/pypy-mobile/makepdf py/extradoc/talk/pycon-us-2009/pytest-advanced/stylesheet.latex - copied unchanged from r62841, pypy/extradoc/talk/openbossa2009/pypy-mobile/stylesheet.latex py/extradoc/talk/pycon-us-2009/pytest-advanced/title.latex - copied, changed from r62841, pypy/extradoc/talk/openbossa2009/pypy-mobile/title.latex Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.html py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Log: a first draft of the advanced talk, lots of pictures Copied: py/extradoc/talk/pycon-us-2009/pytest-advanced/author.latex (from r62841, pypy/extradoc/talk/openbossa2009/pypy-mobile/author.latex) ============================================================================== --- pypy/extradoc/talk/openbossa2009/pypy-mobile/author.latex (original) +++ py/extradoc/talk/pycon-us-2009/pytest-advanced/author.latex Thu Mar 19 16:22:59 2009 @@ -1,7 +1,7 @@ \definecolor{rrblitbackground}{rgb}{0.0, 0.0, 0.0} -\title[PyPy status / mobile perspectives]{PyPy status / mobile perspectives} +\title[pytest advanced testing] {pytest advanced} \author[H. Krekel]{Holger Krekel \\ http://merlinux.eu} -\institute[OpenBossa 2009, Recife, Brazil]{OpenBossa 2009, Brazil} -\date{March 11, 2009} +\institute[Pycon US 2009, Chicago]{Pycon US 2009, Chicago} +\date{March 26, 2009} Added: py/extradoc/talk/pycon-us-2009/pytest-advanced/img/in_a_long_time_by_marikaz.jpg ============================================================================== Binary file. No diff available. Added: py/extradoc/talk/pycon-us-2009/pytest-advanced/img/large.graffle ============================================================================== Binary file. No diff available. Added: py/extradoc/talk/pycon-us-2009/pytest-advanced/img/large.png ============================================================================== Binary file. No diff available. Added: py/extradoc/talk/pycon-us-2009/pytest-advanced/img/medium.graffle ============================================================================== Binary file. No diff available. Added: py/extradoc/talk/pycon-us-2009/pytest-advanced/img/medium.png ============================================================================== Binary file. No diff available. Added: py/extradoc/talk/pycon-us-2009/pytest-advanced/img/rails_in_the_city_by_marikaz.jpg ============================================================================== Binary file. No diff available. Added: py/extradoc/talk/pycon-us-2009/pytest-advanced/img/small.graffle ============================================================================== Binary file. No diff available. Added: py/extradoc/talk/pycon-us-2009/pytest-advanced/img/small.png ============================================================================== Binary file. No diff available. Copied: py/extradoc/talk/pycon-us-2009/pytest-advanced/makepdf (from r62841, pypy/extradoc/talk/openbossa2009/pypy-mobile/makepdf) ============================================================================== --- pypy/extradoc/talk/openbossa2009/pypy-mobile/makepdf (original) +++ py/extradoc/talk/pycon-us-2009/pytest-advanced/makepdf Thu Mar 19 16:22:59 2009 @@ -6,7 +6,7 @@ # WARNING: to work, it needs this patch for docutils # https://sourceforge.net/tracker/?func=detail&atid=422032&aid=1459707&group_id=38414 -BASE=talk +BASE=pytest-advanced rst2beamer.py --stylesheet=stylesheet.latex --documentoptions=14pt $BASE.txt $BASE.latex || exit sed 's/\\date{}/\\input{author.latex}/' $BASE.latex >tmpfile || exit sed 's/\\maketitle/\\input{title.latex}/' tmpfile >$BASE.latex || exit Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.html ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.html (original) +++ py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.html Thu Mar 19 16:22:59 2009 @@ -1,327 +1,18 @@ - + - - - + + - - - - - - - - - - - - -

-
-
- -
-

Title: py.test - cross-platform and distributed testing -Presenter: Holger Krekel <holger@merlinux.eu>, Brian <brian@dorseys.org> +Presenter: Holger Krekel <holger@merlinux.eu>, Brian <brian@dorseys.org> Tutorial format: interactive lecture Recording: I give permission to record and publish my PyCon tutorial for free distribution. Intended Audience: Python programmers @@ -329,9 +20,7 @@ Perequisites/knowledge: good knowledge of python programming, basic familiarity with automated testing Requirements: Attendees are welcome to bring their laptops with Python installed (version 2.4 or higher). Notes to reviewer: visting the beginner-oriented tutorial "rapid testing with minimal effort" is recommended, but not required.

- -
-
+

Tutorial Summary

Want to know more about advanced automated testing with Python? Use a tool that allows you to ad-hoc distribute tests to multiple @@ -347,20 +36,101 @@

The tutorial format will be an interactive lecture with plenty of time for questions.

-
-

Overview

-

Terminology/Overview (20 minutes)

+
+

Terminology/Overview (20 minutes)

+img/little_red_riding_hood_by_marikaz.jpg +

http://marikaz.deviantart.com/ CC 3.0 AN-ND

+
+
+

Terminology/Overview

    -
  • developer and "customer" tests
  • +
  • Benefits of automated tests
  • small/medium/big tests aka unit/functional/integration
  • acceptance tests
  • benefits of automated testing
  • -
  • existing python testing tools
  • +
  • related testing tools
  • +
+
+
+

Benefits of automated tests

+
    +
  • Quality of code
  • +
  • evolve codebase more easily
  • +
  • better collaboration
  • +
  • perfect fit for Python
  • +
+
+
+

Some Test terminology

+
    +
  • developer and "customer" tests
  • +
  • unit tests
  • +
  • functional tests
  • +
  • integration / system tests
  • +
+
+
+

Small Tests

+
img/small.png
+
+
+

Medium Tests

+
img/medium.png
+
+
+

Large Tests

+
img/large.png
+
+ +
+

Walkthrough Advanced Features (30 minutes)

+img/new_color_in_dark_old_city_by_marikaz.jpg +

http://marikaz.deviantart.com/ CC 3.0 AN-ND

+
+
+

If you want to try yourself

+
-
-

Basic features

-

Walkthrough py.test basic features (30 minutes)

+
+

per-class/instance set up of test state

+

example:

+
+class LocalSetup:
+    def setup_class(cls):
+      cls.root = py.test.ensuretemp(cls.__name__)
+      cls.root.ensure(dir=1)
+      setuptestfs(cls.root)
+
+    def setup_method(self, method):
+      self.tmpdir = self.root.mkdir(method.__name__)
+
+
+
+

funcargs: per-function setup

+

XXX find example:

+
+def test_pypkpath():
+  datadir = py.test.ensuretemp("pypkgdir")
+  pkg = datadir.ensure('pkg1', dir=1)
+  pkg.ensure("__init__.py")
+  pkg.ensure("subdir/__init__.py")
+  assert pkg.pypkgpath() == pkg
+  subinit = pkg.join('subdir', '__init__.py')
+  assert subinit.pypkgpath() == pkg
+
  • example of test module
  • working with failures, tracebacks
  • @@ -369,38 +139,221 @@
  • looponfailing: run large test set until all tests pass
-
-

Using Extensions

-

Using extensions (40 minutes)

+
+

Skipping tests

+

use py.test.skip:

+
+def test_something_on_windows():
+    if sys.platform == "win32":
+        py.test.skip("win32 required")
+     ...
+
+

don't use py.test.skip() in classic setup functions +because it makes distribution to other platforms +harder.

+
+
+

Skipping Doctests / ReST

+

XXX check with code

+

with 'pytest_restdoc' plugin:

+
+.. >>> mypkg = py.test.importorskip("mypkg")
+>>> x = 3
+>>> print x
+3
+
+
+
+

Generative / Parametrized tests

+

creating three tests with "yield":

+
+def check(x):
+    assert x >= 0
+
+def test_gen():
+    for x in 1,2,3:
+        yield check, x
+
+
+
+

Named Parametrized tests

+

creating three tests with "yield":

+
+def check(x):
+    assert x >= 0
+
+def test_gen():
+    for x in 1,2,3:
+        yield "check_check, x
+
+
+
+

Selection/Reporting

+
    +
  • -l | --showlocals: show local variables in traceback
  • +
  • --pdb: jump into pdb for failures
  • +
  • -k KEYWORD: select tests to run
  • +
  • -x | --exitfirst: stop on first test failure
  • +
+
+
+

Test Driven Development - at its best!

+

meet py.test --looponfailing:

+
    +
  • does a full test run
  • +
  • keeps a set of failing tests
  • +
  • checks for file changes and re-runs failing tests
  • +
  • if all failures fixed, reruns whole test suite
  • +
+

great for refactoring!

+
+
+

Using Plugins and Extensions (40 minutes)

+img/end_of_a_age_by_marikaz.jpg +

http://marikaz.deviantart.com/ CC 3.0 AN-ND

+
+
+

History

+
    +
  • 0.9.x uses conftest's for extension and configuration
  • +
  • 1.0 uses "plugins" for extending and conftest.py for configuration
  • +
+
+
+

Conftest.py

+
    +
  • can be put into test directory or higher up
  • +
  • contains test configuration values
  • +
  • default values for command line options
  • +
  • specifies plugins to use
  • +
  • (old) can specify collection and test items
  • +
+
+
+

Customizing py.test

+
    +
  • modify/extend the test collection process

    +
  • +
  • add command line options

    +
  • +
  • register to reporting events (trunk/1.0)

    +
  • +
  • write conftest.py files

    +
  • +
  • write local or global plugins

    +
  • +
  • for debugging:

    +
    +py.test --collectonly
    +py.test --traceconfig
    +
    +
  • +
+
+
+

The collection tree

+
    +
  • items: runnable tests - item.runtest()
  • +
  • collectors: return collectors or items - collector.collect()
  • +
  • collection tree is built iteratively
  • +
  • test collection starts from directories or files (via cmdline)
  • +
  • test collection not limited to Python files!
  • +
  • use py.test --collectonly for debugging
  • +
+
+
+

Important Collection Objects

+
    +
  • Directory collect files in directory
  • +
  • FSCollector a Collector for a File
  • +
  • Module collect python Classes and Functions
  • +
  • Class/Instance collect test methods
  • +
  • Generator collect generative tests
  • +
  • Function sets up and executes a python test function
  • +
  • DoctestFile collect doctests in a .txt file
  • +
+
+
+

Specifying plugins

+
    +
  • -p|-plugin: load comma-separated list of plugins
  • +
  • plugins are always named "pytest_NAME" and can be +anywhere in your import path
  • +
+
+
+

Specifying plugins in a conftest

+

you can write a local conftest.py based plugin:

+
+class ConftestPlugin:
+    def pytest_addoption(self, parser):
+        parser.addoption("--myworld", action="store_true")
+    ...
+
+
+
+

Plugin Examples

  • integrate collection/run of traditional unit-tests
  • run functions in their own tempdir
  • testing ReST documents
  • -
  • running Javascript tests
  • -
  • running Prolog tests
  • -
  • html reporting for nightly runs
  • +
  • running Prolog tests (XXX conftest based)
  • +
  • running Javascript tests (conftest based)
  • +
  • html reporting for nightly runs (conftest-based)
-
+

Break

+img/flying_lady_by_marikaz.jpg +

http://marikaz.deviantart.com/ CC 3.0 AN-ND

-
-

Writing Extensions

-

Writing extensions (30 minutes)

-
    -
  • overview on extensibility
  • -
  • per-project hooks for customization
  • -
  • conftest.py mechanism
  • -
  • global hooks for plugins
  • +
    +

    Writing Plugins (30 minutes)

    +
      +
    • plugin hooks and events
    • event system for custom reporting
    • test collection hooks
    • test running hooks
    • writing cross-project plugins
    -
    +
    +

    Hooks and Events

    +
      +
    • Plugin hook methods interact with +configuration/collection/running of tests
    • +
    • Events signal Plugin hook methods interact with +configuration/collection/running of tests
    • +
    +
    +
    +

    Collection Hooks

    + +

    pytest_collect_file +pytest_collect_module +...

    +
    +
    +

    test run hooks

    + +

    pytest_pyfunc_call +pytest_report*

    +
    +
    +

    Writing cross-project plugins

    +
      +
    • put plugin into pytest_name.py or package
    • +
    • make a NamePlugin class available
    • +
    • release or copy to somewhere importable
    • +
    +
    +
    +

    Distributed Testing (45 minutes)

    +img/rails_in_the_city_by_marikaz.jpg +

    http://marikaz.deviantart.com/ CC 3.0 AN-ND

    +
    +

    Distributed Testing

    -

    Distributed testing (45 minutes)

    • motivation/vision
    • run your tests on multiple CPUs/multicore
    • @@ -409,7 +362,7 @@
    • do's and dont's for cross-process testing
    -
    +

    Questions

    Q&A (15 minutes)

    Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Thu Mar 19 16:22:59 2009 @@ -27,21 +27,120 @@ The tutorial format will be an interactive lecture with plenty of time for questions. -Overview -======== - Terminology/Overview (20 minutes) +============================================================================ + +.. image:: img/little_red_riding_hood_by_marikaz.jpg + :scale: 100 + :align: left + +http://marikaz.deviantart.com/ CC 3.0 AN-ND + -- developer and "customer" tests +Terminology/Overview +======================================= + +- Benefits of automated tests - small/medium/big tests aka unit/functional/integration - acceptance tests - benefits of automated testing -- existing python testing tools +- related testing tools + +Benefits of automated tests +============================== + +- Quality of code +- evolve codebase more easily +- better collaboration +- perfect fit for Python + +Some Test terminology +============================== + +- developer and "customer" tests +- unit tests +- functional tests +- integration / system tests + +Small Tests +============================== + +.. image:: img/small.png + :align: center + :scale: 70 + +Medium Tests +============================== + +.. image:: img/medium.png + :align: center + :scale: 70 + +Large Tests +============================== + +.. image:: img/large.png + :align: center + :scale: 70 + + +Related testing tools +========================= + +- nosetests +- unittest.py +- zope3-test runner +- trial +- twill +- windmill + + +Walkthrough Advanced Features (30 minutes) +============================================================================ + +.. image:: img/new_color_in_dark_old_city_by_marikaz.jpg + :scale: 100 + :align: left + +http://marikaz.deviantart.com/ CC 3.0 AN-ND + +If you want to try yourself +============================ + +- svn checkout http://codespeak.net/svn/py/dist + +- run "python setup.py" with "install" or "develop" -Basic features -============== -Walkthrough py.test basic features (30 minutes) +per-class/instance set up of test state +========================================== + +example:: + + class LocalSetup: + def setup_class(cls): + cls.root = py.test.ensuretemp(cls.__name__) + cls.root.ensure(dir=1) + setuptestfs(cls.root) + + def setup_method(self, method): + self.tmpdir = self.root.mkdir(method.__name__) + + +funcargs: per-function setup +=================================== + +XXX find example:: + + def test_pypkpath(): + datadir = py.test.ensuretemp("pypkgdir") + pkg = datadir.ensure('pkg1', dir=1) + pkg.ensure("__init__.py") + pkg.ensure("subdir/__init__.py") + assert pkg.pypkgpath() == pkg + subinit = pkg.join('subdir', '__init__.py') + assert subinit.pypkgpath() == pkg + - example of test module - working with failures, tracebacks @@ -49,39 +148,230 @@ - skipping chunks within doctests - looponfailing: run large test set until all tests pass -Using Extensions -================ +Skipping tests +=================== + +use ``py.test.skip``:: + + def test_something_on_windows(): + if sys.platform == "win32": + py.test.skip("win32 required") + ... + +don't use py.test.skip() in classic setup functions +because it makes distribution to other platforms +harder. + +Skipping Doctests / ReST +====================================== + +XXX check with code + +with 'pytest_restdoc' plugin:: + + .. >>> mypkg = py.test.importorskip("mypkg") + >>> x = 3 + >>> print x + 3 + + +Generative / Parametrized tests +================================= + +creating three tests with "yield":: + + def check(x): + assert x >= 0 + + def test_gen(): + for x in 1,2,3: + yield check, x + +Named Parametrized tests +================================= + +creating three tests with "yield":: + + def check(x): + assert x >= 0 -Using extensions (40 minutes) + def test_gen(): + for x in 1,2,3: + yield "check_check, x + + +Selection/Reporting +============================ + +- ``-l | --showlocals``: show local variables in traceback +- ``--pdb``: jump into pdb for failures +- ``-k KEYWORD``: select tests to run +- ``-x | --exitfirst``: stop on first test failure + + +Test Driven Development - at its best! +======================================= + +meet ``py.test --looponfailing``: + +- does a full test run +- keeps a set of failing tests +- checks for file changes and re-runs failing tests +- if all failures fixed, reruns whole test suite + +great for refactoring! + + +Using Plugins and Extensions (40 minutes) +========================================= + +.. image:: img/end_of_a_age_by_marikaz.jpg + :scale: 100 + :align: left + +http://marikaz.deviantart.com/ CC 3.0 AN-ND + + +History +========================== + +- 0.9.x uses conftest's for extension and configuration +- 1.0 uses "plugins" for extending and conftest.py for configuration + +Conftest.py +=============== + +- can be put into test directory or higher up +- contains test configuration values +- default values for command line options +- specifies plugins to use +- (old) can specify collection and test items + +Customizing py.test +=========================== + +- modify/extend the test collection process +- add command line options +- register to reporting events (trunk/1.0) +- write conftest.py files +- write local or global plugins +- for debugging:: + + py.test --collectonly + py.test --traceconfig + +The collection tree +=========================== + +- items: runnable tests - ``item.runtest()`` +- collectors: return collectors or items - ``collector.collect()`` +- collection tree is built iteratively +- test collection starts from directories or files (via cmdline) +- test collection not limited to Python files! +- use ``py.test --collectonly`` for debugging + +Important Collection Objects +================================== + +- **Directory** collect files in directory +- **FSCollector** a Collector for a File +- **Module** collect python Classes and Functions +- **Class**/**Instance** collect test methods +- **Generator** collect generative tests +- **Function** sets up and executes a python test function +- **DoctestFile** collect doctests in a .txt file + +Specifying plugins +======================== + +- ``-p|-plugin``: load comma-separated list of plugins +- plugins are always named "pytest_NAME" and can be + anywhere in your import path + +Specifying plugins in a conftest +==================================== + +you can write a local conftest.py based plugin:: + + class ConftestPlugin: + def pytest_addoption(self, parser): + parser.addoption("--myworld", action="store_true") + ... + +Plugin Examples +========================================= - integrate collection/run of traditional unit-tests - run functions in their own tempdir - testing ReST documents -- running Javascript tests -- running Prolog tests -- html reporting for nightly runs +- running Prolog tests (XXX conftest based) +- running Javascript tests (conftest based) +- html reporting for nightly runs (conftest-based) Break ===== -Writing Extensions -================== +.. image:: img/flying_lady_by_marikaz.jpg + :scale: 100 + :align: left + +http://marikaz.deviantart.com/ CC 3.0 AN-ND -Writing extensions (30 minutes) +Writing Plugins (30 minutes) +============================================================================ -- overview on extensibility -- per-project hooks for customization -- conftest.py mechanism -- global hooks for plugins +- plugin hooks and events - event system for custom reporting - test collection hooks - test running hooks - writing cross-project plugins -Distributed Testing + +Hooks and Events =================== -Distributed testing (45 minutes) +- Plugin hook methods interact with + configuration/collection/running of tests + +- Events signal Plugin hook methods interact with + configuration/collection/running of tests + +Collection Hooks +================= + +.. XXX + +pytest_collect_file +pytest_collect_module +... + +test run hooks +================= + +.. XXX + +pytest_pyfunc_call +pytest_report* + + +Writing cross-project plugins +================================== + +- put plugin into pytest_name.py or package +- make a NamePlugin class available +- release or copy to somewhere importable + +Distributed Testing (45 minutes) +==================================== + +.. image:: img/rails_in_the_city_by_marikaz.jpg + :scale: 100 + :align: left + +http://marikaz.deviantart.com/ CC 3.0 AN-ND + +Distributed Testing +=================== - motivation/vision - run your tests on multiple CPUs/multicore Copied: py/extradoc/talk/pycon-us-2009/pytest-advanced/title.latex (from r62841, pypy/extradoc/talk/openbossa2009/pypy-mobile/title.latex) ============================================================================== --- pypy/extradoc/talk/openbossa2009/pypy-mobile/title.latex (original) +++ py/extradoc/talk/pycon-us-2009/pytest-advanced/title.latex Thu Mar 19 16:22:59 2009 @@ -1,7 +1,7 @@ \begin{titlepage} \begin{figure}[h] -\includegraphics[width=64px,height=64px]{merlinux-logo.jpg} +\includegraphics[width=64px,height=64px]{img/merlinux-logo.jpg} \qquad -\includegraphics[width=80px]{../../img/py-web.png} +\includegraphics[width=80px]{img/py-web.png} \end{figure} \end{titlepage} From hpk at codespeak.net Thu Mar 19 16:29:12 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 19 Mar 2009 16:29:12 +0100 (CET) Subject: [py-svn] r63084 - py/extradoc/talk/pycon-us-2009/pytest-introduction Message-ID: <20090319152912.17DB8168464@codespeak.net> Author: hpk Date: Thu Mar 19 16:29:11 2009 New Revision: 63084 Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt Log: add a bit at the beginning Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt Thu Mar 19 16:29:11 2009 @@ -57,14 +57,21 @@ *The fundamental features of py.test (about 10 minutes)* -- function or class -- automatic test discovery -- assert introspection -- print() debugging -- simplicity -- and many more... +- a good tool for test-driven development! +- no boilerplate: write tests rapidly +- run all or subsets of tests +- lots of useful information when a test fails +Why test-driven development? +============================ + +- make sure code does the right thing +- make sure changes don't break expected behaviour +- incremental work style +- faster develpment +- easier maintenance + function or class ================= From hpk at codespeak.net Thu Mar 19 17:32:42 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 19 Mar 2009 17:32:42 +0100 (CET) Subject: [py-svn] r63088 - py/extradoc/talk/pycon-us-2009/pytest-advanced Message-ID: <20090319163242.75291168406@codespeak.net> Author: hpk Date: Thu Mar 19 17:32:42 2009 New Revision: 63088 Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Log: doctests needs work, but for now skipping with ValueErrors is to stay. Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Thu Mar 19 17:32:42 2009 @@ -165,11 +165,10 @@ Skipping Doctests / ReST ====================================== -XXX check with code - with 'pytest_restdoc' plugin:: - .. >>> mypkg = py.test.importorskip("mypkg") + .. >>> import py + .. >>> if py.test.config.option.xyz: raise ValueError("skipchunk") >>> x = 3 >>> print x 3 From hpk at codespeak.net Thu Mar 19 18:05:42 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 19 Mar 2009 18:05:42 +0100 (CET) Subject: [py-svn] r63089 - in py/trunk/py: . execnet Message-ID: <20090319170542.136E0168453@codespeak.net> Author: hpk Date: Thu Mar 19 18:05:41 2009 New Revision: 63089 Added: py/trunk/py/execnet/multi.py - copied, changed from r63084, py/trunk/py/execnet/gwmanage.py Modified: py/trunk/py/__init__.py py/trunk/py/execnet/gwmanage.py Log: factor out MultiChannel and MultiGateway helpers Modified: py/trunk/py/__init__.py ============================================================================== --- py/trunk/py/__init__.py (original) +++ py/trunk/py/__init__.py Thu Mar 19 18:05:41 2009 @@ -152,8 +152,8 @@ 'execnet.PopenGateway' : ('./execnet/register.py', 'PopenGateway'), 'execnet.SshGateway' : ('./execnet/register.py', 'SshGateway'), 'execnet.GatewaySpec' : ('./execnet/gwmanage.py', 'GatewaySpec'), - 'execnet.MultiGateway' : ('./execnet/gwmanage.py', 'MultiGateway'), - 'execnet.MultiChannel' : ('./execnet/gwmanage.py', 'MultiChannel'), + 'execnet.MultiGateway' : ('./execnet/multi.py', 'MultiGateway'), + 'execnet.MultiChannel' : ('./execnet/multi.py', 'MultiChannel'), # execnet scripts 'execnet.RSync' : ('./execnet/rsync.py', 'RSync'), Modified: py/trunk/py/execnet/gwmanage.py ============================================================================== --- py/trunk/py/execnet/gwmanage.py (original) +++ py/trunk/py/execnet/gwmanage.py Thu Mar 19 18:05:41 2009 @@ -94,64 +94,6 @@ gw.spec = self return gw -class MultiChannel: - def __init__(self, channels): - self._channels = channels - - def send_each(self, item): - for ch in self._channels: - ch.send(item) - - def receive_each(self, withchannel=False): - assert not hasattr(self, '_queue') - l = [] - for ch in self._channels: - obj = ch.receive() - if withchannel: - l.append((ch, obj)) - else: - l.append(obj) - return l - - def make_receive_queue(self, endmarker=NO_ENDMARKER_WANTED): - try: - return self._queue - except AttributeError: - self._queue = py.std.Queue.Queue() - for ch in self._channels: - def putreceived(obj, channel=ch): - self._queue.put((channel, obj)) - if endmarker is NO_ENDMARKER_WANTED: - ch.setcallback(putreceived) - else: - ch.setcallback(putreceived, endmarker=endmarker) - return self._queue - - - def waitclose(self): - first = None - for ch in self._channels: - try: - ch.waitclose() - except ch.RemoteError: - if first is None: - first = py.std.sys.exc_info() - if first: - raise first[0], first[1], first[2] - -class MultiGateway: - RemoteError = RemoteError - def __init__(self, gateways): - self.gateways = gateways - def remote_exec(self, source): - channels = [] - for gw in self.gateways: - channels.append(gw.remote_exec(source)) - return MultiChannel(channels) - def exit(self): - for gw in self.gateways: - gw.exit() - class GatewayManager: RemoteError = RemoteError @@ -179,7 +121,7 @@ else: if remote: l.append(gw) - return MultiGateway(gateways=l) + return py.execnet.MultiGateway(gateways=l) def multi_exec(self, source, inplacelocal=True): """ remote execute code on all gateways. Copied: py/trunk/py/execnet/multi.py (from r63084, py/trunk/py/execnet/gwmanage.py) ============================================================================== --- py/trunk/py/execnet/gwmanage.py (original) +++ py/trunk/py/execnet/multi.py Thu Mar 19 18:05:41 2009 @@ -1,98 +1,24 @@ """ - instantiating, managing and rsyncing to hosts - -Host specification strings and implied gateways: - - socket:hostname:port:path SocketGateway - popen[-executable][:path] PopenGateway - [ssh:]spec:path SshGateway - * [SshGateway] - -on hostspec.makeconnection() a Host object -will be created which has an instantiated gateway. -the remote side will be chdir()ed to the specified path. -if no path was specified, do no chdir() at all. - + Working with multiple channels and gateways """ import py -import sys, os -from py.__.test.dsession.masterslave import MasterNode -from py.__.test import event from py.__.execnet.channel import RemoteError NO_ENDMARKER_WANTED = object() -class GatewaySpec(object): - python = None - def __init__(self, spec, defaultjoinpath="pyexecnetcache"): - if spec == "popen" or spec.startswith("popen:"): - parts = spec.split(":", 2) - self.type = self.address = parts.pop(0) - if parts: - python = parts.pop(0) - # XXX XXX XXX do better GWSPEC that can deal - # with "C:" - if py.std.sys.platform == "win32" and len(python) == 1: - python = "%s:%s" %(python, parts.pop(0)) - self.python = python - if parts: - self.joinpath = parts.pop(0) - else: - self.joinpath = "" - if not self.python: - self.python = py.std.sys.executable - - elif spec.startswith("socket:"): - parts = spec[7:].split(":", 2) - self.address = parts.pop(0) - if parts: - port = int(parts.pop(0)) - self.address = self.address, port - self.joinpath = parts and parts.pop(0) or "" - self.type = "socket" - else: - if spec.startswith("ssh:"): - spec = spec[4:] - parts = spec.split(":", 2) - self.address = parts.pop(0) - self.python = parts and parts.pop(0) or "python" - self.joinpath = parts and parts.pop(0) or "" - self.type = "ssh" - if not self.joinpath and not self.inplacelocal(): - self.joinpath = defaultjoinpath - - def inplacelocal(self): - return bool(self.type == "popen" and not self.joinpath) - - def __str__(self): - return "" % (self.address, self.joinpath) - __repr__ = __str__ - - def makegateway(self, waitclose=True): - if self.type == "popen": - gw = py.execnet.PopenGateway(python=self.python) - elif self.type == "socket": - gw = py.execnet.SocketGateway(*self.address) - elif self.type == "ssh": - gw = py.execnet.SshGateway(self.address, remotepython=self.python) - if self.joinpath: - channel = gw.remote_exec(""" - import os - path = %r - try: - os.chdir(path) - except OSError: - os.mkdir(path) - os.chdir(path) - """ % self.joinpath) - if waitclose: - channel.waitclose() - else: - if waitclose: - gw.remote_exec("").waitclose() - gw.spec = self - return gw +class MultiGateway: + RemoteError = RemoteError + def __init__(self, gateways): + self.gateways = gateways + def remote_exec(self, source): + channels = [] + for gw in self.gateways: + channels.append(gw.remote_exec(source)) + return MultiChannel(channels) + def exit(self): + for gw in self.gateways: + gw.exit() class MultiChannel: def __init__(self, channels): @@ -139,119 +65,4 @@ if first: raise first[0], first[1], first[2] -class MultiGateway: - RemoteError = RemoteError - def __init__(self, gateways): - self.gateways = gateways - def remote_exec(self, source): - channels = [] - for gw in self.gateways: - channels.append(gw.remote_exec(source)) - return MultiChannel(channels) - def exit(self): - for gw in self.gateways: - gw.exit() - -class GatewayManager: - RemoteError = RemoteError - - def __init__(self, specs): - self.specs = [GatewaySpec(spec) for spec in specs] - self.gateways = [] - - def trace(self, msg): - py._com.pyplugins.notify("trace", "gatewaymanage", msg) - - def makegateways(self): - assert not self.gateways - for spec in self.specs: - self.trace("makegateway %s" %(spec)) - self.gateways.append(spec.makegateway()) - - def getgateways(self, remote=True, inplacelocal=True): - if not self.gateways and self.specs: - self.makegateways() - l = [] - for gw in self.gateways: - if gw.spec.inplacelocal(): - if inplacelocal: - l.append(gw) - else: - if remote: - l.append(gw) - return MultiGateway(gateways=l) - - def multi_exec(self, source, inplacelocal=True): - """ remote execute code on all gateways. - @param inplacelocal=False: don't send code to inplacelocal hosts. - """ - multigw = self.getgateways(inplacelocal=inplacelocal) - return multigw.remote_exec(source) - - def multi_chdir(self, basename, inplacelocal=True): - """ perform a remote chdir to the given path, may be relative. - @param inplacelocal=False: don't send code to inplacelocal hosts. - """ - self.multi_exec("import os ; os.chdir(%r)" % basename, - inplacelocal=inplacelocal).waitclose() - - def rsync(self, source, notify=None, verbose=False, ignores=None): - """ perform rsync to all remote hosts. - """ - rsync = HostRSync(source, verbose=verbose, ignores=ignores) - added = False - for gateway in self.gateways: - spec = gateway.spec - if not spec.inplacelocal(): - self.trace("add_target_host %r" %(gateway,)) - def finished(): - if notify: - notify("rsyncrootready", spec, source) - rsync.add_target_host(gateway, finished=finished) - added = True - if added: - self.trace("rsyncing %r" % source) - rsync.send() - self.trace("rsyncing %r finished" % source) - else: - self.trace("rsync: nothing to do.") - - def exit(self): - while self.gateways: - gw = self.gateways.pop() - self.trace("exiting gateway %s" % gw) - gw.exit() -class HostRSync(py.execnet.RSync): - """ RSyncer that filters out common files - """ - def __init__(self, sourcedir, *args, **kwargs): - self._synced = {} - ignores= None - if 'ignores' in kwargs: - ignores = kwargs.pop('ignores') - self._ignores = ignores or [] - super(HostRSync, self).__init__(sourcedir=sourcedir, **kwargs) - - def filter(self, path): - path = py.path.local(path) - if not path.ext in ('.pyc', '.pyo'): - if not path.basename.endswith('~'): - if path.check(dotfile=0): - for x in self._ignores: - if path == x: - break - else: - return True - - def add_target_host(self, gateway, finished=None): - remotepath = os.path.basename(self._sourcedir) - super(HostRSync, self).add_target(gateway, remotepath, - finishedcallback=finished, - delete=True,) - - def _report_send_file(self, gateway, modified_rel_path): - if self._verbose: - path = os.path.basename(self._sourcedir) + "/" + modified_rel_path - remotepath = gateway.spec.joinpath - print '%s:%s <= %s' % (gateway.remoteaddress, remotepath, path) From hpk at codespeak.net Thu Mar 19 19:25:15 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 19 Mar 2009 19:25:15 +0100 (CET) Subject: [py-svn] r63091 - in py/trunk/py: execnet execnet/testing test/dsession test/dsession/testing test/plugin Message-ID: <20090319182515.391B6168466@codespeak.net> Author: hpk Date: Thu Mar 19 19:25:13 2009 New Revision: 63091 Modified: py/trunk/py/execnet/gwmanage.py py/trunk/py/execnet/testing/test_gateway.py py/trunk/py/execnet/testing/test_gwmanage.py py/trunk/py/test/dsession/hostmanage.py py/trunk/py/test/dsession/testing/test_functional_dsession.py py/trunk/py/test/dsession/testing/test_hostmanage.py py/trunk/py/test/plugin/pytest_plugintester.py py/trunk/py/test/plugin/pytest_terminal.py Log: report some more info on dist-setup shift comments Modified: py/trunk/py/execnet/gwmanage.py ============================================================================== --- py/trunk/py/execnet/gwmanage.py (original) +++ py/trunk/py/execnet/gwmanage.py Thu Mar 19 19:25:13 2009 @@ -26,6 +26,7 @@ class GatewaySpec(object): python = None def __init__(self, spec, defaultjoinpath="pyexecnetcache"): + self._spec = spec if spec == "popen" or spec.startswith("popen:"): parts = spec.split(":", 2) self.type = self.address = parts.pop(0) @@ -66,7 +67,7 @@ return bool(self.type == "popen" and not self.joinpath) def __str__(self): - return "" % (self.address, self.joinpath) + return "" % self._spec __repr__ = __str__ def makegateway(self, waitclose=True): @@ -102,13 +103,18 @@ self.gateways = [] def trace(self, msg): - py._com.pyplugins.notify("trace", "gatewaymanage", msg) + self.notify("trace", "gatewaymanage", msg) + + def notify(self, eventname, *args, **kwargs): + py._com.pyplugins.notify(eventname, *args, **kwargs) def makegateways(self): assert not self.gateways for spec in self.specs: - self.trace("makegateway %s" %(spec)) - self.gateways.append(spec.makegateway()) + gw = spec.makegateway() + self.gateways.append(gw) + gw.id = "[%s]" % len(self.gateways) + self.notify("gwmanage_newgateway", gw) def getgateways(self, remote=True, inplacelocal=True): if not self.gateways and self.specs: Modified: py/trunk/py/execnet/testing/test_gateway.py ============================================================================== --- py/trunk/py/execnet/testing/test_gateway.py (original) +++ py/trunk/py/execnet/testing/test_gateway.py Thu Mar 19 19:25:13 2009 @@ -565,9 +565,9 @@ ## cls.gw.exit() ## cls.proxygw.exit() -def getsshhost(): +def getsshhost(withpython=False): sshhost = py.test.config.getvalueorskip("sshhost") - if sshhost.find(":") != -1: + if not withpython and sshhost.find(":") != -1: sshhost = sshhost.split(":")[0] ssh = py.path.local.sysfind("ssh") if not ssh: Modified: py/trunk/py/execnet/testing/test_gwmanage.py ============================================================================== --- py/trunk/py/execnet/testing/test_gwmanage.py (original) +++ py/trunk/py/execnet/testing/test_gwmanage.py Thu Mar 19 19:25:13 2009 @@ -1,7 +1,5 @@ """ tests for - - gateway specifications - - multi channels and multi gateways - gateway management - manage rsyncing of hosts @@ -10,10 +8,18 @@ import py from py.__.execnet.gwmanage import GatewayManager, HostRSync +pytest_plugins = "pytest_pytester" + class TestGatewayManagerPopen: - def test_hostmanager_popen_makegateway(self): + def test_hostmanager_popen_makegateway(self, eventrecorder): hm = GatewayManager(["popen"] * 2) hm.makegateways() + event = eventrecorder.popevent("gwmanage_newgateway") + gw = event.args[0] + assert gw.id == "[1]" + event = eventrecorder.popevent("gwmanage_newgateway") + gw = event.args[0] + assert gw.id == "[2]" assert len(hm.gateways) == 2 hm.exit() assert not len(hm.gateways) Modified: py/trunk/py/test/dsession/hostmanage.py ============================================================================== --- py/trunk/py/test/dsession/hostmanage.py (original) +++ py/trunk/py/test/dsession/hostmanage.py Thu Mar 19 19:25:13 2009 @@ -50,6 +50,11 @@ self.gwmanager = GatewayManager(hosts) def makegateways(self): + # we change to the topdir sot that + # PopenGateways will have their cwd + # such that unpickling configs will + # pick it up as the right topdir + # (for other gateways this chdir is irrelevant) old = self.config.topdir.chdir() try: self.gwmanager.makegateways() @@ -74,11 +79,6 @@ have the same set of roots in their current directory. """ - # we change to the topdir sot that - # PopenGateways will have their cwd - # such that unpickling configs will - # pick it up as the right topdir - # (for other gateways this chdir is irrelevant) self.makegateways() options = { 'ignores': self.config_getignores(), Modified: py/trunk/py/test/dsession/testing/test_functional_dsession.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_functional_dsession.py (original) +++ py/trunk/py/test/dsession/testing/test_functional_dsession.py Thu Mar 19 19:25:13 2009 @@ -65,9 +65,12 @@ p = subdir.join("test_one.py") p.write("def test_5(): assert not __file__.startswith(%r)" % str(p)) result = testdir.runpytest("-d", "--rsyncdirs=%(subdir)s" % locals(), - "--gateways=popen::%(dest)s" % locals(), p) + "--gateways=popen::%(dest)s" % locals(), p) assert result.ret == 0 result.stdout.fnmatch_lines([ + "*1* instantiated gateway *popen*", + #"RSyncStart: [G1]", + #"RSyncFinished: [G1]", "*1 passed*" ]) assert dest.join(subdir.basename).check(dir=1) Modified: py/trunk/py/test/dsession/testing/test_hostmanage.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_hostmanage.py (original) +++ py/trunk/py/test/dsession/testing/test_hostmanage.py Thu Mar 19 19:25:13 2009 @@ -136,7 +136,7 @@ hm.teardown_hosts() def test_hostmanage_ssh_setup_hosts(self, testdir): - sshhost = getsshhost() + sshhost = getsshhost(withpython=True) testdir.makepyfile(__init__="", test_x=""" def test_one(): pass @@ -149,7 +149,7 @@ @py.test.mark.xfail("implement double-rsync test") def test_ssh_rsync_samehost_twice(self): - sshhost = getsshhost() + sshhost = getsshhost(withpython=True) host1 = Host("%s" % (sshhost, )) host2 = Host("%s" % (sshhost, )) hm = HostManager(config, hosts=[host1, host2]) Modified: py/trunk/py/test/plugin/pytest_plugintester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_plugintester.py (original) +++ py/trunk/py/test/plugin/pytest_plugintester.py Thu Mar 19 19:25:13 2009 @@ -186,7 +186,13 @@ """ whole test run starts. """ def pyevent_testrunfinish(self, event): - """ whole test run starts. """ + """ whole test run finishes. """ + + def pyevent_gwmanage_newgateway(self, gateway): + """ execnet gateway manager has instantiated a gateway. + The gateway will have an 'id' attribute that is unique + within the gateway manager context. + """ def pyevent_hostup(self, event): """ Host is up. """ Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Thu Mar 19 19:25:13 2009 @@ -84,6 +84,9 @@ for line in str(event.repr).split("\n"): self.write_line("InternalException: " + line) + def pyevent_gwmanage_newgateway(self, gateway): + self.write_line("%s instantiated gateway from spec %r" %(gateway.id, gateway.spec._spec)) + def pyevent_hostgatewayready(self, event): if self.config.option.verbose: self.write_line("HostGatewayReady: %s" %(event.host,)) From hpk at codespeak.net Fri Mar 20 00:57:12 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 20 Mar 2009 00:57:12 +0100 (CET) Subject: [py-svn] r63102 - in py/trunk/py/path/local: . testing Message-ID: <20090319235712.4AEE416847F@codespeak.net> Author: hpk Date: Fri Mar 20 00:57:10 2009 New Revision: 63102 Modified: py/trunk/py/path/local/local.py py/trunk/py/path/local/testing/test_local.py Log: add a "samefile" method to local path objects. Modified: py/trunk/py/path/local/local.py ============================================================================== --- py/trunk/py/path/local/local.py (original) +++ py/trunk/py/path/local/local.py Fri Mar 20 00:57:10 2009 @@ -201,6 +201,10 @@ s2 = s2.lower() return s1 == s2 + def samefile(self, other): + """ return True if other refers to the same stat object as self. """ + return py.std.os.path.samefile(str(self), str(other)) + def open(self, mode='r'): """ return an opened file with the given mode. """ return self._callex(open, self.strpath, mode) Modified: py/trunk/py/path/local/testing/test_local.py ============================================================================== --- py/trunk/py/path/local/testing/test_local.py (original) +++ py/trunk/py/path/local/testing/test_local.py Fri Mar 20 00:57:10 2009 @@ -14,6 +14,11 @@ class TestLocalPath(LocalSetup, CommonFSTests): + def test_samefile(self): + assert self.tmpdir.samefile(self.tmpdir) + p = self.tmpdir.ensure("hello") + assert p.samefile(p) + def test_join_normpath(self): assert self.tmpdir.join(".") == self.tmpdir p = self.tmpdir.join("../%s" % self.tmpdir.basename) From hpk at codespeak.net Fri Mar 20 01:30:35 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 20 Mar 2009 01:30:35 +0100 (CET) Subject: [py-svn] r63103 - in py/trunk/py/path/local: . testing Message-ID: <20090320003035.52486168486@codespeak.net> Author: hpk Date: Fri Mar 20 01:30:32 2009 New Revision: 63103 Modified: py/trunk/py/path/local/local.py py/trunk/py/path/local/posix.py py/trunk/py/path/local/testing/test_local.py py/trunk/py/path/local/testing/test_posix.py Log: samefile is only available on posix Modified: py/trunk/py/path/local/local.py ============================================================================== --- py/trunk/py/path/local/local.py (original) +++ py/trunk/py/path/local/local.py Fri Mar 20 01:30:32 2009 @@ -201,10 +201,6 @@ s2 = s2.lower() return s1 == s2 - def samefile(self, other): - """ return True if other refers to the same stat object as self. """ - return py.std.os.path.samefile(str(self), str(other)) - def open(self, mode='r'): """ return an opened file with the given mode. """ return self._callex(open, self.strpath, mode) Modified: py/trunk/py/path/local/posix.py ============================================================================== --- py/trunk/py/path/local/posix.py (original) +++ py/trunk/py/path/local/posix.py Fri Mar 20 01:30:32 2009 @@ -110,6 +110,9 @@ else: self._callex(os.remove, self.strpath) + def samefile(self, other): + """ return True if other refers to the same stat object as self. """ + return py.std.os.path.samefile(str(self), str(other)) def getuserid(user): import pwd Modified: py/trunk/py/path/local/testing/test_local.py ============================================================================== --- py/trunk/py/path/local/testing/test_local.py (original) +++ py/trunk/py/path/local/testing/test_local.py Fri Mar 20 01:30:32 2009 @@ -14,11 +14,6 @@ class TestLocalPath(LocalSetup, CommonFSTests): - def test_samefile(self): - assert self.tmpdir.samefile(self.tmpdir) - p = self.tmpdir.ensure("hello") - assert p.samefile(p) - def test_join_normpath(self): assert self.tmpdir.join(".") == self.tmpdir p = self.tmpdir.join("../%s" % self.tmpdir.basename) Modified: py/trunk/py/path/local/testing/test_posix.py ============================================================================== --- py/trunk/py/path/local/testing/test_posix.py (original) +++ py/trunk/py/path/local/testing/test_posix.py Fri Mar 20 01:30:32 2009 @@ -10,6 +10,11 @@ name = method.im_func.func_name self.tmpdir = self.root.ensure(name, dir=1) + def test_samefile(self): + assert self.tmpdir.samefile(self.tmpdir) + p = self.tmpdir.ensure("hello") + assert p.samefile(p) + def test_hardlink(self): tmpdir = self.tmpdir linkpath = tmpdir.join('test') From hpk at codespeak.net Fri Mar 20 01:34:59 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 20 Mar 2009 01:34:59 +0100 (CET) Subject: [py-svn] r63104 - in py/trunk/py: execnet execnet/testing test/dsession/testing test/plugin test/testing Message-ID: <20090320003459.079C1168487@codespeak.net> Author: hpk Date: Fri Mar 20 01:34:59 2009 New Revision: 63104 Modified: py/trunk/py/execnet/gwmanage.py py/trunk/py/execnet/testing/test_gwmanage.py py/trunk/py/test/dsession/testing/test_hostmanage.py py/trunk/py/test/plugin/pytest_plugintester.py py/trunk/py/test/plugin/pytest_terminal.py py/trunk/py/test/testing/test_pickling.py Log: * introduce rsync events * only rsync once if a gateway is specified multiply Modified: py/trunk/py/execnet/gwmanage.py ============================================================================== --- py/trunk/py/execnet/gwmanage.py (original) +++ py/trunk/py/execnet/gwmanage.py Fri Mar 20 01:34:59 2009 @@ -147,20 +147,22 @@ """ perform rsync to all remote hosts. """ rsync = HostRSync(source, verbose=verbose, ignores=ignores) - added = False + seen = {} for gateway in self.gateways: spec = gateway.spec if not spec.inplacelocal(): - self.trace("add_target_host %r" %(gateway,)) + key = spec.type, spec.address, spec.joinpath + if key in seen: + continue def finished(): if notify: notify("rsyncrootready", spec, source) rsync.add_target_host(gateway, finished=finished) - added = True - if added: - self.trace("rsyncing %r" % source) + seen[key] = gateway + if seen: + self.notify("gwmanage_rsyncstart", source=source, gateways=seen.values()) rsync.send() - self.trace("rsyncing %r finished" % source) + self.notify("gwmanage_rsyncfinish", source=source, gateways=seen.values()) else: self.trace("rsync: nothing to do.") Modified: py/trunk/py/execnet/testing/test_gwmanage.py ============================================================================== --- py/trunk/py/execnet/testing/test_gwmanage.py (original) +++ py/trunk/py/execnet/testing/test_gwmanage.py Fri Mar 20 01:34:59 2009 @@ -50,18 +50,18 @@ assert dest.join("dir1", "dir2").check() assert dest.join("dir1", "dir2", 'hello').check() - def XXXtest_ssh_rsync_samehost_twice(self): - #XXX we have no easy way to have a temp directory remotely! - option = py.test.config.option - if option.sshhost is None: - py.test.skip("no known ssh target, use -S to set one") - host1 = Host("%s" % (option.sshhost, )) - host2 = Host("%s" % (option.sshhost, )) - hm = HostManager(config, hosts=[host1, host2]) - events = [] - hm.init_rsync(events.append) - print events - assert 0 + def test_hostmanage_rsync_same_popen_twice(self, source, dest, eventrecorder): + hm = GatewayManager(["popen::%s" %dest] * 2) + hm.makegateways() + source.ensure("dir1", "dir2", "hello") + hm.rsync(source) + event = eventrecorder.popevent("gwmanage_rsyncstart") + source2 = event.kwargs['source'] + gws = event.kwargs['gateways'] + assert source2 == source + assert len(gws) == 1 + assert hm.gateways[0] == gws[0] + event = eventrecorder.popevent("gwmanage_rsyncfinish") def test_multi_chdir_popen_with_path(self, testdir): import os @@ -132,4 +132,3 @@ gw.exit() assert dest.join(source.basename, "hello.py").check() assert len(finished) == 1 - Modified: py/trunk/py/test/dsession/testing/test_hostmanage.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_hostmanage.py (original) +++ py/trunk/py/test/dsession/testing/test_hostmanage.py Fri Mar 20 01:34:59 2009 @@ -147,9 +147,7 @@ ev = sorter.getfirstnamed("itemtestreport") assert ev.passed - @py.test.mark.xfail("implement double-rsync test") - def test_ssh_rsync_samehost_twice(self): - sshhost = getsshhost(withpython=True) + def test_rsync_samehost_twice(self): host1 = Host("%s" % (sshhost, )) host2 = Host("%s" % (sshhost, )) hm = HostManager(config, hosts=[host1, host2]) Modified: py/trunk/py/test/plugin/pytest_plugintester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_plugintester.py (original) +++ py/trunk/py/test/plugin/pytest_plugintester.py Fri Mar 20 01:34:59 2009 @@ -161,6 +161,12 @@ def pyevent_gateway_exit(self, gateway): """ called when gateway is being exited. """ + def pyevent_gwmanage_rsyncstart(self, source, gateways): + """ called before rsyncing a directory to remote gateways takes place. """ + + def pyevent_gwmanage_rsyncfinish(self, source, gateways): + """ called after rsyncing a directory to remote gateways takes place. """ + def pyevent_trace(self, category, msg): """ called for tracing events. """ Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Fri Mar 20 01:34:59 2009 @@ -87,9 +87,13 @@ def pyevent_gwmanage_newgateway(self, gateway): self.write_line("%s instantiated gateway from spec %r" %(gateway.id, gateway.spec._spec)) - def pyevent_hostgatewayready(self, event): - if self.config.option.verbose: - self.write_line("HostGatewayReady: %s" %(event.host,)) + def pyevent_gwmanage_rsyncstart(self, source, gateways): + targets = ", ".join([gw.id for gw in gateways]) + self.write_line("rsyncstart: %s -> %s" %(source, targets)) + + def pyevent_gwmanage_rsyncfinish(self, source, gateways): + targets = ", ".join([gw.id for gw in gateways]) + self.write_line("rsyncfinish: %s -> %s" %(source, targets)) def pyevent_plugin_registered(self, plugin): if self.config.option.traceconfig: @@ -323,7 +327,6 @@ class TestTerminal: def test_hostup(self, testdir, linecomp): - from py.__.execnet.gwmanage import GatewaySpec item = testdir.getitem("def test_func(): pass") rep = TerminalReporter(item.config, linecomp.stringio) rep.pyevent_hostup(makehostup()) @@ -416,21 +419,31 @@ "InternalException: >*raise ValueError*" ]) - def test_hostready_crash(self, testdir, linecomp): - from py.__.execnet.gwmanage import GatewaySpec + def test_gwmanage_events(self, testdir, linecomp): modcol = testdir.getmodulecol(""" def test_one(): pass """, configargs=("-v",)) - host1 = GatewaySpec("localhost") + rep = TerminalReporter(modcol.config, file=linecomp.stringio) - rep.pyevent_hostgatewayready(event.HostGatewayReady(host1, None)) + class gw1: + id = "X1" + spec = py.execnet.GatewaySpec("popen") + class gw2: + id = "X2" + spec = py.execnet.GatewaySpec("popen") + rep.pyevent_gwmanage_newgateway(gateway=gw1) + linecomp.assert_contains_lines([ + "X1 instantiated gateway from spec*", + ]) + + rep.pyevent_gwmanage_rsyncstart(source="hello", gateways=[gw1, gw2]) linecomp.assert_contains_lines([ - "*HostGatewayReady*" + "rsyncstart: hello -> X1, X2" ]) - rep.pyevent_hostdown(event.HostDown(host1, "myerror")) + rep.pyevent_gwmanage_rsyncfinish(source="hello", gateways=[gw1, gw2]) linecomp.assert_contains_lines([ - "*HostDown*myerror*", + "rsyncfinish: hello -> X1, X2" ]) def test_writeline(self, testdir, linecomp): Modified: py/trunk/py/test/testing/test_pickling.py ============================================================================== --- py/trunk/py/test/testing/test_pickling.py (original) +++ py/trunk/py/test/testing/test_pickling.py Fri Mar 20 01:34:59 2009 @@ -44,7 +44,7 @@ assert config1.topdir == testdir.tmpdir testdir.chdir() p2config = pickletransport.p1_to_p2(config1) - assert p2config.topdir == config1.topdir + assert p2config.topdir.realpath() == config1.topdir.realpath() config_back = pickletransport.p2_to_p1(p2config) assert config_back is config1 @@ -160,10 +160,10 @@ newcol3 = unpickler.load() assert newcol2.config is newcol.config assert newcol2.parent == newcol - assert newcol2.config.topdir == topdir - assert newcol.fspath == topdir + assert newcol2.config.topdir.realpath() == topdir.realpath() + assert newcol.fspath.realpath() == topdir.realpath() assert newcol2.fspath.basename == dir1.basename - assert newcol2.fspath.relto(topdir) + assert newcol2.fspath.relto(newcol2.config.topdir) finally: old.chdir() From hpk at codespeak.net Fri Mar 20 01:40:04 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 20 Mar 2009 01:40:04 +0100 (CET) Subject: [py-svn] r63105 - py/trunk/py/test/plugin Message-ID: <20090320004004.070A1168486@codespeak.net> Author: hpk Date: Fri Mar 20 01:40:04 2009 New Revision: 63105 Modified: py/trunk/py/test/plugin/pytest_plugintester.py Log: remove non-existing event Modified: py/trunk/py/test/plugin/pytest_plugintester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_plugintester.py (original) +++ py/trunk/py/test/plugin/pytest_plugintester.py Fri Mar 20 01:40:04 2009 @@ -203,9 +203,6 @@ def pyevent_hostup(self, event): """ Host is up. """ - def pyevent_hostgatewayready(self, event): - """ Connection to Host is ready. """ - def pyevent_hostdown(self, event): """ Host is down. """ From hpk at codespeak.net Fri Mar 20 02:09:30 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 20 Mar 2009 02:09:30 +0100 (CET) Subject: [py-svn] r63106 - in py/trunk/py/test: . dsession dsession/testing plugin Message-ID: <20090320010930.10714168473@codespeak.net> Author: hpk Date: Fri Mar 20 02:09:28 2009 New Revision: 63106 Modified: py/trunk/py/test/dsession/dsession.py py/trunk/py/test/dsession/masterslave.py py/trunk/py/test/dsession/testing/test_dsession.py py/trunk/py/test/dsession/testing/test_functional_dsession.py py/trunk/py/test/dsession/testing/test_hostmanage.py py/trunk/py/test/dsession/testing/test_masterslave.py py/trunk/py/test/plugin/pytest_plugintester.py py/trunk/py/test/plugin/pytest_terminal.py py/trunk/py/test/session.py Log: rename host -> gateway in a couple of places Modified: py/trunk/py/test/dsession/dsession.py ============================================================================== --- py/trunk/py/test/dsession/dsession.py (original) +++ py/trunk/py/test/dsession/dsession.py Fri Mar 20 02:09:28 2009 @@ -108,9 +108,9 @@ elif eventname == "collectionreport": if ev.passed: colitems.extend(ev.result) - elif eventname == "hostup": + elif eventname == "testnodeready": self.addhost(ev.host) - elif eventname == "hostdown": + elif eventname == "testnodedown": pending = self.removehost(ev.host) if pending: crashitem = pending.pop(0) @@ -132,9 +132,9 @@ # once we are in shutdown mode we dont send # events other than HostDown upstream eventname, args, kwargs = self.queue.get() - if eventname == "hostdown": + if eventname == "testnodedown": ev, = args - self.bus.notify("hostdown", ev) + self.bus.notify("testnodedown", ev) self.removehost(ev.host) if not self.host2pending: # finished @@ -173,7 +173,7 @@ try: pending = self.host2pending.pop(host) except KeyError: - # this happens if we didn't receive a hostup event yet + # this happens if we didn't receive a testnodeready event yet return [] for item in pending: del self.item2host[item] Modified: py/trunk/py/test/dsession/masterslave.py ============================================================================== --- py/trunk/py/test/dsession/masterslave.py (original) +++ py/trunk/py/test/dsession/masterslave.py Fri Mar 20 02:09:28 2009 @@ -33,11 +33,11 @@ if not self._down: if not err: err = "TERMINATED" - self.notify("hostdown", event.HostDown(self.host, err)) + self.notify("testnodedown", event.HostDown(self.host, err)) return elif eventcall is None: self._down = True - self.notify("hostdown", event.HostDown(self.host, None)) + self.notify("testnodedown", event.HostDown(self.host, None)) return except KeyboardInterrupt: raise @@ -95,7 +95,7 @@ if basetemp: self.config.basetemp = py.path.local(basetemp) self.config.pytestplugins.do_configure(self.config) - self.sendevent("hostup", makehostup(host)) + self.sendevent("testnodeready", maketestnodeready(host)) try: while 1: task = channel.receive() @@ -120,7 +120,7 @@ self.sendevent("itemtestreport", testrep) -def makehostup(host="INPROCESS"): +def maketestnodeready(host="INPROCESS"): import sys platinfo = {} for name in 'platform', 'executable', 'version_info': Modified: py/trunk/py/test/dsession/testing/test_dsession.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_dsession.py (original) +++ py/trunk/py/test/dsession/testing/test_dsession.py Fri Mar 20 02:09:28 2009 @@ -1,5 +1,5 @@ from py.__.test.dsession.dsession import DSession, LoopState -from py.__.test.dsession.masterslave import makehostup +from py.__.test.dsession.masterslave import maketestnodeready from py.__.execnet.gwmanage import GatewaySpec from py.__.test.runner import basic_collect_report from py.__.test import event @@ -146,7 +146,7 @@ # setup a HostDown event ev = event.HostDown(host1, None) - session.queueevent("hostdown", ev) + session.queueevent("testnodedown", ev) loopstate = LoopState([item]) loopstate.dowork = False @@ -154,7 +154,7 @@ dumpqueue(session.queue) assert loopstate.exitstatus == outcome.EXIT_NOHOSTS - def test_hostdown_causes_reschedule_pending(self, testdir, EventRecorder): + def test_testnodedown_causes_reschedule_pending(self, testdir, EventRecorder): modcol = testdir.getmodulecol(""" def test_crash(): assert 0 @@ -176,7 +176,7 @@ session.senditems([item1, item2]) host = session.item2host[item1] ev = event.HostDown(host, None) - session.queueevent("hostdown", ev) + session.queueevent("testnodedown", ev) evrec = EventRecorder(session.bus) loopstate = LoopState([]) session.loop_once(loopstate) @@ -188,13 +188,13 @@ assert str(testrep.longrepr).find("crashed") != -1 assert str(testrep.longrepr).find(host.address) != -1 - def test_hostup_adds_to_available(self, testdir): + def test_testnodeready_adds_to_available(self, testdir): item = testdir.getitem("def test_func(): pass") # setup a session with two hosts session = DSession(item.config) host1 = GatewaySpec("localhost") - hostup = makehostup(host1) - session.queueevent("hostup", hostup) + testnodeready = maketestnodeready(host1) + session.queueevent("testnodeready", testnodeready) loopstate = LoopState([item]) loopstate.dowork = False assert len(session.host2pending) == 0 @@ -225,7 +225,7 @@ session.queueevent("itemtestreport", ev) session.loop_once(loopstate) assert loopstate.shuttingdown - session.queueevent("hostdown", event.HostDown(host1, None)) + session.queueevent("testnodedown", event.HostDown(host1, None)) session.loop_once(loopstate) dumpqueue(session.queue) return session, loopstate.exitstatus @@ -278,11 +278,11 @@ evrec = EventRecorder(session.bus) session.queueevent("itemtestreport", run(item)) session.loop_once(loopstate) - assert not evrec.getfirstnamed("hostdown") + assert not evrec.getfirstnamed("testnodedown") ev = event.HostDown(host) - session.queueevent("hostdown", ev) + session.queueevent("testnodedown", ev) session.loop_once(loopstate) - assert evrec.getfirstnamed('hostdown') == ev + assert evrec.getfirstnamed('testnodedown') == ev def test_filteritems(self, testdir, EventRecorder): modcol = testdir.getmodulecol(""" @@ -313,7 +313,7 @@ assert event.name == "deselected" assert event.args[0].items == [items[1]] - def test_hostdown_shutdown_after_completion(self, testdir): + def test_testnodedown_shutdown_after_completion(self, testdir): item = testdir.getitem("def test_func(): pass") session = DSession(item.config) @@ -325,9 +325,9 @@ loopstate = LoopState([]) session.loop_once(loopstate) assert host.node._shutdown is True - assert loopstate.exitstatus is None, "loop did not wait for hostdown" + assert loopstate.exitstatus is None, "loop did not wait for testnodedown" assert loopstate.shuttingdown - session.queueevent("hostdown", event.HostDown(host, None)) + session.queueevent("testnodedown", event.HostDown(host, None)) session.loop_once(loopstate) assert loopstate.exitstatus == 0 Modified: py/trunk/py/test/dsession/testing/test_functional_dsession.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_functional_dsession.py (original) +++ py/trunk/py/test/dsession/testing/test_functional_dsession.py Fri Mar 20 02:09:28 2009 @@ -53,7 +53,7 @@ ev, = eq.geteventargs("itemtestreport") assert ev.failed # see that the host is really down - ev, = eq.geteventargs("hostdown") + ev, = eq.geteventargs("testnodedown") assert ev.host.address == "popen" ev, = eq.geteventargs("testrunfinish") Modified: py/trunk/py/test/dsession/testing/test_hostmanage.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_hostmanage.py (original) +++ py/trunk/py/test/dsession/testing/test_hostmanage.py Fri Mar 20 02:09:28 2009 @@ -147,15 +147,6 @@ ev = sorter.getfirstnamed("itemtestreport") assert ev.passed - def test_rsync_samehost_twice(self): - host1 = Host("%s" % (sshhost, )) - host2 = Host("%s" % (sshhost, )) - hm = HostManager(config, hosts=[host1, host2]) - events = [] - hm.init_rsync(events.append) - print events - assert 0 - def test_getconfiggwspecs_numprocesses(): config = py.test.config._reparse(['-n3']) Modified: py/trunk/py/test/dsession/testing/test_masterslave.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_masterslave.py (original) +++ py/trunk/py/test/dsession/testing/test_masterslave.py Fri Mar 20 02:09:28 2009 @@ -70,7 +70,7 @@ def test_crash_invalid_item(self, mysetup): node = mysetup.makenode() node.send(123) # invalid item - ev, = mysetup.geteventargs("hostdown") + ev, = mysetup.geteventargs("testnodedown") assert ev.host == mysetup.host assert str(ev.error).find("AttributeError") != -1 @@ -84,19 +84,19 @@ """) node = mysetup.makenode(item.config) node.send(item) - ev, = mysetup.geteventargs("hostdown") + ev, = mysetup.geteventargs("testnodedown") assert ev.host == mysetup.host assert str(ev.error).find("TERMINATED") != -1 def test_node_down(self, mysetup): node = mysetup.makenode() node.shutdown() - ev, = mysetup.geteventargs("hostdown") + ev, = mysetup.geteventargs("testnodedown") assert ev.host == mysetup.host assert not ev.error node.callback(node.ENDMARK) excinfo = py.test.raises(IOError, - "mysetup.geteventargs('hostdown', timeout=0.01)") + "mysetup.geteventargs('testnodedown', timeout=0.01)") def test_send_on_closed_channel(self, testdir, mysetup): item = testdir.getitem("def test_func(): pass") Modified: py/trunk/py/test/plugin/pytest_plugintester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_plugintester.py (original) +++ py/trunk/py/test/plugin/pytest_plugintester.py Fri Mar 20 02:09:28 2009 @@ -200,10 +200,10 @@ within the gateway manager context. """ - def pyevent_hostup(self, event): + def pyevent_testnodeready(self, event): """ Host is up. """ - def pyevent_hostdown(self, event): + def pyevent_testnodedown(self, event): """ Host is down. """ def pyevent_rescheduleitems(self, event): Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Fri Mar 20 02:09:28 2009 @@ -103,7 +103,7 @@ # which garbles our output if we use self.write_line self.write_line(msg) - def pyevent_hostup(self, event): + def pyevent_testnodeready(self, event): d = event.platinfo.copy() d['host'] = getattr(event.host, 'address', event.host) d['version'] = repr_pythonversion(d['sys.version_info']) @@ -111,7 +111,7 @@ "%(sys.executable)s - Python %(version)s" % d) - def pyevent_hostdown(self, event): + def pyevent_testnodedown(self, event): host = event.host error = event.error if error: @@ -323,13 +323,13 @@ from py.__.test import event from py.__.test.runner import basic_run_report -from py.__.test.dsession.masterslave import makehostup +from py.__.test.dsession.masterslave import maketestnodeready class TestTerminal: - def test_hostup(self, testdir, linecomp): + def test_testnodeready(self, testdir, linecomp): item = testdir.getitem("def test_func(): pass") rep = TerminalReporter(item.config, linecomp.stringio) - rep.pyevent_hostup(makehostup()) + rep.pyevent_testnodeready(maketestnodeready()) linecomp.assert_contains_lines([ "*INPROCESS* %s %s - Python %s" %(sys.platform, sys.executable, repr_pythonversion(sys.version_info)) Modified: py/trunk/py/test/session.py ============================================================================== --- py/trunk/py/test/session.py (original) +++ py/trunk/py/test/session.py Fri Mar 20 02:09:28 2009 @@ -12,7 +12,7 @@ Item = py.test.collect.Item Collector = py.test.collect.Collector from runner import basic_collect_report -from py.__.test.dsession.masterslave import makehostup +from py.__.test.dsession.masterslave import maketestnodeready class Session(object): """ @@ -117,7 +117,7 @@ colitems = self.getinitialitems(colitems) self.shouldstop = False self.sessionstarts() - self.bus.notify("hostup", makehostup()) + self.bus.notify("testnodeready", maketestnodeready()) exitstatus = outcome.EXIT_OK captured_excinfo = None try: From hpk at codespeak.net Fri Mar 20 03:13:32 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 20 Mar 2009 03:13:32 +0100 (CET) Subject: [py-svn] r63107 - in py/trunk/py/test/dsession: . testing Message-ID: <20090320021332.0C7A3168486@codespeak.net> Author: hpk Date: Fri Mar 20 03:13:31 2009 New Revision: 63107 Modified: py/trunk/py/test/dsession/dsession.py py/trunk/py/test/dsession/testing/test_dsession.py Log: cleanup event handling of dsession Modified: py/trunk/py/test/dsession/dsession.py ============================================================================== --- py/trunk/py/test/dsession/dsession.py (original) +++ py/trunk/py/test/dsession/dsession.py Fri Mar 20 03:13:31 2009 @@ -13,7 +13,8 @@ import Queue class LoopState(object): - def __init__(self, colitems): + def __init__(self, dsession, colitems): + self.dsession = dsession self.colitems = colitems self.exitstatus = None # loopstate.dowork is False after reschedule events @@ -23,6 +24,30 @@ self.shuttingdown = False self.testsfailed = False + def pyevent_itemtestreport(self, event): + if event.colitem in self.dsession.item2host: + self.dsession.removeitem(event.colitem) + if event.failed: + self.testsfailed = True + + def pyevent_collectionreport(self, event): + if event.passed: + self.colitems.extend(event.result) + + def pyevent_testnodeready(self, event): + self.dsession.addhost(event.host) + + def pyevent_testnodedown(self, event): + pending = self.dsession.removehost(event.host) + if pending: + crashitem = pending[0] + self.dsession.handle_crashitem(crashitem, event.host) + self.colitems.extend(pending[1:]) + + def pyevent_rescheduleitems(self, event): + self.colitems.extend(event.items) + self.dowork = False # avoid busywait + class DSession(Session): """ Session drives the collection and running of tests @@ -36,7 +61,6 @@ self.queue = Queue.Queue() self.host2pending = {} self.item2host = {} - self._testsfailed = False if self.config.getvalue("executable") and \ not self.config.getvalue("numprocesses"): self.config.option.numprocesses = 1 @@ -83,8 +107,8 @@ if loopstate.shuttingdown: return self.loop_once_shutdown(loopstate) colitems = loopstate.colitems - if loopstate.dowork and loopstate.colitems: - self.triggertesting(colitems) + if loopstate.dowork and colitems: + self.triggertesting(loopstate.colitems) colitems[:] = [] # we use a timeout here so that control-C gets through while 1: @@ -94,31 +118,9 @@ except Queue.Empty: continue loopstate.dowork = True - + eventname, args, kwargs = eventcall self.bus.notify(eventname, *args, **kwargs) - if args: - ev, = args - else: - ev = None - if eventname == "itemtestreport": - self.removeitem(ev.colitem) - if ev.failed: - loopstate.testsfailed = True - elif eventname == "collectionreport": - if ev.passed: - colitems.extend(ev.result) - elif eventname == "testnodeready": - self.addhost(ev.host) - elif eventname == "testnodedown": - pending = self.removehost(ev.host) - if pending: - crashitem = pending.pop(0) - self.handle_crashitem(crashitem, ev.host) - colitems.extend(pending) - elif eventname == "rescheduleitems": - colitems.extend(ev.items) - loopstate.dowork = False # avoid busywait # termination conditions if ((loopstate.testsfailed and self.config.option.exitfirst) or @@ -143,9 +145,14 @@ else: loopstate.exitstatus = outcome.EXIT_OK + def _initloopstate(self, colitems): + loopstate = LoopState(self, colitems) + self.config.bus.register(loopstate) + return loopstate + def loop(self, colitems): try: - loopstate = LoopState(colitems) + loopstate = self._initloopstate(colitems) loopstate.dowork = False # first receive at least one HostUp events while 1: self.loop_once(loopstate) @@ -157,6 +164,7 @@ except: self.bus.notify("internalerror", event.InternalException()) exitstatus = outcome.EXIT_INTERNALERROR + self.config.bus.unregister(loopstate) if exitstatus == 0 and self._testsfailed: exitstatus = outcome.EXIT_TESTSFAILED return exitstatus Modified: py/trunk/py/test/dsession/testing/test_dsession.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_dsession.py (original) +++ py/trunk/py/test/dsession/testing/test_dsession.py Fri Mar 20 03:13:31 2009 @@ -1,4 +1,4 @@ -from py.__.test.dsession.dsession import DSession, LoopState +from py.__.test.dsession.dsession import DSession from py.__.test.dsession.masterslave import maketestnodeready from py.__.execnet.gwmanage import GatewaySpec from py.__.test.runner import basic_collect_report @@ -120,7 +120,7 @@ host1.node = MockNode() session.addhost(host1) ev = event.RescheduleItems([item]) - loopstate = LoopState([]) + loopstate = session._initloopstate([]) session.queueevent("rescheduleitems", ev) session.loop_once(loopstate) # check that RescheduleEvents are not immediately @@ -148,7 +148,7 @@ ev = event.HostDown(host1, None) session.queueevent("testnodedown", ev) - loopstate = LoopState([item]) + loopstate = session._initloopstate([item]) loopstate.dowork = False session.loop_once(loopstate) dumpqueue(session.queue) @@ -178,7 +178,8 @@ ev = event.HostDown(host, None) session.queueevent("testnodedown", ev) evrec = EventRecorder(session.bus) - loopstate = LoopState([]) + print session.item2host + loopstate = session._initloopstate([]) session.loop_once(loopstate) assert loopstate.colitems == [item2] # do not reschedule crash item @@ -195,7 +196,7 @@ host1 = GatewaySpec("localhost") testnodeready = maketestnodeready(host1) session.queueevent("testnodeready", testnodeready) - loopstate = LoopState([item]) + loopstate = session._initloopstate([item]) loopstate.dowork = False assert len(session.host2pending) == 0 session.loop_once(loopstate) @@ -207,7 +208,7 @@ evrec = EventRecorder(session.bus) session.queueevent("NOPevent", 42) - session.loop_once(LoopState([])) + session.loop_once(session._initloopstate([])) assert evrec.getfirstnamed('NOPevent') def runthrough(self, item): @@ -215,7 +216,7 @@ host1 = GatewaySpec("localhost") host1.node = MockNode() session.addhost(host1) - loopstate = LoopState([item]) + loopstate = session._initloopstate([item]) session.queueevent("NOP") session.loop_once(loopstate) @@ -263,7 +264,7 @@ session.queueevent("itemtestreport", ev1) # a failing one session.queueevent("itemtestreport", ev2) # now call the loop - loopstate = LoopState(items) + loopstate = session._initloopstate(items) session.loop_once(loopstate) assert loopstate.testsfailed assert loopstate.shuttingdown @@ -273,7 +274,7 @@ session = DSession(item.config) host = GatewaySpec("localhost") session.addhost(host) - loopstate = LoopState([]) + loopstate = session._initloopstate([]) loopstate.shuttingdown = True evrec = EventRecorder(session.bus) session.queueevent("itemtestreport", run(item)) @@ -322,7 +323,7 @@ session.addhost(host) session.senditems([item]) session.queueevent("itemtestreport", run(item)) - loopstate = LoopState([]) + loopstate = session._initloopstate([]) session.loop_once(loopstate) assert host.node._shutdown is True assert loopstate.exitstatus is None, "loop did not wait for testnodedown" @@ -353,7 +354,7 @@ # but we have a collection pending session.queueevent("collectionreport", colreport) - loopstate = LoopState([]) + loopstate = session._initloopstate([]) session.loop_once(loopstate) assert loopstate.exitstatus is None, "loop did not care for collection report" assert not loopstate.colitems From afa at codespeak.net Fri Mar 20 09:09:57 2009 From: afa at codespeak.net (afa at codespeak.net) Date: Fri, 20 Mar 2009 09:09:57 +0100 (CET) Subject: [py-svn] r63110 - py/trunk/py/io Message-ID: <20090320080957.27B30168444@codespeak.net> Author: afa Date: Fri Mar 20 09:09:56 2009 New Revision: 63110 Modified: py/trunk/py/io/terminalwriter.py Log: Typo in a (unused) constant Modified: py/trunk/py/io/terminalwriter.py ============================================================================== --- py/trunk/py/io/terminalwriter.py (original) +++ py/trunk/py/io/terminalwriter.py Fri Mar 20 09:09:56 2009 @@ -27,7 +27,7 @@ BACKGROUND_BLUE = 0x0010 # background color contains blue. BACKGROUND_GREEN = 0x0020 # background color contains green. BACKGROUND_RED = 0x0040 # background color contains red. - BACKGROUND_WHITE = 0x0007 + BACKGROUND_WHITE = 0x0070 BACKGROUND_INTENSITY = 0x0080 # background color is intensified. def GetStdHandle(kind): From hpk at codespeak.net Fri Mar 20 10:26:58 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 20 Mar 2009 10:26:58 +0100 (CET) Subject: [py-svn] r63117 - py/extradoc/talk/pycon-us-2009/pytest-advanced Message-ID: <20090320092658.6DEDF168494@codespeak.net> Author: hpk Date: Fri Mar 20 10:26:57 2009 New Revision: 63117 Removed: py/extradoc/talk/pycon-us-2009/pytest-advanced/build.sh Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Log: a bit of cleanup Deleted: /py/extradoc/talk/pycon-us-2009/pytest-advanced/build.sh ============================================================================== --- /py/extradoc/talk/pycon-us-2009/pytest-advanced/build.sh Fri Mar 20 10:26:57 2009 +++ (empty file) @@ -1 +0,0 @@ -rst2s5.py pytest-advanced.txt pytest-advanced.html Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Fri Mar 20 10:26:57 2009 @@ -1,32 +1,3 @@ -Title: py.test - cross-platform and distributed testing -Presenter: Holger Krekel , Brian -Tutorial format: interactive lecture -Recording: I give permission to record and publish my PyCon tutorial for free distribution. -Intended Audience: Python programmers -Maximum number of students: maybe 30 -Perequisites/knowledge: good knowledge of python programming, basic familiarity with automated testing -Requirements: Attendees are welcome to bring their laptops with Python installed (version 2.4 or higher). -Notes to reviewer: visting the beginner-oriented tutorial "rapid testing with minimal effort" is recommended, but not required. - -Tutorial Summary -================ - -Want to know more about advanced automated testing with Python? -Use a tool that allows you to ad-hoc distribute tests to multiple -CPUs for speed and to multiple platforms for compatibility checks? -With tons of debugging help in failure situations? - -This tutorial provides in-depth information on advanced usages -of the popular py.test tool. We highlight its current feature set -including using and writing extensions for generating HTML pages, -testing Javascript or ReST documents. We showcase and discuss ways -of distributing tests across CPUs and platforms and will leave -time to discuss and tackle specific scenarios brought up -during the session. - -The tutorial format will be an interactive lecture with plenty -of time for questions. - Terminology/Overview (20 minutes) ============================================================================ From hpk at codespeak.net Fri Mar 20 14:22:09 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 20 Mar 2009 14:22:09 +0100 (CET) Subject: [py-svn] r63135 - in py/trunk/py/execnet: . testing Message-ID: <20090320132209.85F6E168486@codespeak.net> Author: hpk Date: Fri Mar 20 14:22:07 2009 New Revision: 63135 Added: py/trunk/py/execnet/testing/test_xspec.py py/trunk/py/execnet/xspec.py Log: adding a new gateway specification method Added: py/trunk/py/execnet/testing/test_xspec.py ============================================================================== --- (empty file) +++ py/trunk/py/execnet/testing/test_xspec.py Fri Mar 20 14:22:07 2009 @@ -0,0 +1,34 @@ +import py + +def test_XSpec_attributes(): + XSpec = py.execnet.XSpec + spec = XSpec("socket=192.168.102.2:8888//python=c:/this/python2.5//path=d:\hello") + assert spec.socket == "192.168.102.2:8888" + assert spec.python == "c:/this/python2.5" + assert spec.path == "d:\hello" + assert spec.xyz is None + + py.test.raises(AttributeError, "spec._hello") + + spec = XSpec("socket=192.168.102.2:8888//python=python2.5") + assert spec.socket == "192.168.102.2:8888" + assert spec.python == "python2.5" + assert spec.path is None + + spec = XSpec("ssh=user at host//path=/hello/this//python=/usr/bin/python2.5") + assert spec.ssh == "user at host" + assert spec.python == "/usr/bin/python2.5" + assert spec.path == "/hello/this" + + spec = XSpec("popen") + assert spec.popen == True + + at py.test.mark.xfail +def test_makegateway_popen(): + spec = py.execnet.XSpec("popen") + gw = py.execnet.makegateway(spec) + assert gw.spec == spec + rinfo = gw.remote_info() + assert rinfo.executable == py.std.sys.executable + assert rinfo.curdir == py.std.os.getcwd() + assert rinfo.version_info == py.std.sys.version_info Added: py/trunk/py/execnet/xspec.py ============================================================================== --- (empty file) +++ py/trunk/py/execnet/xspec.py Fri Mar 20 14:22:07 2009 @@ -0,0 +1,29 @@ + +import py + +class XSpec: + """ Execution Specification: key1=value1//key2=value2 ... + * keys need to be unique within the specification scope + * neither key nor value are allowed to contain "//" + * keys are not allowed to contain "=" + * keys are not allowed to start with underscore + * if no "=value" is given, assume a boolean True value + """ + def __init__(self, *strings): + for string in strings: + for keyvalue in string.split("//"): + i = keyvalue.find("=") + if i == -1: + setattr(self, keyvalue, True) + else: + setattr(self, keyvalue[:i], keyvalue[i+1:]) + + def __getattr__(self, name): + if name[0] == "_": + raise AttributeError(name) + return None + +def makegateway(spec): + pass + + From hpk at codespeak.net Fri Mar 20 14:31:03 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 20 Mar 2009 14:31:03 +0100 (CET) Subject: [py-svn] r63136 - py/trunk/py/execnet Message-ID: <20090320133103.3E2EE1684A7@codespeak.net> Author: hpk Date: Fri Mar 20 14:31:02 2009 New Revision: 63136 Modified: py/trunk/py/execnet/gateway.py Log: better grouping of gateway public API Modified: py/trunk/py/execnet/gateway.py ============================================================================== --- py/trunk/py/execnet/gateway.py (original) +++ py/trunk/py/execnet/gateway.py Fri Mar 20 14:31:02 2009 @@ -202,28 +202,6 @@ if joining: self.join() - def remote_init_threads(self, num=None): - """ start up to 'num' threads for subsequent - remote_exec() invocations to allow concurrent - execution. - """ - if hasattr(self, '_remotechannelthread'): - raise IOError("remote threads already running") - from py.__.thread import pool - source = py.code.Source(pool, """ - execpool = WorkerPool(maxthreads=%r) - gw = channel.gateway - while 1: - task = gw._requestqueue.get() - if task is None: - gw._stopsend() - execpool.shutdown() - execpool.join() - raise gw._StopExecLoop - execpool.dispatch(gw._executetask, task) - """ % num) - self._remotechannelthread = self.remote_exec(source) - def _executetask(self, item): """ execute channel/source items. """ from sys import exc_info @@ -268,10 +246,6 @@ # High Level Interface # _____________________________________________________________________ # - def newchannel(self): - """ return new channel object. """ - return self._channelfactory.new() - def remote_exec(self, source, stdout=None, stderr=None): """ return channel object and connect it to a remote execution thread where the given 'source' executes @@ -295,6 +269,53 @@ channel.id, (source, outid, errid))) return channel + def remote_init_threads(self, num=None): + """ start up to 'num' threads for subsequent + remote_exec() invocations to allow concurrent + execution. + """ + if hasattr(self, '_remotechannelthread'): + raise IOError("remote threads already running") + from py.__.thread import pool + source = py.code.Source(pool, """ + execpool = WorkerPool(maxthreads=%r) + gw = channel.gateway + while 1: + task = gw._requestqueue.get() + if task is None: + gw._stopsend() + execpool.shutdown() + execpool.join() + raise gw._StopExecLoop + execpool.dispatch(gw._executetask, task) + """ % num) + self._remotechannelthread = self.remote_exec(source) + + def newchannel(self): + """ return new channel object. """ + return self._channelfactory.new() + + def join(self, joinexec=True): + """ Wait for all IO (and by default all execution activity) + to stop. the joinexec parameter is obsolete. + """ + current = threading.currentThread() + if self._receiverthread.isAlive(): + self._trace("joining receiver thread") + self._receiverthread.join() + + def exit(self): + """ Try to stop all exec and IO activity. """ + self._cleanup.unregister(self) + self._stopexec() + self._stopsend() + try: + py._com.pyplugins.notify("gateway_exit", self) + except NameError: + # XXX on the remote side 'py' is not imported + # and so we can't notify + pass + def _remote_redirect(self, stdout=None, stderr=None): """ return a handle representing a redirection of a remote end's stdout to a local file object. with handle.close() @@ -325,17 +346,6 @@ c.waitclose() return Handle() - def exit(self): - """ Try to stop all exec and IO activity. """ - self._cleanup.unregister(self) - self._stopexec() - self._stopsend() - try: - py._com.pyplugins.notify("gateway_exit", self) - except NameError: - # on the remote side 'py' is not imported - # and so we can't notify (XXX: make execnet synchronous) - pass def _stopsend(self): self._send(None) @@ -344,14 +354,6 @@ if self._requestqueue is not None: self._requestqueue.put(None) - def join(self, joinexec=True): - """ Wait for all IO (and by default all execution activity) - to stop. the joinexec parameter is obsolete. - """ - current = threading.currentThread() - if self._receiverthread.isAlive(): - self._trace("joining receiver thread") - self._receiverthread.join() def getid(gw, cache={}): name = gw.__class__.__name__ From hpk at codespeak.net Fri Mar 20 14:41:44 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 20 Mar 2009 14:41:44 +0100 (CET) Subject: [py-svn] r63137 - in py/trunk/py/execnet: . testing Message-ID: <20090320134144.3DA141684A9@codespeak.net> Author: hpk Date: Fri Mar 20 14:41:43 2009 New Revision: 63137 Modified: py/trunk/py/execnet/gateway.py py/trunk/py/execnet/testing/test_gateway.py Log: a helper some basic remote info Modified: py/trunk/py/execnet/gateway.py ============================================================================== --- py/trunk/py/execnet/gateway.py (original) +++ py/trunk/py/execnet/gateway.py Fri Mar 20 14:41:43 2009 @@ -241,6 +241,17 @@ chan.setcallback(callback) return chan.id + def _rinfo(self): + """ return some sys/env information from remote. """ + return RInfo(**self.remote_exec(""" + import sys, os + channel.send(dict( + executable = sys.executable, + version_info = sys.version_info, + curdir = os.getcwd(), + )) + """).receive()) + # _____________________________________________________________________ # # High Level Interface @@ -354,6 +365,9 @@ if self._requestqueue is not None: self._requestqueue.put(None) +class RInfo: + def __init__(self, **kwargs): + self.__dict__.update(kwargs) def getid(gw, cache={}): name = gw.__class__.__name__ Modified: py/trunk/py/execnet/testing/test_gateway.py ============================================================================== --- py/trunk/py/execnet/testing/test_gateway.py (original) +++ py/trunk/py/execnet/testing/test_gateway.py Fri Mar 20 14:41:43 2009 @@ -441,6 +441,13 @@ text = c1.receive() assert text.find("execution disallowed") != -1 + def test__rinfo(self): + rinfo = self.gw._rinfo() + assert rinfo.executable + assert rinfo.curdir + assert rinfo.version_info + + class BasicCmdbasedRemoteExecution(BasicRemoteExecution): def test_cmdattr(self): assert hasattr(self.gw, '_cmd') @@ -480,7 +487,12 @@ # assert x == 17 class TestPopenGateway(PopenGatewayTestSetup, BasicRemoteExecution): - #disabled = True + def test_remote_info_popen(self): + rinfo = self.gw._rinfo() + assert rinfo.executable == py.std.sys.executable + assert rinfo.curdir == py.std.os.getcwd() + assert rinfo.version_info == py.std.sys.version_info + def test_chdir_separation(self): old = py.test.ensuretemp('chdirtest').chdir() try: From hpk at codespeak.net Fri Mar 20 15:04:16 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 20 Mar 2009 15:04:16 +0100 (CET) Subject: [py-svn] r63139 - in py/trunk/py/execnet: . testing Message-ID: <20090320140416.1A7011684AA@codespeak.net> Author: hpk Date: Fri Mar 20 15:04:15 2009 New Revision: 63139 Modified: py/trunk/py/execnet/gateway.py py/trunk/py/execnet/testing/test_gateway.py Log: make _rinfo() cache results by default Modified: py/trunk/py/execnet/gateway.py ============================================================================== --- py/trunk/py/execnet/gateway.py (original) +++ py/trunk/py/execnet/gateway.py Fri Mar 20 15:04:15 2009 @@ -241,16 +241,18 @@ chan.setcallback(callback) return chan.id - def _rinfo(self): + def _rinfo(self, update=False): """ return some sys/env information from remote. """ - return RInfo(**self.remote_exec(""" - import sys, os - channel.send(dict( - executable = sys.executable, - version_info = sys.version_info, - curdir = os.getcwd(), - )) - """).receive()) + if update or not hasattr(self, '_cache_rinfo'): + self._cache_rinfo = RInfo(**self.remote_exec(""" + import sys, os + channel.send(dict( + executable = sys.executable, + version_info = sys.version_info, + cwd = os.getcwd(), + )) + """).receive()) + return self._cache_rinfo # _____________________________________________________________________ # Modified: py/trunk/py/execnet/testing/test_gateway.py ============================================================================== --- py/trunk/py/execnet/testing/test_gateway.py (original) +++ py/trunk/py/execnet/testing/test_gateway.py Fri Mar 20 15:04:15 2009 @@ -444,9 +444,22 @@ def test__rinfo(self): rinfo = self.gw._rinfo() assert rinfo.executable - assert rinfo.curdir + assert rinfo.cwd assert rinfo.version_info - + old = self.gw.remote_exec(""" + import os.path + cwd = os.getcwd() + channel.send(os.path.basename(cwd)) + os.chdir('..') + """).receive() + try: + rinfo2 = self.gw._rinfo() + assert rinfo2.cwd == rinfo.cwd + rinfo3 = self.gw._rinfo(update=True) + assert rinfo3.cwd != rinfo2.cwd + finally: + self.gw._cache_rinfo = rinfo + self.gw.remote_exec("import os ; os.chdir(%r)" % old).waitclose() class BasicCmdbasedRemoteExecution(BasicRemoteExecution): def test_cmdattr(self): @@ -487,10 +500,11 @@ # assert x == 17 class TestPopenGateway(PopenGatewayTestSetup, BasicRemoteExecution): - def test_remote_info_popen(self): + def test_rinfo_popen(self): + #rinfo = py.execnet.PopenGateway()._rinfo() rinfo = self.gw._rinfo() assert rinfo.executable == py.std.sys.executable - assert rinfo.curdir == py.std.os.getcwd() + assert rinfo.cwd == py.std.os.getcwd() assert rinfo.version_info == py.std.sys.version_info def test_chdir_separation(self): From hpk at codespeak.net Fri Mar 20 15:20:42 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 20 Mar 2009 15:20:42 +0100 (CET) Subject: [py-svn] r63141 - in py/trunk/py/execnet: . testing Message-ID: <20090320142042.D2CA11684B4@codespeak.net> Author: hpk Date: Fri Mar 20 15:20:40 2009 New Revision: 63141 Modified: py/trunk/py/execnet/testing/test_xspec.py py/trunk/py/execnet/xspec.py Log: makegateway works for plain popen and popen+python-version Modified: py/trunk/py/execnet/testing/test_xspec.py ============================================================================== --- py/trunk/py/execnet/testing/test_xspec.py (original) +++ py/trunk/py/execnet/testing/test_xspec.py Fri Mar 20 15:20:40 2009 @@ -1,34 +1,74 @@ import py -def test_XSpec_attributes(): - XSpec = py.execnet.XSpec - spec = XSpec("socket=192.168.102.2:8888//python=c:/this/python2.5//path=d:\hello") - assert spec.socket == "192.168.102.2:8888" - assert spec.python == "c:/this/python2.5" - assert spec.path == "d:\hello" - assert spec.xyz is None - - py.test.raises(AttributeError, "spec._hello") - - spec = XSpec("socket=192.168.102.2:8888//python=python2.5") - assert spec.socket == "192.168.102.2:8888" - assert spec.python == "python2.5" - assert spec.path is None - - spec = XSpec("ssh=user at host//path=/hello/this//python=/usr/bin/python2.5") - assert spec.ssh == "user at host" - assert spec.python == "/usr/bin/python2.5" - assert spec.path == "/hello/this" - - spec = XSpec("popen") - assert spec.popen == True - - at py.test.mark.xfail -def test_makegateway_popen(): - spec = py.execnet.XSpec("popen") - gw = py.execnet.makegateway(spec) - assert gw.spec == spec - rinfo = gw.remote_info() - assert rinfo.executable == py.std.sys.executable - assert rinfo.curdir == py.std.os.getcwd() - assert rinfo.version_info == py.std.sys.version_info +XSpec = py.execnet.XSpec + +class TestXSpec: + def test_attributes(self): + spec = XSpec("socket=192.168.102.2:8888//python=c:/this/python2.5//path=d:\hello") + assert spec.socket == "192.168.102.2:8888" + assert spec.python == "c:/this/python2.5" + assert spec.path == "d:\hello" + assert spec.xyz is None + + py.test.raises(AttributeError, "spec._hello") + + spec = XSpec("socket=192.168.102.2:8888//python=python2.5") + assert spec.socket == "192.168.102.2:8888" + assert spec.python == "python2.5" + assert spec.path is None + + spec = XSpec("ssh=user at host//path=/hello/this//python=/usr/bin/python2.5") + assert spec.ssh == "user at host" + assert spec.python == "/usr/bin/python2.5" + assert spec.path == "/hello/this" + + spec = XSpec("popen") + assert spec.popen == True + + def test__samefilesystem(self): + assert XSpec("popen")._samefilesystem() + assert XSpec("popen//python=123")._samefilesystem() + assert not XSpec("popen//path=hello")._samefilesystem() + +class TestMakegateway: + def test_popen(self): + gw = py.execnet.makegateway("popen") + assert gw.spec.python == None + rinfo = gw._rinfo() + assert rinfo.executable == py.std.sys.executable + assert rinfo.cwd == py.std.os.getcwd() + assert rinfo.version_info == py.std.sys.version_info + + def test_popen_explicit(self): + gw = py.execnet.makegateway("popen//python=%s" % py.std.sys.executable) + assert gw.spec.python == py.std.sys.executable + rinfo = gw._rinfo() + assert rinfo.executable == py.std.sys.executable + assert rinfo.cwd == py.std.os.getcwd() + assert rinfo.version_info == py.std.sys.version_info + + def test_popen_cpython24(self): + for trypath in ('python2.4', r'C:\Python24\python.exe'): + cpython24 = py.path.local.sysfind(trypath) + if cpython24 is not None: + break + else: + py.test.skip("cpython2.4 not found") + gw = py.execnet.makegateway("popen//python=%s" % cpython24) + rinfo = gw._rinfo() + assert rinfo.executable == cpython24 + assert rinfo.cwd == py.std.os.getcwd() + assert rinfo.version_info[:2] == (2,4) + + def test_popen_cpython26(self): + for trypath in ('python2.6', r'C:\Python26\python.exe'): + cpython26 = py.path.local.sysfind(trypath) + if cpython26 is not None: + break + else: + py.test.skip("cpython2.6 not found") + gw = py.execnet.makegateway("popen//python=%s" % cpython26) + rinfo = gw._rinfo() + assert rinfo.executable == cpython26 + assert rinfo.cwd == py.std.os.getcwd() + assert rinfo.version_info[:2] == (2,6) Modified: py/trunk/py/execnet/xspec.py ============================================================================== --- py/trunk/py/execnet/xspec.py (original) +++ py/trunk/py/execnet/xspec.py Fri Mar 20 15:20:40 2009 @@ -23,7 +23,13 @@ raise AttributeError(name) return None -def makegateway(spec): - pass + def _samefilesystem(self): + return bool(self.popen and not self.path) - +def makegateway(spec): + if not isinstance(spec, XSpec): + spec = XSpec(spec) + if spec.popen: + gw = py.execnet.PopenGateway(python=spec.python) + gw.spec = spec + return gw From hpk at codespeak.net Fri Mar 20 16:36:46 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 20 Mar 2009 16:36:46 +0100 (CET) Subject: [py-svn] r63144 - in py/trunk/py: . execnet execnet/testing Message-ID: <20090320153646.BF8491684B9@codespeak.net> Author: hpk Date: Fri Mar 20 16:36:45 2009 New Revision: 63144 Modified: py/trunk/py/__init__.py py/trunk/py/conftest.py py/trunk/py/execnet/testing/test_gateway.py py/trunk/py/execnet/testing/test_gwspec.py py/trunk/py/execnet/testing/test_xspec.py py/trunk/py/execnet/xspec.py Log: have socket and ssh gateways Modified: py/trunk/py/__init__.py ============================================================================== --- py/trunk/py/__init__.py (original) +++ py/trunk/py/__init__.py Fri Mar 20 16:36:45 2009 @@ -152,6 +152,8 @@ 'execnet.PopenGateway' : ('./execnet/register.py', 'PopenGateway'), 'execnet.SshGateway' : ('./execnet/register.py', 'SshGateway'), 'execnet.GatewaySpec' : ('./execnet/gwmanage.py', 'GatewaySpec'), + 'execnet.XSpec' : ('./execnet/xspec.py', 'XSpec'), + 'execnet.makegateway' : ('./execnet/xspec.py', 'makegateway'), 'execnet.MultiGateway' : ('./execnet/multi.py', 'MultiGateway'), 'execnet.MultiChannel' : ('./execnet/multi.py', 'MultiChannel'), Modified: py/trunk/py/conftest.py ============================================================================== --- py/trunk/py/conftest.py (original) +++ py/trunk/py/conftest.py Fri Mar 20 16:36:45 2009 @@ -1,20 +1,45 @@ -dist_rsync_roots = ['.'] # XXX - pytest_plugins = 'pytest_doctest', 'pytest_pytester', 'pytest_restdoc' - rsyncignore = ['c-extension/greenlet/build'] import py -class PylibTestPlugin: +class PylibTestconfigPlugin: + def pytest_pyfuncarg_specssh(self, pyfuncitem): + return getspecssh(pyfuncitem.config) + def pytest_pyfuncarg_specsocket(self, pyfuncitem): + return getsocketspec(pyfuncitem.config) + def pytest_addoption(self, parser): group = parser.addgroup("pylib", "py lib testing options") group.addoption('--sshhost', action="store", dest="sshhost", default=None, help=("target to run tests requiring ssh, e.g. " "user at codespeak.net")) + group.addoption('--gx', + action="append", dest="gspecs", default=None, + help=("add a global test environment, XSpec-syntax. ")), group.addoption('--runslowtests', action="store_true", dest="runslowtests", default=False, - help="run slow tests") + help=("run slow tests")) + +ConftestPlugin = PylibTestconfigPlugin + +# configuration information for tests +def getgspecs(config=None): + if config is None: + config = py.test.config + return [py.execnet.XSpec(spec) + for spec in config.getvalueorskip("gspecs")] -ConftestPlugin = PylibTestPlugin +def getspecssh(config=None): + xspecs = getgspecs(config) + for spec in xspecs: + if spec.ssh: + if not py.path.local.sysfind("ssh"): + py.test.skip("command not found: ssh") + return spec +def getsocketspec(config=None): + xspecs = getgspecs(config) + for spec in xspecs: + if spec.socket: + return spec Modified: py/trunk/py/execnet/testing/test_gateway.py ============================================================================== --- py/trunk/py/execnet/testing/test_gateway.py (original) +++ py/trunk/py/execnet/testing/test_gateway.py Fri Mar 20 16:36:45 2009 @@ -542,8 +542,8 @@ ret = channel.receive() assert ret == 42 + @py.test.mark.xfail("fix needed: dying remote process does not cause waitclose() to fail") def test_waitclose_on_remote_killed(self): - py.test.skip("fix needed: dying remote process does not cause waitclose() to fail") gw = py.execnet.PopenGateway() channel = gw.remote_exec(""" import os @@ -591,21 +591,13 @@ ## cls.gw.exit() ## cls.proxygw.exit() -def getsshhost(withpython=False): - sshhost = py.test.config.getvalueorskip("sshhost") - if not withpython and sshhost.find(":") != -1: - sshhost = sshhost.split(":")[0] - ssh = py.path.local.sysfind("ssh") - if not ssh: - py.test.skip("command not found: ssh") - return sshhost - class TestSocketGateway(SocketGatewaySetup, BasicRemoteExecution): pass class TestSshGateway(BasicRemoteExecution): def setup_class(cls): - cls.sshhost = getsshhost() + from py.__.conftest import getspecssh + cls.sshhost = getspecssh().ssh cls.gw = py.execnet.SshGateway(cls.sshhost) def test_sshconfig_functional(self): Modified: py/trunk/py/execnet/testing/test_gwspec.py ============================================================================== --- py/trunk/py/execnet/testing/test_gwspec.py (original) +++ py/trunk/py/execnet/testing/test_gwspec.py Fri Mar 20 16:36:45 2009 @@ -3,7 +3,6 @@ """ import py -from test_gateway import getsshhost class TestGatewaySpec: """ @@ -88,8 +87,8 @@ assert py.std.sys.executable == py.std.sys.executable gw.exit() - def test_ssh(self): - sshhost = getsshhost() + def test_ssh(self, specssh): + sshhost = specssh.ssh spec = py.execnet.GatewaySpec("ssh:" + sshhost) gw = spec.makegateway() p = gw.remote_exec("import os ; channel.send(os.getcwd())").receive() Modified: py/trunk/py/execnet/testing/test_xspec.py ============================================================================== --- py/trunk/py/execnet/testing/test_xspec.py (original) +++ py/trunk/py/execnet/testing/test_xspec.py Fri Mar 20 16:36:45 2009 @@ -72,3 +72,27 @@ assert rinfo.executable == cpython26 assert rinfo.cwd == py.std.os.getcwd() assert rinfo.version_info[:2] == (2,6) + + def test_ssh(self, specssh): + sshhost = specssh.ssh + gw = py.execnet.makegateway("ssh=%s" % sshhost) + rinfo = gw._rinfo() + gw2 = py.execnet.SshGateway(sshhost) + rinfo2 = gw2._rinfo() + assert rinfo.executable == rinfo2.executable + assert rinfo.cwd == rinfo2.cwd + assert rinfo.version_info == rinfo2.version_info + + def test_socket(self, specsocket): + gw = py.execnet.makegateway("socket=%s" % specsocket.socket) + rinfo = gw._rinfo() + assert rinfo.executable + assert rinfo.cwd + assert rinfo.version_info + # we cannot instantiate a second gateway + + #gw2 = py.execnet.SocketGateway(*specsocket.socket.split(":")) + #rinfo2 = gw2._rinfo() + #assert rinfo.executable == rinfo2.executable + #assert rinfo.cwd == rinfo2.cwd + #assert rinfo.version_info == rinfo2.version_info Modified: py/trunk/py/execnet/xspec.py ============================================================================== --- py/trunk/py/execnet/xspec.py (original) +++ py/trunk/py/execnet/xspec.py Fri Mar 20 16:36:45 2009 @@ -31,5 +31,11 @@ spec = XSpec(spec) if spec.popen: gw = py.execnet.PopenGateway(python=spec.python) + elif spec.ssh: + gw = py.execnet.SshGateway(spec.ssh, remotepython=spec.python) + elif spec.socket: + assert not spec.python, "socket: specifying python executables not supported" + hostport = spec.socket.split(":") + gw = py.execnet.SocketGateway(*hostport) gw.spec = spec return gw From hpk at codespeak.net Fri Mar 20 16:52:42 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 20 Mar 2009 16:52:42 +0100 (CET) Subject: [py-svn] r63145 - in py/trunk/py/execnet: . testing Message-ID: <20090320155242.547AB1684B5@codespeak.net> Author: hpk Date: Fri Mar 20 16:52:39 2009 New Revision: 63145 Modified: py/trunk/py/execnet/testing/test_xspec.py py/trunk/py/execnet/xspec.py Log: add support for "chdir" Modified: py/trunk/py/execnet/testing/test_xspec.py ============================================================================== --- py/trunk/py/execnet/testing/test_xspec.py (original) +++ py/trunk/py/execnet/testing/test_xspec.py Fri Mar 20 16:52:39 2009 @@ -73,6 +73,17 @@ assert rinfo.cwd == py.std.os.getcwd() assert rinfo.version_info[:2] == (2,6) + def test_popen_chdir_absolute(self, testdir): + gw = py.execnet.makegateway("popen//chdir=%s" % testdir.tmpdir) + rinfo = gw._rinfo() + assert rinfo.cwd == str(testdir.tmpdir) + + def test_popen_chdir_newsub(self, testdir): + testdir.chdir() + gw = py.execnet.makegateway("popen//chdir=hello") + rinfo = gw._rinfo() + assert rinfo.cwd == str(testdir.tmpdir.join("hello")) + def test_ssh(self, specssh): sshhost = specssh.ssh gw = py.execnet.makegateway("ssh=%s" % sshhost) Modified: py/trunk/py/execnet/xspec.py ============================================================================== --- py/trunk/py/execnet/xspec.py (original) +++ py/trunk/py/execnet/xspec.py Fri Mar 20 16:52:39 2009 @@ -38,4 +38,15 @@ hostport = spec.socket.split(":") gw = py.execnet.SocketGateway(*hostport) gw.spec = spec + # XXX events + if spec.chdir: + gw.remote_exec(""" + import os + path = %r + try: + os.chdir(path) + except OSError: + os.mkdir(path) + os.chdir(path) + """ % spec.chdir).waitclose() return gw From hpk at codespeak.net Fri Mar 20 17:28:14 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 20 Mar 2009 17:28:14 +0100 (CET) Subject: [py-svn] r63147 - in py/trunk/py: . execnet execnet/testing Message-ID: <20090320162814.C10A41684C5@codespeak.net> Author: hpk Date: Fri Mar 20 17:28:14 2009 New Revision: 63147 Removed: py/trunk/py/execnet/testing/test_gwspec.py Modified: py/trunk/py/__init__.py py/trunk/py/conftest.py py/trunk/py/execnet/gwmanage.py py/trunk/py/execnet/testing/test_gwmanage.py py/trunk/py/execnet/testing/test_xspec.py py/trunk/py/execnet/xspec.py Log: get rid of old method for specifying remote execution places. Modified: py/trunk/py/__init__.py ============================================================================== --- py/trunk/py/__init__.py (original) +++ py/trunk/py/__init__.py Fri Mar 20 17:28:14 2009 @@ -151,7 +151,6 @@ 'execnet.SocketGateway' : ('./execnet/register.py', 'SocketGateway'), 'execnet.PopenGateway' : ('./execnet/register.py', 'PopenGateway'), 'execnet.SshGateway' : ('./execnet/register.py', 'SshGateway'), - 'execnet.GatewaySpec' : ('./execnet/gwmanage.py', 'GatewaySpec'), 'execnet.XSpec' : ('./execnet/xspec.py', 'XSpec'), 'execnet.makegateway' : ('./execnet/xspec.py', 'makegateway'), 'execnet.MultiGateway' : ('./execnet/multi.py', 'MultiGateway'), Modified: py/trunk/py/conftest.py ============================================================================== --- py/trunk/py/conftest.py (original) +++ py/trunk/py/conftest.py Fri Mar 20 17:28:14 2009 @@ -37,9 +37,11 @@ if not py.path.local.sysfind("ssh"): py.test.skip("command not found: ssh") return spec + py.test.skip("need '--gx ssh=...'") def getsocketspec(config=None): xspecs = getgspecs(config) for spec in xspecs: if spec.socket: return spec + py.test.skip("need '--gx socket=...'") Modified: py/trunk/py/execnet/gwmanage.py ============================================================================== --- py/trunk/py/execnet/gwmanage.py (original) +++ py/trunk/py/execnet/gwmanage.py Fri Mar 20 17:28:14 2009 @@ -1,105 +1,19 @@ """ instantiating, managing and rsyncing to hosts -Host specification strings and implied gateways: - - socket:hostname:port:path SocketGateway - popen[-executable][:path] PopenGateway - [ssh:]spec:path SshGateway - * [SshGateway] - -on hostspec.makeconnection() a Host object -will be created which has an instantiated gateway. -the remote side will be chdir()ed to the specified path. -if no path was specified, do no chdir() at all. - - """ + import py import sys, os -from py.__.test.dsession.masterslave import MasterNode -from py.__.test import event from py.__.execnet.channel import RemoteError NO_ENDMARKER_WANTED = object() -class GatewaySpec(object): - python = None - def __init__(self, spec, defaultjoinpath="pyexecnetcache"): - self._spec = spec - if spec == "popen" or spec.startswith("popen:"): - parts = spec.split(":", 2) - self.type = self.address = parts.pop(0) - if parts: - python = parts.pop(0) - # XXX XXX XXX do better GWSPEC that can deal - # with "C:" - if py.std.sys.platform == "win32" and len(python) == 1: - python = "%s:%s" %(python, parts.pop(0)) - self.python = python - if parts: - self.joinpath = parts.pop(0) - else: - self.joinpath = "" - if not self.python: - self.python = py.std.sys.executable - - elif spec.startswith("socket:"): - parts = spec[7:].split(":", 2) - self.address = parts.pop(0) - if parts: - port = int(parts.pop(0)) - self.address = self.address, port - self.joinpath = parts and parts.pop(0) or "" - self.type = "socket" - else: - if spec.startswith("ssh:"): - spec = spec[4:] - parts = spec.split(":", 2) - self.address = parts.pop(0) - self.python = parts and parts.pop(0) or "python" - self.joinpath = parts and parts.pop(0) or "" - self.type = "ssh" - if not self.joinpath and not self.inplacelocal(): - self.joinpath = defaultjoinpath - - def inplacelocal(self): - return bool(self.type == "popen" and not self.joinpath) - - def __str__(self): - return "" % self._spec - __repr__ = __str__ - - def makegateway(self, waitclose=True): - if self.type == "popen": - gw = py.execnet.PopenGateway(python=self.python) - elif self.type == "socket": - gw = py.execnet.SocketGateway(*self.address) - elif self.type == "ssh": - gw = py.execnet.SshGateway(self.address, remotepython=self.python) - if self.joinpath: - channel = gw.remote_exec(""" - import os - path = %r - try: - os.chdir(path) - except OSError: - os.mkdir(path) - os.chdir(path) - """ % self.joinpath) - if waitclose: - channel.waitclose() - else: - if waitclose: - gw.remote_exec("").waitclose() - gw.spec = self - return gw - class GatewayManager: RemoteError = RemoteError def __init__(self, specs): - self.specs = [GatewaySpec(spec) for spec in specs] + self.specs = [py.execnet.XSpec(spec) for spec in specs] self.gateways = [] def trace(self, msg): @@ -111,7 +25,7 @@ def makegateways(self): assert not self.gateways for spec in self.specs: - gw = spec.makegateway() + gw = py.execnet.makegateway(spec) self.gateways.append(gw) gw.id = "[%s]" % len(self.gateways) self.notify("gwmanage_newgateway", gw) @@ -121,7 +35,7 @@ self.makegateways() l = [] for gw in self.gateways: - if gw.spec.inplacelocal(): + if gw.spec._samefilesystem(): if inplacelocal: l.append(gw) else: @@ -150,15 +64,14 @@ seen = {} for gateway in self.gateways: spec = gateway.spec - if not spec.inplacelocal(): - key = spec.type, spec.address, spec.joinpath - if key in seen: + if not spec._samefilesystem(): + if spec in seen: continue def finished(): if notify: notify("rsyncrootready", spec, source) rsync.add_target_host(gateway, finished=finished) - seen[key] = gateway + seen[spec] = gateway if seen: self.notify("gwmanage_rsyncstart", source=source, gateways=seen.values()) rsync.send() @@ -203,5 +116,5 @@ def _report_send_file(self, gateway, modified_rel_path): if self._verbose: path = os.path.basename(self._sourcedir) + "/" + modified_rel_path - remotepath = gateway.spec.joinpath + remotepath = gateway.spec.chdir print '%s:%s <= %s' % (gateway.remoteaddress, remotepath, path) Modified: py/trunk/py/execnet/testing/test_gwmanage.py ============================================================================== --- py/trunk/py/execnet/testing/test_gwmanage.py (original) +++ py/trunk/py/execnet/testing/test_gwmanage.py Fri Mar 20 17:28:14 2009 @@ -37,7 +37,7 @@ assert not len(hm.gateways) def test_hostmanager_rsync_popen_with_path(self, source, dest): - hm = GatewayManager(["popen::%s" %dest] * 1) + hm = GatewayManager(["popen//chdir=%s" %dest] * 1) hm.makegateways() source.ensure("dir1", "dir2", "hello") l = [] @@ -51,7 +51,7 @@ assert dest.join("dir1", "dir2", 'hello').check() def test_hostmanage_rsync_same_popen_twice(self, source, dest, eventrecorder): - hm = GatewayManager(["popen::%s" %dest] * 2) + hm = GatewayManager(["popen//chdir=%s" %dest] * 2) hm.makegateways() source.ensure("dir1", "dir2", "hello") hm.rsync(source) @@ -65,7 +65,7 @@ def test_multi_chdir_popen_with_path(self, testdir): import os - hm = GatewayManager(["popen::hello"] * 2) + hm = GatewayManager(["popen//chdir=hello"] * 2) testdir.tmpdir.chdir() hellopath = testdir.tmpdir.mkdir("hello") hm.makegateways() @@ -122,8 +122,7 @@ assert 'somedir' in basenames def test_hrsync_one_host(self, source, dest): - spec = py.execnet.GatewaySpec("popen::%s" % dest) - gw = spec.makegateway() + gw = py.execnet.makegateway("popen//chdir=%s" % dest) finished = [] rsync = HostRSync(source) rsync.add_target_host(gw, finished=lambda: finished.append(1)) Deleted: /py/trunk/py/execnet/testing/test_gwspec.py ============================================================================== --- /py/trunk/py/execnet/testing/test_gwspec.py Fri Mar 20 17:28:14 2009 +++ (empty file) @@ -1,101 +0,0 @@ -""" - tests for py.execnet.GatewaySpec -""" - -import py - -class TestGatewaySpec: - """ - socket:hostname:port:path SocketGateway - popen[-executable][:path] PopenGateway - [ssh:]spec:path SshGateway - * [SshGateway] - """ - def test_popen(self): - for python in ('', 'python2.4'): - for joinpath in ('', 'abc', 'ab:cd', '/x/y'): - s = ":".join(["popen", python, joinpath]) - print s - spec = py.execnet.GatewaySpec(s) - assert spec.address == "popen" - assert spec.python == (python or py.std.sys.executable) - assert spec.joinpath == joinpath - assert spec.type == "popen" - spec2 = py.execnet.GatewaySpec("popen" + joinpath) - self._equality(spec, spec2) - - def test_ssh(self): - for prefix in ('ssh', ''): # ssh is default - for hostpart in ('x.y', 'xyz at x.y'): - for python in ('python', 'python2.5'): - for joinpath in ('', 'abc', 'ab:cd', '/tmp'): - specstring = ":".join([prefix, hostpart, python, joinpath]) - if specstring[0] == ":": - specstring = specstring[1:] - print specstring - spec = py.execnet.GatewaySpec(specstring) - assert spec.address == hostpart - assert spec.python == python - if joinpath: - assert spec.joinpath == joinpath - else: - assert spec.joinpath == "pyexecnetcache" - assert spec.type == "ssh" - spec2 = py.execnet.GatewaySpec(specstring) - self._equality(spec, spec2) - - def test_socket(self): - for hostpart in ('x.y', 'x', 'popen'): - for port in ":80", ":1000": - for joinpath in ('', ':abc', ':abc:de'): - spec = py.execnet.GatewaySpec("socket:" + hostpart + port + joinpath) - assert spec.address == (hostpart, int(port[1:])) - if joinpath[1:]: - assert spec.joinpath == joinpath[1:] - else: - assert spec.joinpath == "pyexecnetcache" - assert spec.type == "socket" - spec2 = py.execnet.GatewaySpec("socket:" + hostpart + port + joinpath) - self._equality(spec, spec2) - - def _equality(self, spec1, spec2): - assert spec1 != spec2 - assert hash(spec1) != hash(spec2) - assert not (spec1 == spec2) - - -class TestGatewaySpecAPI: - def test_popen_nopath_makegateway(self, testdir): - spec = py.execnet.GatewaySpec("popen") - gw = spec.makegateway() - p = gw.remote_exec("import os; channel.send(os.getcwd())").receive() - curdir = py.std.os.getcwd() - assert curdir == p - gw.exit() - - def test_popen_makegateway(self, testdir): - spec = py.execnet.GatewaySpec("popen::" + str(testdir.tmpdir)) - gw = spec.makegateway() - p = gw.remote_exec("import os; channel.send(os.getcwd())").receive() - assert spec.joinpath == p - gw.exit() - - def test_popen_makegateway_python(self, testdir): - spec = py.execnet.GatewaySpec("popen:%s" % py.std.sys.executable) - gw = spec.makegateway() - res = gw.remote_exec("import sys ; channel.send(sys.executable)").receive() - assert py.std.sys.executable == py.std.sys.executable - gw.exit() - - def test_ssh(self, specssh): - sshhost = specssh.ssh - spec = py.execnet.GatewaySpec("ssh:" + sshhost) - gw = spec.makegateway() - p = gw.remote_exec("import os ; channel.send(os.getcwd())").receive() - gw.exit() - - @py.test.mark.xfail("implement socketserver test scenario") - def test_socketgateway(self): - gw = py.execnet.PopenGateway() - spec = py.execnet.GatewaySpec("ssh:" + sshhost) - Modified: py/trunk/py/execnet/testing/test_xspec.py ============================================================================== --- py/trunk/py/execnet/testing/test_xspec.py (original) +++ py/trunk/py/execnet/testing/test_xspec.py Fri Mar 20 17:28:14 2009 @@ -4,23 +4,23 @@ class TestXSpec: def test_attributes(self): - spec = XSpec("socket=192.168.102.2:8888//python=c:/this/python2.5//path=d:\hello") + spec = XSpec("socket=192.168.102.2:8888//python=c:/this/python2.5//chdir=d:\hello") assert spec.socket == "192.168.102.2:8888" assert spec.python == "c:/this/python2.5" - assert spec.path == "d:\hello" - assert spec.xyz is None + assert spec.chdir == "d:\hello" + assert not hasattr(spec, 'xyz') py.test.raises(AttributeError, "spec._hello") spec = XSpec("socket=192.168.102.2:8888//python=python2.5") assert spec.socket == "192.168.102.2:8888" assert spec.python == "python2.5" - assert spec.path is None + assert spec.chdir is None - spec = XSpec("ssh=user at host//path=/hello/this//python=/usr/bin/python2.5") + spec = XSpec("ssh=user at host//chdir=/hello/this//python=/usr/bin/python2.5") assert spec.ssh == "user at host" assert spec.python == "/usr/bin/python2.5" - assert spec.path == "/hello/this" + assert spec.chdir == "/hello/this" spec = XSpec("popen") assert spec.popen == True @@ -28,7 +28,17 @@ def test__samefilesystem(self): assert XSpec("popen")._samefilesystem() assert XSpec("popen//python=123")._samefilesystem() - assert not XSpec("popen//path=hello")._samefilesystem() + assert not XSpec("popen//chdir=hello")._samefilesystem() + + def test__spec_spec(self): + for x in ("popen", "popen//python=this"): + assert XSpec(x)._spec == x + + def test_hash_equality(self): + assert XSpec("popen") == XSpec("popen") + assert hash(XSpec("popen")) == hash(XSpec("popen")) + assert XSpec("popen//python=123") != XSpec("popen") + assert hash(XSpec("socket=hello:8080")) != hash(XSpec("popen")) class TestMakegateway: def test_popen(self): Modified: py/trunk/py/execnet/xspec.py ============================================================================== --- py/trunk/py/execnet/xspec.py (original) +++ py/trunk/py/execnet/xspec.py Fri Mar 20 17:28:14 2009 @@ -9,22 +9,36 @@ * keys are not allowed to start with underscore * if no "=value" is given, assume a boolean True value """ - def __init__(self, *strings): - for string in strings: - for keyvalue in string.split("//"): - i = keyvalue.find("=") - if i == -1: - setattr(self, keyvalue, True) - else: - setattr(self, keyvalue[:i], keyvalue[i+1:]) + # XXX for now we are very restrictive about actually allowed key-values + popen = ssh = socket = python = chdir = None - def __getattr__(self, name): - if name[0] == "_": - raise AttributeError(name) - return None + def __init__(self, string): + self._spec = string + for keyvalue in string.split("//"): + i = keyvalue.find("=") + if i == -1: + key, value = keyvalue, True + else: + key, value = keyvalue[:i], keyvalue[i+1:] + # XXX be restrictive for now + if key not in XSpec.__dict__: + raise AttributeError("%r not a valid attribute" % key) + setattr(self, key, value) + + def __hash__(self): + return hash(self._spec) + def __eq__(self, other): + return self._spec == getattr(other, '_spec', None) + def __ne__(self, other): + return self._spec != getattr(other, '_spec', None) + + #def __getattr__(self, name): + # if name[0] == "_": + # raise AttributeError(name) + # return None def _samefilesystem(self): - return bool(self.popen and not self.path) + return bool(self.popen and not self.chdir) def makegateway(spec): if not isinstance(spec, XSpec): From hpk at codespeak.net Fri Mar 20 17:58:35 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 20 Mar 2009 17:58:35 +0100 (CET) Subject: [py-svn] r63148 - in py/trunk/py/execnet: . testing Message-ID: <20090320165835.8026C1683FA@codespeak.net> Author: hpk Date: Fri Mar 20 17:58:32 2009 New Revision: 63148 Modified: py/trunk/py/execnet/testing/test_xspec.py py/trunk/py/execnet/xspec.py Log: add a __repr__ for xspecs Modified: py/trunk/py/execnet/testing/test_xspec.py ============================================================================== --- py/trunk/py/execnet/testing/test_xspec.py (original) +++ py/trunk/py/execnet/testing/test_xspec.py Fri Mar 20 17:58:32 2009 @@ -34,6 +34,10 @@ for x in ("popen", "popen//python=this"): assert XSpec(x)._spec == x + def test_repr(self): + for x in ("popen", "popen//python=this"): + assert repr(XSpec(x)).find("popen") != -1 + def test_hash_equality(self): assert XSpec("popen") == XSpec("popen") assert hash(XSpec("popen")) == hash(XSpec("popen")) Modified: py/trunk/py/execnet/xspec.py ============================================================================== --- py/trunk/py/execnet/xspec.py (original) +++ py/trunk/py/execnet/xspec.py Fri Mar 20 17:58:32 2009 @@ -22,9 +22,12 @@ key, value = keyvalue[:i], keyvalue[i+1:] # XXX be restrictive for now if key not in XSpec.__dict__: - raise AttributeError("%r not a valid attribute" % key) + raise AttributeError("%r not a valid XSpec key" % key) setattr(self, key, value) + def __repr__(self): + return "" %(self._spec,) + def __hash__(self): return hash(self._spec) def __eq__(self, other): From hpk at codespeak.net Fri Mar 20 18:14:49 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 20 Mar 2009 18:14:49 +0100 (CET) Subject: [py-svn] r63149 - in py/trunk/py/execnet: . testing Message-ID: <20090320171449.8F009168454@codespeak.net> Author: hpk Date: Fri Mar 20 18:14:45 2009 New Revision: 63149 Modified: py/trunk/py/execnet/gwmanage.py py/trunk/py/execnet/testing/test_gwmanage.py Log: always have a default chdir Modified: py/trunk/py/execnet/gwmanage.py ============================================================================== --- py/trunk/py/execnet/gwmanage.py (original) +++ py/trunk/py/execnet/gwmanage.py Fri Mar 20 18:14:45 2009 @@ -12,9 +12,15 @@ class GatewayManager: RemoteError = RemoteError - def __init__(self, specs): - self.specs = [py.execnet.XSpec(spec) for spec in specs] + def __init__(self, specs, defaultchdir="pyexecnetcache"): self.gateways = [] + self.specs = [] + for spec in specs: + if not isinstance(spec, py.execnet.XSpec): + spec = py.execnet.XSpec(spec) + if not spec.chdir and not spec.popen: + spec.chdir = defaultchdir + self.specs.append(spec) def trace(self, msg): self.notify("trace", "gatewaymanage", msg) Modified: py/trunk/py/execnet/testing/test_gwmanage.py ============================================================================== --- py/trunk/py/execnet/testing/test_gwmanage.py (original) +++ py/trunk/py/execnet/testing/test_gwmanage.py Fri Mar 20 18:14:45 2009 @@ -11,6 +11,17 @@ pytest_plugins = "pytest_pytester" class TestGatewayManagerPopen: + def test_popen_no_default_chdir(self): + gm = GatewayManager(["popen"]) + assert gm.specs[0].chdir is None + + def test_default_chdir(self): + l = ["ssh=noco", "socket=xyz"] + for spec in GatewayManager(l).specs: + assert spec.chdir == "pyexecnetcache" + for spec in GatewayManager(l, defaultchdir="abc").specs: + assert spec.chdir == "abc" + def test_hostmanager_popen_makegateway(self, eventrecorder): hm = GatewayManager(["popen"] * 2) hm.makegateways() From hpk at codespeak.net Fri Mar 20 18:29:09 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 20 Mar 2009 18:29:09 +0100 (CET) Subject: [py-svn] r63150 - in py/trunk/py/test: dsession dsession/testing plugin Message-ID: <20090320172909.3A0C0168487@codespeak.net> Author: hpk Date: Fri Mar 20 18:29:08 2009 New Revision: 63150 Modified: py/trunk/py/test/dsession/hostmanage.py py/trunk/py/test/dsession/masterslave.py py/trunk/py/test/dsession/testing/test_hostmanage.py py/trunk/py/test/plugin/pytest_default.py Log: fix hostmanager to work with new xspecs Modified: py/trunk/py/test/dsession/hostmanage.py ============================================================================== --- py/trunk/py/test/dsession/hostmanage.py (original) +++ py/trunk/py/test/dsession/hostmanage.py Fri Mar 20 18:29:08 2009 @@ -4,21 +4,20 @@ from py.__.execnet.gwmanage import GatewayManager from py.__.test import event -def getconfiggwspecs(config): +def getxspecs(config): if config.option.numprocesses: if config.option.executable: - s = 'popen:%s' % config.option.executable + s = 'popen//python=%s' % config.option.executable else: s = 'popen' - gwspecs = [s] * config.option.numprocesses + xspecs = [s] * config.option.numprocesses else: - gwspecs = config.option.gateways - if not gwspecs: - gwspecs = config.getvalue("gateways") - else: - gwspecs = gwspecs.split(",") - assert gwspecs is not None - return gwspecs + xspecs = config.option.xspecs + if not xspecs: + xspecs = config.getvalue("xspecs") + assert xspecs is not None + #print "option value for xspecs", xspecs + return [py.execnet.XSpec(x) for x in xspecs] def getconfigroots(config): roots = config.option.rsyncdirs @@ -45,7 +44,7 @@ def __init__(self, config, hosts=None): self.config = config if hosts is None: - hosts = getconfiggwspecs(self.config) + hosts = getxspecs(self.config) self.roots = getconfigroots(config) self.gwmanager = GatewayManager(hosts) @@ -82,7 +81,7 @@ self.makegateways() options = { 'ignores': self.config_getignores(), - 'verbose': 1, # self.config.option.verbose + 'verbose': self.config.option.verbose, } if self.roots: # send each rsync root Modified: py/trunk/py/test/dsession/masterslave.py ============================================================================== --- py/trunk/py/test/dsession/masterslave.py (original) +++ py/trunk/py/test/dsession/masterslave.py Fri Mar 20 18:29:08 2009 @@ -71,7 +71,7 @@ """) channel = PickleChannel(channel) basetemp = None - if host.type == "popen": + if host.popen: popenbase = config.ensuretemp("popen") basetemp = py.path.local.make_numbered_dir(prefix="slave-", keep=0, rootdir=popenbase) Modified: py/trunk/py/test/dsession/testing/test_hostmanage.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_hostmanage.py (original) +++ py/trunk/py/test/dsession/testing/test_hostmanage.py Fri Mar 20 18:29:08 2009 @@ -3,9 +3,7 @@ """ import py -from py.__.test.dsession.hostmanage import HostManager, getconfiggwspecs, getconfigroots -from py.__.execnet.gwmanage import GatewaySpec as Host -from py.__.execnet.testing.test_gateway import getsshhost +from py.__.test.dsession.hostmanage import HostManager, getxspecs, getconfigroots from py.__.test import event @@ -16,24 +14,6 @@ return dest class TestHostManager: - def gethostmanager(self, source, hosts, rsyncdirs=None): - def opt(optname, l): - return '%s=%s' % (optname, ",".join(map(str, l))) - args = [opt('--gateways', hosts)] - if rsyncdirs: - args.append(opt('--rsyncdir', [source.join(x, abs=True) for x in rsyncdirs])) - args.append(source) - config = py.test.config._reparse(args) - assert config.topdir == source - hm = HostManager(config) - assert hm.gwmanager.specs - return hm - - def xxtest_hostmanager_custom_hosts(self, source, dest): - session = py.test.config._reparse([source]).initsession() - hm = HostManager(session.config, hosts=[1,2,3]) - assert hm.hosts == [1,2,3] - @py.test.mark.xfail("consider / forbid implicit rsyncdirs?") def test_hostmanager_rsync_roots_no_roots(self, source, dest): source.ensure("dir1", "file1").write("hello") @@ -48,32 +28,24 @@ assert p.join("dir1").check() assert p.join("dir1", "file1").check() - def test_hostmanager_rsync_roots_roots(self, source, dest): - dir2 = source.ensure("dir1", "dir2", dir=1) - dir2.ensure("hello") - hm = self.gethostmanager(source, - hosts = ["popen::%s" % dest], - rsyncdirs = ['dir1'] - ) - assert hm.config.topdir == source - hm.rsync_roots() - assert dest.join("dir1").check() - assert dest.join("dir1", "dir2").check() - assert dest.join("dir1", "dir2", 'hello').check() - - def test_hostmanager_init_rsync_topdir_explicit(self, source, dest): - dir2 = source.ensure("dir1", "dir2", dir=1) + def test_popen_rsync_subdir(self, testdir, source, dest): + dir1 = source.mkdir("dir1") + dir2 = dir1.mkdir("dir2") dir2.ensure("hello") - hm = self.gethostmanager(source, - hosts = ["popen::%s" % dest], - rsyncdirs = [str(source)] - ) - assert hm.config.topdir == source - hm.rsync_roots() - dest = dest.join(source.basename) - assert dest.join("dir1").check() - assert dest.join("dir1", "dir2").check() - assert dest.join("dir1", "dir2", 'hello').check() + for rsyncroot in (dir1, source): + dest.remove() + hm = HostManager(testdir.parseconfig( + "--tx", "popen//chdir=%s" % dest, + "--rsyncdirs", rsyncroot, + source, + )) + assert hm.config.topdir == source + hm.rsync_roots() + if rsyncroot == source: + dest = dest.join("source") + assert dest.join("dir1").check() + assert dest.join("dir1", "dir2").check() + assert dest.join("dir1", "dir2", 'hello').check() def test_hostmanager_init_rsync_roots(self, source, dest): dir2 = source.ensure("dir1", "dir2", dir=1) @@ -85,7 +57,7 @@ """)) session = py.test.config._reparse([source]).initsession() hm = HostManager(session.config, - hosts=["popen::" + str(dest)]) + hosts=["popen//chdir=%s" % dest]) hm.rsync_roots() assert dest.join("dir2").check() assert not dest.join("dir1").check() @@ -102,7 +74,7 @@ """)) session = py.test.config._reparse([source]).initsession() hm = HostManager(session.config, - hosts=["popen::" + str(dest)]) + hosts=["popen//chdir=%s" % dest]) hm.rsync_roots() assert dest.join("dir1").check() assert not dest.join("dir1", "dir2").check() @@ -117,8 +89,8 @@ hm = HostManager(config, hosts=hosts) hm.rsync_roots() for gwspec in hm.gwmanager.specs: - assert gwspec.inplacelocal() - assert not gwspec.joinpath + assert gwspec._samefilesystem() + assert not gwspec.chdir def test_hostmanage_setup_hosts_DEBUG(self, source, EventRecorder): hosts = ["popen"] * 2 @@ -135,48 +107,48 @@ assert l hm.teardown_hosts() - def test_hostmanage_ssh_setup_hosts(self, testdir): - sshhost = getsshhost(withpython=True) + def test_hostmanage_ssh_setup_hosts(self, specssh, testdir): testdir.makepyfile(__init__="", test_x=""" def test_one(): pass """) - sorter = testdir.inline_run("-d", "--rsyncdirs=%s" % testdir.tmpdir, - "--gateways=%s" % sshhost, testdir.tmpdir) + "--tx=%s" % specssh, testdir.tmpdir) ev = sorter.getfirstnamed("itemtestreport") assert ev.passed - -def test_getconfiggwspecs_numprocesses(): - config = py.test.config._reparse(['-n3']) - hosts = getconfiggwspecs(config) - assert len(hosts) == 3 - -def test_getconfiggwspecs_disthosts(): - config = py.test.config._reparse(['--gateways=a,b,c']) - hosts = getconfiggwspecs(config) - assert len(hosts) == 3 - assert hosts == ['a', 'b', 'c'] - -def test_getconfigroots(testdir): - config = testdir.parseconfig('--rsyncdirs=' + str(testdir.tmpdir)) - roots = getconfigroots(config) - assert len(roots) == 1 + 1 - assert testdir.tmpdir in roots - -def test_getconfigroots_with_conftest(testdir): - testdir.chdir() - p = py.path.local() - for bn in 'x y z'.split(): - p.mkdir(bn) - testdir.makeconftest(""" - rsyncdirs= 'x', - """) - config = testdir.parseconfig(testdir.tmpdir, '--rsyncdirs=y,z') - roots = getconfigroots(config) - assert len(roots) == 3 + 1 - assert py.path.local('y') in roots - assert py.path.local('z') in roots - assert testdir.tmpdir.join('x') in roots +class TestOptionsAndConfiguration: + def test_getxspecs_numprocesses(self, testdir): + config = testdir.parseconfig("-n3") + xspecs = getxspecs(config) + assert len(xspecs) == 3 + + def test_getxspecs(self, testdir): + config = testdir.parseconfig("--tx=popen", "--tx", "ssh=xyz") + xspecs = getxspecs(config) + assert len(xspecs) == 2 + print xspecs + assert xspecs[0].popen + assert xspecs[1].ssh == "xyz" + + def test_getconfigroots(self, testdir): + config = testdir.parseconfig('--rsyncdirs=' + str(testdir.tmpdir)) + roots = getconfigroots(config) + assert len(roots) == 1 + 1 + assert testdir.tmpdir in roots + + def test_getconfigroots_with_conftest(self, testdir): + testdir.chdir() + p = py.path.local() + for bn in 'x y z'.split(): + p.mkdir(bn) + testdir.makeconftest(""" + rsyncdirs= 'x', + """) + config = testdir.parseconfig(testdir.tmpdir, '--rsyncdirs=y,z') + roots = getconfigroots(config) + assert len(roots) == 3 + 1 + assert py.path.local('y') in roots + assert py.path.local('z') in roots + assert testdir.tmpdir.join('x') in roots Modified: py/trunk/py/test/plugin/pytest_default.py ============================================================================== --- py/trunk/py/test/plugin/pytest_default.py (original) +++ py/trunk/py/test/plugin/pytest_default.py Fri Mar 20 18:29:08 2009 @@ -64,8 +64,8 @@ group._addoption('-s', '--nocapture', action="store_true", dest="nocapture", default=False, help="disable catching of sys.stdout/stderr output."), - group.addoption('--basetemp', dest="basetemp", default=None, - help="directory to use for this test run.") + group.addoption('--basetemp', dest="basetemp", default=None, metavar="dir", + help="temporary directory for this test run.") group.addoption('--boxed', action="store_true", dest="boxed", default=False, help="box each test run in a separate process"), @@ -97,8 +97,9 @@ group.addoption('--rsyncdirs', dest="rsyncdirs", default=None, metavar="dir1,dir2,...", help="comma-separated list of directories to rsync. All those roots will be rsynced " "into a corresponding subdir on the remote sides. ") - group.addoption('--gateways', dest="gateways", default=None, metavar="spec1,spec2,...", - help="comma-separated list of gateway specs, used by test distribution modes") + group.addoption('--tx', dest="xspecs", action="append", + help=("add a test environment, specified in XSpec syntax. examples: " + "--tx popen//python=python2.5 --tx socket=192.168.1.102")) group._addoption('--exec', action="store", dest="executable", default=None, help="python executable to run the tests with.") From hpk at codespeak.net Fri Mar 20 18:58:38 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 20 Mar 2009 18:58:38 +0100 (CET) Subject: [py-svn] r63151 - in py/trunk/py/test: dsession dsession/testing testing Message-ID: <20090320175838.80F791684A8@codespeak.net> Author: hpk Date: Fri Mar 20 18:58:36 2009 New Revision: 63151 Modified: py/trunk/py/test/dsession/dsession.py py/trunk/py/test/dsession/testing/test_dsession.py py/trunk/py/test/dsession/testing/test_functional_dsession.py py/trunk/py/test/dsession/testing/test_masterslave.py py/trunk/py/test/testing/acceptance_test.py Log: fix various bits, many tests pass modulo dsession host/node handling Modified: py/trunk/py/test/dsession/dsession.py ============================================================================== --- py/trunk/py/test/dsession/dsession.py (original) +++ py/trunk/py/test/dsession/dsession.py Fri Mar 20 18:58:36 2009 @@ -86,12 +86,12 @@ try: config.getvalue('hosts') except KeyError: - print "Please specify hosts for distribution of tests:" - print "cmdline: --gateways=host1,host2,..." - print "conftest.py: pytest_option_hosts=['host1','host2',]" - print "environment: PYTEST_OPTION_HOSTS=host1,host2,host3" + print "Please specify test environments for distribution of tests:" + print "py.test --tx ssh=user at somehost --tx popen//python=python2.5" + print "conftest.py: pytest_option_tx=['ssh=user at somehost','popen']" + print "environment: PYTEST_OPTION_TX=ssh=@somehost,popen" print - print "see also: http://codespeak.net/py/current/doc/test.html#automated-distributed-testing" + #print "see also: http://codespeak.net/py/current/doc/test.html#automated-distributed-testing" raise SystemExit def main(self, colitems=None): Modified: py/trunk/py/test/dsession/testing/test_dsession.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_dsession.py (original) +++ py/trunk/py/test/dsession/testing/test_dsession.py Fri Mar 20 18:58:36 2009 @@ -1,6 +1,5 @@ from py.__.test.dsession.dsession import DSession from py.__.test.dsession.masterslave import maketestnodeready -from py.__.execnet.gwmanage import GatewaySpec from py.__.test.runner import basic_collect_report from py.__.test import event from py.__.test import outcome @@ -38,7 +37,7 @@ item = testdir.getitem("def test_func(): pass") rep = run(item) session = DSession(item.config) - host = GatewaySpec("localhost") + host = py.execnet.XSpec("popen") host.node = MockNode() assert not session.host2pending session.addhost(host) @@ -54,7 +53,7 @@ item = testdir.getitem("def test_func(): pass") rep = run(item) session = DSession(item.config) - host = GatewaySpec("localhost") + host = py.execnet.XSpec("popen") host.node = MockNode() session.addhost(host) session.senditems([item]) @@ -79,9 +78,9 @@ def test_triggertesting_item(self, testdir): item = testdir.getitem("def test_func(): pass") session = DSession(item.config) - host1 = GatewaySpec("localhost") + host1 = py.execnet.XSpec("popen") host1.node = MockNode() - host2 = GatewaySpec("localhost") + host2 = py.execnet.XSpec("popen") host2.node = MockNode() session.addhost(host1) session.addhost(host2) Modified: py/trunk/py/test/dsession/testing/test_functional_dsession.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_functional_dsession.py (original) +++ py/trunk/py/test/dsession/testing/test_functional_dsession.py Fri Mar 20 18:58:36 2009 @@ -42,7 +42,7 @@ def test_fail(): assert 0 """) - config = testdir.parseconfig('-d', p1, '--gateways=popen') + config = testdir.parseconfig('-d', p1, '--tx=popen') dsession = DSession(config) eq = EventQueue(config.bus) dsession.main([config.getfsnode(p1)]) @@ -54,7 +54,7 @@ assert ev.failed # see that the host is really down ev, = eq.geteventargs("testnodedown") - assert ev.host.address == "popen" + assert ev.host.popen ev, = eq.geteventargs("testrunfinish") def test_distribution_rsyncdirs_example(self, testdir): @@ -65,7 +65,7 @@ p = subdir.join("test_one.py") p.write("def test_5(): assert not __file__.startswith(%r)" % str(p)) result = testdir.runpytest("-d", "--rsyncdirs=%(subdir)s" % locals(), - "--gateways=popen::%(dest)s" % locals(), p) + "--tx=popen//chdir=%(dest)s" % locals(), p) assert result.ret == 0 result.stdout.fnmatch_lines([ "*1* instantiated gateway *popen*", @@ -88,7 +88,7 @@ import os assert os.nice(0) == 10 """) - evrec = testdir.inline_run('-d', p1, '--gateways=popen') + evrec = testdir.inline_run('-d', p1, '--tx=popen') ev = evrec.getreport('test_nice') assert ev.passed Modified: py/trunk/py/test/dsession/testing/test_masterslave.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_masterslave.py (original) +++ py/trunk/py/test/dsession/testing/test_masterslave.py Fri Mar 20 18:58:36 2009 @@ -1,7 +1,6 @@ import py from py.__.test.dsession.masterslave import MasterNode -from py.__.execnet.gwmanage import GatewaySpec class EventQueue: def __init__(self, bus, queue=None): @@ -43,8 +42,8 @@ config = py.test.config._reparse([]) self.config = config self.queue = py.std.Queue.Queue() - self.host = GatewaySpec("popen") - self.gateway = self.host.makegateway() + self.host = py.execnet.XSpec("popen") + self.gateway = py.execnet.makegateway(self.host) self.node = MasterNode(self.host, self.gateway, self.config, putevent=self.queue.put) assert not self.node.channel.isclosed() return self.node Modified: py/trunk/py/test/testing/acceptance_test.py ============================================================================== --- py/trunk/py/test/testing/acceptance_test.py (original) +++ py/trunk/py/test/testing/acceptance_test.py Fri Mar 20 18:58:36 2009 @@ -265,7 +265,7 @@ py.test.skip("hello") """, ) - result = testdir.runpytest(p1, '-d', '--gateways=popen,popen') + result = testdir.runpytest(p1, '-d', '--tx popen --tx popen') result.stdout.fnmatch_lines([ "HOSTUP: popen*Python*", #"HOSTUP: localhost*Python*", @@ -288,7 +288,7 @@ """, ) testdir.makeconftest(""" - pytest_option_gateways='popen,popen,popen' + pytest_option_tx = 'popen popen popen'.split() """) result = testdir.runpytest(p1, '-d') result.stdout.fnmatch_lines([ @@ -320,7 +320,7 @@ os.kill(os.getpid(), 15) """ ) - result = testdir.runpytest(p1, '-d', '--gateways=popen,popen,popen') + result = testdir.runpytest(p1, '-d', '-n 3') result.stdout.fnmatch_lines([ "*popen*Python*", "*popen*Python*", @@ -434,7 +434,10 @@ print sys.version_info[:2] assert 0 """) - result = testdir.runpytest("--dist-each", "--gateways=popen-python2.5,popen-python2.4") + result = testdir.runpytest("--dist-each", + "--tx=popen//python2.4", + "--tx=popen//python2.5", + ) assert result.ret == 1 result.stdout.fnmatch_lines([ "*popen-python2.5*FAIL*", From hpk at codespeak.net Fri Mar 20 20:04:37 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 20 Mar 2009 20:04:37 +0100 (CET) Subject: [py-svn] r63153 - in py/trunk/py/test: . dsession dsession/testing plugin Message-ID: <20090320190437.3A5AE16847F@codespeak.net> Author: hpk Date: Fri Mar 20 20:04:36 2009 New Revision: 63153 Modified: py/trunk/py/test/dsession/dsession.py py/trunk/py/test/dsession/hostmanage.py py/trunk/py/test/dsession/masterslave.py py/trunk/py/test/dsession/testing/test_dsession.py py/trunk/py/test/dsession/testing/test_functional_dsession.py py/trunk/py/test/dsession/testing/test_hostmanage.py py/trunk/py/test/dsession/testing/test_masterslave.py py/trunk/py/test/event.py py/trunk/py/test/plugin/pytest_plugintester.py py/trunk/py/test/plugin/pytest_terminal.py py/trunk/py/test/session.py Log: refactoring almost complete, apart from testnodeready info Modified: py/trunk/py/test/dsession/dsession.py ============================================================================== --- py/trunk/py/test/dsession/dsession.py (original) +++ py/trunk/py/test/dsession/dsession.py Fri Mar 20 20:04:36 2009 @@ -25,7 +25,7 @@ self.testsfailed = False def pyevent_itemtestreport(self, event): - if event.colitem in self.dsession.item2host: + if event.colitem in self.dsession.item2node: self.dsession.removeitem(event.colitem) if event.failed: self.testsfailed = True @@ -34,14 +34,14 @@ if event.passed: self.colitems.extend(event.result) - def pyevent_testnodeready(self, event): - self.dsession.addhost(event.host) + def pyevent_testnodeready(self, node): + self.dsession.addnode(node) - def pyevent_testnodedown(self, event): - pending = self.dsession.removehost(event.host) + def pyevent_testnodedown(self, node, error=None): + pending = self.dsession.removenode(node) if pending: crashitem = pending[0] - self.dsession.handle_crashitem(crashitem, event.host) + self.dsession.handle_crashitem(crashitem, node) self.colitems.extend(pending[1:]) def pyevent_rescheduleitems(self, event): @@ -59,8 +59,8 @@ super(DSession, self).__init__(config=config) self.queue = Queue.Queue() - self.host2pending = {} - self.item2host = {} + self.node2pending = {} + self.item2node = {} if self.config.getvalue("executable") and \ not self.config.getvalue("numprocesses"): self.config.option.numprocesses = 1 @@ -84,7 +84,7 @@ if config.option.numprocesses: return try: - config.getvalue('hosts') + config.getvalue('xspecs') except KeyError: print "Please specify test environments for distribution of tests:" print "py.test --tx ssh=user at somehost --tx popen//python=python2.5" @@ -97,9 +97,9 @@ def main(self, colitems=None): colitems = self.getinitialitems(colitems) self.sessionstarts() - self.setup_hosts() + self.setup_nodes() exitstatus = self.loop(colitems) - self.teardown_hosts() + self.teardown_nodes() self.sessionfinishes() return exitstatus @@ -124,10 +124,10 @@ # termination conditions if ((loopstate.testsfailed and self.config.option.exitfirst) or - (not self.item2host and not colitems and not self.queue.qsize())): + (not self.item2node and not colitems and not self.queue.qsize())): self.triggershutdown() loopstate.shuttingdown = True - elif not self.host2pending: + elif not self.node2pending: loopstate.exitstatus = outcome.EXIT_NOHOSTS def loop_once_shutdown(self, loopstate): @@ -135,10 +135,10 @@ # events other than HostDown upstream eventname, args, kwargs = self.queue.get() if eventname == "testnodedown": - ev, = args - self.bus.notify("testnodedown", ev) - self.removehost(ev.host) - if not self.host2pending: + node, error = args[0], args[1] + self.bus.notify("testnodedown", node, error) + self.removenode(node) + if not self.node2pending: # finished if loopstate.testsfailed: loopstate.exitstatus = outcome.EXIT_TESTSFAILED @@ -170,21 +170,21 @@ return exitstatus def triggershutdown(self): - for host in self.host2pending: - host.node.shutdown() + for node in self.node2pending: + node.shutdown() - def addhost(self, host): - assert host not in self.host2pending - self.host2pending[host] = [] + def addnode(self, node): + assert node not in self.node2pending + self.node2pending[node] = [] - def removehost(self, host): + def removenode(self, node): try: - pending = self.host2pending.pop(host) + pending = self.node2pending.pop(node) except KeyError: # this happens if we didn't receive a testnodeready event yet return [] for item in pending: - del self.item2host[item] + del self.item2node[item] return pending def triggertesting(self, colitems): @@ -204,17 +204,17 @@ def senditems(self, tosend): if not tosend: return - for host, pending in self.host2pending.items(): + for node, pending in self.node2pending.items(): room = self.MAXITEMSPERHOST - len(pending) if room > 0: sending = tosend[:room] - host.node.sendlist(sending) + node.sendlist(sending) for item in sending: - #assert item not in self.item2host, ( - # "sending same item %r to multiple hosts " + #assert item not in self.item2node, ( + # "sending same item %r to multiple " # "not implemented" %(item,)) - self.item2host[item] = host - self.bus.notify("itemstart", event.ItemStart(item, host)) + self.item2node[item] = node + self.bus.notify("itemstart", event.ItemStart(item, node)) pending.extend(sending) tosend[:] = tosend[room:] # update inplace if not tosend: @@ -224,24 +224,24 @@ self.queueevent("rescheduleitems", event.RescheduleItems(tosend)) def removeitem(self, item): - if item not in self.item2host: - raise AssertionError(item, self.item2host) - host = self.item2host.pop(item) - self.host2pending[host].remove(item) + if item not in self.item2node: + raise AssertionError(item, self.item2node) + node = self.item2node.pop(item) + self.node2pending[node].remove(item) #self.config.bus.notify("removeitem", item, host.hostid) - def handle_crashitem(self, item, host): - longrepr = "!!! Host %r crashed during running of test %r" %(host, item) + def handle_crashitem(self, item, node): + longrepr = "!!! Node %r crashed during running of test %r" %(node, item) rep = event.ItemTestReport(item, when="???", excinfo=longrepr) self.bus.notify("itemtestreport", rep) - def setup_hosts(self): + def setup_nodes(self): """ setup any neccessary resources ahead of the test run. """ from py.__.test.dsession.hostmanage import HostManager self.hm = HostManager(self.config) self.hm.setup_hosts(putevent=self.queue.put) - def teardown_hosts(self): + def teardown_nodes(self): """ teardown any resources after a test run. """ self.hm.teardown_hosts() Modified: py/trunk/py/test/dsession/hostmanage.py ============================================================================== --- py/trunk/py/test/dsession/hostmanage.py (original) +++ py/trunk/py/test/dsession/hostmanage.py Fri Mar 20 20:04:36 2009 @@ -47,6 +47,7 @@ hosts = getxspecs(self.config) self.roots = getconfigroots(config) self.gwmanager = GatewayManager(hosts) + self.nodes = [] def makegateways(self): # we change to the topdir sot that @@ -116,11 +117,9 @@ """).waitclose() for gateway in self.gwmanager.gateways: - host = gateway.spec - host.node = MasterNode(host, - gateway, - self.config, - putevent) + node = MasterNode(gateway, self.config, putevent) + self.nodes.append(node) def teardown_hosts(self): + # XXX teardown nodes? self.gwmanager.exit() Modified: py/trunk/py/test/dsession/masterslave.py ============================================================================== --- py/trunk/py/test/dsession/masterslave.py (original) +++ py/trunk/py/test/dsession/masterslave.py Fri Mar 20 20:04:36 2009 @@ -6,13 +6,14 @@ from py.__.test.dsession.mypickle import PickleChannel class MasterNode(object): + """ Install slave code, manage sending test tasks & receiving results """ ENDMARK = -1 - def __init__(self, host, gateway, config, putevent): - self.host = host + def __init__(self, gateway, config, putevent): self.config = config self.putevent = putevent - self.channel = install_slave(host, gateway, config) + self.gateway = gateway + self.channel = install_slave(gateway, config) self.channel.setcallback(self.callback, endmarker=self.ENDMARK) self._down = False @@ -32,23 +33,25 @@ err = self.channel._getremoteerror() if not self._down: if not err: - err = "TERMINATED" - self.notify("testnodedown", event.HostDown(self.host, err)) + err = "Not properly terminated" + self.notify("testnodedown", self, err) + self._down = True return - elif eventcall is None: + eventname, args, kwargs = eventcall + if eventname == "slaveready": + self.notify("testnodeready", self) + elif eventname == "slavefinished": self._down = True - self.notify("testnodedown", event.HostDown(self.host, None)) - return - except KeyboardInterrupt: + self.notify("testnodedown", self, None) + else: + self.notify(eventname, *args, **kwargs) + except KeyboardInterrupt: + # should not land in receiver-thread raise except: excinfo = py.code.ExceptionInfo() print "!" * 20, excinfo self.notify("internalerror", event.InternalException(excinfo)) - else: - # XXX we need to have the proper event name - eventname, args, kwargs = eventcall - self.notify(eventname, *args, **kwargs) def send(self, item): assert item is not None @@ -61,7 +64,7 @@ self.channel.send(None) # setting up slave code -def install_slave(host, gateway, config): +def install_slave(gateway, config): channel = gateway.remote_exec(source=""" from py.__.test.dsession.mypickle import PickleChannel from py.__.test.dsession.masterslave import SlaveNode @@ -71,12 +74,12 @@ """) channel = PickleChannel(channel) basetemp = None - if host.popen: + if gateway.spec.popen: popenbase = config.ensuretemp("popen") basetemp = py.path.local.make_numbered_dir(prefix="slave-", keep=0, rootdir=popenbase) basetemp = str(basetemp) - channel.send((host, config, basetemp)) + channel.send((config, basetemp)) return channel class SlaveNode(object): @@ -91,17 +94,16 @@ def run(self): channel = self.channel - host, self.config, basetemp = channel.receive() + self.config, basetemp = channel.receive() if basetemp: self.config.basetemp = py.path.local(basetemp) self.config.pytestplugins.do_configure(self.config) - self.sendevent("testnodeready", maketestnodeready(host)) + self.sendevent("slaveready") try: while 1: task = channel.receive() - self.config.bus.notify("masterslave_receivedtask", task) - if task is None: # shutdown - self.channel.send(None) + if task is None: + self.sendevent("slavefinished") break if isinstance(task, list): for item in task: @@ -119,10 +121,3 @@ testrep = runner(item) self.sendevent("itemtestreport", testrep) - -def maketestnodeready(host="INPROCESS"): - import sys - platinfo = {} - for name in 'platform', 'executable', 'version_info': - platinfo["sys."+name] = getattr(sys, name) - return event.HostUp(host, platinfo) Modified: py/trunk/py/test/dsession/testing/test_dsession.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_dsession.py (original) +++ py/trunk/py/test/dsession/testing/test_dsession.py Fri Mar 20 20:04:36 2009 @@ -1,10 +1,11 @@ from py.__.test.dsession.dsession import DSession -from py.__.test.dsession.masterslave import maketestnodeready from py.__.test.runner import basic_collect_report from py.__.test import event from py.__.test import outcome import py +XSpec = py.execnet.XSpec + def run(item): runner = item._getrunner() return runner(item) @@ -33,35 +34,33 @@ config.initsession().fixoptions() assert config.option.numprocesses == 3 - def test_add_remove_host(self, testdir): + def test_add_remove_node(self, testdir): item = testdir.getitem("def test_func(): pass") rep = run(item) session = DSession(item.config) - host = py.execnet.XSpec("popen") - host.node = MockNode() - assert not session.host2pending - session.addhost(host) - assert len(session.host2pending) == 1 + node = MockNode() + assert not session.node2pending + session.addnode(node) + assert len(session.node2pending) == 1 session.senditems([item]) - pending = session.removehost(host) + pending = session.removenode(node) assert pending == [item] - assert item not in session.item2host - l = session.removehost(host) + assert item not in session.item2node + l = session.removenode(node) assert not l def test_senditems_removeitems(self, testdir): item = testdir.getitem("def test_func(): pass") rep = run(item) session = DSession(item.config) - host = py.execnet.XSpec("popen") - host.node = MockNode() - session.addhost(host) + node = MockNode() + session.addnode(node) session.senditems([item]) - assert session.host2pending[host] == [item] - assert session.item2host[item] == host + assert session.node2pending[node] == [item] + assert session.item2node[item] == node session.removeitem(item) - assert not session.host2pending[host] - assert not session.item2host + assert not session.node2pending[node] + assert not session.item2node def test_triggertesting_collect(self, testdir): modcol = testdir.getmodulecol(""" @@ -78,19 +77,17 @@ def test_triggertesting_item(self, testdir): item = testdir.getitem("def test_func(): pass") session = DSession(item.config) - host1 = py.execnet.XSpec("popen") - host1.node = MockNode() - host2 = py.execnet.XSpec("popen") - host2.node = MockNode() - session.addhost(host1) - session.addhost(host2) + node1 = MockNode() + node2 = MockNode() + session.addnode(node1) + session.addnode(node2) session.triggertesting([item] * (session.MAXITEMSPERHOST*2 + 1)) - host1_sent = host1.node.sent[0] - host2_sent = host2.node.sent[0] - assert host1_sent == [item] * session.MAXITEMSPERHOST - assert host2_sent == [item] * session.MAXITEMSPERHOST - assert session.host2pending[host1] == host1_sent - assert session.host2pending[host2] == host2_sent + sent1 = node1.sent[0] + sent2 = node2.sent[0] + assert sent1 == [item] * session.MAXITEMSPERHOST + assert sent2 == [item] * session.MAXITEMSPERHOST + assert session.node2pending[node1] == sent1 + assert session.node2pending[node2] == sent2 name, args, kwargs = session.queue.get(block=False) assert name == "rescheduleitems" ev, = args @@ -115,37 +112,34 @@ def test_rescheduleevent(self, testdir): item = testdir.getitem("def test_func(): pass") session = DSession(item.config) - host1 = GatewaySpec("localhost") - host1.node = MockNode() - session.addhost(host1) + node = MockNode() + session.addnode(node) ev = event.RescheduleItems([item]) loopstate = session._initloopstate([]) session.queueevent("rescheduleitems", ev) session.loop_once(loopstate) # check that RescheduleEvents are not immediately - # rescheduled if there are no hosts + # rescheduled if there are no nodes assert loopstate.dowork == False session.queueevent("anonymous", event.NOP()) session.loop_once(loopstate) session.queueevent("anonymous", event.NOP()) session.loop_once(loopstate) - assert host1.node.sent == [[item]] + assert node.sent == [[item]] session.queueevent("itemtestreport", run(item)) session.loop_once(loopstate) assert loopstate.shuttingdown assert not loopstate.testsfailed - def test_no_hosts_remaining_for_tests(self, testdir): + def test_no_node_remaining_for_tests(self, testdir): item = testdir.getitem("def test_func(): pass") - # setup a session with one host + # setup a session with one node session = DSession(item.config) - host1 = GatewaySpec("localhost") - host1.node = MockNode() - session.addhost(host1) + node = MockNode() + session.addnode(node) # setup a HostDown event - ev = event.HostDown(host1, None) - session.queueevent("testnodedown", ev) + session.queueevent("testnodedown", node, None) loopstate = session._initloopstate([item]) loopstate.dowork = False @@ -162,22 +156,18 @@ """) item1, item2 = modcol.collect() - # setup a session with two hosts + # setup a session with two nodes session = DSession(item1.config) - host1 = GatewaySpec("localhost") - host1.node = MockNode() - session.addhost(host1) - host2 = GatewaySpec("localhost") - host2.node = MockNode() - session.addhost(host2) + node1, node2 = MockNode(), MockNode() + session.addnode(node1) + session.addnode(node2) - # have one test pending for a host that goes down + # have one test pending for a node that goes down session.senditems([item1, item2]) - host = session.item2host[item1] - ev = event.HostDown(host, None) - session.queueevent("testnodedown", ev) + node = session.item2node[item1] + session.queueevent("testnodedown", node, None) evrec = EventRecorder(session.bus) - print session.item2host + print session.item2node loopstate = session._initloopstate([]) session.loop_once(loopstate) @@ -186,20 +176,19 @@ assert testrep.failed assert testrep.colitem == item1 assert str(testrep.longrepr).find("crashed") != -1 - assert str(testrep.longrepr).find(host.address) != -1 + #assert str(testrep.longrepr).find(node.gateway.spec) != -1 def test_testnodeready_adds_to_available(self, testdir): item = testdir.getitem("def test_func(): pass") - # setup a session with two hosts + # setup a session with two nodes session = DSession(item.config) - host1 = GatewaySpec("localhost") - testnodeready = maketestnodeready(host1) - session.queueevent("testnodeready", testnodeready) + node1 = MockNode() + session.queueevent("testnodeready", node1) loopstate = session._initloopstate([item]) loopstate.dowork = False - assert len(session.host2pending) == 0 + assert len(session.node2pending) == 0 session.loop_once(loopstate) - assert len(session.host2pending) == 1 + assert len(session.node2pending) == 1 def test_event_propagation(self, testdir, EventRecorder): item = testdir.getitem("def test_func(): pass") @@ -212,20 +201,19 @@ def runthrough(self, item): session = DSession(item.config) - host1 = GatewaySpec("localhost") - host1.node = MockNode() - session.addhost(host1) + node = MockNode() + session.addnode(node) loopstate = session._initloopstate([item]) session.queueevent("NOP") session.loop_once(loopstate) - assert host1.node.sent == [[item]] + assert node.sent == [[item]] ev = run(item) session.queueevent("itemtestreport", ev) session.loop_once(loopstate) assert loopstate.shuttingdown - session.queueevent("testnodedown", event.HostDown(host1, None)) + session.queueevent("testnodedown", node, None) session.loop_once(loopstate) dumpqueue(session.queue) return session, loopstate.exitstatus @@ -249,12 +237,11 @@ """) modcol.config.option.exitfirst = True session = DSession(modcol.config) - host1 = GatewaySpec("localhost") - host1.node = MockNode() - session.addhost(host1) + node = MockNode() + session.addnode(node) items = basic_collect_report(modcol).result - # trigger testing - this sends tests to host1 + # trigger testing - this sends tests to the node session.triggertesting(items) # run tests ourselves and produce reports @@ -271,18 +258,17 @@ def test_shuttingdown_filters_events(self, testdir, EventRecorder): item = testdir.getitem("def test_func(): pass") session = DSession(item.config) - host = GatewaySpec("localhost") - session.addhost(host) + node = MockNode() + session.addnode(node) loopstate = session._initloopstate([]) loopstate.shuttingdown = True evrec = EventRecorder(session.bus) session.queueevent("itemtestreport", run(item)) session.loop_once(loopstate) assert not evrec.getfirstnamed("testnodedown") - ev = event.HostDown(host) - session.queueevent("testnodedown", ev) + session.queueevent("testnodedown", node, None) session.loop_once(loopstate) - assert evrec.getfirstnamed('testnodedown') == ev + assert evrec.getfirstnamed('testnodedown') == node def test_filteritems(self, testdir, EventRecorder): modcol = testdir.getmodulecol(""" @@ -317,17 +303,16 @@ item = testdir.getitem("def test_func(): pass") session = DSession(item.config) - host = GatewaySpec("localhost") - host.node = MockNode() - session.addhost(host) + node = MockNode() + session.addnode(node) session.senditems([item]) session.queueevent("itemtestreport", run(item)) loopstate = session._initloopstate([]) session.loop_once(loopstate) - assert host.node._shutdown is True + assert node._shutdown is True assert loopstate.exitstatus is None, "loop did not wait for testnodedown" assert loopstate.shuttingdown - session.queueevent("testnodedown", event.HostDown(host, None)) + session.queueevent("testnodedown", node, None) session.loop_once(loopstate) assert loopstate.exitstatus == 0 @@ -339,15 +324,13 @@ pass """) session = DSession(modcol.config) - host = GatewaySpec("localhost") - host.node = MockNode() - session.addhost(host) + node = MockNode() + session.addnode(node) colreport = basic_collect_report(modcol) item1, item2 = colreport.result session.senditems([item1]) - # host2pending will become empty when the loop sees - # the report + # node2pending will become empty when the loop sees the report session.queueevent("itemtestreport", run(item1)) # but we have a collection pending Modified: py/trunk/py/test/dsession/testing/test_functional_dsession.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_functional_dsession.py (original) +++ py/trunk/py/test/dsession/testing/test_functional_dsession.py Fri Mar 20 20:04:36 2009 @@ -52,10 +52,10 @@ assert ev.skipped ev, = eq.geteventargs("itemtestreport") assert ev.failed - # see that the host is really down - ev, = eq.geteventargs("testnodedown") - assert ev.host.popen - ev, = eq.geteventargs("testrunfinish") + # see that the node is really down + node, error = eq.geteventargs("testnodedown") + assert node.gateway.spec.popen + eq.geteventargs("testrunfinish") def test_distribution_rsyncdirs_example(self, testdir): source = testdir.mkdir("source") Modified: py/trunk/py/test/dsession/testing/test_hostmanage.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_hostmanage.py (original) +++ py/trunk/py/test/dsession/testing/test_hostmanage.py Fri Mar 20 20:04:36 2009 @@ -47,7 +47,7 @@ assert dest.join("dir1", "dir2").check() assert dest.join("dir1", "dir2", 'hello').check() - def test_hostmanager_init_rsync_roots(self, source, dest): + def test_init_rsync_roots(self, source, dest): dir2 = source.ensure("dir1", "dir2", dir=1) source.ensure("dir1", "somefile", dir=1) dir2.ensure("hello") @@ -63,7 +63,7 @@ assert not dest.join("dir1").check() assert not dest.join("bogus").check() - def test_hostmanager_rsyncignore(self, source, dest): + def test_rsyncignore(self, source, dest): dir2 = source.ensure("dir1", "dir2", dir=1) dir5 = source.ensure("dir5", "dir6", "bogus") dirf = source.ensure("dir5", "file") @@ -81,7 +81,7 @@ assert dest.join("dir5","file").check() assert not dest.join("dir6").check() - def test_hostmanage_optimise_popen(self, source, dest): + def test_optimise_popen(self, source, dest): hosts = ["popen"] * 3 source.join("conftest.py").write("rsyncdirs = ['a']") source.ensure('a', dir=1) @@ -92,7 +92,7 @@ assert gwspec._samefilesystem() assert not gwspec.chdir - def test_hostmanage_setup_hosts_DEBUG(self, source, EventRecorder): + def test_setup_hosts_DEBUG(self, source, EventRecorder): hosts = ["popen"] * 2 source.join("conftest.py").write("rsyncdirs = ['a']") source.ensure('a', dir=1) @@ -107,7 +107,7 @@ assert l hm.teardown_hosts() - def test_hostmanage_ssh_setup_hosts(self, specssh, testdir): + def test_ssh_setup_hosts(self, specssh, testdir): testdir.makepyfile(__init__="", test_x=""" def test_one(): pass Modified: py/trunk/py/test/dsession/testing/test_masterslave.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_masterslave.py (original) +++ py/trunk/py/test/dsession/testing/test_masterslave.py Fri Mar 20 20:04:36 2009 @@ -42,9 +42,9 @@ config = py.test.config._reparse([]) self.config = config self.queue = py.std.Queue.Queue() - self.host = py.execnet.XSpec("popen") - self.gateway = py.execnet.makegateway(self.host) - self.node = MasterNode(self.host, self.gateway, self.config, putevent=self.queue.put) + self.xspec = py.execnet.XSpec("popen") + self.gateway = py.execnet.makegateway(self.xspec) + self.node = MasterNode(self.gateway, self.config, putevent=self.queue.put) assert not self.node.channel.isclosed() return self.node @@ -64,14 +64,20 @@ testdir.chdir() return testdir -class TestMasterSlaveConnection: +def test_node_hash_equality(mysetup): + node = mysetup.makenode() + node2 = mysetup.makenode() + assert node != node2 + assert node == node + assert not (node != node) +class TestMasterSlaveConnection: def test_crash_invalid_item(self, mysetup): node = mysetup.makenode() node.send(123) # invalid item - ev, = mysetup.geteventargs("testnodedown") - assert ev.host == mysetup.host - assert str(ev.error).find("AttributeError") != -1 + n, error = mysetup.geteventargs("testnodedown") + assert n is node + assert str(error).find("AttributeError") != -1 def test_crash_killed(self, testdir, mysetup): if not hasattr(py.std.os, 'kill'): @@ -83,16 +89,16 @@ """) node = mysetup.makenode(item.config) node.send(item) - ev, = mysetup.geteventargs("testnodedown") - assert ev.host == mysetup.host - assert str(ev.error).find("TERMINATED") != -1 + n, error = mysetup.geteventargs("testnodedown") + assert n is node + assert str(error).find("Not properly terminated") != -1 def test_node_down(self, mysetup): node = mysetup.makenode() node.shutdown() - ev, = mysetup.geteventargs("testnodedown") - assert ev.host == mysetup.host - assert not ev.error + n, error = mysetup.geteventargs("testnodedown") + assert n is node + assert not error node.callback(node.ENDMARK) excinfo = py.test.raises(IOError, "mysetup.geteventargs('testnodedown', timeout=0.01)") Modified: py/trunk/py/test/event.py ============================================================================== --- py/trunk/py/test/event.py (original) +++ py/trunk/py/test/event.py Fri Mar 20 20:04:36 2009 @@ -146,20 +146,10 @@ def __init__(self, items): self.items = items -class HostGatewayReady(BaseEvent): - def __init__(self, host, roots): - self.host = host - self.roots = roots - -class HostUp(BaseEvent): - def __init__(self, host, platinfo): - self.host = host - self.platinfo = platinfo - -class HostDown(BaseEvent): - def __init__(self, host, error=None): - self.host = host - self.error = error +#class HostGatewayReady(BaseEvent): +# def __init__(self, host, roots): +# self.host = host +# self.roots = roots # --------------------------------------------------------------------- # Events related to rsyncing Modified: py/trunk/py/test/plugin/pytest_plugintester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_plugintester.py (original) +++ py/trunk/py/test/plugin/pytest_plugintester.py Fri Mar 20 20:04:36 2009 @@ -200,11 +200,11 @@ within the gateway manager context. """ - def pyevent_testnodeready(self, event): - """ Host is up. """ + def pyevent_testnodeready(self, node): + """ Node is ready to operate. """ - def pyevent_testnodedown(self, event): - """ Host is down. """ + def pyevent_testnodedown(self, node, error): + """ Node is down. """ def pyevent_rescheduleitems(self, event): """ Items from a host that went down. """ Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Fri Mar 20 20:04:36 2009 @@ -103,19 +103,20 @@ # which garbles our output if we use self.write_line self.write_line(msg) - def pyevent_testnodeready(self, event): - d = event.platinfo.copy() - d['host'] = getattr(event.host, 'address', event.host) - d['version'] = repr_pythonversion(d['sys.version_info']) - self.write_line("HOSTUP: %(host)s %(sys.platform)s " - "%(sys.executable)s - Python %(version)s" % - d) - - def pyevent_testnodedown(self, event): - host = event.host - error = event.error + def pyevent_testnodeready(self, node): + # XXX + self.write_line("Node Ready: %r, spec %r" % (node,node.gateway.spec)) + + #d = event.platinfo.copy() + #d['host'] = getattr(event.host, 'address', event.host) + #d['version'] = repr_pythonversion(d['sys.version_info']) + #self.write_line("HOSTUP: %(host)s %(sys.platform)s " + # "%(sys.executable)s - Python %(version)s" % + # d) + + def pyevent_testnodedown(self, node, error): if error: - self.write_line("HostDown %s: %s" %(host, error)) + self.write_line("Node Down: %r: %r" %(node, error)) def pyevent_trace(self, category, msg): if self.config.option.debug or \ @@ -323,13 +324,13 @@ from py.__.test import event from py.__.test.runner import basic_run_report -from py.__.test.dsession.masterslave import maketestnodeready class TestTerminal: + @py.test.mark.xfail def test_testnodeready(self, testdir, linecomp): item = testdir.getitem("def test_func(): pass") rep = TerminalReporter(item.config, linecomp.stringio) - rep.pyevent_testnodeready(maketestnodeready()) + XXX # rep.pyevent_testnodeready(maketestnodeready()) linecomp.assert_contains_lines([ "*INPROCESS* %s %s - Python %s" %(sys.platform, sys.executable, repr_pythonversion(sys.version_info)) @@ -428,10 +429,10 @@ rep = TerminalReporter(modcol.config, file=linecomp.stringio) class gw1: id = "X1" - spec = py.execnet.GatewaySpec("popen") + spec = py.execnet.XSpec("popen") class gw2: id = "X2" - spec = py.execnet.GatewaySpec("popen") + spec = py.execnet.XSpec("popen") rep.pyevent_gwmanage_newgateway(gateway=gw1) linecomp.assert_contains_lines([ "X1 instantiated gateway from spec*", Modified: py/trunk/py/test/session.py ============================================================================== --- py/trunk/py/test/session.py (original) +++ py/trunk/py/test/session.py Fri Mar 20 20:04:36 2009 @@ -12,7 +12,6 @@ Item = py.test.collect.Item Collector = py.test.collect.Collector from runner import basic_collect_report -from py.__.test.dsession.masterslave import maketestnodeready class Session(object): """ @@ -117,7 +116,7 @@ colitems = self.getinitialitems(colitems) self.shouldstop = False self.sessionstarts() - self.bus.notify("testnodeready", maketestnodeready()) + #self.bus.notify("testnodeready", maketestnodeready()) exitstatus = outcome.EXIT_OK captured_excinfo = None try: From py-svn at codespeak.net Fri Mar 20 20:14:49 2009 From: py-svn at codespeak.net (Health News Alert) Date: Fri, 20 Mar 2009 20:14:49 +0100 (CET) Subject: [py-svn] Passing you our talk Message-ID: <20090320191449.BC431168499@codespeak.net> An HTML attachment was scrubbed... URL: From hpk at codespeak.net Fri Mar 20 20:33:06 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 20 Mar 2009 20:33:06 +0100 (CET) Subject: [py-svn] r63154 - in py/trunk/py/test/dsession: . testing Message-ID: <20090320193306.EDC70168499@codespeak.net> Author: hpk Date: Fri Mar 20 20:33:05 2009 New Revision: 63154 Added: py/trunk/py/test/dsession/nodemanage.py - copied, changed from r63153, py/trunk/py/test/dsession/hostmanage.py py/trunk/py/test/dsession/testing/test_nodemanage.py - copied, changed from r63153, py/trunk/py/test/dsession/testing/test_hostmanage.py Removed: py/trunk/py/test/dsession/hostmanage.py py/trunk/py/test/dsession/testing/test_hostmanage.py Modified: py/trunk/py/test/dsession/dsession.py py/trunk/py/test/dsession/testing/test_masterslave.py Log: use "node" instead of "host" everywhere Modified: py/trunk/py/test/dsession/dsession.py ============================================================================== --- py/trunk/py/test/dsession/dsession.py (original) +++ py/trunk/py/test/dsession/dsession.py Fri Mar 20 20:33:05 2009 @@ -9,6 +9,7 @@ from py.__.test.runner import basic_run_report, basic_collect_report from py.__.test.session import Session from py.__.test import outcome +from py.__.test.dsession.nodemanage import NodeManager import Queue @@ -97,9 +98,9 @@ def main(self, colitems=None): colitems = self.getinitialitems(colitems) self.sessionstarts() - self.setup_nodes() + self.setup() exitstatus = self.loop(colitems) - self.teardown_nodes() + self.teardown() self.sessionfinishes() return exitstatus @@ -235,15 +236,14 @@ rep = event.ItemTestReport(item, when="???", excinfo=longrepr) self.bus.notify("itemtestreport", rep) - def setup_nodes(self): + def setup(self): """ setup any neccessary resources ahead of the test run. """ - from py.__.test.dsession.hostmanage import HostManager - self.hm = HostManager(self.config) - self.hm.setup_hosts(putevent=self.queue.put) + self.nodemanager = NodeManager(self.config) + self.nodemanager.setup_nodes(putevent=self.queue.put) - def teardown_nodes(self): + def teardown(self): """ teardown any resources after a test run. """ - self.hm.teardown_hosts() + self.nodemanager.teardown_nodes() # debugging function def dump_picklestate(item): Deleted: /py/trunk/py/test/dsession/hostmanage.py ============================================================================== --- /py/trunk/py/test/dsession/hostmanage.py Fri Mar 20 20:33:05 2009 +++ (empty file) @@ -1,125 +0,0 @@ -import py -import sys, os -from py.__.test.dsession.masterslave import MasterNode -from py.__.execnet.gwmanage import GatewayManager -from py.__.test import event - -def getxspecs(config): - if config.option.numprocesses: - if config.option.executable: - s = 'popen//python=%s' % config.option.executable - else: - s = 'popen' - xspecs = [s] * config.option.numprocesses - else: - xspecs = config.option.xspecs - if not xspecs: - xspecs = config.getvalue("xspecs") - assert xspecs is not None - #print "option value for xspecs", xspecs - return [py.execnet.XSpec(x) for x in xspecs] - -def getconfigroots(config): - roots = config.option.rsyncdirs - if roots: - roots = [py.path.local(x) for x in roots.split(',')] - else: - roots = [] - conftestroots = config.getconftest_pathlist("rsyncdirs") - if conftestroots: - roots.extend(conftestroots) - pydir = py.path.local(py.__file__).dirpath() - for root in roots: - if not root.check(): - raise ValueError("rsyncdir doesn't exist: %r" %(root,)) - if pydir is not None and root.basename == "py": - if root != pydir: - raise ValueError("root %r conflicts with current %r" %(root, pydir)) - pydir = None - if pydir is not None: - roots.append(pydir) - return roots - -class HostManager(object): - def __init__(self, config, hosts=None): - self.config = config - if hosts is None: - hosts = getxspecs(self.config) - self.roots = getconfigroots(config) - self.gwmanager = GatewayManager(hosts) - self.nodes = [] - - def makegateways(self): - # we change to the topdir sot that - # PopenGateways will have their cwd - # such that unpickling configs will - # pick it up as the right topdir - # (for other gateways this chdir is irrelevant) - old = self.config.topdir.chdir() - try: - self.gwmanager.makegateways() - finally: - old.chdir() - self.trace_hoststatus() - - def trace_hoststatus(self): - if self.config.option.debug: - for ch, result in self.gwmanager.multi_exec(""" - import sys, os - channel.send((sys.executable, os.getcwd(), sys.path)) - """).receive_each(withchannel=True): - self.trace("spec %r, execuable %r, cwd %r, syspath %r" %( - ch.gateway.spec, result[0], result[1], result[2])) - - def config_getignores(self): - return self.config.getconftest_pathlist("rsyncignore") - - def rsync_roots(self): - """ make sure that all remote gateways - have the same set of roots in their - current directory. - """ - self.makegateways() - options = { - 'ignores': self.config_getignores(), - 'verbose': self.config.option.verbose, - } - if self.roots: - # send each rsync root - for root in self.roots: - self.gwmanager.rsync(root, **options) - else: - XXX # do we want to care for situations without explicit rsyncdirs? - # we transfer our topdir as the root - self.gwmanager.rsync(self.config.topdir, **options) - # and cd into it - self.gwmanager.multi_chdir(self.config.topdir.basename, inplacelocal=False) - self.config.bus.notify("rsyncfinished", event.RsyncFinished()) - - def trace(self, msg): - self.config.bus.notify("trace", "testhostmanage", msg) - - def setup_hosts(self, putevent): - self.rsync_roots() - nice = self.config.getvalue("dist_nicelevel") - if nice != 0: - self.gwmanager.multi_exec(""" - import os - if hasattr(os, 'nice'): - os.nice(%r) - """ % nice).waitclose() - - self.trace_hoststatus() - multigw = self.gwmanager.getgateways(inplacelocal=False, remote=True) - multigw.remote_exec(""" - import os, sys - sys.path.insert(0, os.getcwd()) - """).waitclose() - - for gateway in self.gwmanager.gateways: - node = MasterNode(gateway, self.config, putevent) - self.nodes.append(node) - - def teardown_hosts(self): - # XXX teardown nodes? - self.gwmanager.exit() Copied: py/trunk/py/test/dsession/nodemanage.py (from r63153, py/trunk/py/test/dsession/hostmanage.py) ============================================================================== --- py/trunk/py/test/dsession/hostmanage.py (original) +++ py/trunk/py/test/dsession/nodemanage.py Fri Mar 20 20:33:05 2009 @@ -40,13 +40,13 @@ roots.append(pydir) return roots -class HostManager(object): - def __init__(self, config, hosts=None): +class NodeManager(object): + def __init__(self, config, specs=None): self.config = config - if hosts is None: - hosts = getxspecs(self.config) + if specs is None: + specs = getxspecs(self.config) self.roots = getconfigroots(config) - self.gwmanager = GatewayManager(hosts) + self.gwmanager = GatewayManager(specs) self.nodes = [] def makegateways(self): @@ -60,9 +60,9 @@ self.gwmanager.makegateways() finally: old.chdir() - self.trace_hoststatus() + self.trace_nodestatus() - def trace_hoststatus(self): + def trace_nodestatus(self): if self.config.option.debug: for ch, result in self.gwmanager.multi_exec(""" import sys, os @@ -97,9 +97,9 @@ self.config.bus.notify("rsyncfinished", event.RsyncFinished()) def trace(self, msg): - self.config.bus.notify("trace", "testhostmanage", msg) + self.config.bus.notify("trace", "nodemanage", msg) - def setup_hosts(self, putevent): + def setup_nodes(self, putevent): self.rsync_roots() nice = self.config.getvalue("dist_nicelevel") if nice != 0: @@ -109,7 +109,7 @@ os.nice(%r) """ % nice).waitclose() - self.trace_hoststatus() + self.trace_nodestatus() multigw = self.gwmanager.getgateways(inplacelocal=False, remote=True) multigw.remote_exec(""" import os, sys @@ -120,6 +120,6 @@ node = MasterNode(gateway, self.config, putevent) self.nodes.append(node) - def teardown_hosts(self): + def teardown_nodes(self): # XXX teardown nodes? self.gwmanager.exit() Deleted: /py/trunk/py/test/dsession/testing/test_hostmanage.py ============================================================================== --- /py/trunk/py/test/dsession/testing/test_hostmanage.py Fri Mar 20 20:33:05 2009 +++ (empty file) @@ -1,154 +0,0 @@ - -""" RSync filter test -""" - -import py -from py.__.test.dsession.hostmanage import HostManager, getxspecs, getconfigroots - -from py.__.test import event - -def pytest_pyfuncarg_source(pyfuncitem): - return py.test.ensuretemp(pyfuncitem.getmodpath()).mkdir("source") -def pytest_pyfuncarg_dest(pyfuncitem): - dest = py.test.ensuretemp(pyfuncitem.getmodpath()).mkdir("dest") - return dest - -class TestHostManager: - @py.test.mark.xfail("consider / forbid implicit rsyncdirs?") - def test_hostmanager_rsync_roots_no_roots(self, source, dest): - source.ensure("dir1", "file1").write("hello") - config = py.test.config._reparse([source]) - hm = HostManager(config, hosts=["popen::%s" % dest]) - assert hm.config.topdir == source == config.topdir - hm.rsync_roots() - p, = hm.gwmanager.multi_exec("import os ; channel.send(os.getcwd())").receive_each() - p = py.path.local(p) - print "remote curdir", p - assert p == dest.join(config.topdir.basename) - assert p.join("dir1").check() - assert p.join("dir1", "file1").check() - - def test_popen_rsync_subdir(self, testdir, source, dest): - dir1 = source.mkdir("dir1") - dir2 = dir1.mkdir("dir2") - dir2.ensure("hello") - for rsyncroot in (dir1, source): - dest.remove() - hm = HostManager(testdir.parseconfig( - "--tx", "popen//chdir=%s" % dest, - "--rsyncdirs", rsyncroot, - source, - )) - assert hm.config.topdir == source - hm.rsync_roots() - if rsyncroot == source: - dest = dest.join("source") - assert dest.join("dir1").check() - assert dest.join("dir1", "dir2").check() - assert dest.join("dir1", "dir2", 'hello').check() - - def test_init_rsync_roots(self, source, dest): - dir2 = source.ensure("dir1", "dir2", dir=1) - source.ensure("dir1", "somefile", dir=1) - dir2.ensure("hello") - source.ensure("bogusdir", "file") - source.join("conftest.py").write(py.code.Source(""" - rsyncdirs = ['dir1/dir2'] - """)) - session = py.test.config._reparse([source]).initsession() - hm = HostManager(session.config, - hosts=["popen//chdir=%s" % dest]) - hm.rsync_roots() - assert dest.join("dir2").check() - assert not dest.join("dir1").check() - assert not dest.join("bogus").check() - - def test_rsyncignore(self, source, dest): - dir2 = source.ensure("dir1", "dir2", dir=1) - dir5 = source.ensure("dir5", "dir6", "bogus") - dirf = source.ensure("dir5", "file") - dir2.ensure("hello") - source.join("conftest.py").write(py.code.Source(""" - rsyncdirs = ['dir1', 'dir5'] - rsyncignore = ['dir1/dir2', 'dir5/dir6'] - """)) - session = py.test.config._reparse([source]).initsession() - hm = HostManager(session.config, - hosts=["popen//chdir=%s" % dest]) - hm.rsync_roots() - assert dest.join("dir1").check() - assert not dest.join("dir1", "dir2").check() - assert dest.join("dir5","file").check() - assert not dest.join("dir6").check() - - def test_optimise_popen(self, source, dest): - hosts = ["popen"] * 3 - source.join("conftest.py").write("rsyncdirs = ['a']") - source.ensure('a', dir=1) - config = py.test.config._reparse([source]) - hm = HostManager(config, hosts=hosts) - hm.rsync_roots() - for gwspec in hm.gwmanager.specs: - assert gwspec._samefilesystem() - assert not gwspec.chdir - - def test_setup_hosts_DEBUG(self, source, EventRecorder): - hosts = ["popen"] * 2 - source.join("conftest.py").write("rsyncdirs = ['a']") - source.ensure('a', dir=1) - config = py.test.config._reparse([source, '--debug']) - assert config.option.debug - hm = HostManager(config, hosts=hosts) - evrec = EventRecorder(config.bus, debug=True) - hm.setup_hosts(putevent=[].append) - for host in hm.gwmanager.specs: - l = evrec.getnamed("trace") - print evrec.events - assert l - hm.teardown_hosts() - - def test_ssh_setup_hosts(self, specssh, testdir): - testdir.makepyfile(__init__="", test_x=""" - def test_one(): - pass - """) - sorter = testdir.inline_run("-d", "--rsyncdirs=%s" % testdir.tmpdir, - "--tx=%s" % specssh, testdir.tmpdir) - ev = sorter.getfirstnamed("itemtestreport") - assert ev.passed - -class TestOptionsAndConfiguration: - def test_getxspecs_numprocesses(self, testdir): - config = testdir.parseconfig("-n3") - xspecs = getxspecs(config) - assert len(xspecs) == 3 - - def test_getxspecs(self, testdir): - config = testdir.parseconfig("--tx=popen", "--tx", "ssh=xyz") - xspecs = getxspecs(config) - assert len(xspecs) == 2 - print xspecs - assert xspecs[0].popen - assert xspecs[1].ssh == "xyz" - - def test_getconfigroots(self, testdir): - config = testdir.parseconfig('--rsyncdirs=' + str(testdir.tmpdir)) - roots = getconfigroots(config) - assert len(roots) == 1 + 1 - assert testdir.tmpdir in roots - - def test_getconfigroots_with_conftest(self, testdir): - testdir.chdir() - p = py.path.local() - for bn in 'x y z'.split(): - p.mkdir(bn) - testdir.makeconftest(""" - rsyncdirs= 'x', - """) - config = testdir.parseconfig(testdir.tmpdir, '--rsyncdirs=y,z') - roots = getconfigroots(config) - assert len(roots) == 3 + 1 - assert py.path.local('y') in roots - assert py.path.local('z') in roots - assert testdir.tmpdir.join('x') in roots - Modified: py/trunk/py/test/dsession/testing/test_masterslave.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_masterslave.py (original) +++ py/trunk/py/test/dsession/testing/test_masterslave.py Fri Mar 20 20:33:05 2009 @@ -49,9 +49,10 @@ return self.node def finalize(self): - if hasattr(self, 'host'): - print "exiting:", self.gateway - self.gateway.exit() + if hasattr(self, 'node'): + gw = self.node.gateway + print "exiting:", gw + gw.exit() def pytest_pyfuncarg_mysetup(pyfuncitem): mysetup = MySetup(pyfuncitem) Copied: py/trunk/py/test/dsession/testing/test_nodemanage.py (from r63153, py/trunk/py/test/dsession/testing/test_hostmanage.py) ============================================================================== --- py/trunk/py/test/dsession/testing/test_hostmanage.py (original) +++ py/trunk/py/test/dsession/testing/test_nodemanage.py Fri Mar 20 20:33:05 2009 @@ -3,7 +3,7 @@ """ import py -from py.__.test.dsession.hostmanage import HostManager, getxspecs, getconfigroots +from py.__.test.dsession.nodemanage import NodeManager, getxspecs, getconfigroots from py.__.test import event @@ -13,15 +13,15 @@ dest = py.test.ensuretemp(pyfuncitem.getmodpath()).mkdir("dest") return dest -class TestHostManager: +class TestNodeManager: @py.test.mark.xfail("consider / forbid implicit rsyncdirs?") - def test_hostmanager_rsync_roots_no_roots(self, source, dest): + def test_rsync_roots_no_roots(self, source, dest): source.ensure("dir1", "file1").write("hello") config = py.test.config._reparse([source]) - hm = HostManager(config, hosts=["popen::%s" % dest]) - assert hm.config.topdir == source == config.topdir - hm.rsync_roots() - p, = hm.gwmanager.multi_exec("import os ; channel.send(os.getcwd())").receive_each() + nodemanager = NodeManager(config, ["popen//chdir=%s" % dest]) + assert nodemanager.config.topdir == source == config.topdir + nodemanager.rsync_roots() + p, = nodemanager.gwmanager.multi_exec("import os ; channel.send(os.getcwd())").receive_each() p = py.path.local(p) print "remote curdir", p assert p == dest.join(config.topdir.basename) @@ -34,13 +34,13 @@ dir2.ensure("hello") for rsyncroot in (dir1, source): dest.remove() - hm = HostManager(testdir.parseconfig( + nodemanager = NodeManager(testdir.parseconfig( "--tx", "popen//chdir=%s" % dest, "--rsyncdirs", rsyncroot, source, )) - assert hm.config.topdir == source - hm.rsync_roots() + assert nodemanager.config.topdir == source + nodemanager.rsync_roots() if rsyncroot == source: dest = dest.join("source") assert dest.join("dir1").check() @@ -56,9 +56,8 @@ rsyncdirs = ['dir1/dir2'] """)) session = py.test.config._reparse([source]).initsession() - hm = HostManager(session.config, - hosts=["popen//chdir=%s" % dest]) - hm.rsync_roots() + nodemanager = NodeManager(session.config, ["popen//chdir=%s" % dest]) + nodemanager.rsync_roots() assert dest.join("dir2").check() assert not dest.join("dir1").check() assert not dest.join("bogus").check() @@ -73,41 +72,41 @@ rsyncignore = ['dir1/dir2', 'dir5/dir6'] """)) session = py.test.config._reparse([source]).initsession() - hm = HostManager(session.config, - hosts=["popen//chdir=%s" % dest]) - hm.rsync_roots() + nodemanager = NodeManager(session.config, + ["popen//chdir=%s" % dest]) + nodemanager.rsync_roots() assert dest.join("dir1").check() assert not dest.join("dir1", "dir2").check() assert dest.join("dir5","file").check() assert not dest.join("dir6").check() def test_optimise_popen(self, source, dest): - hosts = ["popen"] * 3 + specs = ["popen"] * 3 source.join("conftest.py").write("rsyncdirs = ['a']") source.ensure('a', dir=1) config = py.test.config._reparse([source]) - hm = HostManager(config, hosts=hosts) - hm.rsync_roots() - for gwspec in hm.gwmanager.specs: + nodemanager = NodeManager(config, specs) + nodemanager.rsync_roots() + for gwspec in nodemanager.gwmanager.specs: assert gwspec._samefilesystem() assert not gwspec.chdir - def test_setup_hosts_DEBUG(self, source, EventRecorder): - hosts = ["popen"] * 2 + def test_setup_DEBUG(self, source, EventRecorder): + specs = ["popen"] * 2 source.join("conftest.py").write("rsyncdirs = ['a']") source.ensure('a', dir=1) config = py.test.config._reparse([source, '--debug']) assert config.option.debug - hm = HostManager(config, hosts=hosts) + nodemanager = NodeManager(config, specs) evrec = EventRecorder(config.bus, debug=True) - hm.setup_hosts(putevent=[].append) - for host in hm.gwmanager.specs: + nodemanager.setup_nodes(putevent=[].append) + for spec in nodemanager.gwmanager.specs: l = evrec.getnamed("trace") print evrec.events assert l - hm.teardown_hosts() + nodemanager.teardown_nodes() - def test_ssh_setup_hosts(self, specssh, testdir): + def test_ssh_setup_nodes(self, specssh, testdir): testdir.makepyfile(__init__="", test_x=""" def test_one(): pass From hpk at codespeak.net Fri Mar 20 20:42:17 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 20 Mar 2009 20:42:17 +0100 (CET) Subject: [py-svn] r63155 - in py/trunk/py/test: . plugin Message-ID: <20090320194217.AAEAA1684BC@codespeak.net> Author: hpk Date: Fri Mar 20 20:42:17 2009 New Revision: 63155 Modified: py/trunk/py/test/defaultconftest.py py/trunk/py/test/event.py py/trunk/py/test/plugin/pytest_plugintester.py Log: comment cleanup Modified: py/trunk/py/test/defaultconftest.py ============================================================================== --- py/trunk/py/test/defaultconftest.py (original) +++ py/trunk/py/test/defaultconftest.py Fri Mar 20 20:42:17 2009 @@ -14,17 +14,11 @@ pytest_plugins = "default terminal xfail tmpdir execnetcleanup resultlog monkeypatch".split() # =================================================== -# Distributed testing specific options +# settings in conftest only (for now) - for distribution -#dist_hosts: needs to be provided by user -#dist_rsync_roots: might be provided by user, if not present or None, -# whole pkgdir will be rsynced - -dist_taskspernode = 15 dist_boxed = False if hasattr(py.std.os, 'nice'): dist_nicelevel = py.std.os.nice(0) # nice py.test works else: dist_nicelevel = 0 -dist_rsync_ignore = [] Modified: py/trunk/py/test/event.py ============================================================================== --- py/trunk/py/test/event.py (original) +++ py/trunk/py/test/event.py Fri Mar 20 20:42:17 2009 @@ -146,11 +146,6 @@ def __init__(self, items): self.items = items -#class HostGatewayReady(BaseEvent): -# def __init__(self, host, roots): -# self.host = host -# self.roots = roots - # --------------------------------------------------------------------- # Events related to rsyncing # --------------------------------------------------------------------- Modified: py/trunk/py/test/plugin/pytest_plugintester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_plugintester.py (original) +++ py/trunk/py/test/plugin/pytest_plugintester.py Fri Mar 20 20:42:17 2009 @@ -207,7 +207,7 @@ """ Node is down. """ def pyevent_rescheduleitems(self, event): - """ Items from a host that went down. """ + """ Items from a node that went down. """ def pyevent_looponfailinginfo(self, event): """ info for repeating failing tests. """ From hpk at codespeak.net Fri Mar 20 21:38:52 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 20 Mar 2009 21:38:52 +0100 (CET) Subject: [py-svn] r63157 - in py/trunk/py/test: . dsession dsession/testing plugin testing Message-ID: <20090320203852.CA10616849C@codespeak.net> Author: hpk Date: Fri Mar 20 21:38:50 2009 New Revision: 63157 Modified: py/trunk/py/test/dsession/nodemanage.py py/trunk/py/test/dsession/testing/test_functional_dsession.py py/trunk/py/test/event.py py/trunk/py/test/plugin/pytest_terminal.py py/trunk/py/test/testing/test_config.py Log: remove old events, some more renamings Modified: py/trunk/py/test/dsession/nodemanage.py ============================================================================== --- py/trunk/py/test/dsession/nodemanage.py (original) +++ py/trunk/py/test/dsession/nodemanage.py Fri Mar 20 21:38:50 2009 @@ -4,41 +4,6 @@ from py.__.execnet.gwmanage import GatewayManager from py.__.test import event -def getxspecs(config): - if config.option.numprocesses: - if config.option.executable: - s = 'popen//python=%s' % config.option.executable - else: - s = 'popen' - xspecs = [s] * config.option.numprocesses - else: - xspecs = config.option.xspecs - if not xspecs: - xspecs = config.getvalue("xspecs") - assert xspecs is not None - #print "option value for xspecs", xspecs - return [py.execnet.XSpec(x) for x in xspecs] - -def getconfigroots(config): - roots = config.option.rsyncdirs - if roots: - roots = [py.path.local(x) for x in roots.split(',')] - else: - roots = [] - conftestroots = config.getconftest_pathlist("rsyncdirs") - if conftestroots: - roots.extend(conftestroots) - pydir = py.path.local(py.__file__).dirpath() - for root in roots: - if not root.check(): - raise ValueError("rsyncdir doesn't exist: %r" %(root,)) - if pydir is not None and root.basename == "py": - if root != pydir: - raise ValueError("root %r conflicts with current %r" %(root, pydir)) - pydir = None - if pydir is not None: - roots.append(pydir) - return roots class NodeManager(object): def __init__(self, config, specs=None): @@ -94,7 +59,7 @@ self.gwmanager.rsync(self.config.topdir, **options) # and cd into it self.gwmanager.multi_chdir(self.config.topdir.basename, inplacelocal=False) - self.config.bus.notify("rsyncfinished", event.RsyncFinished()) + self.config.bus.notify("rsyncfinished") def trace(self, msg): self.config.bus.notify("trace", "nodemanage", msg) @@ -123,3 +88,39 @@ def teardown_nodes(self): # XXX teardown nodes? self.gwmanager.exit() + +def getxspecs(config): + if config.option.numprocesses: + if config.option.executable: + s = 'popen//python=%s' % config.option.executable + else: + s = 'popen' + xspecs = [s] * config.option.numprocesses + else: + xspecs = config.option.xspecs + if not xspecs: + xspecs = config.getvalue("xspecs") + assert xspecs is not None + #print "option value for xspecs", xspecs + return [py.execnet.XSpec(x) for x in xspecs] + +def getconfigroots(config): + roots = config.option.rsyncdirs + if roots: + roots = [py.path.local(x) for x in roots.split(',')] + else: + roots = [] + conftestroots = config.getconftest_pathlist("rsyncdirs") + if conftestroots: + roots.extend(conftestroots) + pydir = py.path.local(py.__file__).dirpath() + for root in roots: + if not root.check(): + raise ValueError("rsyncdir doesn't exist: %r" %(root,)) + if pydir is not None and root.basename == "py": + if root != pydir: + raise ValueError("root %r conflicts with current %r" %(root, pydir)) + pydir = None + if pydir is not None: + roots.append(pydir) + return roots Modified: py/trunk/py/test/dsession/testing/test_functional_dsession.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_functional_dsession.py (original) +++ py/trunk/py/test/dsession/testing/test_functional_dsession.py Fri Mar 20 21:38:50 2009 @@ -89,6 +89,6 @@ assert os.nice(0) == 10 """) evrec = testdir.inline_run('-d', p1, '--tx=popen') - ev = evrec.getreport('test_nice') + ev = evrec.getfirstnamed('itemtestreport') assert ev.passed Modified: py/trunk/py/test/event.py ============================================================================== --- py/trunk/py/test/event.py (original) +++ py/trunk/py/test/event.py Fri Mar 20 21:38:50 2009 @@ -157,16 +157,11 @@ self.remotepath = remotepath self.synced = synced -class RsyncFinished(BaseEvent): - def __init__(self): - self.time = timestamp() - class HostRSyncRootReady(BaseEvent): def __init__(self, host, root): self.host = host self.root = root - # make all eventclasses available on BaseEvent so that # consumers of events can easily filter by # 'isinstance(event, event.Name)' checks Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Fri Mar 20 21:38:50 2009 @@ -258,11 +258,6 @@ else: excrepr.reprcrash.toterminal(self._tw) - def out_hostinfo(self): - self._tw.line("host 0: %s %s - Python %s" % - (py.std.sys.platform, - py.std.sys.executable, - repr_pythonversion())) class CollectonlyReporter: INDENT = " " Modified: py/trunk/py/test/testing/test_config.py ============================================================================== --- py/trunk/py/test/testing/test_config.py (original) +++ py/trunk/py/test/testing/test_config.py Fri Mar 20 21:38:50 2009 @@ -240,7 +240,6 @@ class TestOptionEffects: def test_boxed_option_default(self, testdir): - testdir.makepyfile(conftest="dist_hosts=[]") tmpdir = testdir.tmpdir.ensure("subdir", dir=1) config = py.test.config._reparse([tmpdir]) config.initsession() @@ -254,10 +253,8 @@ assert not config.option.boxed def test_boxed_option_from_conftest(self, testdir): - testdir.makepyfile(conftest="dist_hosts=[]") tmpdir = testdir.tmpdir.ensure("subdir", dir=1) tmpdir.join("conftest.py").write(py.code.Source(""" - dist_hosts = [] dist_boxed = True """)) config = py.test.config._reparse(['--dist', tmpdir]) From hpk at codespeak.net Fri Mar 20 22:13:49 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 20 Mar 2009 22:13:49 +0100 (CET) Subject: [py-svn] r63158 - in py/trunk/py/test: dsession dsession/testing plugin Message-ID: <20090320211349.BFF711684B3@codespeak.net> Author: hpk Date: Fri Mar 20 22:13:47 2009 New Revision: 63158 Modified: py/trunk/py/test/dsession/dsession.py py/trunk/py/test/dsession/nodemanage.py py/trunk/py/test/dsession/testing/test_nodemanage.py py/trunk/py/test/plugin/pytest_terminal.py Log: UI improvements Modified: py/trunk/py/test/dsession/dsession.py ============================================================================== --- py/trunk/py/test/dsession/dsession.py (original) +++ py/trunk/py/test/dsession/dsession.py Fri Mar 20 22:13:47 2009 @@ -9,7 +9,7 @@ from py.__.test.runner import basic_run_report, basic_collect_report from py.__.test.session import Session from py.__.test import outcome -from py.__.test.dsession.nodemanage import NodeManager +from py.__.test.dsession.nodemanage import NodeManager, getxspecs import Queue @@ -81,12 +81,9 @@ raise ValueError, "--exec together with --pdb not supported." if option.executable and not option.dist and not option.numprocesses: option.numprocesses = 1 - config = self.config - if config.option.numprocesses: - return try: - config.getvalue('xspecs') - except KeyError: + getxspecs(self.config) + except self.config.Error: print "Please specify test environments for distribution of tests:" print "py.test --tx ssh=user at somehost --tx popen//python=python2.5" print "conftest.py: pytest_option_tx=['ssh=user at somehost','popen']" Modified: py/trunk/py/test/dsession/nodemanage.py ============================================================================== --- py/trunk/py/test/dsession/nodemanage.py (original) +++ py/trunk/py/test/dsession/nodemanage.py Fri Mar 20 22:13:47 2009 @@ -59,7 +59,6 @@ self.gwmanager.rsync(self.config.topdir, **options) # and cd into it self.gwmanager.multi_chdir(self.config.topdir.basename, inplacelocal=False) - self.config.bus.notify("rsyncfinished") def trace(self, msg): self.config.bus.notify("trace", "nodemanage", msg) @@ -100,7 +99,8 @@ xspecs = config.option.xspecs if not xspecs: xspecs = config.getvalue("xspecs") - assert xspecs is not None + if xspecs is None: + raise config.Error("MISSING test execution (tx) nodes: please specify --tx") #print "option value for xspecs", xspecs return [py.execnet.XSpec(x) for x in xspecs] Modified: py/trunk/py/test/dsession/testing/test_nodemanage.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_nodemanage.py (original) +++ py/trunk/py/test/dsession/testing/test_nodemanage.py Fri Mar 20 22:13:47 2009 @@ -46,6 +46,7 @@ assert dest.join("dir1").check() assert dest.join("dir1", "dir2").check() assert dest.join("dir1", "dir2", 'hello').check() + nodemanager.gwmanager.exit() def test_init_rsync_roots(self, source, dest): dir2 = source.ensure("dir1", "dir2", dir=1) Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Fri Mar 20 22:13:47 2009 @@ -89,7 +89,10 @@ def pyevent_gwmanage_rsyncstart(self, source, gateways): targets = ", ".join([gw.id for gw in gateways]) - self.write_line("rsyncstart: %s -> %s" %(source, targets)) + msg = "rsyncstart: %s -> %s" %(source, targets) + if not self.config.option.verbose: + msg += " # use --verbose to see rsync progress" + self.write_line(msg) def pyevent_gwmanage_rsyncfinish(self, source, gateways): targets = ", ".join([gw.id for gw in gateways]) From hpk at codespeak.net Fri Mar 20 22:17:18 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 20 Mar 2009 22:17:18 +0100 (CET) Subject: [py-svn] r63159 - py/trunk/py/test/dsession Message-ID: <20090320211718.26B821684AD@codespeak.net> Author: hpk Date: Fri Mar 20 22:17:15 2009 New Revision: 63159 Modified: py/trunk/py/test/dsession/nodemanage.py Log: re-group methods Modified: py/trunk/py/test/dsession/nodemanage.py ============================================================================== --- py/trunk/py/test/dsession/nodemanage.py (original) +++ py/trunk/py/test/dsession/nodemanage.py Fri Mar 20 22:17:15 2009 @@ -14,18 +14,8 @@ self.gwmanager = GatewayManager(specs) self.nodes = [] - def makegateways(self): - # we change to the topdir sot that - # PopenGateways will have their cwd - # such that unpickling configs will - # pick it up as the right topdir - # (for other gateways this chdir is irrelevant) - old = self.config.topdir.chdir() - try: - self.gwmanager.makegateways() - finally: - old.chdir() - self.trace_nodestatus() + def trace(self, msg): + self.config.bus.notify("trace", "nodemanage", msg) def trace_nodestatus(self): if self.config.option.debug: @@ -60,8 +50,19 @@ # and cd into it self.gwmanager.multi_chdir(self.config.topdir.basename, inplacelocal=False) - def trace(self, msg): - self.config.bus.notify("trace", "nodemanage", msg) + def makegateways(self): + # we change to the topdir sot that + # PopenGateways will have their cwd + # such that unpickling configs will + # pick it up as the right topdir + # (for other gateways this chdir is irrelevant) + old = self.config.topdir.chdir() + try: + self.gwmanager.makegateways() + finally: + old.chdir() + self.trace_nodestatus() + def setup_nodes(self, putevent): self.rsync_roots() From hpk at codespeak.net Fri Mar 20 22:19:04 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 20 Mar 2009 22:19:04 +0100 (CET) Subject: [py-svn] r63160 - in py/trunk/py/test/dsession: . testing Message-ID: <20090320211904.0AC3B1684AD@codespeak.net> Author: hpk Date: Fri Mar 20 22:19:03 2009 New Revision: 63160 Added: py/trunk/py/test/dsession/testing/test_txnode.py - copied, changed from r63154, py/trunk/py/test/dsession/testing/test_masterslave.py py/trunk/py/test/dsession/txnode.py - copied, changed from r63153, py/trunk/py/test/dsession/masterslave.py Removed: py/trunk/py/test/dsession/masterslave.py py/trunk/py/test/dsession/testing/test_masterslave.py Modified: py/trunk/py/test/dsession/nodemanage.py py/trunk/py/test/dsession/testing/test_functional_dsession.py Log: rename masterslave -> txnode Deleted: /py/trunk/py/test/dsession/masterslave.py ============================================================================== --- /py/trunk/py/test/dsession/masterslave.py Fri Mar 20 22:19:03 2009 +++ (empty file) @@ -1,123 +0,0 @@ -""" - Manage setup, running and local representation of remote nodes/processes. -""" -import py -from py.__.test import event -from py.__.test.dsession.mypickle import PickleChannel - -class MasterNode(object): - """ Install slave code, manage sending test tasks & receiving results """ - ENDMARK = -1 - - def __init__(self, gateway, config, putevent): - self.config = config - self.putevent = putevent - self.gateway = gateway - self.channel = install_slave(gateway, config) - self.channel.setcallback(self.callback, endmarker=self.ENDMARK) - self._down = False - - def notify(self, eventname, *args, **kwargs): - self.putevent((eventname, args, kwargs)) - - def callback(self, eventcall): - """ this gets called for each object we receive from - the other side and if the channel closes. - - Note that channel callbacks run in the receiver - thread of execnet gateways - we need to - avoid raising exceptions or doing heavy work. - """ - try: - if eventcall == self.ENDMARK: - err = self.channel._getremoteerror() - if not self._down: - if not err: - err = "Not properly terminated" - self.notify("testnodedown", self, err) - self._down = True - return - eventname, args, kwargs = eventcall - if eventname == "slaveready": - self.notify("testnodeready", self) - elif eventname == "slavefinished": - self._down = True - self.notify("testnodedown", self, None) - else: - self.notify(eventname, *args, **kwargs) - except KeyboardInterrupt: - # should not land in receiver-thread - raise - except: - excinfo = py.code.ExceptionInfo() - print "!" * 20, excinfo - self.notify("internalerror", event.InternalException(excinfo)) - - def send(self, item): - assert item is not None - self.channel.send(item) - - def sendlist(self, itemlist): - self.channel.send(itemlist) - - def shutdown(self): - self.channel.send(None) - -# setting up slave code -def install_slave(gateway, config): - channel = gateway.remote_exec(source=""" - from py.__.test.dsession.mypickle import PickleChannel - from py.__.test.dsession.masterslave import SlaveNode - channel = PickleChannel(channel) - slavenode = SlaveNode(channel) - slavenode.run() - """) - channel = PickleChannel(channel) - basetemp = None - if gateway.spec.popen: - popenbase = config.ensuretemp("popen") - basetemp = py.path.local.make_numbered_dir(prefix="slave-", - keep=0, rootdir=popenbase) - basetemp = str(basetemp) - channel.send((config, basetemp)) - return channel - -class SlaveNode(object): - def __init__(self, channel): - self.channel = channel - - def __repr__(self): - return "<%s channel=%s>" %(self.__class__.__name__, self.channel) - - def sendevent(self, eventname, *args, **kwargs): - self.channel.send((eventname, args, kwargs)) - - def run(self): - channel = self.channel - self.config, basetemp = channel.receive() - if basetemp: - self.config.basetemp = py.path.local(basetemp) - self.config.pytestplugins.do_configure(self.config) - self.sendevent("slaveready") - try: - while 1: - task = channel.receive() - if task is None: - self.sendevent("slavefinished") - break - if isinstance(task, list): - for item in task: - self.runtest(item) - else: - self.runtest(task) - except KeyboardInterrupt: - raise - except: - self.sendevent("internalerror", event.InternalException()) - raise - - def runtest(self, item): - runner = item._getrunner() - testrep = runner(item) - self.sendevent("itemtestreport", testrep) - Modified: py/trunk/py/test/dsession/nodemanage.py ============================================================================== --- py/trunk/py/test/dsession/nodemanage.py (original) +++ py/trunk/py/test/dsession/nodemanage.py Fri Mar 20 22:19:03 2009 @@ -1,8 +1,7 @@ import py import sys, os -from py.__.test.dsession.masterslave import MasterNode +from py.__.test.dsession.txnode import MasterNode from py.__.execnet.gwmanage import GatewayManager -from py.__.test import event class NodeManager(object): Modified: py/trunk/py/test/dsession/testing/test_functional_dsession.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_functional_dsession.py (original) +++ py/trunk/py/test/dsession/testing/test_functional_dsession.py Fri Mar 20 22:19:03 2009 @@ -1,6 +1,6 @@ import py from py.__.test.dsession.dsession import DSession -from test_masterslave import EventQueue +from test_txnode import EventQueue class TestAsyncFunctional: def test_conftest_options(self, testdir): Deleted: /py/trunk/py/test/dsession/testing/test_masterslave.py ============================================================================== --- /py/trunk/py/test/dsession/testing/test_masterslave.py Fri Mar 20 22:19:03 2009 +++ (empty file) @@ -1,145 +0,0 @@ - -import py -from py.__.test.dsession.masterslave import MasterNode - -class EventQueue: - def __init__(self, bus, queue=None): - if queue is None: - queue = py.std.Queue.Queue() - self.queue = queue - bus.register(self) - - def pyevent(self, eventname, *args, **kwargs): - self.queue.put((eventname, args, kwargs)) - - def geteventargs(self, eventname, timeout=2.0): - events = [] - while 1: - try: - eventcall = self.queue.get(timeout=timeout) - except py.std.Queue.Empty: - #print "node channel", self.node.channel - #print "remoteerror", self.node.channel._getremoteerror() - print "seen events", events - raise IOError("did not see %r events" % (eventname)) - else: - name, args, kwargs = eventcall - assert isinstance(name, str) - if name == eventname: - return args - events.append(name) - -class MySetup: - def __init__(self, pyfuncitem): - self.pyfuncitem = pyfuncitem - - def geteventargs(self, eventname, timeout=2.0): - eq = EventQueue(self.config.bus, self.queue) - return eq.geteventargs(eventname, timeout=timeout) - - def makenode(self, config=None): - if config is None: - config = py.test.config._reparse([]) - self.config = config - self.queue = py.std.Queue.Queue() - self.xspec = py.execnet.XSpec("popen") - self.gateway = py.execnet.makegateway(self.xspec) - self.node = MasterNode(self.gateway, self.config, putevent=self.queue.put) - assert not self.node.channel.isclosed() - return self.node - - def finalize(self): - if hasattr(self, 'node'): - gw = self.node.gateway - print "exiting:", gw - gw.exit() - -def pytest_pyfuncarg_mysetup(pyfuncitem): - mysetup = MySetup(pyfuncitem) - pyfuncitem.addfinalizer(mysetup.finalize) - return mysetup - -def pytest_pyfuncarg_testdir(__call__, pyfuncitem): - # decorate to make us always change to testdir - testdir = __call__.execute(firstresult=True) - testdir.chdir() - return testdir - -def test_node_hash_equality(mysetup): - node = mysetup.makenode() - node2 = mysetup.makenode() - assert node != node2 - assert node == node - assert not (node != node) - -class TestMasterSlaveConnection: - def test_crash_invalid_item(self, mysetup): - node = mysetup.makenode() - node.send(123) # invalid item - n, error = mysetup.geteventargs("testnodedown") - assert n is node - assert str(error).find("AttributeError") != -1 - - def test_crash_killed(self, testdir, mysetup): - if not hasattr(py.std.os, 'kill'): - py.test.skip("no os.kill") - item = testdir.getitem(""" - def test_func(): - import os - os.kill(os.getpid(), 15) - """) - node = mysetup.makenode(item.config) - node.send(item) - n, error = mysetup.geteventargs("testnodedown") - assert n is node - assert str(error).find("Not properly terminated") != -1 - - def test_node_down(self, mysetup): - node = mysetup.makenode() - node.shutdown() - n, error = mysetup.geteventargs("testnodedown") - assert n is node - assert not error - node.callback(node.ENDMARK) - excinfo = py.test.raises(IOError, - "mysetup.geteventargs('testnodedown', timeout=0.01)") - - def test_send_on_closed_channel(self, testdir, mysetup): - item = testdir.getitem("def test_func(): pass") - node = mysetup.makenode(item.config) - node.channel.close() - py.test.raises(IOError, "node.send(item)") - #ev = self.geteventargs(event.InternalException) - #assert ev.excinfo.errisinstance(IOError) - - def test_send_one(self, testdir, mysetup): - item = testdir.getitem("def test_func(): pass") - node = mysetup.makenode(item.config) - node.send(item) - ev, = mysetup.geteventargs("itemtestreport") - assert ev.passed - assert ev.colitem == item - #assert event.item == item - #assert event.item is not item - - def test_send_some(self, testdir, mysetup): - items = testdir.getitems(""" - def test_pass(): - pass - def test_fail(): - assert 0 - def test_skip(): - import py - py.test.skip("x") - """) - node = mysetup.makenode(items[0].config) - for item in items: - node.send(item) - for outcome in "passed failed skipped".split(): - ev, = mysetup.geteventargs("itemtestreport") - assert getattr(ev, outcome) - - node.sendlist(items) - for outcome in "passed failed skipped".split(): - ev, = mysetup.geteventargs("itemtestreport") - assert getattr(ev, outcome) Copied: py/trunk/py/test/dsession/testing/test_txnode.py (from r63154, py/trunk/py/test/dsession/testing/test_masterslave.py) ============================================================================== --- py/trunk/py/test/dsession/testing/test_masterslave.py (original) +++ py/trunk/py/test/dsession/testing/test_txnode.py Fri Mar 20 22:19:03 2009 @@ -1,6 +1,6 @@ import py -from py.__.test.dsession.masterslave import MasterNode +from py.__.test.dsession.txnode import MasterNode class EventQueue: def __init__(self, bus, queue=None): Copied: py/trunk/py/test/dsession/txnode.py (from r63153, py/trunk/py/test/dsession/masterslave.py) ============================================================================== --- py/trunk/py/test/dsession/masterslave.py (original) +++ py/trunk/py/test/dsession/txnode.py Fri Mar 20 22:19:03 2009 @@ -67,7 +67,7 @@ def install_slave(gateway, config): channel = gateway.remote_exec(source=""" from py.__.test.dsession.mypickle import PickleChannel - from py.__.test.dsession.masterslave import SlaveNode + from py.__.test.dsession.txnode import SlaveNode channel = PickleChannel(channel) slavenode = SlaveNode(channel) slavenode.run() From hpk at codespeak.net Sat Mar 21 02:31:29 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 21 Mar 2009 02:31:29 +0100 (CET) Subject: [py-svn] r63162 - in py/trunk/py: . execnet execnet/testing test test/dsession test/dsession/testing test/plugin test/testing Message-ID: <20090321013129.A31AA16849E@codespeak.net> Author: hpk Date: Sat Mar 21 02:31:27 2009 New Revision: 63162 Added: py/trunk/py/execnet/testing/conftest.py Modified: py/trunk/py/_com.py py/trunk/py/execnet/gateway.py py/trunk/py/execnet/gwmanage.py py/trunk/py/execnet/testing/test_gwmanage.py py/trunk/py/test/config.py py/trunk/py/test/dsession/dsession.py py/trunk/py/test/dsession/nodemanage.py py/trunk/py/test/dsession/testing/test_functional_dsession.py py/trunk/py/test/dsession/testing/test_nodemanage.py py/trunk/py/test/plugin/pytest_default.py py/trunk/py/test/plugin/pytest_terminal.py py/trunk/py/test/testing/acceptance_test.py py/trunk/py/test/testing/test_config.py Log: all tests pass again, output on test node setup and initialization is now much nicer. Modified: py/trunk/py/_com.py ============================================================================== --- py/trunk/py/_com.py (original) +++ py/trunk/py/_com.py Sat Mar 21 02:31:27 2009 @@ -36,22 +36,22 @@ def execute(self, firstresult=False): while self.methods: - self.currentmethod = self.methods.pop() + self.currentmethod = currentmethod = self.methods.pop() # provide call introspection if "__call__" is the first positional argument - if hasattr(self.currentmethod, 'im_self'): - varnames = self.currentmethod.im_func.func_code.co_varnames + if hasattr(currentmethod, 'im_self'): + varnames = currentmethod.im_func.func_code.co_varnames needscall = varnames[1:2] == ('__call__',) else: try: - varnames = self.currentmethod.func_code.co_varnames + varnames = currentmethod.func_code.co_varnames except AttributeError: # builtin function varnames = () needscall = varnames[:1] == ('__call__',) if needscall: - res = self.currentmethod(self, *self.args, **self.kwargs) + res = currentmethod(self, *self.args, **self.kwargs) else: - res = self.currentmethod(*self.args, **self.kwargs) + res = currentmethod(*self.args, **self.kwargs) if hasattr(self, '_ex1'): self.results = [res] break Modified: py/trunk/py/execnet/gateway.py ============================================================================== --- py/trunk/py/execnet/gateway.py (original) +++ py/trunk/py/execnet/gateway.py Sat Mar 21 02:31:27 2009 @@ -249,6 +249,7 @@ channel.send(dict( executable = sys.executable, version_info = sys.version_info, + platform = sys.platform, cwd = os.getcwd(), )) """).receive()) Modified: py/trunk/py/execnet/gwmanage.py ============================================================================== --- py/trunk/py/execnet/gwmanage.py (original) +++ py/trunk/py/execnet/gwmanage.py Sat Mar 21 02:31:27 2009 @@ -34,7 +34,7 @@ gw = py.execnet.makegateway(spec) self.gateways.append(gw) gw.id = "[%s]" % len(self.gateways) - self.notify("gwmanage_newgateway", gw) + self.notify("gwmanage_newgateway", gw, gw._rinfo()) def getgateways(self, remote=True, inplacelocal=True): if not self.gateways and self.specs: Added: py/trunk/py/execnet/testing/conftest.py ============================================================================== --- (empty file) +++ py/trunk/py/execnet/testing/conftest.py Sat Mar 21 02:31:27 2009 @@ -0,0 +1,3 @@ + +pytest_plugins = "pytest_pytester" + Modified: py/trunk/py/execnet/testing/test_gwmanage.py ============================================================================== --- py/trunk/py/execnet/testing/test_gwmanage.py (original) +++ py/trunk/py/execnet/testing/test_gwmanage.py Sat Mar 21 02:31:27 2009 @@ -8,8 +8,6 @@ import py from py.__.execnet.gwmanage import GatewayManager, HostRSync -pytest_plugins = "pytest_pytester" - class TestGatewayManagerPopen: def test_popen_no_default_chdir(self): gm = GatewayManager(["popen"]) @@ -22,20 +20,21 @@ for spec in GatewayManager(l, defaultchdir="abc").specs: assert spec.chdir == "abc" - def test_hostmanager_popen_makegateway(self, eventrecorder): + def test_popen_makegateway_events(self, eventrecorder): hm = GatewayManager(["popen"] * 2) hm.makegateways() event = eventrecorder.popevent("gwmanage_newgateway") - gw = event.args[0] + gw, platinfo = event.args[:2] assert gw.id == "[1]" + platinfo.executable = gw._rinfo().executable event = eventrecorder.popevent("gwmanage_newgateway") - gw = event.args[0] + gw, platinfo = event.args[:2] assert gw.id == "[2]" assert len(hm.gateways) == 2 hm.exit() assert not len(hm.gateways) - def test_hostmanager_popens_rsync(self, source): + def test_popens_rsync(self, source): hm = GatewayManager(["popen"] * 2) hm.makegateways() assert len(hm.gateways) == 2 @@ -47,7 +46,7 @@ hm.exit() assert not len(hm.gateways) - def test_hostmanager_rsync_popen_with_path(self, source, dest): + def test_rsync_popen_with_path(self, source, dest): hm = GatewayManager(["popen//chdir=%s" %dest] * 1) hm.makegateways() source.ensure("dir1", "dir2", "hello") Modified: py/trunk/py/test/config.py ============================================================================== --- py/trunk/py/test/config.py (original) +++ py/trunk/py/test/config.py Sat Mar 21 02:31:27 2009 @@ -250,6 +250,45 @@ else: raise ValueError("unknown io capturing: " + iocapture) + def getxspecs(self): + config = self + if config.option.numprocesses: + if config.option.executable: + s = 'popen//python=%s' % config.option.executable + else: + s = 'popen' + xspec = [s] * config.option.numprocesses + else: + xspec = config.option.xspec + if not xspec: + xspec = config.getvalue("xspec") + if xspec is None: + raise config.Error("MISSING test execution (tx) nodes: please specify --tx") + #print "option value for xspecs", xspec + return [py.execnet.XSpec(x) for x in xspec] + + def getrsyncdirs(self): + config = self + roots = config.option.rsyncdirs + if roots: + roots = [py.path.local(x) for x in roots.split(',')] + else: + roots = [] + conftestroots = config.getconftest_pathlist("rsyncdirs") + if conftestroots: + roots.extend(conftestroots) + pydir = py.path.local(py.__file__).dirpath() + for root in roots: + if not root.check(): + raise ValueError("rsyncdir doesn't exist: %r" %(root,)) + if pydir is not None and root.basename == "py": + if root != pydir: + raise ValueError("root %r conflicts with current %r" %(root, pydir)) + pydir = None + if pydir is not None: + roots.append(pydir) + return roots + # # helpers Modified: py/trunk/py/test/dsession/dsession.py ============================================================================== --- py/trunk/py/test/dsession/dsession.py (original) +++ py/trunk/py/test/dsession/dsession.py Sat Mar 21 02:31:27 2009 @@ -9,7 +9,7 @@ from py.__.test.runner import basic_run_report, basic_collect_report from py.__.test.session import Session from py.__.test import outcome -from py.__.test.dsession.nodemanage import NodeManager, getxspecs +from py.__.test.dsession.nodemanage import NodeManager import Queue @@ -82,7 +82,7 @@ if option.executable and not option.dist and not option.numprocesses: option.numprocesses = 1 try: - getxspecs(self.config) + self.config.getxspecs() except self.config.Error: print "Please specify test environments for distribution of tests:" print "py.test --tx ssh=user at somehost --tx popen//python=python2.5" Modified: py/trunk/py/test/dsession/nodemanage.py ============================================================================== --- py/trunk/py/test/dsession/nodemanage.py (original) +++ py/trunk/py/test/dsession/nodemanage.py Sat Mar 21 02:31:27 2009 @@ -8,8 +8,8 @@ def __init__(self, config, specs=None): self.config = config if specs is None: - specs = getxspecs(self.config) - self.roots = getconfigroots(config) + specs = self.config.getxspecs() + self.roots = self.config.getrsyncdirs() self.gwmanager = GatewayManager(specs) self.nodes = [] @@ -88,39 +88,3 @@ # XXX teardown nodes? self.gwmanager.exit() -def getxspecs(config): - if config.option.numprocesses: - if config.option.executable: - s = 'popen//python=%s' % config.option.executable - else: - s = 'popen' - xspecs = [s] * config.option.numprocesses - else: - xspecs = config.option.xspecs - if not xspecs: - xspecs = config.getvalue("xspecs") - if xspecs is None: - raise config.Error("MISSING test execution (tx) nodes: please specify --tx") - #print "option value for xspecs", xspecs - return [py.execnet.XSpec(x) for x in xspecs] - -def getconfigroots(config): - roots = config.option.rsyncdirs - if roots: - roots = [py.path.local(x) for x in roots.split(',')] - else: - roots = [] - conftestroots = config.getconftest_pathlist("rsyncdirs") - if conftestroots: - roots.extend(conftestroots) - pydir = py.path.local(py.__file__).dirpath() - for root in roots: - if not root.check(): - raise ValueError("rsyncdir doesn't exist: %r" %(root,)) - if pydir is not None and root.basename == "py": - if root != pydir: - raise ValueError("root %r conflicts with current %r" %(root, pydir)) - pydir = None - if pydir is not None: - roots.append(pydir) - return roots Modified: py/trunk/py/test/dsession/testing/test_functional_dsession.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_functional_dsession.py (original) +++ py/trunk/py/test/dsession/testing/test_functional_dsession.py Sat Mar 21 02:31:27 2009 @@ -68,7 +68,7 @@ "--tx=popen//chdir=%(dest)s" % locals(), p) assert result.ret == 0 result.stdout.fnmatch_lines([ - "*1* instantiated gateway *popen*", + "*1* new *popen*platform*", #"RSyncStart: [G1]", #"RSyncFinished: [G1]", "*1 passed*" Modified: py/trunk/py/test/dsession/testing/test_nodemanage.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_nodemanage.py (original) +++ py/trunk/py/test/dsession/testing/test_nodemanage.py Sat Mar 21 02:31:27 2009 @@ -3,7 +3,7 @@ """ import py -from py.__.test.dsession.nodemanage import NodeManager, getxspecs, getconfigroots +from py.__.test.dsession.nodemanage import NodeManager from py.__.test import event @@ -120,12 +120,12 @@ class TestOptionsAndConfiguration: def test_getxspecs_numprocesses(self, testdir): config = testdir.parseconfig("-n3") - xspecs = getxspecs(config) + xspecs = config.getxspecs() assert len(xspecs) == 3 def test_getxspecs(self, testdir): config = testdir.parseconfig("--tx=popen", "--tx", "ssh=xyz") - xspecs = getxspecs(config) + xspecs = config.getxspecs() assert len(xspecs) == 2 print xspecs assert xspecs[0].popen @@ -133,7 +133,7 @@ def test_getconfigroots(self, testdir): config = testdir.parseconfig('--rsyncdirs=' + str(testdir.tmpdir)) - roots = getconfigroots(config) + roots = config.getrsyncdirs() assert len(roots) == 1 + 1 assert testdir.tmpdir in roots @@ -146,7 +146,7 @@ rsyncdirs= 'x', """) config = testdir.parseconfig(testdir.tmpdir, '--rsyncdirs=y,z') - roots = getconfigroots(config) + roots = config.getrsyncdirs() assert len(roots) == 3 + 1 assert py.path.local('y') in roots assert py.path.local('z') in roots Modified: py/trunk/py/test/plugin/pytest_default.py ============================================================================== --- py/trunk/py/test/plugin/pytest_default.py (original) +++ py/trunk/py/test/plugin/pytest_default.py Sat Mar 21 02:31:27 2009 @@ -97,7 +97,7 @@ group.addoption('--rsyncdirs', dest="rsyncdirs", default=None, metavar="dir1,dir2,...", help="comma-separated list of directories to rsync. All those roots will be rsynced " "into a corresponding subdir on the remote sides. ") - group.addoption('--tx', dest="xspecs", action="append", + group._addoption('--xspec', '--tx', '-t', dest="xspec", action="append", help=("add a test environment, specified in XSpec syntax. examples: " "--tx popen//python=python2.5 --tx socket=192.168.1.102")) group._addoption('--exec', Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Sat Mar 21 02:31:27 2009 @@ -84,8 +84,22 @@ for line in str(event.repr).split("\n"): self.write_line("InternalException: " + line) - def pyevent_gwmanage_newgateway(self, gateway): - self.write_line("%s instantiated gateway from spec %r" %(gateway.id, gateway.spec._spec)) + def pyevent_gwmanage_newgateway(self, gateway, rinfo): + #self.write_line("%s instantiated gateway from spec %r" %(gateway.id, gateway.spec._spec)) + d = {} + d['version'] = repr_pythonversion(rinfo.version_info) + d['id'] = gateway.id + d['spec'] = gateway.spec._spec + d['platform'] = rinfo.platform + if self.config.option.verbose: + d['extra'] = "- " + rinfo.executable + else: + d['extra'] = "" + d['cwd'] = rinfo.cwd + self.write_line("%(id)s new %(spec)r -- platform %(platform)s, " + "Python %(version)s " + "cwd: %(cwd)s" + "%(extra)s" % d) def pyevent_gwmanage_rsyncstart(self, source, gateways): targets = ", ".join([gw.id for gw in gateways]) @@ -107,19 +121,12 @@ self.write_line(msg) def pyevent_testnodeready(self, node): - # XXX - self.write_line("Node Ready: %r, spec %r" % (node,node.gateway.spec)) + self.write_line("%s node ready to receive tests" %(node.gateway.id,)) - #d = event.platinfo.copy() - #d['host'] = getattr(event.host, 'address', event.host) - #d['version'] = repr_pythonversion(d['sys.version_info']) - #self.write_line("HOSTUP: %(host)s %(sys.platform)s " - # "%(sys.executable)s - Python %(version)s" % - # d) def pyevent_testnodedown(self, node, error): if error: - self.write_line("Node Down: %r: %r" %(node, error)) + self.write_line("%s node down, error: %s" %(node.gateway.id, error)) def pyevent_trace(self, category, msg): if self.config.option.debug or \ @@ -175,6 +182,13 @@ def pyevent_testrunstart(self, event): self.write_sep("=", "test session starts", bold=True) self._sessionstarttime = py.std.time.time() + + verinfo = ".".join(map(str, sys.version_info[:3])) + msg = "python: platform %s -- Python %s" % (sys.platform, verinfo) + if self.config.option.verbose or self.config.option.debug: + msg += " -- " + str(sys.executable) + self.write_line(msg) + rev = py.__pkg__.getrev() self.write_line("using py lib: %s " % ( py.path.local(py.__file__).dirpath(), rev)) @@ -431,9 +445,15 @@ class gw2: id = "X2" spec = py.execnet.XSpec("popen") - rep.pyevent_gwmanage_newgateway(gateway=gw1) + class rinfo: + version_info = (2, 5, 1, 'final', 0) + executable = "hello" + platform = "xyz" + cwd = "qwe" + + rep.pyevent_gwmanage_newgateway(gw1, rinfo) linecomp.assert_contains_lines([ - "X1 instantiated gateway from spec*", + "X1 new 'popen' *xyz*2.5*" ]) rep.pyevent_gwmanage_rsyncstart(source="hello", gateways=[gw1, gw2]) Modified: py/trunk/py/test/testing/acceptance_test.py ============================================================================== --- py/trunk/py/test/testing/acceptance_test.py (original) +++ py/trunk/py/test/testing/acceptance_test.py Sat Mar 21 02:31:27 2009 @@ -179,8 +179,8 @@ verinfo = ".".join(map(str, py.std.sys.version_info[:3])) extra = result.stdout.fnmatch_lines([ "*===== test session starts ====*", - "HOSTUP*INPROCESS* %s %s - Python %s*" %( - py.std.sys.platform, py.std.sys.executable, verinfo), + "python: platform %s -- Python %s*" %( + py.std.sys.platform, verinfo), # , py.std.sys.executable), "*test_header_trailer_info.py .", "=* 1 passed in *.[0-9][0-9] seconds *=", ]) @@ -265,11 +265,10 @@ py.test.skip("hello") """, ) - result = testdir.runpytest(p1, '-d', '--tx popen --tx popen') + result = testdir.runpytest(p1, '-d', '--tx=popen', '--tx=popen') result.stdout.fnmatch_lines([ - "HOSTUP: popen*Python*", - #"HOSTUP: localhost*Python*", - #"HOSTUP: localhost*Python*", + "*1*popen*Python*", + "*2*popen*Python*", "*2 failed, 1 passed, 1 skipped*", ]) assert result.ret == 1 @@ -288,13 +287,13 @@ """, ) testdir.makeconftest(""" - pytest_option_tx = 'popen popen popen'.split() + pytest_option_xspec = 'popen popen popen'.split() """) result = testdir.runpytest(p1, '-d') result.stdout.fnmatch_lines([ - "HOSTUP: popen*Python*", - #"HOSTUP: localhost*Python*", - #"HOSTUP: localhost*Python*", + "*1*popen*Python*", + "*2*popen*Python*", + "*3*popen*Python*", "*2 failed, 1 passed, 1 skipped*", ]) assert result.ret == 1 @@ -325,7 +324,7 @@ "*popen*Python*", "*popen*Python*", "*popen*Python*", - "HostDown*TERMINATED*", + "*node down*", "*3 failed, 1 passed, 1 skipped*" ]) assert result.ret == 1 Modified: py/trunk/py/test/testing/test_config.py ============================================================================== --- py/trunk/py/test/testing/test_config.py (original) +++ py/trunk/py/test/testing/test_config.py Sat Mar 21 02:31:27 2009 @@ -111,8 +111,6 @@ assert config2.basetemp != config3.basetemp class TestConfigAPI: - - def test_config_getvalue_honours_conftest(self, testdir): testdir.makepyfile(conftest="x=1") testdir.mkdir("sub").join("conftest.py").write("x=2 ; y = 3") From hpk at codespeak.net Sat Mar 21 03:04:47 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 21 Mar 2009 03:04:47 +0100 (CET) Subject: [py-svn] r63163 - in py/trunk/py: doc test test/dsession test/dsession/testing test/plugin test/testing Message-ID: <20090321020447.652FD1684C0@codespeak.net> Author: hpk Date: Sat Mar 21 03:04:44 2009 New Revision: 63163 Modified: py/trunk/py/doc/execnet.txt py/trunk/py/test/dsession/dsession.py py/trunk/py/test/dsession/testing/test_nodemanage.py py/trunk/py/test/dsession/txnode.py py/trunk/py/test/event.py py/trunk/py/test/plugin/pytest_plugintester.py py/trunk/py/test/plugin/pytest_terminal.py py/trunk/py/test/session.py py/trunk/py/test/testing/acceptance_test.py Log: fixing tests, better verbose output for dist-runs Modified: py/trunk/py/doc/execnet.txt ============================================================================== --- py/trunk/py/doc/execnet.txt (original) +++ py/trunk/py/doc/execnet.txt Sat Mar 21 03:04:44 2009 @@ -137,8 +137,7 @@ To specify Gateways with a String:: >>> import py - >>> gwspec = py.execnet.GatewaySpec("popen") - >>> gw = gwspec.makegateway() + >>> gw = py.execnet.makegateway("popen") >>> ex = gw.remote_exec("import sys ; channel.send(sys.executable)").receive() >>> assert ex == py.std.sys.executable, (ex, py.std.sys.executable) >>> Modified: py/trunk/py/test/dsession/dsession.py ============================================================================== --- py/trunk/py/test/dsession/dsession.py (original) +++ py/trunk/py/test/dsession/dsession.py Sat Mar 21 03:04:44 2009 @@ -212,7 +212,7 @@ # "sending same item %r to multiple " # "not implemented" %(item,)) self.item2node[item] = node - self.bus.notify("itemstart", event.ItemStart(item, node)) + self.bus.notify("itemstart", item, node) pending.extend(sending) tosend[:] = tosend[room:] # update inplace if not tosend: Modified: py/trunk/py/test/dsession/testing/test_nodemanage.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_nodemanage.py (original) +++ py/trunk/py/test/dsession/testing/test_nodemanage.py Sat Mar 21 03:04:44 2009 @@ -124,6 +124,7 @@ assert len(xspecs) == 3 def test_getxspecs(self, testdir): + testdir.chdir() config = testdir.parseconfig("--tx=popen", "--tx", "ssh=xyz") xspecs = config.getxspecs() assert len(xspecs) == 2 Modified: py/trunk/py/test/dsession/txnode.py ============================================================================== --- py/trunk/py/test/dsession/txnode.py (original) +++ py/trunk/py/test/dsession/txnode.py Sat Mar 21 03:04:44 2009 @@ -43,6 +43,10 @@ elif eventname == "slavefinished": self._down = True self.notify("testnodedown", self, None) + elif eventname == "itemtestreport": + rep = args[0] + rep.node = self + self.notify("itemtestreport", rep) else: self.notify(eventname, *args, **kwargs) except KeyboardInterrupt: Modified: py/trunk/py/test/event.py ============================================================================== --- py/trunk/py/test/event.py (original) +++ py/trunk/py/test/event.py Sat Mar 21 03:04:44 2009 @@ -45,12 +45,6 @@ # Events related to collecting and executing test Items # ---------------------------------------------------------------------- -class ItemStart(BaseEvent): - def __init__(self, item, host=None): - self.item = item - self.host = host - self.time = timestamp() - class Deselected(BaseEvent): def __init__(self, items): self.items = items Modified: py/trunk/py/test/plugin/pytest_plugintester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_plugintester.py (original) +++ py/trunk/py/test/plugin/pytest_plugintester.py Sat Mar 21 03:04:44 2009 @@ -173,7 +173,7 @@ def pyevent_internalerror(self, event): """ called for internal errors. """ - def pyevent_itemstart(self, event): + def pyevent_itemstart(self, item, node): """ test item gets collected. """ def pyevent_itemtestreport(self, event): Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Sat Mar 21 03:04:44 2009 @@ -133,19 +133,19 @@ self.config.option.traceconfig and category.find("config") != -1: self.write_line("[%s] %s" %(category, msg)) - def pyevent_itemstart(self, event): - if self.config.option.verbose: - info = event.item.repr_metainfo() + def pyevent_itemstart(self, item, node=None): + if self.config.option.debug: + info = item.repr_metainfo() line = info.verboseline(basedir=self.curdir) + " " extra = "" - if event.host: - extra = "-> " + str(event.host) + if node: + extra = "-> " + str(node.gateway.id) self.write_ensure_prefix(line, extra) - else: - # ensure that the path is printed before the 1st test of - # a module starts running - fspath = event.item.fspath - self.write_fspath_result(fspath, "") + #else: + # # ensure that the path is printed before the 1st test of + # # a module starts running + # fspath = item.fspath + # self.write_fspath_result(fspath, "") def pyevent_rescheduleitems(self, event): if self.config.option.debug: @@ -167,7 +167,13 @@ else: info = event.colitem.repr_metainfo() line = info.verboseline(basedir=self.curdir) + " " - self.write_ensure_prefix(line, word, **markup) + #self.write_ensure_prefix(line, word, **markup) + self.ensure_newline() + if hasattr(event, 'node'): + self._tw.write("%s " % event.node.gateway.id) + self._tw.write(word, **markup) + self._tw.write(" " + line) + self.currentfspath = -2 def pyevent_collectionreport(self, event): if not event.passed: @@ -294,8 +300,8 @@ self.outindent(event.collector) self.indent += self.INDENT - def pyevent_itemstart(self, event): - self.outindent(event.item) + def pyevent_itemstart(self, item, node=None): + self.outindent(item) def pyevent_collectionreport(self, event): if not event.passed: @@ -389,16 +395,17 @@ rep.config.bus.register(rep) rep.config.bus.notify("testrunstart", event.TestrunStart()) items = modcol.collect() + rep.config.option.debug = True # for item in items: - rep.config.bus.notify("itemstart", event.ItemStart(item)) + rep.config.bus.notify("itemstart", item, None) s = linecomp.stringio.getvalue().strip() assert s.endswith(item.name) rep.config.bus.notify("itemtestreport", basic_run_report(item)) linecomp.assert_contains_lines([ - "*test_pass_skip_fail_verbose.py:2: *test_ok*PASS", - "*test_pass_skip_fail_verbose.py:4: *test_skip*SKIP", - "*test_pass_skip_fail_verbose.py:6: *test_func*FAIL", + "*PASS*test_pass_skip_fail_verbose.py:2: *test_ok*", + "*SKIP*test_pass_skip_fail_verbose.py:4: *test_skip*", + "*FAIL*test_pass_skip_fail_verbose.py:6: *test_func*", ]) rep.config.bus.notify("testrunfinish", event.TestrunFinish()) linecomp.assert_contains_lines([ @@ -536,7 +543,8 @@ modcol.config.bus.register(rep) l = list(testdir.genitems([modcol])) assert len(l) == 1 - rep.config.bus.notify("itemstart", event.ItemStart(l[0])) + modcol.config.option.debug = True + rep.config.bus.notify("itemstart", l[0]) linecomp.assert_contains_lines([ "*test_show_path_before_running_test.py*" ]) @@ -619,7 +627,7 @@ "" ]) item = modcol.join("test_func") - rep.config.bus.notify("itemstart", event.ItemStart(item)) + rep.config.bus.notify("itemstart", item) linecomp.assert_contains_lines([ " ", ]) Modified: py/trunk/py/test/session.py ============================================================================== --- py/trunk/py/test/session.py (original) +++ py/trunk/py/test/session.py Sat Mar 21 03:04:44 2009 @@ -50,7 +50,7 @@ if isinstance(next, Item): remaining = self.filteritems([next]) if remaining: - notify("itemstart", event.ItemStart(next)) + notify("itemstart", next) yield next else: assert isinstance(next, Collector) Modified: py/trunk/py/test/testing/acceptance_test.py ============================================================================== --- py/trunk/py/test/testing/acceptance_test.py (original) +++ py/trunk/py/test/testing/acceptance_test.py Sat Mar 21 03:04:44 2009 @@ -361,10 +361,10 @@ """) result = testdir.runpytest(p1, '-v') result.stdout.fnmatch_lines([ - "*test_verbose_reporting.py:2: test_fail*FAIL", - "*test_verbose_reporting.py:4: test_pass*PASS", - "*test_verbose_reporting.py:7: TestClass.test_skip*SKIP", - "*test_verbose_reporting.py:10: test_gen*FAIL", + "*FAIL*test_verbose_reporting.py:2: test_fail*", + "*PASS*test_verbose_reporting.py:4: test_pass*", + "*SKIP*test_verbose_reporting.py:7: TestClass.test_skip*", + "*FAIL*test_verbose_reporting.py:10: test_gen*", ]) assert result.ret == 1 From hpk at codespeak.net Sat Mar 21 03:21:47 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 21 Mar 2009 03:21:47 +0100 (CET) Subject: [py-svn] r63164 - in py/trunk: . py Message-ID: <20090321022147.8F9D1168440@codespeak.net> Author: hpk Date: Sat Mar 21 03:21:45 2009 New Revision: 63164 Modified: py/trunk/MANIFEST py/trunk/py/__init__.py py/trunk/setup.py Log: rebuild setup.py Modified: py/trunk/MANIFEST ============================================================================== --- py/trunk/MANIFEST (original) +++ py/trunk/MANIFEST Sat Mar 21 03:21:45 2009 @@ -6,61 +6,7 @@ ez_setup.py py/LICENSE py/__init__.py -py/apigen/__init__.py -py/apigen/api.js -py/apigen/apigen.js -py/apigen/apigen.py -py/apigen/conftest.py -py/apigen/html.py -py/apigen/htmlgen.py -py/apigen/layout.py -py/apigen/linker.py -py/apigen/project.py -py/apigen/rest/__init__.py -py/apigen/rest/genrest.py -py/apigen/rest/htmlhandlers.py -py/apigen/rest/testing/__init__.py -py/apigen/rest/testing/somemodule.py -py/apigen/rest/testing/someothermodule.py -py/apigen/rest/testing/test_htmlhandlers.py -py/apigen/rest/testing/test_rest.py -py/apigen/source/__init__.py -py/apigen/source/browser.py -py/apigen/source/color.py -py/apigen/source/html.py -py/apigen/source/index.cgi -py/apigen/source/path.py -py/apigen/source/server.py -py/apigen/source/testing/__init__.py -py/apigen/source/testing/test_browser.py -py/apigen/source/testing/test_color.py -py/apigen/source/testing/test_html.py -py/apigen/style.css -py/apigen/testing/__init__.py -py/apigen/testing/test_apigen_example.py -py/apigen/testing/test_apigen_functional.py -py/apigen/testing/test_htmlgen.py -py/apigen/testing/test_linker.py -py/apigen/todo-apigen.txt -py/apigen/todo.txt -py/apigen/tracer/__init__.py -py/apigen/tracer/description.py -py/apigen/tracer/docstorage.py -py/apigen/tracer/magic.py -py/apigen/tracer/model.py -py/apigen/tracer/permastore.py -py/apigen/tracer/testing/__init__.py -py/apigen/tracer/testing/package/__init__.py -py/apigen/tracer/testing/package/submodule/__init__.py -py/apigen/tracer/testing/package/submodule/pak/__init__.py -py/apigen/tracer/testing/package/submodule/pak/mod.py -py/apigen/tracer/testing/package/submodule/pak/somenamespace.py -py/apigen/tracer/testing/runtest.py -py/apigen/tracer/testing/test_desc.py -py/apigen/tracer/testing/test_docgen.py -py/apigen/tracer/testing/test_model.py -py/apigen/tracer/testing/test_package.py -py/apigen/tracer/tracer.py +py/_com.py py/bin/_findpy.py py/bin/_genscripts.py py/bin/gendoc.py @@ -151,15 +97,14 @@ py/compat/textwrap.py py/conftest.py py/doc/__init__.py -py/doc/apigen.txt py/doc/apigen_refactorings.txt py/doc/bin.txt py/doc/code.txt py/doc/coding-style.txt py/doc/confrest.py -py/doc/conftest.py py/doc/contact.txt py/doc/download.txt +py/doc/draft_pyfs py/doc/example/genhtml.py py/doc/example/genhtmlcss.py py/doc/example/genxml.py @@ -179,8 +124,9 @@ py/doc/release-0.9.0.txt py/doc/release-0.9.2.txt py/doc/style.css +py/doc/test-config.txt +py/doc/test-plugins.txt py/doc/test.txt -py/doc/test_conftest.py py/doc/why_py.txt py/doc/xml.txt py/env.cmd @@ -189,8 +135,11 @@ py/execnet/__init__.py py/execnet/channel.py py/execnet/gateway.py +py/execnet/gwmanage.py +py/execnet/improve-remote-tracebacks.txt py/execnet/inputoutput.py py/execnet/message.py +py/execnet/multi.py py/execnet/register.py py/execnet/rsync.py py/execnet/rsync_remote.py @@ -202,9 +151,15 @@ py/execnet/script/socketserverservice.py py/execnet/script/xx.py py/execnet/testing/__init__.py +py/execnet/testing/conftest.py +py/execnet/testing/test_event.py py/execnet/testing/test_gateway.py +py/execnet/testing/test_gwmanage.py +py/execnet/testing/test_multi.py py/execnet/testing/test_pickle.py py/execnet/testing/test_rsync.py +py/execnet/testing/test_xspec.py +py/execnet/xspec.py py/initpkg.py py/io/__init__.py py/io/dupfile.py @@ -257,6 +212,7 @@ py/misc/testing/data/svnlookrepo.dump py/misc/testing/test_api.py py/misc/testing/test_cache.py +py/misc/testing/test_com.py py/misc/testing/test_error.py py/misc/testing/test_initpkg.py py/misc/testing/test_std.py @@ -338,15 +294,15 @@ py/test/defaultconftest.py py/test/dsession/__init__.py py/test/dsession/dsession.py -py/test/dsession/hostmanage.py -py/test/dsession/masterslave.py py/test/dsession/mypickle.py +py/test/dsession/nodemanage.py py/test/dsession/testing/__init__.py py/test/dsession/testing/test_dsession.py py/test/dsession/testing/test_functional_dsession.py -py/test/dsession/testing/test_hostmanage.py -py/test/dsession/testing/test_masterslave.py py/test/dsession/testing/test_mypickle.py +py/test/dsession/testing/test_nodemanage.py +py/test/dsession/testing/test_txnode.py +py/test/dsession/txnode.py py/test/event.py py/test/looponfail/__init__.py py/test/looponfail/remote.py @@ -355,49 +311,52 @@ py/test/looponfail/testing/test_util.py py/test/looponfail/util.py py/test/outcome.py +py/test/parseopt.py +py/test/plugin/__init__.py +py/test/plugin/conftest.py +py/test/plugin/pytest_default.py +py/test/plugin/pytest_doctest.py +py/test/plugin/pytest_eventlog.py +py/test/plugin/pytest_execnetcleanup.py +py/test/plugin/pytest_iocapture.py +py/test/plugin/pytest_monkeypatch.py +py/test/plugin/pytest_plugintester.py +py/test/plugin/pytest_pocoo.py +py/test/plugin/pytest_pytester.py +py/test/plugin/pytest_restdoc.py +py/test/plugin/pytest_resultlog.py +py/test/plugin/pytest_terminal.py +py/test/plugin/pytest_tmpdir.py +py/test/plugin/pytest_unittest.py +py/test/plugin/pytest_xfail.py py/test/pycollect.py -py/test/report/__init__.py -py/test/report/base.py -py/test/report/collectonly.py -py/test/report/rest.py -py/test/report/terminal.py -py/test/report/testing/__init__.py -py/test/report/testing/test_basereporter.py -py/test/report/testing/test_collectonly.py -py/test/report/testing/test_rest.py -py/test/report/testing/test_terminal.py -py/test/report/testing/test_web.py -py/test/report/testing/test_webjs.py -py/test/report/web.py -py/test/report/webdata/__init__.py -py/test/report/webdata/index.html -py/test/report/webdata/json.py -py/test/report/webdata/source.js -py/test/report/webjs.py -py/test/resultlog.py +py/test/pytestplugin.py py/test/runner.py py/test/session.py py/test/testing/__init__.py py/test/testing/acceptance_test.py +py/test/testing/conftest.py py/test/testing/import_test/package/__init__.py py/test/testing/import_test/package/absolute_import_shared_lib.py py/test/testing/import_test/package/module_that_imports_shared_lib.py py/test/testing/import_test/package/shared_lib.py py/test/testing/import_test/package/test_import.py -py/test/testing/suptest.py py/test/testing/test_collect.py py/test/testing/test_compat.py py/test/testing/test_config.py py/test/testing/test_conftesthandle.py py/test/testing/test_deprecated_api.py -py/test/testing/test_doctest.py -py/test/testing/test_event.py +py/test/testing/test_genitems.py py/test/testing/test_outcome.py +py/test/testing/test_parseopt.py +py/test/testing/test_pickling.py +py/test/testing/test_pycollect.py +py/test/testing/test_pytestplugin.py py/test/testing/test_recording.py -py/test/testing/test_resultlog.py py/test/testing/test_runner_functional.py py/test/testing/test_session.py py/test/testing/test_setup_nested.py +py/test/testing/test_traceback.py py/test/web/__init__.py py/test/web/exception.py py/test/web/post_multipart.py Modified: py/trunk/py/__init__.py ============================================================================== --- py/trunk/py/__init__.py (original) +++ py/trunk/py/__init__.py Sat Mar 21 03:21:45 2009 @@ -23,7 +23,7 @@ """ from initpkg import initpkg -version = "1.0.0a1" +version = "1.0.0a2" initpkg(__name__, description = "pylib and py.test: agile development and test support library", @@ -31,14 +31,14 @@ lastchangedate = '$LastChangedDate$', version = version, url = "http://pylib.org", - download_url = "http://codespeak.net/py/0.9.2/download.html", + download_url = "http://codespeak.net/py/%s/download.html" % version, license = "MIT license", platforms = ['unix', 'linux', 'osx', 'cygwin', 'win32'], author = "holger krekel, Guido Wesdorp, Carl Friedrich Bolz, Armin Rigo, Maciej Fijalkowski & others", author_email = "holger at merlinux.eu, py-dev at codespeak.net", long_description = globals()['__doc__'], classifiers = [ - "Development Status :: 3 - Alpha", + "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: POSIX", Modified: py/trunk/setup.py ============================================================================== --- py/trunk/setup.py (original) +++ py/trunk/setup.py Sat Mar 21 03:21:45 2009 @@ -1,7 +1,7 @@ """ setup file for 'py' package based on: - https://codespeak.net/svn/py/trunk, revision=58363 + https://codespeak.net/svn/py/trunk, revision=63163 autogenerated by gensetup.py """ @@ -13,13 +13,14 @@ long_description = """ -The py lib is a development support library featuring these tools and APIs: +The py lib is an extensible library for testing, distributed processing and +interacting with filesystems. - `py.test`_: cross-project testing tool with many advanced features - `py.execnet`_: ad-hoc code distribution to SSH, Socket and local sub processes -- `py.magic.greenlet`_: micro-threads on standard CPython ("stackless-light") and PyPy - `py.path`_: path abstractions over local and subversion files - `py.code`_: dynamic code compile and traceback printing support +- `py.magic.greenlet`_: micro-threads on standard CPython ("stackless-light") and PyPy The py lib and its tools should work well on Linux, Win32, OSX, Python versions 2.3-2.6. For questions please go to @@ -38,9 +39,9 @@ name='py', description='pylib and py.test: agile development and test support library', long_description = long_description, - version='1.0.0a1', + version='1.0.0a2', url='http://pylib.org', - download_url='http://codespeak.net/py/0.9.2/download.html', + download_url='http://codespeak.net/py/1.0.0a2/download.html', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel, Guido Wesdorp, Carl Friedrich Bolz, Armin Rigo, Maciej Fijalkowski & others', @@ -66,7 +67,11 @@ 'Topic :: System :: Distributed Computing', 'Topic :: Utilities', 'Programming Language :: Python'], - packages=['py', + packages=['contrib.pygreen', + 'contrib.pygreen.pipe', + 'contrib.pygreen.server', + 'contrib.pygreen.test', + 'py', 'py.builtin', 'py.builtin.testing', 'py.c-extension', @@ -105,6 +110,7 @@ 'py.test.dsession.testing', 'py.test.looponfail', 'py.test.looponfail.testing', + 'py.test.plugin', 'py.test.testing', 'py.test.testing.import_test.package', 'py.test.web', @@ -114,13 +120,9 @@ 'py.tool.testing', 'py.xmlobj', 'py.xmlobj.testing'], - package_data={'py': ['LICENSE', - 'apigen/api.js', - 'apigen/apigen.js', - 'apigen/source/index.cgi', - 'apigen/style.css', - 'apigen/todo-apigen.txt', - 'apigen/todo.txt', + package_data={'py': ['', + '', + 'LICENSE', 'bin/_findpy.py', 'bin/_genscripts.py', 'bin/gendoc.py', @@ -160,13 +162,13 @@ 'compat/LICENSE', 'compat/testing/test_doctest.txt', 'compat/testing/test_doctest2.txt', - 'doc/apigen.txt', 'doc/apigen_refactorings.txt', 'doc/bin.txt', 'doc/code.txt', 'doc/coding-style.txt', 'doc/contact.txt', 'doc/download.txt', + 'doc/draft_pyfs', 'doc/example/genhtml.py', 'doc/example/genhtmlcss.py', 'doc/example/genxml.py', @@ -186,11 +188,14 @@ 'doc/release-0.9.0.txt', 'doc/release-0.9.2.txt', 'doc/style.css', + 'doc/test-config.txt', + 'doc/test-plugins.txt', 'doc/test.txt', 'doc/why_py.txt', 'doc/xml.txt', 'env.cmd', 'execnet/NOTES', + 'execnet/improve-remote-tracebacks.txt', 'misc/testing/data/svnlookrepo.dump', 'path/gateway/TODO.txt', 'path/svn/quoting.txt', @@ -203,9 +208,7 @@ 'rest/testing/data/graphviz.txt', 'rest/testing/data/part1.txt', 'rest/testing/data/part2.txt', - 'rest/testing/data/tocdepth.rst2pdfconfig', - 'test/report/webdata/index.html', - 'test/report/webdata/source.js']}, + 'rest/testing/data/tocdepth.rst2pdfconfig']}, zip_safe=False, ) From briandorsey at codespeak.net Sat Mar 21 03:49:40 2009 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Sat, 21 Mar 2009 03:49:40 +0100 (CET) Subject: [py-svn] r63165 - py/extradoc/talk/pycon-us-2009/pytest-introduction Message-ID: <20090321024940.4B3C31684AB@codespeak.net> Author: briandorsey Date: Sat Mar 21 03:49:37 2009 New Revision: 63165 Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/TODO.txt py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt Log: various small updates, renamings and edits fleshed out slides for py.test basics section & exercises Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/TODO.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-introduction/TODO.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/TODO.txt Sat Mar 21 03:49:37 2009 @@ -1,11 +1,11 @@ -- research question - any other plaintext presentation tools? - -- experiment with funcargs -- talk with hogler about funcargs +- finish outline - determine which demos & exercises to write +- placeholder: write demos +- experiment with funcargs - read: http://us.pycon.org/2009/conference/helpforspeakers/ ================= +2009/03/10 - talk with hogler about funcargs 2009/03/03 - choose presentation tool 2009/03/03 - figure out how s5 and bruce deal with different resolutions 2009/03/03 - experiment with bruce Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt Sat Mar 21 03:49:37 2009 @@ -45,6 +45,7 @@ *why automated testing? (about 10 minutes)* - what is unit testing and how does it compare to other types of testing +- focusing mostly on unit testing (small tests) in this tutorial - why do automated testing? benefits, etc - existing python testing tools @@ -52,6 +53,25 @@ (steal some from Titus: http://ivory.idyll.org/articles/nose-intro.html) + +why test-driven development? +============================ + +- make sure code does the right thing +- make sure changes don't break expected behaviour +- incremental work style +- faster develpment +- easier maintenance + + +TDD spectrum +============ + +- Full Test Driven Development +- Stupidity Driven Testing: "I don't do test-driven development; I do stupidity-driven testing. When I do something stupid, I write a test to make sure I don't do it again." - Titus Brown +- Happy path testing - to help write better APIs then SDT. + + what you get with py.test ========================= @@ -63,31 +83,22 @@ - lots of useful information when a test fails -Why test-driven development? -============================ - -- make sure code does the right thing -- make sure changes don't break expected behaviour -- incremental work style -- faster develpment -- easier maintenance - function or class ================= :: - def test_function(): + def test_something(): assert True - class TestAutomatic(): - def test_method(): + class TestSomething(): + def test_the_thing(): assert True .. class:: handout Use individual test functions or group tests in classes. Use whichever - makes the most sense for your those tests. + makes the most sense for each test or group of tests. automatic test discovery @@ -95,11 +106,11 @@ :: - def test_function(): + def test_something(): assert True - class TestAutomatic(): - def test_method(): + class TestSomething(): + def test_the_thing(): assert True .. class:: handout @@ -114,7 +125,7 @@ :: - def test_function(): + def test_something(): assert True # assertTrue() assert 1 == 1 # assertEqual() assert 1 == 2 # assertNotEqual() @@ -137,11 +148,11 @@ :: - def test_function1(): + def test_something1(): print "Useful debugging information." assert True - def test_function2(): + def test_something2(): print "Useful debugging information." assert False @@ -157,12 +168,11 @@ Many extra benefits included if you need them, ignore if not. -- multiple python version -- distributed testing - doctests -- etc, etc - -TODO: fill in from advanced slides +- setup / teardown of test runtime environment +- testing on multiple platforms & python versions +- distributed testing - across both processes and machines +- extending with plugins nose and py.test @@ -171,8 +181,8 @@ *"nose provides an alternate test discovery and running process for unittest, one that is intended to mimic the behavior of py.test as much as is reasonably possible without resorting to too much magic."* -- from nose home page - nosetest and py.test share basic philosophy and features. -- py.test used to be more difficult to distribute and had a perception of too - much magic. +- py.test used to be more difficult to distribute and had (has?) a perception + of having too much magic. - opinion: nose is basically a subset of py.test - for basic use, can be difficult to choose - luckily you already have. :) @@ -186,7 +196,6 @@ confirm with Jason P that this is still true) - installation ============ @@ -195,9 +204,21 @@ - installation - test functions - 20 minute work time -- Basic setup and working through inevitable setup problems. Some people will finish very quickly - ask them to help others get setup. -TODO: split +.. class:: handout + + Basic setup and working through inevitable setup problems. Some people + will finish very quickly - ask them to help others get setup. + +exercise 1 (~20 min) +===================== + +- Install from: http://codespeak.net/py/dist/download.html +- Get a copy of this presentation & exercises +- run: py.test test_hello.py +- find someone else to help successfully run test_hello.py + +TODO: make sure we ask them to install the right versions of python and pylib basic usage @@ -209,12 +230,66 @@ - working with failures - debug with print - exceptions - 10 minute work time - exercises +- ------------------ - test classes - setup and teardown test state (special methods / funcargs) -- skipping tests - 10 minute work time - exercises -TODO: split + +exercise 1 +========== + +- reinterpretation of asserts +- working with failures - debug with print +- bonus: exceptions +- 10 minute work time - exercises + +exercise 1 (~10 min) +===================== + + def test_one(): + print "debug text" + assert True + def test_two(): + print "debug text" + assert 1 == 1 + def test_three(): + print "debug text" + assert 1 == 2 + def test_four(): + print "debug text" + assert False + + def test_bonus(): + py.test.raises(ValueError, int, 'foo') + +TODO: write handout/exercise + + +exercise 2 +========== + +- test classes +- setup and teardown test state (special methods / funcargs) +- 10 minute work time - exercises + + +exercise 2 (~10 min) +===================== + + class TestSomething(): + def setup_class(cls): + pass + def setup_method(self, method): + pass + + def test_one(): + assert True + def test_two(tmpdir): + assert 1 == 1 + +TODO: write handout/exercise +TODO: use some built-in funcarg for the example break @@ -243,8 +318,8 @@ Branching out (20 minutes) -- generative tests - skipping tests +- generative tests - --pdb - 10 minute work time - exercises From briandorsey at codespeak.net Sat Mar 21 04:19:06 2009 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Sat, 21 Mar 2009 04:19:06 +0100 (CET) Subject: [py-svn] r63166 - py/extradoc/talk/pycon-us-2009/pytest-introduction Message-ID: <20090321031906.F141616849F@codespeak.net> Author: briandorsey Date: Sat Mar 21 04:19:04 2009 New Revision: 63166 Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt Log: edits for readability/layout in slide view Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html (original) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html Sat Mar 21 04:19:04 2009 @@ -351,46 +351,66 @@

    why automated testing? (about 10 minutes)

    • what is unit testing and how does it compare to other types of testing
    • +
    • focusing mostly on unit testing (small tests) in this tutorial
    • why do automated testing? benefits, etc
    • existing python testing tools

    TODO: split

    (steal some from Titus: http://ivory.idyll.org/articles/nose-intro.html)

    +
    +

    why test-driven development?

    +
      +
    • make sure code does the right thing
    • +
    • make sure changes don't break expected behaviour
    • +
    • incremental work style
    • +
    • faster develpment
    • +
    • easier maintenance
    • +
    +
    +
    +

    TDD spectrum

    +

    "I don't do test-driven development; I do stupidity-driven testing. When I do something stupid, I write a test to make sure I don't do it again." - Titus Brown

    +
      +
    • Full Test Driven Development
    • +
    • Stupidity Driven Testing
    • +
    • Happy path testing - to help write better APIs then SDT
    • +
    +

    what you get with py.test

    The fundamental features of py.test (about 10 minutes)

      -
    • function or class
    • -
    • automatic test discovery
    • -
    • assert introspection
    • -
    • print() debugging
    • -
    • simplicity
    • -
    • and many more...
    • +
    • a good tool for test-driven development!
    • +
    • no boilerplate: write tests rapidly
    • +
    • run all or subsets of tests
    • +
    • lots of useful information when a test fails

    function or class

    -def test_function():
    +def test_something():
         assert True
     
    -class TestAutomatic():
    -    def test_method():
    +class TestSomething():
    +    def test_the_thing():
             assert True
     

    Use individual test functions or group tests in classes. Use whichever -makes the most sense for your those tests.

    +makes the most sense for each test or group of tests.

    automatic test discovery

    -def test_function():
    +def test_something():
         assert True
     
    -class TestAutomatic():
    -    def test_method():
    +class TestSomething():
    +    def test_the_thing():
             assert True
    +
    +# search all test_* and *_test files
     

    py.test searches for all modules which start with test_ or end with _test. Within those modules, it collects all functions and methods which @@ -399,7 +419,7 @@

    assert introspection

    -def test_function():
    +def test_something():
         assert True     # assertTrue()
         assert 1 == 1   # assertEqual()
         assert 1 == 2   # assertNotEqual()
    @@ -416,11 +436,11 @@
     

    stdout is captured for each test, and only printed if the test fails. You can leave your debugging print statments in place to help your future self.

    -def test_function1():
    +def test_something1():
         print "Useful debugging information."
         assert True
     
    -def test_function2():
    +def test_something2():
         print "Useful debugging information."
         assert False
     
    @@ -433,20 +453,20 @@

    and many more

    Many extra benefits included if you need them, ignore if not.

      -
    • multiple python version
    • -
    • distributed testing
    • doctests
    • -
    • etc, etc
    • +
    • setup / teardown of test runtime environment
    • +
    • testing on multiple platforms & python versions
    • +
    • distributed testing - across both processes and machines
    • +
    • extending with plugins
    -

    TODO: fill in from advanced slides

    nose and py.test

    "nose provides an alternate test discovery and running process for unittest, one that is intended to mimic the behavior of py.test as much as is reasonably possible without resorting to too much magic." -- from nose home page

    • nosetest and py.test share basic philosophy and features.
    • -
    • py.test used to be more difficult to distribute and had a perception of too -much magic.
    • +
    • py.test used to be more difficult to distribute and had (has?) a perception +of having too much magic.
    • opinion: nose is basically a subset of py.test
    • for basic use, can be difficult to choose - luckily you already have. :)
    @@ -463,9 +483,20 @@
  • installation
  • test functions
  • 20 minute work time
  • -
  • Basic setup and working through inevitable setup problems. Some people will finish very quickly - ask them to help others get setup.
-

TODO: split

+

Basic setup and working through inevitable setup problems. Some people +will finish very quickly - ask them to help others get setup.

+
+
+

exercise 1 (~20 min)

+ +

TODO: make sure we ask them to install the right versions of python and pylib +TODO: put a copy of this presentation somewhere online and on USB

basic usage

@@ -474,13 +505,67 @@
  • reinterpretation of asserts
  • working with failures - debug with print
  • exceptions
  • -
  • 10 minute work time - exercises
  • +
  • 10 minute work time - exercise
  • +
  • test classes
  • setup and teardown test state (special methods / funcargs)
  • -
  • skipping tests
  • -
  • 10 minute work time - exercises
  • +
  • 10 minute work time - exercise
  • -

    TODO: split

    +
    +
    +

    exercise 2

    +
      +
    • reinterpretation of asserts
    • +
    • working with failures - debug with print
    • +
    • bonus: exceptions
    • +
    • 10 minute work time - exercise
    • +
    +
    +
    +

    exercise 2 (~10 min)

    +
    +def test_one():
    +    print "debug text"
    +    assert True
    +def test_two():
    +    print "debug text"
    +    assert 1 == 1
    +def test_three():
    +    print "debug text"
    +    assert 1 == 2
    +def test_four():       | def test_bonus():
    +    print "debug text" |   py.test.raises(
    +    assert False       |     ValueError, int, 'foo')
    +
    +def test_bonus():
    +    py.test.raises(ValueError, int, 'foo')
    +
    +

    TODO: write handout/exercise

    +
    +
    +

    exercise 3

    +
      +
    • test classes
    • +
    • setup and teardown test state (special methods / funcargs)
    • +
    • 10 minute work time - exercise
    • +
    +
    +
    +

    exercise 3 (~10 min)

    +
    +class TestSomething():
    +    def setup_class(cls):
    +        pass
    +    def setup_method(self, method):
    +        pass
    +
    +    def test_one():
    +        assert True
    +    def test_two(tmpdir):
    +        assert 1 == 1
    +
    +

    TODO: write handout/exercise +TODO: use some built-in funcarg for the example

    break

    @@ -504,8 +589,8 @@

    branching out

    Branching out (20 minutes)

      -
    • generative tests
    • skipping tests
    • +
    • generative tests
    • --pdb
    • 10 minute work time - exercises
    Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt Sat Mar 21 04:19:04 2009 @@ -67,9 +67,11 @@ TDD spectrum ============ +*"I don't do test-driven development; I do stupidity-driven testing. When I do something stupid, I write a test to make sure I don't do it again."* - Titus Brown + - Full Test Driven Development -- Stupidity Driven Testing: "I don't do test-driven development; I do stupidity-driven testing. When I do something stupid, I write a test to make sure I don't do it again." - Titus Brown -- Happy path testing - to help write better APIs then SDT. +- Stupidity Driven Testing +- Happy path testing - to help write better APIs then SDT what you get with py.test @@ -113,6 +115,8 @@ def test_the_thing(): assert True + # search all test_* and *_test files + .. class:: handout py.test searches for all modules which start with `test_` or end with @@ -219,6 +223,7 @@ - find someone else to help successfully run test_hello.py TODO: make sure we ask them to install the right versions of python and pylib +TODO: put a copy of this presentation somewhere online and on USB basic usage @@ -229,36 +234,37 @@ - reinterpretation of asserts - working with failures - debug with print - exceptions -- 10 minute work time - exercises -- ------------------ +- 10 minute work time - exercise +- - test classes - setup and teardown test state (special methods / funcargs) -- 10 minute work time - exercises +- 10 minute work time - exercise -exercise 1 +exercise 2 ========== - reinterpretation of asserts - working with failures - debug with print - bonus: exceptions -- 10 minute work time - exercises -exercise 1 (~10 min) +exercise 2 (~10 min) ===================== - def test_one(): - print "debug text" - assert True +:: + + def test_one(): + print "debug text" + assert True def test_two(): print "debug text" assert 1 == 1 def test_three(): print "debug text" assert 1 == 2 - def test_four(): - print "debug text" - assert False + def test_four(): | def test_bonus(): + print "debug text" | py.test.raises( + assert False | ValueError, int, 'foo') def test_bonus(): py.test.raises(ValueError, int, 'foo') @@ -266,17 +272,18 @@ TODO: write handout/exercise -exercise 2 +exercise 3 ========== - test classes - setup and teardown test state (special methods / funcargs) -- 10 minute work time - exercises -exercise 2 (~10 min) +exercise 3 (~10 min) ===================== +:: + class TestSomething(): def setup_class(cls): pass From briandorsey at codespeak.net Sat Mar 21 07:40:33 2009 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Sat, 21 Mar 2009 07:40:33 +0100 (CET) Subject: [py-svn] r63167 - py/extradoc/talk/pycon-us-2009/pytest-introduction Message-ID: <20090321064033.52978168436@codespeak.net> Author: briandorsey Date: Sat Mar 21 07:40:31 2009 New Revision: 63167 Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt Log: added TOC links fleshed out options section Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html (original) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html Sat Mar 21 07:40:31 2009 @@ -335,15 +335,15 @@

    the plan

    @@ -500,7 +500,7 @@

    basic usage

    -

    Basic usage of py.test (40 minutes)

    +

    Basic usage of py.test (40 minutes)

    • reinterpretation of asserts
    • working with failures - debug with print
    • @@ -518,7 +518,6 @@
    • reinterpretation of asserts
    • working with failures - debug with print
    • bonus: exceptions
    • -
    • 10 minute work time - exercise
    @@ -547,7 +546,6 @@
    • test classes
    • setup and teardown test state (special methods / funcargs)
    • -
    • 10 minute work time - exercise
    @@ -572,22 +570,100 @@

    options

    -

    Options (25 minutes)

    -
      -
    • --exitfirst
    • -
    • --nocapture
    • -
    • --showlocals
    • -
    • --looponfailing - run large test set until all tests pass
    • -
    • -k - to run tests matching name or keyword
    • -
    • --exec - to use different Python interpreters
    • +
      +$ py.test --help
      +usage: py.test [options] [file_or_dir] [file_or_dir] [...]
      +
      +options:
      +-h, --help            show this help message and exit
      +
      +misc:
      +    --resultlog=RESULTLOG
      +                        path for machine-readable result log
      +    --collectonly       only collect tests, don't execute them.
      +...
      +
      +
      +general:
      +    -v, --verbose       increase verbosity.
      +    -x, --exitfirst     exit instantly on first error or failed test.
      +    -s, --nocapture     disable catching of sys.stdout/stderr output.
      +    -k KEYWORD          only run test items matching the given space separated
      +                        keywords.  precede a keyword with '-' to negate.
      +                        Terminate the expression with ':' to treat a match as
      +                        a signal to run all subsequent tests.
      +    -l, --showlocals    show locals in tracebacks (disabled by default).
      +    --showskipsummary   always show summary of skipped testse work time - exercise
      +    --pdb               start pdb (the Python debugger) on errors.
      +    --tb=TBSTYLE        traceback verboseness (long/short/no).
      +    --fulltrace         don't cut any tracebacks (default is to cut).
      +    --nomagic           refrain from using magic as much as possible.
      +    --traceconfig       trace considerations of conftest.py files.
      +    -f, --looponfailing
      +                        loop on failing test set.
      +    --exec=EXECUTABLE   python executable to run the tests with.
      +    -n NUMPROCESSES, --numprocesses=NUMPROCESSES
      +                        number of local test processes.
      +    --debug             turn on debugging information.
      +
      +experimental:
      +    -d, --dist          ad-hoc distribute tests across machines (requires
      +                        conftest settings)
      +    -w, --startserver   starts local web server for displaying test progress.
      +    -r, --runbrowser    run browser (implies --startserver).
      +    --boxed             box each test run in a separate process
      +    --rest              restructured text output reporting.
      +
      +
    +
    +

    selected options

    +

    Options (25 minutes)

    +
      +
    • --nocapture - disable catching of sys.stdout/stderr output.
    • +
    • --exitfirst (*) - exit instantly on first error or failed test.
    • +
    • --showlocals (*) - show locals in tracebacks.
    • +
    • --looponfailing (*) - loop on failing test set.
    • +
    • -k (*) - only run test items matching the given keyword.
    • +
    • --exec (*) - python executable to run the tests with.
    • --tb/fulltrace - different options to control traceback generation
    • -
    • 10 minute work time - exercises
    -

    TODO: split

    +

    (*) more detail in advanced tutorial

    +
    +
    +

    demo: --nocapture

    +

    TODO: write demo script

    +
    +
    +

    demo: --exitfirst

    +

    TODO: write demo script

    +
    +
    +

    demo: --showlocals

    +

    TODO: write demo script

    +
    +
    +

    demo: -k

    +

    TODO: write demo script

    +
    +
    +

    exercise 4 (~20 min)

    +
      +
    • --nocapture - disable catching of sys.stdout/stderr output.
    • +
    • --exitfirst (*) - exit instantly on first error or failed test.
    • +
    • --showlocals (*) - show locals in tracebacks.
    • +
    • --looponfailing (*) - loop on failing test set.
    • +
    • -k (*) - only run test items matching the given keyword.
    • +
    • --exec (*) - python executable to run the tests with.
    • +
    • --tb/fulltrace - different options to control traceback generation
    • +
    +

    Experiment with a few of these options, ask questions and play.

    +

    If you're in the advanced class this afternoon. Think about other options +you'd like. Maybe we can make a plugin.

    +

    TODO: check with holger - time to make a plugin?

    branching out

    -

    Branching out (20 minutes)

    +

    Branching out (20 minutes)

    • skipping tests
    • generative tests
    • @@ -598,7 +674,7 @@

    using doctests

    -

    Using doctests (25 minutes)

    +

    Using doctests (25 minutes)

    • what are doctests
    • two usage scenarios - docstrings & stand alone files
    • @@ -609,7 +685,7 @@

    wrap up & questions

    -

    Wrapping up and questions (20 minutes)

    +

    Wrapping up and questions (20 minutes)

    • where to go from here
    • quesitons and student topics
    • Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt Sat Mar 21 07:40:31 2009 @@ -28,15 +28,15 @@ the plan ======== -- motivation -- what you get with py.test -- installation (w/exercise) -- basic usage (w/exercise) -- break -- options (w/exercise) -- branching out (w/exercise) -- using doctests (w/exercise) -- wrap up & questions +- `motivation`_ +- `what you get with py.test`_ +- `installation`_ (w/exercise) +- `basic usage`_ (w/exercise) +- `break`_ +- `options`_ (w/exercise) +- `branching out`_ (w/exercise) +- `using doctests`_ (w/exercise) +- `wrap up & questions`_ motivation @@ -229,7 +229,7 @@ basic usage =========== -Basic usage of py.test (40 minutes) +*Basic usage of py.test (40 minutes)* - reinterpretation of asserts - working with failures - debug with print @@ -306,24 +306,120 @@ options ======= -Options (25 minutes) +:: + + $ py.test --help + usage: py.test [options] [file_or_dir] [file_or_dir] [...] -- --exitfirst -- --nocapture -- --showlocals -- --looponfailing - run large test set until all tests pass -- -k - to run tests matching name or keyword -- --exec - to use different Python interpreters -- --tb/fulltrace - different options to control traceback generation -- 10 minute work time - exercises + options: + -h, --help show this help message and exit + + misc: + --resultlog=RESULTLOG + path for machine-readable result log + --collectonly only collect tests, don't execute them. + ... + + +.. class:: handout + +:: + + general: + -v, --verbose increase verbosity. + -x, --exitfirst exit instantly on first error or failed test. + -s, --nocapture disable catching of sys.stdout/stderr output. + -k KEYWORD only run test items matching the given space separated + keywords. precede a keyword with '-' to negate. + Terminate the expression with ':' to treat a match as + a signal to run all subsequent tests. + -l, --showlocals show locals in tracebacks (disabled by default). + --showskipsummary always show summary of skipped testse work time - exercise + --pdb start pdb (the Python debugger) on errors. + --tb=TBSTYLE traceback verboseness (long/short/no). + --fulltrace don't cut any tracebacks (default is to cut). + --nomagic refrain from using magic as much as possible. + --traceconfig trace considerations of conftest.py files. + -f, --looponfailing + loop on failing test set. + --exec=EXECUTABLE python executable to run the tests with. + -n NUMPROCESSES, --numprocesses=NUMPROCESSES + number of local test processes. + --debug turn on debugging information. + + experimental: + -d, --dist ad-hoc distribute tests across machines (requires + conftest settings) + -w, --startserver starts local web server for displaying test progress. + -r, --runbrowser run browser (implies --startserver). + --boxed box each test run in a separate process + --rest restructured text output reporting. + + +selected options +================ + +*Options (25 minutes)* + +- --nocapture - disable catching of sys.stdout/stderr output. +- --exitfirst (*) - exit instantly on first error or failed test. +- --showlocals (*) - show locals in tracebacks. +- --looponfailing (*) - loop on failing test set. +- -k (*) - only run test items matching the given keyword. +- --exec (*) - python executable to run the tests with. +- --tb/fulltrace - different options to control traceback generation. + +(*) more detail in advanced tutorial + +demo: --nocapture +================= + +TODO: write demo script + + +demo: --exitfirst +================= + +TODO: write demo script + + +demo: --showlocals +================== + +TODO: write demo script + + +demo: -k +======== + +TODO: write demo script + + +exercise 4 (~10 min) +===================== + +- --nocapture - disable catching of sys.stdout/stderr output. +- --exitfirst (*) - exit instantly on first error or failed test. +- --showlocals (*) - show locals in tracebacks. +- --looponfailing (*) - loop on failing test set. +- -k (*) - only run test items matching the given keyword. +- --exec (*) - python executable to run the tests with. +- --tb/fulltrace - different options to control traceback generation. + +.. class:: handout + + Experiment with a few of these options, ask questions and play. + + If you're in the advanced class this afternoon. Think about other options + you'd like. Maybe we can make a plugin. + + TODO: check with holger - time to make a plugin? -TODO: split - branching out ============= -Branching out (20 minutes) +*Branching out (20 minutes)* - skipping tests - generative tests @@ -336,7 +432,7 @@ using doctests ============== -Using doctests (25 minutes) +*Using doctests (25 minutes)* - what are doctests - two usage scenarios - docstrings & stand alone files @@ -349,7 +445,7 @@ wrap up & questions =================== -Wrapping up and questions (20 minutes) +*Wrapping up and questions (20 minutes)* - where to go from here - quesitons and student topics From briandorsey at codespeak.net Sat Mar 21 09:06:20 2009 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Sat, 21 Mar 2009 09:06:20 +0100 (CET) Subject: [py-svn] r63168 - py/extradoc/talk/pycon-us-2009/pytest-introduction Message-ID: <20090321080620.5D21C16849E@codespeak.net> Author: briandorsey Date: Sat Mar 21 09:06:18 2009 New Revision: 63168 Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt Log: added slides for the 'branching out' section Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html (original) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html Sat Mar 21 09:06:18 2009 @@ -622,10 +622,11 @@
    • --nocapture - disable catching of sys.stdout/stderr output.
    • --exitfirst (*) - exit instantly on first error or failed test.
    • --showlocals (*) - show locals in tracebacks.
    • +
    • --pdb (*) - start pdb (the Python debugger) on errors.
    • --looponfailing (*) - loop on failing test set.
    • -k (*) - only run test items matching the given keyword.
    • --exec (*) - python executable to run the tests with.
    • -
    • --tb/fulltrace - different options to control traceback generation
    • +
    • --tb/fulltrace - different options to control traceback generation.

    (*) more detail in advanced tutorial

    @@ -645,21 +646,22 @@

    demo: -k

    TODO: write demo script

    -
    -

    exercise 4 (~20 min)

    +
    +

    exercise 4 (~10 min)

    • --nocapture - disable catching of sys.stdout/stderr output.
    • --exitfirst (*) - exit instantly on first error or failed test.
    • --showlocals (*) - show locals in tracebacks.
    • +
    • --pdb (*) - start pdb (the Python debugger) on errors.
    • --looponfailing (*) - loop on failing test set.
    • -k (*) - only run test items matching the given keyword.
    • --exec (*) - python executable to run the tests with.
    • -
    • --tb/fulltrace - different options to control traceback generation
    • +
    • --tb/fulltrace - different options to control traceback generation.

    Experiment with a few of these options, ask questions and play.

    If you're in the advanced class this afternoon. Think about other options you'd like. Maybe we can make a plugin.

    -

    TODO: check with holger - time to make a plugin?

    +

    TODO: check with holger - time to make a plugin in the advanced class?

    branching out

    @@ -667,10 +669,40 @@
    • skipping tests
    • generative tests
    • -
    • --pdb
    • 10 minute work time - exercises
    -

    TODO: split

    +
    +
    +

    skipping tests

    +
    +import py
    +
    +def test_something_we_want_to_skip():
    +    some_code()
    +    py.test.skip("need to decide what it should do.")
    +    more_code()
    +    assert something == True
    +
    +
    +
    +

    generative tests

    +
    +def contains_spam(item):
    +    assert 'spam' in item.lower()
    +
    +def test_ensure_sufficient_spam():
    +    data = ['spam', 'SPAM', 'eggs & spam',
    +            'fish','spammy', 'more spam']
    +    for item in data:
    +        yeild contains_spam, item
    +
    +
    +
    +

    exercise 5 (~10 min)

    +
      +
    • skipping tests
    • +
    • generative tests
    • +

    using doctests

    Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt Sat Mar 21 09:06:18 2009 @@ -266,9 +266,6 @@ print "debug text" | py.test.raises( assert False | ValueError, int, 'foo') - def test_bonus(): - py.test.raises(ValueError, int, 'foo') - TODO: write handout/exercise @@ -364,6 +361,7 @@ - --nocapture - disable catching of sys.stdout/stderr output. - --exitfirst (*) - exit instantly on first error or failed test. - --showlocals (*) - show locals in tracebacks. +- --pdb (*) - start pdb (the Python debugger) on errors. - --looponfailing (*) - loop on failing test set. - -k (*) - only run test items matching the given keyword. - --exec (*) - python executable to run the tests with. @@ -401,6 +399,7 @@ - --nocapture - disable catching of sys.stdout/stderr output. - --exitfirst (*) - exit instantly on first error or failed test. - --showlocals (*) - show locals in tracebacks. +- --pdb (*) - start pdb (the Python debugger) on errors. - --looponfailing (*) - loop on failing test set. - -k (*) - only run test items matching the given keyword. - --exec (*) - python executable to run the tests with. @@ -413,7 +412,7 @@ If you're in the advanced class this afternoon. Think about other options you'd like. Maybe we can make a plugin. - TODO: check with holger - time to make a plugin? + TODO: check with holger - time to make a plugin in the advanced class? branching out @@ -423,10 +422,69 @@ - skipping tests - generative tests -- --pdb - 10 minute work time - exercises -TODO: split + +skipping tests +============== + +:: + + import py + + def test_something_we_want_to_skip(): + some_code() + py.test.skip("need to decide what it should do.") + more_code() + assert something == True + + +generative tests 1 +================== + +:: + + def test_ensure_sufficient_spam(): + assert 'spam' in 'spam'.lower() + assert 'spam' in 'SPAM'.lower() + assert 'spam' in 'eggs & spam'.lower() + assert 'spam' in 'fish'.lower() + assert 'spam' in 'spammy'.lower() + assert 'spam' in 'more spam'.lower() + + +generative tests 2 +================== + +:: + + def test_ensure_sufficient_spam(): + data = ['spam', 'SPAM', 'eggs & spam', + 'fish','spammy', 'more spam'] + for item in data: + assert 'spam' in item.lower() + + +generative tests 3 +================== + +:: + + def contains_spam(item): + assert 'spam' in item.lower() + + def test_ensure_sufficient_spam(): + data = ['spam', 'SPAM', 'eggs & spam', + 'fish','spammy', 'more spam'] + for item in data: + yeild contains_spam, item + + +exercise 5 (~10 min) +===================== + +- skipping tests +- generative tests using doctests From hpk at codespeak.net Sat Mar 21 12:40:31 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 21 Mar 2009 12:40:31 +0100 (CET) Subject: [py-svn] r63171 - py/trunk/contrib/pygreen/test Message-ID: <20090321114031.C5C261684C5@codespeak.net> Author: hpk Date: Sat Mar 21 12:40:31 2009 New Revision: 63171 Modified: py/trunk/contrib/pygreen/test/test_pipelayer.py Log: avoid random writes to tempdir Modified: py/trunk/contrib/pygreen/test/test_pipelayer.py ============================================================================== --- py/trunk/contrib/pygreen/test/test_pipelayer.py (original) +++ py/trunk/contrib/pygreen/test/test_pipelayer.py Sat Mar 21 12:40:31 2009 @@ -1,5 +1,6 @@ import os, random from pygreen.pipelayer import PipeLayer, pipe_over_udp, PipeOverUdp +import py def test_simple(): data1 = os.urandom(1000) @@ -169,21 +170,28 @@ import thread s1, s2 = udpsockpair() - fd1 = os.open(__file__, os.O_RDONLY) - fd2 = os.open('test_pipelayer.py~copy', os.O_WRONLY|os.O_CREAT|os.O_TRUNC) - - thread.start_new_thread(pipe_over_udp, (s1, fd1)) - pipe_over_udp(s2, recv_fd=fd2, inactivity_timeout=2.5) - os.close(fd1) - os.close(fd2) - f = open(__file__, 'rb') - data1 = f.read() - f.close() - f = open('test_pipelayer.py~copy', 'rb') - data2 = f.read() - f.close() - assert data1 == data2 - os.unlink('test_pipelayer.py~copy') + tmp = py.test.ensuretemp("pipeoverudp") + p = py.path.local(__file__) + p.copy(tmp.join(p.basename)) + old = tmp.chdir() + try: + fd1 = os.open(__file__, os.O_RDONLY) + fd2 = os.open('test_pipelayer.py~copy', os.O_WRONLY|os.O_CREAT|os.O_TRUNC) + + thread.start_new_thread(pipe_over_udp, (s1, fd1)) + pipe_over_udp(s2, recv_fd=fd2, inactivity_timeout=2.5) + os.close(fd1) + os.close(fd2) + f = open(__file__, 'rb') + data1 = f.read() + f.close() + f = open('test_pipelayer.py~copy', 'rb') + data2 = f.read() + f.close() + assert data1 == data2 + os.unlink('test_pipelayer.py~copy') + finally: + old.chdir() def test_PipeOverUdp(): s1, s2 = udpsockpair() From hpk at codespeak.net Sat Mar 21 14:09:21 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 21 Mar 2009 14:09:21 +0100 (CET) Subject: [py-svn] r63174 - py/extradoc/talk/pycon-us-2009/pytest-introduction Message-ID: <20090321130921.81CC116849E@codespeak.net> Author: hpk Date: Sat Mar 21 14:09:19 2009 New Revision: 63174 Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt Log: presenting more facts, less opinion :) Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt Sat Mar 21 14:09:19 2009 @@ -185,10 +185,10 @@ *"nose provides an alternate test discovery and running process for unittest, one that is intended to mimic the behavior of py.test as much as is reasonably possible without resorting to too much magic."* -- from nose home page - nosetest and py.test share basic philosophy and features. -- py.test used to be more difficult to distribute and had (has?) a perception - of having too much magic. -- opinion: nose is basically a subset of py.test -- for basic use, can be difficult to choose - luckily you already have. :) +- py.test has a more refined debugging/failure API +- nose has plugins - upcoming pytest-1.0 as well +- for basic use, not much difference +- more co-operation between the projects upcoming .. class:: handout From hpk at codespeak.net Sat Mar 21 14:54:41 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 21 Mar 2009 14:54:41 +0100 (CET) Subject: [py-svn] r63177 - in py/trunk/py: . test/plugin Message-ID: <20090321135441.536DE1684C0@codespeak.net> Author: hpk Date: Sat Mar 21 14:54:39 2009 New Revision: 63177 Modified: py/trunk/py/__init__.py py/trunk/py/test/plugin/pytest_default.py Log: add a "-p" option to allow to add plugins from the command line. Modified: py/trunk/py/__init__.py ============================================================================== --- py/trunk/py/__init__.py (original) +++ py/trunk/py/__init__.py Sat Mar 21 14:54:39 2009 @@ -38,7 +38,7 @@ author_email = "holger at merlinux.eu, py-dev at codespeak.net", long_description = globals()['__doc__'], classifiers = [ - "Development Status :: 4 - Beta", + "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: POSIX", Modified: py/trunk/py/test/plugin/pytest_default.py ============================================================================== --- py/trunk/py/test/plugin/pytest_default.py (original) +++ py/trunk/py/test/plugin/pytest_default.py Sat Mar 21 14:54:39 2009 @@ -33,7 +33,7 @@ return Directory(path, parent=parent) def pytest_addoption(self, parser): - group = parser.addgroup("general", "general options") + group = parser.addgroup("general", "test selection and failure debug options") group._addoption('-v', '--verbose', action="count", dest="verbose", default=0, help="increase verbosity."), group._addoption('-x', '--exitfirst', @@ -68,7 +68,11 @@ help="temporary directory for this test run.") group.addoption('--boxed', action="store_true", dest="boxed", default=False, - help="box each test run in a separate process"), + help="box each test run in a separate process") + group._addoption('--plugin', '-p', action="append", dest="plugin", default = [], + help=("load the specified plugin after command line parsing. " + "Example: '-p hello' will trigger 'import pytest_hello' " + "and instantiate 'HelloPlugin' from the module.")) group._addoption('-f', '--looponfailing', action="store_true", dest="looponfailing", default=False, help="loop on failing test set.") @@ -117,6 +121,12 @@ def pytest_configure(self, config): self.setsession(config) + self.loadplugins(config) + + def loadplugins(self, config): + for name in config.getvalue("plugin"): + print "importing", name + config.pytestplugins.import_plugin(name) def setsession(self, config): val = config.getvalue @@ -149,3 +159,19 @@ assert x('--exec=x') == 'DSession' assert x('-f', '--exec=x') == 'LooponfailingSession' assert x('--dist', '--exec=x', '--collectonly') == 'Session' + + + +def test_generic(plugintester): + plugintester.apicheck(DefaultPlugin) + +def test_plugin_specify(testdir): + testdir.chdir() + config = testdir.parseconfig("-p", "nqweotexistent") + py.test.raises(ImportError, + "config.pytestplugins.do_configure(config)" + ) + +def test_plugin_already_exists(testdir): + config = testdir.parseconfig("--plugin", "default") + assert config.option.plugin == ['default'] From hpk at codespeak.net Sat Mar 21 15:00:02 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 21 Mar 2009 15:00:02 +0100 (CET) Subject: [py-svn] r63178 - py/trunk/py/test/plugin Message-ID: <20090321140002.193C11684D2@codespeak.net> Author: hpk Date: Sat Mar 21 15:00:01 2009 New Revision: 63178 Modified: py/trunk/py/test/plugin/pytest_pocoo.py Log: remove wrong assert Modified: py/trunk/py/test/plugin/pytest_pocoo.py ============================================================================== --- py/trunk/py/test/plugin/pytest_pocoo.py (original) +++ py/trunk/py/test/plugin/pytest_pocoo.py Sat Mar 21 15:00:01 2009 @@ -17,7 +17,6 @@ help="send failures to %s" %(url.base,)) def getproxy(self): - assert 0 return py.std.xmlrpclib.ServerProxy(url.xmlrpc).pastes def pytest_terminal_summary(self, terminalreporter): From hpk at codespeak.net Sat Mar 21 15:22:27 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 21 Mar 2009 15:22:27 +0100 (CET) Subject: [py-svn] r63179 - py/extradoc/talk/pycon-us-2009/pytest-advanced Message-ID: <20090321142227.D3090168487@codespeak.net> Author: hpk Date: Sat Mar 21 15:22:25 2009 New Revision: 63179 Added: py/extradoc/talk/pycon-us-2009/pytest-advanced/rst2beamer.py (contents, props changed) Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/makepdf Log: adding a missing script Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/makepdf ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-advanced/makepdf (original) +++ py/extradoc/talk/pycon-us-2009/pytest-advanced/makepdf Sat Mar 21 15:22:25 2009 @@ -7,7 +7,7 @@ # https://sourceforge.net/tracker/?func=detail&atid=422032&aid=1459707&group_id=38414 BASE=pytest-advanced -rst2beamer.py --stylesheet=stylesheet.latex --documentoptions=14pt $BASE.txt $BASE.latex || exit +python rst2beamer.py --stylesheet=stylesheet.latex --documentoptions=14pt $BASE.txt $BASE.latex || exit sed 's/\\date{}/\\input{author.latex}/' $BASE.latex >tmpfile || exit sed 's/\\maketitle/\\input{title.latex}/' tmpfile >$BASE.latex || exit pdflatex $BASE.latex || exit Added: py/extradoc/talk/pycon-us-2009/pytest-advanced/rst2beamer.py ============================================================================== --- (empty file) +++ py/extradoc/talk/pycon-us-2009/pytest-advanced/rst2beamer.py Sat Mar 21 15:22:25 2009 @@ -0,0 +1,170 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +A docutils script converting restructured text into Beamer-flavoured LaTeX. + +Beamer is a LaTeX document class for presentations. Via this script, ReST can +be used to prepare slides. It can be called:: + + rst2beamer.py infile.txt > outfile.tex + +where ``infile.tex`` contains the produced Beamer LaTeX. + +See for more details. + +""" +# TODO: modifications for handout sections? +# TOOD: sections and subsections? +# TODO: enable beamer themes? +# TODO: convert document metadata to front page fields? +# TODO: toc-conversion? +# TODO: fix descriptions + +# Unless otherwise stated, created by P-M Agapow on 2007-08-21 +# and open for academic & non-commercial use and modification . + +__docformat__ = 'restructuredtext en' +__author__ = "Paul-Michael Agapow " +__version__ = "0.2" + + +### IMPORTS ### + +import locale +from docutils.core import publish_cmdline, default_description +from docutils.writers.latex2e import Writer as Latex2eWriter +from docutils.writers.latex2e import LaTeXTranslator, DocumentClass +from docutils import nodes + +## CONSTANTS & DEFINES: ### + +BEAMER_SPEC = ( + 'Beamer options', + 'These are derived almost entirely from the LaTeX2e options', + tuple ( + [ + ( + 'Specify theme.', + ['--theme'], + {'default': '', } + ), + ( + 'Specify document options. Multiple options can be given, ' + 'separated by commas. Default is "10pt,a4paper".', + ['--documentoptions'], + {'default': '', } + ), + ] + list (Latex2eWriter.settings_spec[2][2:]) + ), +) + +BEAMER_DEFAULTS = { + 'output_encoding': 'latin-1', + 'documentclass': 'beamer', +} + + +### IMPLEMENTATION ### + +try: + locale.setlocale (locale.LC_ALL, '') +except: + pass + +class BeamerTranslator (LaTeXTranslator): + """ + A converter for docutils elements to beamer-flavoured latex. + """ + + def __init__ (self, document): + LaTeXTranslator.__init__ (self, document) + self.head_prefix = [x for x in self.head_prefix if ('{typearea}' not in x)] + hyperref_posn = [i for i in range (len (self.head_prefix)) if ('{hyperref}' in self.head_prefix[i])] + self.head_prefix[hyperref_posn[0]] = '\\usepackage{hyperref}\n' + self.head_prefix.extend ([ + '\\definecolor{rrblitbackground}{rgb}{0.55, 0.3, 0.1}\n', + '\\newenvironment{rtbliteral}{\n', + '\\begin{ttfamily}\n', + '\\color{rrblitbackground}\n', + '}{\n', + '\\end{ttfamily}\n', + '}\n', + ]) + # this fixes the hardcoded section titles in docutils 0.4 + self.d_class = DocumentClass ('article') + + def begin_frametag (self): + return '\\begin{frame}\n' + + def end_frametag (self): + return '\\end{frame}\n' + + def visit_section (self, node): + if (self.section_level == 0): + self.body.append (self.begin_frametag()) + LaTeXTranslator.visit_section (self, node) + + def depart_section (self, node): + # Remove counter for potential subsections: + LaTeXTranslator.depart_section (self, node) + if (self.section_level == 0): + self.body.append (self.end_frametag()) + + def visit_title (self, node): + if (self.section_level == 1): + self.body.append ('\\frametitle{%s}\n\n' % self.encode(node.astext())) + raise nodes.SkipNode + else: + LaTeXTranslator.visit_title (self, node) + + def depart_title (self, node): + if (self.section_level != 1): + LaTeXTranslator.depart_title (self, node) + + def visit_literal_block(self, node): + if not self.active_table.is_open(): + self.body.append('\n\n\\smallskip\n\\begin{rtbliteral}\n') + self.context.append('\\end{rtbliteral}\n\\smallskip\n\n') + else: + self.body.append('\n') + self.context.append('\n') + if (self.settings.use_verbatim_when_possible and (len(node) == 1) + # in case of a parsed-literal containing just a "**bold**" word: + and isinstance(node[0], nodes.Text)): + self.verbatim = 1 + self.body.append('\\begin{verbatim}\n') + else: + self.literal_block = 1 + self.insert_none_breaking_blanks = 1 + + def depart_literal_block(self, node): + if self.verbatim: + self.body.append('\n\\end{verbatim}\n') + self.verbatim = 0 + else: + self.body.append('\n') + self.insert_none_breaking_blanks = 0 + self.literal_block = 0 + self.body.append(self.context.pop()) + + +class BeamerWriter (Latex2eWriter): + """ + A docutils writer that modifies the translator and settings for beamer. + """ + settings_spec = BEAMER_SPEC + settings_defaults = BEAMER_DEFAULTS + + def __init__(self): + Latex2eWriter.__init__(self) + self.translator_class = BeamerTranslator + + +if __name__ == '__main__': + description = ( + "Generates Beamer-flavoured LaTeX for PDF-based presentations." + default_description) + publish_cmdline (writer=BeamerWriter(), description=description) + + +### END ###################################################################### + From hpk at codespeak.net Sat Mar 21 15:31:22 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 21 Mar 2009 15:31:22 +0100 (CET) Subject: [py-svn] r63180 - py/dist Message-ID: <20090321143122.CDE0E168487@codespeak.net> Author: hpk Date: Sat Mar 21 15:31:22 2009 New Revision: 63180 Added: py/dist/ - copied from r63179, py/trunk/ Log: copyint trunk to dist, tests run well enough From briandorsey at codespeak.net Sat Mar 21 15:41:14 2009 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Sat, 21 Mar 2009 15:41:14 +0100 (CET) Subject: [py-svn] r63181 - py/extradoc/talk/pycon-us-2009/pytest-advanced Message-ID: <20090321144114.6A49116844F@codespeak.net> Author: briandorsey Date: Sat Mar 21 15:41:13 2009 New Revision: 63181 Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Log: quick comments Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Sat Mar 21 15:41:13 2009 @@ -97,6 +97,8 @@ def setup_method(self, method): self.tmpdir = self.root.mkdir(method.__name__) +XXX: Example confusing? Without pylib experience will students understand 'ensure'? What is setuptestfs? + funcargs: per-function setup =================================== @@ -169,6 +171,7 @@ for x in 1,2,3: yield "check_check, x +XXX typo in yeild above? Selection/Reporting ============================ @@ -251,6 +254,9 @@ - **Function** sets up and executes a python test function - **DoctestFile** collect doctests in a .txt file +XXX - will you be showing simple code examples or demos? There is are a lot of new abstractions here. + + Specifying plugins ======================== @@ -349,6 +355,8 @@ - running tests on windows, driven by Unix - do's and dont's for cross-process testing +XXX - multiple python versions? + Questions ========= From briandorsey at codespeak.net Sat Mar 21 15:44:16 2009 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Sat, 21 Mar 2009 15:44:16 +0100 (CET) Subject: [py-svn] r63182 - py/extradoc/talk/pycon-us-2009/pytest-introduction Message-ID: <20090321144416.A584016844F@codespeak.net> Author: briandorsey Date: Sat Mar 21 15:44:16 2009 New Revision: 63182 Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/TODO.txt py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html Log: Updated TODO and sync'd html Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/TODO.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-introduction/TODO.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/TODO.txt Sat Mar 21 15:44:16 2009 @@ -1,6 +1,8 @@ - finish outline +- reference /py/dist for install - determine which demos & exercises to write - placeholder: write demos +- run py lib tests on windows - experiment with funcargs - read: http://us.pycon.org/2009/conference/helpforspeakers/ Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html (original) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html Sat Mar 21 15:44:16 2009 @@ -465,10 +465,10 @@

    "nose provides an alternate test discovery and running process for unittest, one that is intended to mimic the behavior of py.test as much as is reasonably possible without resorting to too much magic." -- from nose home page

    • nosetest and py.test share basic philosophy and features.
    • -
    • py.test used to be more difficult to distribute and had (has?) a perception -of having too much magic.
    • -
    • opinion: nose is basically a subset of py.test
    • -
    • for basic use, can be difficult to choose - luckily you already have. :)
    • +
    • py.test has a more refined debugging/failure API
    • +
    • nose has plugins - upcoming pytest-1.0 as well
    • +
    • for basic use, not much difference
    • +
    • more co-operation between the projects upcoming

    None of the above features are in xUnit frameworks (like stdlib unittest)

    Nose doesn't do re-interpretation of failed statements, so it prints less @@ -535,9 +535,6 @@ def test_four(): | def test_bonus(): print "debug text" | py.test.raises( assert False | ValueError, int, 'foo') - -def test_bonus(): - py.test.raises(ValueError, int, 'foo')

    TODO: write handout/exercise

    @@ -684,8 +681,30 @@ assert something == True
    -
    -

    generative tests

    +
    +

    generative tests 1

    +
    +def test_ensure_sufficient_spam():
    +    assert 'spam' in 'spam'.lower()
    +    assert 'spam' in 'SPAM'.lower()
    +    assert 'spam' in 'eggs & spam'.lower()
    +    assert 'spam' in 'fish'.lower()
    +    assert 'spam' in 'spammy'.lower()
    +    assert 'spam' in 'more spam'.lower()
    +
    +
    +
    +

    generative tests 2

    +
    +def test_ensure_sufficient_spam():
    +    data = ['spam', 'SPAM', 'eggs & spam',
    +            'fish','spammy', 'more spam']
    +    for item in data:
    +        assert 'spam' in item.lower()
    +
    +
    +
    +

    generative tests 3

     def contains_spam(item):
         assert 'spam' in item.lower()
    
    
    From hpk at codespeak.net  Sat Mar 21 16:04:30 2009
    From: hpk at codespeak.net (hpk at codespeak.net)
    Date: Sat, 21 Mar 2009 16:04:30 +0100 (CET)
    Subject: [py-svn] r63187 - py/trunk/py/test/plugin
    Message-ID: <20090321150430.E59B416849E@codespeak.net>
    
    Author: hpk
    Date: Sat Mar 21 16:04:30 2009
    New Revision: 63187
    
    Modified:
       py/trunk/py/test/plugin/pytest_default.py
    Log:
    tweaks to cmdline options
    
    
    Modified: py/trunk/py/test/plugin/pytest_default.py
    ==============================================================================
    --- py/trunk/py/test/plugin/pytest_default.py	(original)
    +++ py/trunk/py/test/plugin/pytest_default.py	Sat Mar 21 16:04:30 2009
    @@ -69,7 +69,7 @@
             group.addoption('--boxed',
                        action="store_true", dest="boxed", default=False,
                        help="box each test run in a separate process") 
    -        group._addoption('--plugin', '-p', action="append", dest="plugin", default = [],
    +        group._addoption('-p', action="append", dest="plugin", default = [],
                        help=("load the specified plugin after command line parsing. "
                              "Example: '-p hello' will trigger 'import pytest_hello' "
                              "and instantiate 'HelloPlugin' from the module."))
    @@ -173,5 +173,6 @@
         )
     
     def test_plugin_already_exists(testdir):
    -    config = testdir.parseconfig("--plugin", "default")
    +    config = testdir.parseconfig("-p", "default")
         assert config.option.plugin == ['default']
    +    config.pytestplugins.do_configure(config)
    
    
    From hpk at codespeak.net  Sat Mar 21 16:14:23 2009
    From: hpk at codespeak.net (hpk at codespeak.net)
    Date: Sat, 21 Mar 2009 16:14:23 +0100 (CET)
    Subject: [py-svn] r63188 - in py/trunk/py: . misc/testing
    Message-ID: <20090321151423.12F36168487@codespeak.net>
    
    Author: hpk
    Date: Sat Mar 21 16:14:22 2009
    New Revision: 63188
    
    Modified:
       py/trunk/py/_com.py
       py/trunk/py/misc/testing/test_com.py
    Log:
    allow reversed retrieval of methods
    
    
    Modified: py/trunk/py/_com.py
    ==============================================================================
    --- py/trunk/py/_com.py	(original)
    +++ py/trunk/py/_com.py	Sat Mar 21 16:14:22 2009
    @@ -120,7 +120,7 @@
         def isregistered(self, plugin):
             return plugin in self._plugins 
     
    -    def listattr(self, attrname, plugins=None, extra=()):
    +    def listattr(self, attrname, plugins=None, extra=(), reverse=False):
             l = []
             if plugins is None:
                 plugins = self._plugins
    @@ -131,6 +131,8 @@
                     l.append(getattr(plugin, attrname))
                 except AttributeError:
                     continue 
    +        if reverse:
    +            l.reverse()
             return l
     
         def call_each(self, methname, *args, **kwargs):
    
    Modified: py/trunk/py/misc/testing/test_com.py
    ==============================================================================
    --- py/trunk/py/misc/testing/test_com.py	(original)
    +++ py/trunk/py/misc/testing/test_com.py	Sat Mar 21 16:14:22 2009
    @@ -154,14 +154,18 @@
         def test_listattr(self):
             plugins = PyPlugins()
             class api1:
    -            x = 42
    -        class api2:
                 x = 41
    +        class api2:
    +            x = 42
    +        class api3:
    +            x = 43
             plugins.register(api1())
             plugins.register(api2())
    +        plugins.register(api3())
             l = list(plugins.listattr('x'))
    -        l.sort()
    -        assert l == [41, 42]
    +        assert l == [41, 42, 43]
    +        l = list(plugins.listattr('x', reverse=True))
    +        assert l == [43, 42, 41]
     
         def test_notify_anonymous_ordered(self):
             plugins = PyPlugins()
    
    
    From hpk at codespeak.net  Sat Mar 21 16:17:30 2009
    From: hpk at codespeak.net (hpk at codespeak.net)
    Date: Sat, 21 Mar 2009 16:17:30 +0100 (CET)
    Subject: [py-svn] r63190 - in py/trunk/py/test: . plugin
    Message-ID: <20090321151730.9B0D516849E@codespeak.net>
    
    Author: hpk
    Date: Sat Mar 21 16:17:30 2009
    New Revision: 63190
    
    Modified:
       py/trunk/py/test/defaultconftest.py
       py/trunk/py/test/plugin/pytest_resultlog.py
       py/trunk/py/test/pytestplugin.py
    Log:
    no resultlog plugin by default
    have custom options come last in "py.test -h"
    
    
    
    Modified: py/trunk/py/test/defaultconftest.py
    ==============================================================================
    --- py/trunk/py/test/defaultconftest.py	(original)
    +++ py/trunk/py/test/defaultconftest.py	Sat Mar 21 16:17:30 2009
    @@ -10,8 +10,7 @@
     
     conf_iocapture = "fd" # overridable from conftest.py 
     
    -# XXX resultlog should go, pypy's nightrun depends on it
    -pytest_plugins = "default terminal xfail tmpdir execnetcleanup resultlog monkeypatch".split()
    +pytest_plugins = "default terminal xfail tmpdir execnetcleanup monkeypatch".split()
     
     # ===================================================
     # settings in conftest only (for now) - for distribution
    
    Modified: py/trunk/py/test/plugin/pytest_resultlog.py
    ==============================================================================
    --- py/trunk/py/test/plugin/pytest_resultlog.py	(original)
    +++ py/trunk/py/test/plugin/pytest_resultlog.py	Sat Mar 21 16:17:30 2009
    @@ -5,8 +5,9 @@
            Useful for buildbot integration code. 
         """ 
         def pytest_addoption(self, parser):
    -        parser.addoption('--resultlog', action="store", dest="resultlog", 
    -               help="path for machine-readable result log")
    +        group = parser.addgroup("resultlog", "resultlog plugin options")
    +        group.addoption('--resultlog', action="store", dest="resultlog", metavar="path",
    +               help="path for machine-readable result log.")
         
         def pytest_configure(self, config):
             resultlog = config.option.resultlog
    
    Modified: py/trunk/py/test/pytestplugin.py
    ==============================================================================
    --- py/trunk/py/test/pytestplugin.py	(original)
    +++ py/trunk/py/test/pytestplugin.py	Sat Mar 21 16:17:30 2009
    @@ -78,7 +78,9 @@
             return self.pyplugins.notify(eventname, *args, **kwargs)
     
         def do_addoption(self, parser):
    -        self.pyplugins.call_each('pytest_addoption', parser=parser)
    +        methods = self.pyplugins.listattr("pytest_addoption", reverse=True)
    +        mc = py._com.MultiCall(methods, parser=parser)
    +        mc.execute()
     
         def pyevent_plugin_registered(self, plugin):
             if hasattr(self, '_config'):
    
    
    From hpk at codespeak.net  Sat Mar 21 20:28:38 2009
    From: hpk at codespeak.net (hpk at codespeak.net)
    Date: Sat, 21 Mar 2009 20:28:38 +0100 (CET)
    Subject: [py-svn] r63191 - in py/trunk/py/test: . dsession dsession/testing
    	looponfail plugin testing
    Message-ID: <20090321192838.A1FA316844C@codespeak.net>
    
    Author: hpk
    Date: Sat Mar 21 20:28:35 2009
    New Revision: 63191
    
    Modified:
       py/trunk/py/test/cmdline.py
       py/trunk/py/test/config.py
       py/trunk/py/test/dsession/dsession.py
       py/trunk/py/test/dsession/testing/test_dsession.py
       py/trunk/py/test/looponfail/remote.py
       py/trunk/py/test/plugin/pytest_default.py
       py/trunk/py/test/session.py
       py/trunk/py/test/testing/acceptance_test.py
       py/trunk/py/test/testing/test_config.py
    Log:
    * remove "--exec"
    * cleanup of options
    
    
    
    Modified: py/trunk/py/test/cmdline.py
    ==============================================================================
    --- py/trunk/py/test/cmdline.py	(original)
    +++ py/trunk/py/test/cmdline.py	Sat Mar 21 20:28:35 2009
    @@ -17,7 +17,7 @@
             config.pytestplugins.do_unconfigure(config)
             raise SystemExit(exitstatus)
         except config.Error, e:
    -        py.std.sys.stderr.write("config ERROR: %s\n" %(e.args[0],))
    +        py.std.sys.stderr.write("ERROR: %s\n" %(e.args[0],))
             raise SystemExit(3)
     
     def warn_about_missing_assertion():
    
    Modified: py/trunk/py/test/config.py
    ==============================================================================
    --- py/trunk/py/test/config.py	(original)
    +++ py/trunk/py/test/config.py	Sat Mar 21 20:28:35 2009
    @@ -221,7 +221,9 @@
             if cls is None:
                 from py.__.test.session import Session
                 cls = Session
    -        return cls(self)
    +        session = cls(self)
    +        self.trace("instantiated session %r" % session)
    +        return session
     
         def _reparse(self, args):
             """ this is used from tests that want to re-invoke parse(). """
    @@ -253,11 +255,7 @@
         def getxspecs(self):
             config = self 
             if config.option.numprocesses:
    -            if config.option.executable:
    -                s = 'popen//python=%s' % config.option.executable
    -            else:
    -                s = 'popen'
    -            xspec = [s] * config.option.numprocesses
    +            xspec = ['popen'] * config.option.numprocesses
             else:
                 xspec = config.option.xspec
                 if not xspec:
    
    Modified: py/trunk/py/test/dsession/dsession.py
    ==============================================================================
    --- py/trunk/py/test/dsession/dsession.py	(original)
    +++ py/trunk/py/test/dsession/dsession.py	Sat Mar 21 20:28:35 2009
    @@ -57,30 +57,14 @@
         MAXITEMSPERHOST = 15
         
         def __init__(self, config):
    -        super(DSession, self).__init__(config=config)
    -        
             self.queue = Queue.Queue()
             self.node2pending = {}
             self.item2node = {}
    -        if self.config.getvalue("executable") and \
    -           not self.config.getvalue("numprocesses"):
    -            self.config.option.numprocesses = 1
    -
    -    def fixoptions(self):
    -        """ check, fix and determine conflicting options. """
    -        option = self.config.option 
    -        #if option.runbrowser and not option.startserver:
    -        #    #print "--runbrowser implies --startserver"
    -        #    option.startserver = True
    -        if self.config.getvalue("dist_boxed") and option.dist:
    -            option.boxed = True
    -        # conflicting options
    -        if option.looponfailing and option.usepdb:
    -            raise ValueError, "--looponfailing together with --pdb not supported."
    -        if option.executable and option.usepdb:
    -            raise ValueError, "--exec together with --pdb not supported."
    -        if option.executable and not option.dist and not option.numprocesses:
    -            option.numprocesses = 1
    +        super(DSession, self).__init__(config=config)
    +
    +    def pytest_configure(self, config):
    +        if self.config.getvalue("usepdb"):
    +            raise self.config.Error("--pdb does not work for distributed tests (yet).")
             try:
                 self.config.getxspecs()
             except self.config.Error:
    
    Modified: py/trunk/py/test/dsession/testing/test_dsession.py
    ==============================================================================
    --- py/trunk/py/test/dsession/testing/test_dsession.py	(original)
    +++ py/trunk/py/test/dsession/testing/test_dsession.py	Sat Mar 21 20:28:35 2009
    @@ -25,15 +25,6 @@
             print queue.get()
     
     class TestDSession:
    -    def test_fixoptions(self, testdir):
    -        config = testdir.parseconfig("--exec=xxx")
    -        config.pytestplugins.do_configure(config)
    -        config.initsession().fixoptions()
    -        assert config.option.numprocesses == 1
    -        config = testdir.parseconfig("--exec=xxx", '-n3')
    -        config.initsession().fixoptions()
    -        assert config.option.numprocesses == 3
    -
         def test_add_remove_node(self, testdir):
             item = testdir.getitem("def test_func(): pass")
             rep = run(item)
    
    Modified: py/trunk/py/test/looponfail/remote.py
    ==============================================================================
    --- py/trunk/py/test/looponfail/remote.py	(original)
    +++ py/trunk/py/test/looponfail/remote.py	Sat Mar 21 20:28:35 2009
    @@ -49,17 +49,6 @@
     class RemoteControl(object):
         def __init__(self, config):
             self.config = config
    -        self._setexecutable()
    -
    -    def _setexecutable(self):
    -        # XXX --exec logic should go to DSession 
    -        name = self.config.option.executable
    -        if name is None:
    -            executable = py.std.sys.executable 
    -        else:
    -            executable = py.path.local.sysfind(name)
    -            assert executable is not None, executable 
    -        self.executable = executable 
     
         def trace(self, *args):
             if self.config.option.debug:
    @@ -67,7 +56,7 @@
                 print "RemoteControl:", msg 
     
         def initgateway(self):
    -        return py.execnet.PopenGateway(self.executable)
    +        return py.execnet.PopenGateway()
     
         def setup(self, out=None):
             if out is None:
    @@ -128,7 +117,6 @@
         #config.option.session = None
         config.option.looponfailing = False 
         config.option.usepdb = False 
    -    config.option.executable = None
         trails = channel.receive()
         config.pytestplugins.do_configure(config)
         DEBUG("SLAVE: initsession()")
    
    Modified: py/trunk/py/test/plugin/pytest_default.py
    ==============================================================================
    --- py/trunk/py/test/plugin/pytest_default.py	(original)
    +++ py/trunk/py/test/plugin/pytest_default.py	Sat Mar 21 20:28:35 2009
    @@ -86,7 +86,7 @@
                        help="trace considerations of conftest.py files."),
             group._addoption('--nomagic',
                        action="store_true", dest="nomagic", default=False,
    -                   help="don't use assert reinterpretation and python traceback cutting. ")
    +                   help="don't reinterpret asserts, no traceback cutting. ")
             group.addoption('--debug',
                        action="store_true", dest="debug", default=False,
                        help="generate and show debugging information.")
    @@ -101,20 +101,9 @@
             group.addoption('--rsyncdirs', dest="rsyncdirs", default=None, metavar="dir1,dir2,...", 
                        help="comma-separated list of directories to rsync. All those roots will be rsynced "
                             "into a corresponding subdir on the remote sides. ")
    -        group._addoption('--xspec', '--tx', '-t', dest="xspec", action="append", 
    +        group._addoption('--tx', dest="xspec", action="append", 
                        help=("add a test environment, specified in XSpec syntax. examples: "
                              "--tx popen//python=python2.5 --tx socket=192.168.1.102"))
    -        group._addoption('--exec',
    -                   action="store", dest="executable", default=None,
    -                   help="python executable to run the tests with.")
    -        #group._addoption('-w', '--startserver',
    -        #           action="store_true", dest="startserver", default=False,
    -        #           help="starts local web server for displaying test progress.", 
    -        #           ),
    -        #group._addoption('-r', '--runbrowser',
    -        #           action="store_true", dest="runbrowser", default=False,
    -        #           help="run browser (implies --startserver)."
    -        #           ),
             #group._addoption('--rest',
             #           action='store_true', dest="restreport", default=False,
             #           help="restructured text output reporting."),
    @@ -122,6 +111,14 @@
         def pytest_configure(self, config):
             self.setsession(config)
             self.loadplugins(config)
    +        self.fixoptions(config)
    +
    +    def fixoptions(self, config):
    +        if config.getvalue("usepdb"):
    +            if config.getvalue("looponfailing"):
    +                raise config.Error("--pdb incompatible with --looponfailing.")
    +            if config.getvalue("dist"):
    +                raise config.Error("--pdb incomptaible with distributed testing.")
     
         def loadplugins(self, config):
             for name in config.getvalue("plugin"):
    @@ -133,12 +130,13 @@
             if val("collectonly"):
                 from py.__.test.session import Session
                 config.setsessionclass(Session)
    -        elif val("looponfailing"):
    -            from py.__.test.looponfail.remote import LooponfailingSession
    -            config.setsessionclass(LooponfailingSession)
    -        elif val("numprocesses") or val("dist") or val("executable"):
    -            from py.__.test.dsession.dsession import  DSession
    -            config.setsessionclass(DSession)
    +        else:
    +            if val("looponfailing"):
    +                from py.__.test.looponfail.remote import LooponfailingSession
    +                config.setsessionclass(LooponfailingSession)
    +            elif val("numprocesses") or val("dist"):
    +                from py.__.test.dsession.dsession import  DSession
    +                config.setsessionclass(DSession)
     
         def pytest_item_makereport(self, item, excinfo, when, outerr):
             from py.__.test import event
    @@ -156,11 +154,7 @@
         assert x('--dist') == 'DSession'
         assert x('-n3') == 'DSession'
         assert x('-f') == 'LooponfailingSession'
    -    assert x('--exec=x') == 'DSession'
    -    assert x('-f', '--exec=x') == 'LooponfailingSession'
    -    assert x('--dist', '--exec=x', '--collectonly') == 'Session'
    -
    -
    +    assert x('--dist', '--collectonly') == 'Session'
     
     def test_generic(plugintester):
         plugintester.apicheck(DefaultPlugin)
    @@ -176,3 +170,17 @@
         config = testdir.parseconfig("-p", "default")
         assert config.option.plugin == ['default']
         config.pytestplugins.do_configure(config)
    +
    +def test_conflict_options():
    +    def check_conflict_option(opts):
    +        print "testing if options conflict:", " ".join(opts)
    +        config = py.test.config._reparse(opts)
    +        py.test.raises(config.Error, 
    +            "config.pytestplugins.do_configure(config)")
    +    conflict_options = (
    +        '--looponfailing --pdb',
    +        '--dist --pdb', 
    +    )
    +    for spec in conflict_options: 
    +        opts = spec.split()
    +        yield check_conflict_option, opts
    
    Modified: py/trunk/py/test/session.py
    ==============================================================================
    --- py/trunk/py/test/session.py	(original)
    +++ py/trunk/py/test/session.py	Sat Mar 21 20:28:35 2009
    @@ -26,18 +26,6 @@
             self._nomatch = False
             self.shouldstop = False
     
    -    def fixoptions(self):
    -        """ check, fix and determine conflicting options. """
    -        option = self.config.option 
    -        #if option.runbrowser and not option.startserver:
    -        #    #print "--runbrowser implies --startserver"
    -        #    option.startserver = True
    -        # conflicting options
    -        if option.looponfailing and option.usepdb:
    -            raise ValueError, "--looponfailing together with --pdb not supported."
    -        if option.executable and option.usepdb:
    -            raise ValueError, "--exec together with --pdb not supported."
    -
         def genitems(self, colitems, keywordexpr=None):
             """ yield Items from iterating over the given colitems. """
             while colitems: 
    
    Modified: py/trunk/py/test/testing/acceptance_test.py
    ==============================================================================
    --- py/trunk/py/test/testing/acceptance_test.py	(original)
    +++ py/trunk/py/test/testing/acceptance_test.py	Sat Mar 21 20:28:35 2009
    @@ -14,7 +14,7 @@
             result = testdir.runpytest(testdir.tmpdir)
             assert result.ret != 0
             assert result.stderr.fnmatch_lines([
    -            'config ERROR: hello'
    +            '*ERROR: hello'
             ])
     
         def test_basetemp(self, testdir):
    
    Modified: py/trunk/py/test/testing/test_config.py
    ==============================================================================
    --- py/trunk/py/test/testing/test_config.py	(original)
    +++ py/trunk/py/test/testing/test_config.py	Sat Mar 21 20:28:35 2009
    @@ -67,22 +67,6 @@
             config = py.test.config._reparse([tmpdir])
             py.test.raises(AssertionError, "config.parse([])")
     
    -    def test_conflict_options(self):
    -        def check_conflict_option(opts):
    -            print "testing if options conflict:", " ".join(opts)
    -            config = py.test.config._reparse(opts)
    -            py.test.raises((ValueError, SystemExit), """
    -                config.initsession()
    -            """)
    -        py.test.skip("check on conflict options")
    -        conflict_options = (
    -            '--looponfailing --pdb',
    -            '--dist --pdb', 
    -            '--exec=%s --pdb' % (py.std.sys.executable,),
    -        )
    -        for spec in conflict_options: 
    -            opts = spec.split()
    -            yield check_conflict_option, opts
     
     class TestConfigTmpdir:
         def test_getbasetemp(self, testdir):
    @@ -250,22 +234,6 @@
             config = py.test.config._reparse([testdir.tmpdir])
             assert not config.option.boxed
     
    -    def test_boxed_option_from_conftest(self, testdir):
    -        tmpdir = testdir.tmpdir.ensure("subdir", dir=1)
    -        tmpdir.join("conftest.py").write(py.code.Source("""
    -            dist_boxed = True
    -        """))
    -        config = py.test.config._reparse(['--dist', tmpdir])
    -        config.initsession()
    -        assert config.option.boxed 
    -
    -    def test_boxed_option_from_conftest(self, testdir):
    -        testdir.makepyfile(conftest="dist_boxed=False")
    -        config = py.test.config._reparse([testdir.tmpdir, '--box'])
    -        assert config.option.boxed 
    -        config.initsession()
    -        assert config.option.boxed
    -
         def test_config_iocapturing(self, testdir):
             config = testdir.parseconfig(testdir.tmpdir)
             assert config.getvalue("conf_iocapture")
    
    
    From hpk at codespeak.net  Sat Mar 21 20:31:09 2009
    From: hpk at codespeak.net (hpk at codespeak.net)
    Date: Sat, 21 Mar 2009 20:31:09 +0100 (CET)
    Subject: [py-svn] r63192 - in py/trunk/py/test: . dist dist/testing dsession
    	looponfail plugin testing
    Message-ID: <20090321193109.A0D97168454@codespeak.net>
    
    Author: hpk
    Date: Sat Mar 21 20:31:09 2009
    New Revision: 63192
    
    Added:
       py/trunk/py/test/dist/
          - copied from r63190, py/trunk/py/test/dsession/
       py/trunk/py/test/dist/__init__.py
          - copied unchanged from r63191, py/trunk/py/test/dsession/__init__.py
       py/trunk/py/test/dist/dsession.py
          - copied, changed from r63191, py/trunk/py/test/dsession/dsession.py
       py/trunk/py/test/dist/mypickle.py
          - copied unchanged from r63191, py/trunk/py/test/dsession/mypickle.py
       py/trunk/py/test/dist/nodemanage.py
          - copied, changed from r63191, py/trunk/py/test/dsession/nodemanage.py
       py/trunk/py/test/dist/testing/
          - copied from r63191, py/trunk/py/test/dsession/testing/
       py/trunk/py/test/dist/txnode.py
          - copied, changed from r63191, py/trunk/py/test/dsession/txnode.py
    Removed:
       py/trunk/py/test/dsession/
    Modified:
       py/trunk/py/test/dist/testing/test_dsession.py
       py/trunk/py/test/dist/testing/test_functional_dsession.py
       py/trunk/py/test/dist/testing/test_mypickle.py
       py/trunk/py/test/dist/testing/test_nodemanage.py
       py/trunk/py/test/dist/testing/test_txnode.py
       py/trunk/py/test/looponfail/remote.py
       py/trunk/py/test/plugin/pytest_default.py
       py/trunk/py/test/runner.py
       py/trunk/py/test/testing/test_pickling.py
    Log:
    rename dsession to dist
    
    
    Copied: py/trunk/py/test/dist/dsession.py (from r63191, py/trunk/py/test/dsession/dsession.py)
    ==============================================================================
    --- py/trunk/py/test/dsession/dsession.py	(original)
    +++ py/trunk/py/test/dist/dsession.py	Sat Mar 21 20:31:09 2009
    @@ -9,7 +9,7 @@
     from py.__.test.runner import basic_run_report, basic_collect_report
     from py.__.test.session import Session
     from py.__.test import outcome 
    -from py.__.test.dsession.nodemanage import NodeManager
    +from py.__.test.dist.nodemanage import NodeManager
     
     import Queue 
     
    
    Copied: py/trunk/py/test/dist/nodemanage.py (from r63191, py/trunk/py/test/dsession/nodemanage.py)
    ==============================================================================
    --- py/trunk/py/test/dsession/nodemanage.py	(original)
    +++ py/trunk/py/test/dist/nodemanage.py	Sat Mar 21 20:31:09 2009
    @@ -1,6 +1,6 @@
     import py
     import sys, os
    -from py.__.test.dsession.txnode import MasterNode
    +from py.__.test.dist.txnode import MasterNode
     from py.__.execnet.gwmanage import GatewayManager
     
         
    
    Modified: py/trunk/py/test/dist/testing/test_dsession.py
    ==============================================================================
    --- py/trunk/py/test/dsession/testing/test_dsession.py	(original)
    +++ py/trunk/py/test/dist/testing/test_dsession.py	Sat Mar 21 20:31:09 2009
    @@ -1,4 +1,4 @@
    -from py.__.test.dsession.dsession import DSession
    +from py.__.test.dist.dsession import DSession
     from py.__.test.runner import basic_collect_report 
     from py.__.test import event
     from py.__.test import outcome
    
    Modified: py/trunk/py/test/dist/testing/test_functional_dsession.py
    ==============================================================================
    --- py/trunk/py/test/dsession/testing/test_functional_dsession.py	(original)
    +++ py/trunk/py/test/dist/testing/test_functional_dsession.py	Sat Mar 21 20:31:09 2009
    @@ -1,5 +1,5 @@
     import py
    -from py.__.test.dsession.dsession import DSession
    +from py.__.test.dist.dsession import DSession
     from test_txnode import EventQueue
     
     class TestAsyncFunctional:
    
    Modified: py/trunk/py/test/dist/testing/test_mypickle.py
    ==============================================================================
    --- py/trunk/py/test/dsession/testing/test_mypickle.py	(original)
    +++ py/trunk/py/test/dist/testing/test_mypickle.py	Sat Mar 21 20:31:09 2009
    @@ -1,6 +1,6 @@
     
     import py
    -from py.__.test.dsession.mypickle import ImmutablePickler, PickleChannel, UnpickleError
    +from py.__.test.dist.mypickle import ImmutablePickler, PickleChannel, UnpickleError
     
     class A: 
         pass
    @@ -75,9 +75,9 @@
     
         def test_popen_send_instance(self):
             channel = self.gw.remote_exec("""
    -            from py.__.test.dsession.mypickle import PickleChannel
    +            from py.__.test.dist.mypickle import PickleChannel
                 channel = PickleChannel(channel)
    -            from py.__.test.dsession.testing.test_mypickle import A
    +            from py.__.test.dist.testing.test_mypickle import A
                 a1 = A()
                 a1.hello = 10
                 channel.send(a1)
    @@ -94,9 +94,9 @@
     
         def test_send_concurrent(self):
             channel = self.gw.remote_exec("""
    -            from py.__.test.dsession.mypickle import PickleChannel
    +            from py.__.test.dist.mypickle import PickleChannel
                 channel = PickleChannel(channel)
    -            from py.__.test.dsession.testing.test_mypickle import A
    +            from py.__.test.dist.testing.test_mypickle import A
                 l = [A() for i in range(10)]
                 channel.send(l)
                 other_l = channel.receive() 
    @@ -124,9 +124,9 @@
             
         def test_popen_with_callback(self):
             channel = self.gw.remote_exec("""
    -            from py.__.test.dsession.mypickle import PickleChannel
    +            from py.__.test.dist.mypickle import PickleChannel
                 channel = PickleChannel(channel)
    -            from py.__.test.dsession.testing.test_mypickle import A
    +            from py.__.test.dist.testing.test_mypickle import A
                 a1 = A()
                 a1.hello = 10
                 channel.send(a1)
    @@ -145,9 +145,9 @@
     
         def test_popen_with_callback_with_endmarker(self):
             channel = self.gw.remote_exec("""
    -            from py.__.test.dsession.mypickle import PickleChannel
    +            from py.__.test.dist.mypickle import PickleChannel
                 channel = PickleChannel(channel)
    -            from py.__.test.dsession.testing.test_mypickle import A
    +            from py.__.test.dist.testing.test_mypickle import A
                 a1 = A()
                 a1.hello = 10
                 channel.send(a1)
    @@ -169,9 +169,9 @@
     
         def test_popen_with_callback_with_endmarker_and_unpickling_error(self):
             channel = self.gw.remote_exec("""
    -            from py.__.test.dsession.mypickle import PickleChannel
    +            from py.__.test.dist.mypickle import PickleChannel
                 channel = PickleChannel(channel)
    -            from py.__.test.dsession.testing.test_mypickle import A
    +            from py.__.test.dist.testing.test_mypickle import A
                 a1 = A()
                 channel.send(a1)
                 channel.send(a1)
    @@ -188,7 +188,7 @@
     
         def test_popen_with_newchannel(self):
             channel = self.gw.remote_exec("""
    -            from py.__.test.dsession.mypickle import PickleChannel
    +            from py.__.test.dist.mypickle import PickleChannel
                 channel = PickleChannel(channel)
                 newchannel = channel.receive()
                 newchannel.send(42)
    @@ -202,7 +202,7 @@
     
         def test_popen_with_various_methods(self):
             channel = self.gw.remote_exec("""
    -            from py.__.test.dsession.mypickle import PickleChannel
    +            from py.__.test.dist.mypickle import PickleChannel
                 channel = PickleChannel(channel)
                 channel.receive()
             """)
    
    Modified: py/trunk/py/test/dist/testing/test_nodemanage.py
    ==============================================================================
    --- py/trunk/py/test/dsession/testing/test_nodemanage.py	(original)
    +++ py/trunk/py/test/dist/testing/test_nodemanage.py	Sat Mar 21 20:31:09 2009
    @@ -3,7 +3,7 @@
     """
     
     import py
    -from py.__.test.dsession.nodemanage import NodeManager
    +from py.__.test.dist.nodemanage import NodeManager
     
     from py.__.test import event
     
    
    Modified: py/trunk/py/test/dist/testing/test_txnode.py
    ==============================================================================
    --- py/trunk/py/test/dsession/testing/test_txnode.py	(original)
    +++ py/trunk/py/test/dist/testing/test_txnode.py	Sat Mar 21 20:31:09 2009
    @@ -1,6 +1,6 @@
     
     import py
    -from py.__.test.dsession.txnode import MasterNode
    +from py.__.test.dist.txnode import MasterNode
     
     class EventQueue:
         def __init__(self, bus, queue=None):
    
    Copied: py/trunk/py/test/dist/txnode.py (from r63191, py/trunk/py/test/dsession/txnode.py)
    ==============================================================================
    --- py/trunk/py/test/dsession/txnode.py	(original)
    +++ py/trunk/py/test/dist/txnode.py	Sat Mar 21 20:31:09 2009
    @@ -3,7 +3,7 @@
     """
     import py
     from py.__.test import event
    -from py.__.test.dsession.mypickle import PickleChannel
    +from py.__.test.dist.mypickle import PickleChannel
     
     class MasterNode(object):
         """ Install slave code, manage sending test tasks & receiving results """
    @@ -70,8 +70,8 @@
     # setting up slave code 
     def install_slave(gateway, config):
         channel = gateway.remote_exec(source="""
    -        from py.__.test.dsession.mypickle import PickleChannel
    -        from py.__.test.dsession.txnode import SlaveNode
    +        from py.__.test.dist.mypickle import PickleChannel
    +        from py.__.test.dist.txnode import SlaveNode
             channel = PickleChannel(channel)
             slavenode = SlaveNode(channel)
             slavenode.run()
    
    Modified: py/trunk/py/test/looponfail/remote.py
    ==============================================================================
    --- py/trunk/py/test/looponfail/remote.py	(original)
    +++ py/trunk/py/test/looponfail/remote.py	Sat Mar 21 20:31:09 2009
    @@ -11,7 +11,7 @@
     from __future__ import generators
     import py
     from py.__.test.session import Session
    -from py.__.test.dsession.mypickle import PickleChannel
    +from py.__.test.dist.mypickle import PickleChannel
     from py.__.test import event
     from py.__.test.looponfail import util
     
    @@ -70,7 +70,7 @@
             finally:
                 old.chdir()
             channel = self.gateway.remote_exec(source="""
    -            from py.__.test.dsession.mypickle import PickleChannel
    +            from py.__.test.dist.mypickle import PickleChannel
                 from py.__.test.looponfail.remote import slave_runsession
                 channel = PickleChannel(channel)
                 config, fullwidth, hasmarkup = channel.receive()
    
    Modified: py/trunk/py/test/plugin/pytest_default.py
    ==============================================================================
    --- py/trunk/py/test/plugin/pytest_default.py	(original)
    +++ py/trunk/py/test/plugin/pytest_default.py	Sat Mar 21 20:31:09 2009
    @@ -135,7 +135,7 @@
                     from py.__.test.looponfail.remote import LooponfailingSession
                     config.setsessionclass(LooponfailingSession)
                 elif val("numprocesses") or val("dist"):
    -                from py.__.test.dsession.dsession import  DSession
    +                from py.__.test.dist.dsession import  DSession
                     config.setsessionclass(DSession)
     
         def pytest_item_makereport(self, item, excinfo, when, outerr):
    
    Modified: py/trunk/py/test/runner.py
    ==============================================================================
    --- py/trunk/py/test/runner.py	(original)
    +++ py/trunk/py/test/runner.py	Sat Mar 21 20:31:09 2009
    @@ -10,7 +10,7 @@
     
     from py.__.test import event
     from py.__.test.outcome import Exit
    -from py.__.test.dsession.mypickle import ImmutablePickler
    +from py.__.test.dist.mypickle import ImmutablePickler
     import py.__.test.custompdb
     
     class RobustRun(object):
    
    Modified: py/trunk/py/test/testing/test_pickling.py
    ==============================================================================
    --- py/trunk/py/test/testing/test_pickling.py	(original)
    +++ py/trunk/py/test/testing/test_pickling.py	Sat Mar 21 20:31:09 2009
    @@ -23,7 +23,7 @@
     
     class ImmutablePickleTransport:
         def __init__(self):
    -        from py.__.test.dsession.mypickle import ImmutablePickler
    +        from py.__.test.dist.mypickle import ImmutablePickler
             self.p1 = ImmutablePickler(uneven=0)
             self.p2 = ImmutablePickler(uneven=1)
     
    @@ -168,11 +168,11 @@
                 old.chdir() 
     
     def test_config__setstate__wired_correctly_in_childprocess(testdir):
    -    from py.__.test.dsession.mypickle import PickleChannel
    +    from py.__.test.dist.mypickle import PickleChannel
         gw = py.execnet.PopenGateway()
         channel = gw.remote_exec("""
             import py
    -        from py.__.test.dsession.mypickle import PickleChannel
    +        from py.__.test.dist.mypickle import PickleChannel
             channel = PickleChannel(channel)
             config = channel.receive()
             assert py.test.config.pytestplugins.pyplugins == py._com.pyplugins, "pyplugins wrong"
    
    
    From hpk at codespeak.net  Sat Mar 21 20:58:44 2009
    From: hpk at codespeak.net (hpk at codespeak.net)
    Date: Sat, 21 Mar 2009 20:58:44 +0100 (CET)
    Subject: [py-svn] r63193 - in py/trunk/py/test: . dist/testing plugin testing
    Message-ID: <20090321195844.5693916844C@codespeak.net>
    
    Author: hpk
    Date: Sat Mar 21 20:58:41 2009
    New Revision: 63193
    
    Modified:
       py/trunk/py/test/config.py
       py/trunk/py/test/defaultconftest.py
       py/trunk/py/test/dist/testing/test_functional_dsession.py
       py/trunk/py/test/plugin/pytest_default.py
       py/trunk/py/test/plugin/pytest_terminal.py
       py/trunk/py/test/testing/test_config.py
    Log:
    normalize towards 'iocapture' option
    
    
    Modified: py/trunk/py/test/config.py
    ==============================================================================
    --- py/trunk/py/test/config.py	(original)
    +++ py/trunk/py/test/config.py	Sat Mar 21 20:58:41 2009
    @@ -242,7 +242,7 @@
             if self.option.nocapture:
                 iocapture = "no" 
             else:
    -            iocapture = self.getvalue("conf_iocapture", path=path)
    +            iocapture = self.getvalue("iocapture", path=path)
             if iocapture == "fd": 
                 return py.io.StdCaptureFD()
             elif iocapture == "sys":
    @@ -250,7 +250,7 @@
             elif iocapture == "no": 
                 return py.io.StdCapture(out=False, err=False, in_=False)
             else:
    -            raise ValueError("unknown io capturing: " + iocapture)
    +            raise self.Error("unknown io capturing: " + iocapture)
     
         def getxspecs(self):
             config = self 
    
    Modified: py/trunk/py/test/defaultconftest.py
    ==============================================================================
    --- py/trunk/py/test/defaultconftest.py	(original)
    +++ py/trunk/py/test/defaultconftest.py	Sat Mar 21 20:58:41 2009
    @@ -8,14 +8,12 @@
     Function = py.test.collect.Function
     Instance = py.test.collect.Instance
     
    -conf_iocapture = "fd" # overridable from conftest.py 
     
     pytest_plugins = "default terminal xfail tmpdir execnetcleanup monkeypatch".split()
     
     # ===================================================
     # settings in conftest only (for now) - for distribution
     
    -dist_boxed = False
     if hasattr(py.std.os, 'nice'):
         dist_nicelevel = py.std.os.nice(0) # nice py.test works
     else:
    
    Modified: py/trunk/py/test/dist/testing/test_functional_dsession.py
    ==============================================================================
    --- py/trunk/py/test/dist/testing/test_functional_dsession.py	(original)
    +++ py/trunk/py/test/dist/testing/test_functional_dsession.py	Sat Mar 21 20:58:41 2009
    @@ -68,7 +68,7 @@
                 "--tx=popen//chdir=%(dest)s" % locals(), p)
             assert result.ret == 0
             result.stdout.fnmatch_lines([
    -            "*1* new *popen*platform*",
    +            "*1* *popen*platform*",
                 #"RSyncStart: [G1]",
                 #"RSyncFinished: [G1]",
                 "*1 passed*"
    
    Modified: py/trunk/py/test/plugin/pytest_default.py
    ==============================================================================
    --- py/trunk/py/test/plugin/pytest_default.py	(original)
    +++ py/trunk/py/test/plugin/pytest_default.py	Sat Mar 21 20:58:41 2009
    @@ -33,7 +33,7 @@
             return Directory(path, parent=parent)
     
         def pytest_addoption(self, parser):
    -        group = parser.addgroup("general", "test selection and failure debug options")
    +        group = parser.addgroup("general", "general test process options")
             group._addoption('-v', '--verbose', action="count", 
                        dest="verbose", default=0, help="increase verbosity."),
             group._addoption('-x', '--exitfirst',
    @@ -61,9 +61,11 @@
             group._addoption('--fulltrace',
                        action="store_true", dest="fulltrace", default=False,
                        help="don't cut any tracebacks (default is to cut).")
    -        group._addoption('-s', '--nocapture',
    +        group._addoption('-s', 
                        action="store_true", dest="nocapture", default=False,
    -                   help="disable catching of sys.stdout/stderr output."),
    +                   help="disable catching of stdout/stderr during test run.")
    +        group._addoption('--iocapture', action="store", default="fd", metavar="method",
    +                   help="set iocapturing method: fd|sys|no.")
             group.addoption('--basetemp', dest="basetemp", default=None, metavar="dir",
                        help="temporary directory for this test run.")
             group.addoption('--boxed',
    @@ -75,7 +77,7 @@
                              "and instantiate 'HelloPlugin' from the module."))
             group._addoption('-f', '--looponfailing',
                        action="store_true", dest="looponfailing", default=False,
    -                   help="loop on failing test set.")
    +                   help="run tests, loop on failing test set, until all pass. repeat forever.")
     
             group = parser.addgroup("test process debugging")
             group.addoption('--collectonly',
    
    Modified: py/trunk/py/test/plugin/pytest_terminal.py
    ==============================================================================
    --- py/trunk/py/test/plugin/pytest_terminal.py	(original)
    +++ py/trunk/py/test/plugin/pytest_terminal.py	Sat Mar 21 20:58:41 2009
    @@ -96,7 +96,7 @@
             else:
                 d['extra'] = ""
             d['cwd'] = rinfo.cwd
    -        self.write_line("%(id)s new %(spec)r -- platform %(platform)s, "
    +        self.write_line("%(id)s %(spec)s -- platform %(platform)s, "
                             "Python %(version)s "
                             "cwd: %(cwd)s"
                             "%(extra)s" % d)
    @@ -460,7 +460,7 @@
     
             rep.pyevent_gwmanage_newgateway(gw1, rinfo)
             linecomp.assert_contains_lines([
    -            "X1 new 'popen' *xyz*2.5*"
    +            "X1*popen*xyz*2.5*"
             ])
     
             rep.pyevent_gwmanage_rsyncstart(source="hello", gateways=[gw1, gw2])
    
    Modified: py/trunk/py/test/testing/test_config.py
    ==============================================================================
    --- py/trunk/py/test/testing/test_config.py	(original)
    +++ py/trunk/py/test/testing/test_config.py	Sat Mar 21 20:58:41 2009
    @@ -236,13 +236,13 @@
     
         def test_config_iocapturing(self, testdir):
             config = testdir.parseconfig(testdir.tmpdir)
    -        assert config.getvalue("conf_iocapture")
    +        assert config.getvalue("iocapture")
             tmpdir = testdir.tmpdir.ensure("sub-with-conftest", dir=1)
             tmpdir.join("conftest.py").write(py.code.Source("""
    -            conf_iocapture = "no"
    +            pytest_option_iocapture = "no"
             """))
             config = py.test.config._reparse([tmpdir])
    -        assert config.getvalue("conf_iocapture") == "no"
    +        assert config.getvalue("iocapture") == "no"
             capture = config._getcapture()
             assert isinstance(capture, py.io.StdCapture)
             assert not capture._out
    @@ -252,7 +252,7 @@
             for opt, cls in (("sys", py.io.StdCapture),  
                              ("fd", py.io.StdCaptureFD), 
                             ):
    -            config.option.conf_iocapture = opt
    +            config.option.iocapture = opt
                 capture = config._getcapture()
                 assert isinstance(capture, cls) 
     
    
    
    From hpk at codespeak.net  Sat Mar 21 21:02:05 2009
    From: hpk at codespeak.net (hpk at codespeak.net)
    Date: Sat, 21 Mar 2009 21:02:05 +0100 (CET)
    Subject: [py-svn] r63194 - py/trunk/py/test/plugin
    Message-ID: <20090321200205.6D4D116844C@codespeak.net>
    
    Author: hpk
    Date: Sat Mar 21 21:02:04 2009
    New Revision: 63194
    
    Modified:
       py/trunk/py/test/plugin/pytest_default.py
    Log:
    more option streamlining
    
    
    Modified: py/trunk/py/test/plugin/pytest_default.py
    ==============================================================================
    --- py/trunk/py/test/plugin/pytest_default.py	(original)
    +++ py/trunk/py/test/plugin/pytest_default.py	Sat Mar 21 21:02:04 2009
    @@ -97,12 +97,11 @@
             group._addoption('-d', '--dist',
                        action="store_true", dest="dist", default=False,
                        help="ad-hoc distribute tests across machines (requires conftest settings)") 
    -        group._addoption('-n', '--numprocesses', dest="numprocesses", default=0, metavar="num", 
    +        group._addoption('-n', dest="numprocesses", default=0, metavar="numprocesses", 
                        action="store", type="int", 
                        help="number of local test processes. conflicts with --dist.")
    -        group.addoption('--rsyncdirs', dest="rsyncdirs", default=None, metavar="dir1,dir2,...", 
    -                   help="comma-separated list of directories to rsync. All those roots will be rsynced "
    -                        "into a corresponding subdir on the remote sides. ")
    +        group.addoption('--rsyncdir', action="append", default=[], metavar="dir1", 
    +                   help="add local directory for rsync to remote test nodes.")
             group._addoption('--tx', dest="xspec", action="append", 
                        help=("add a test environment, specified in XSpec syntax. examples: "
                              "--tx popen//python=python2.5 --tx socket=192.168.1.102"))
    
    
    From hpk at codespeak.net  Sat Mar 21 21:07:49 2009
    From: hpk at codespeak.net (hpk at codespeak.net)
    Date: Sat, 21 Mar 2009 21:07:49 +0100 (CET)
    Subject: [py-svn] r63195 - in py/trunk/py/test: . dist/testing looponfail
    	looponfail/testing plugin testing
    Message-ID: <20090321200749.5195316844C@codespeak.net>
    
    Author: hpk
    Date: Sat Mar 21 21:07:45 2009
    New Revision: 63195
    
    Modified:
       py/trunk/py/test/config.py
       py/trunk/py/test/dist/testing/test_functional_dsession.py
       py/trunk/py/test/dist/testing/test_nodemanage.py
       py/trunk/py/test/looponfail/remote.py
       py/trunk/py/test/looponfail/testing/test_remote.py
       py/trunk/py/test/plugin/pytest_default.py
       py/trunk/py/test/plugin/pytest_plugintester.py
       py/trunk/py/test/plugin/pytest_terminal.py
       py/trunk/py/test/testing/acceptance_test.py
    Log:
    * fix rsyncdir usages all around
    * rename looponfailing to looponfail
    
    
    
    Modified: py/trunk/py/test/config.py
    ==============================================================================
    --- py/trunk/py/test/config.py	(original)
    +++ py/trunk/py/test/config.py	Sat Mar 21 21:07:45 2009
    @@ -267,21 +267,18 @@
     
         def getrsyncdirs(self):
             config = self 
    -        roots = config.option.rsyncdirs
    -        if roots:
    -            roots = [py.path.local(x) for x in roots.split(',')]
    -        else:
    -            roots = []
    +        roots = config.option.rsyncdir
             conftestroots = config.getconftest_pathlist("rsyncdirs")
             if conftestroots:
                 roots.extend(conftestroots)
             pydir = py.path.local(py.__file__).dirpath()
    +        roots = [py.path.local(root) for root in roots]
             for root in roots:
                 if not root.check():
    -                raise ValueError("rsyncdir doesn't exist: %r" %(root,))
    +                raise config.Error("rsyncdir doesn't exist: %r" %(root,))
                 if pydir is not None and root.basename == "py":
                     if root != pydir:
    -                    raise ValueError("root %r conflicts with current %r" %(root, pydir))
    +                    raise config.Error("root %r conflicts with imported %r" %(root, pydir))
                     pydir = None
             if pydir is not None:
                 roots.append(pydir)
    
    Modified: py/trunk/py/test/dist/testing/test_functional_dsession.py
    ==============================================================================
    --- py/trunk/py/test/dist/testing/test_functional_dsession.py	(original)
    +++ py/trunk/py/test/dist/testing/test_functional_dsession.py	Sat Mar 21 21:07:45 2009
    @@ -64,7 +64,7 @@
             subdir.ensure("__init__.py")
             p = subdir.join("test_one.py")
             p.write("def test_5(): assert not __file__.startswith(%r)" % str(p))
    -        result = testdir.runpytest("-d", "--rsyncdirs=%(subdir)s" % locals(), 
    +        result = testdir.runpytest("-d", "--rsyncdir=%(subdir)s" % locals(), 
                 "--tx=popen//chdir=%(dest)s" % locals(), p)
             assert result.ret == 0
             result.stdout.fnmatch_lines([
    
    Modified: py/trunk/py/test/dist/testing/test_nodemanage.py
    ==============================================================================
    --- py/trunk/py/test/dist/testing/test_nodemanage.py	(original)
    +++ py/trunk/py/test/dist/testing/test_nodemanage.py	Sat Mar 21 21:07:45 2009
    @@ -36,7 +36,7 @@
                 dest.remove()
                 nodemanager = NodeManager(testdir.parseconfig(
                     "--tx", "popen//chdir=%s" % dest,
    -                "--rsyncdirs", rsyncroot,
    +                "--rsyncdir", rsyncroot,
                     source, 
                 ))
                 assert nodemanager.config.topdir == source
    @@ -112,7 +112,7 @@
                 def test_one():
                     pass
             """)
    -        sorter = testdir.inline_run("-d", "--rsyncdirs=%s" % testdir.tmpdir, 
    +        sorter = testdir.inline_run("-d", "--rsyncdir=%s" % testdir.tmpdir, 
                     "--tx=%s" % specssh, testdir.tmpdir)
             ev = sorter.getfirstnamed("itemtestreport")
             assert ev.passed 
    @@ -133,7 +133,7 @@
             assert xspecs[1].ssh == "xyz"
     
         def test_getconfigroots(self, testdir):
    -        config = testdir.parseconfig('--rsyncdirs=' + str(testdir.tmpdir))
    +        config = testdir.parseconfig('--rsyncdir=' + str(testdir.tmpdir))
             roots = config.getrsyncdirs()
             assert len(roots) == 1 + 1 
             assert testdir.tmpdir in roots
    @@ -146,7 +146,7 @@
             testdir.makeconftest("""
                 rsyncdirs= 'x', 
             """)
    -        config = testdir.parseconfig(testdir.tmpdir, '--rsyncdirs=y,z')
    +        config = testdir.parseconfig(testdir.tmpdir, '--rsyncdir=y', '--rsyncdir=z')
             roots = config.getrsyncdirs()
             assert len(roots) == 3 + 1 
             assert py.path.local('y') in roots 
    
    Modified: py/trunk/py/test/looponfail/remote.py
    ==============================================================================
    --- py/trunk/py/test/looponfail/remote.py	(original)
    +++ py/trunk/py/test/looponfail/remote.py	Sat Mar 21 21:07:45 2009
    @@ -115,7 +115,7 @@
     
         DEBUG("SLAVE: received configuration, using topdir:", config.topdir)
         #config.option.session = None
    -    config.option.looponfailing = False 
    +    config.option.looponfail = False 
         config.option.usepdb = False 
         trails = channel.receive()
         config.pytestplugins.do_configure(config)
    @@ -150,5 +150,5 @@
         DEBUG("SLAVE: starting session.main()")
         session.main(colitems)
         ev = event.LooponfailingInfo(list(failreports), [config.topdir])
    -    session.bus.notify("looponfailinginfo", ev)
    +    session.bus.notify("looponfailinfo", ev)
         channel.send([x.colitem._totrail() for x in failreports if x.failed])
    
    Modified: py/trunk/py/test/looponfail/testing/test_remote.py
    ==============================================================================
    --- py/trunk/py/test/looponfail/testing/test_remote.py	(original)
    +++ py/trunk/py/test/looponfail/testing/test_remote.py	Sat Mar 21 21:07:45 2009
    @@ -46,7 +46,7 @@
             assert str(failures).find("test_new") != -1
     
     class TestLooponFailing:
    -    def test_looponfailing_from_fail_to_ok(self, testdir):
    +    def test_looponfail_from_fail_to_ok(self, testdir):
             modcol = testdir.getmodulecol("""
                 def test_one():
                     x = 0
    @@ -71,7 +71,7 @@
             session.loop_once(loopstate)
             assert not loopstate.colitems 
     
    -    def test_looponfailing_from_one_to_two_tests(self, testdir):
    +    def test_looponfail_from_one_to_two_tests(self, testdir):
             modcol = testdir.getmodulecol("""
                 def test_one():
                     assert 0
    @@ -96,7 +96,7 @@
             session.loop_once(loopstate)
             assert len(loopstate.colitems) == 1
     
    -    def test_looponfailing_removed_test(self, testdir):
    +    def test_looponfail_removed_test(self, testdir):
             modcol = testdir.getmodulecol("""
                 def test_one():
                     assert 0
    
    Modified: py/trunk/py/test/plugin/pytest_default.py
    ==============================================================================
    --- py/trunk/py/test/plugin/pytest_default.py	(original)
    +++ py/trunk/py/test/plugin/pytest_default.py	Sat Mar 21 21:07:45 2009
    @@ -75,8 +75,8 @@
                        help=("load the specified plugin after command line parsing. "
                              "Example: '-p hello' will trigger 'import pytest_hello' "
                              "and instantiate 'HelloPlugin' from the module."))
    -        group._addoption('-f', '--looponfailing',
    -                   action="store_true", dest="looponfailing", default=False,
    +        group._addoption('-f', '--looponfail',
    +                   action="store_true", dest="looponfail", default=False,
                        help="run tests, loop on failing test set, until all pass. repeat forever.")
     
             group = parser.addgroup("test process debugging")
    @@ -116,8 +116,8 @@
     
         def fixoptions(self, config):
             if config.getvalue("usepdb"):
    -            if config.getvalue("looponfailing"):
    -                raise config.Error("--pdb incompatible with --looponfailing.")
    +            if config.getvalue("looponfail"):
    +                raise config.Error("--pdb incompatible with --looponfail.")
                 if config.getvalue("dist"):
                     raise config.Error("--pdb incomptaible with distributed testing.")
     
    @@ -132,7 +132,7 @@
                 from py.__.test.session import Session
                 config.setsessionclass(Session)
             else:
    -            if val("looponfailing"):
    +            if val("looponfail"):
                     from py.__.test.looponfail.remote import LooponfailingSession
                     config.setsessionclass(LooponfailingSession)
                 elif val("numprocesses") or val("dist"):
    @@ -179,7 +179,7 @@
             py.test.raises(config.Error, 
                 "config.pytestplugins.do_configure(config)")
         conflict_options = (
    -        '--looponfailing --pdb',
    +        '--looponfail --pdb',
             '--dist --pdb', 
         )
         for spec in conflict_options: 
    
    Modified: py/trunk/py/test/plugin/pytest_plugintester.py
    ==============================================================================
    --- py/trunk/py/test/plugin/pytest_plugintester.py	(original)
    +++ py/trunk/py/test/plugin/pytest_plugintester.py	Sat Mar 21 21:07:45 2009
    @@ -209,7 +209,7 @@
         def pyevent_rescheduleitems(self, event):
             """ Items from a node that went down. """
     
    -    def pyevent_looponfailinginfo(self, event):
    +    def pyevent_looponfailinfo(self, event):
             """ info for repeating failing tests. """
     
         def pyevent_plugin_registered(self, plugin):
    
    Modified: py/trunk/py/test/plugin/pytest_terminal.py
    ==============================================================================
    --- py/trunk/py/test/plugin/pytest_terminal.py	(original)
    +++ py/trunk/py/test/plugin/pytest_terminal.py	Sat Mar 21 21:07:45 2009
    @@ -223,7 +223,7 @@
             self.summary_deselected()
             self.summary_stats()
     
    -    def pyevent_looponfailinginfo(self, event):
    +    def pyevent_looponfailinfo(self, event):
             if event.failreports:
                 self.write_sep("#", "LOOPONFAILING", red=True)
                 for report in event.failreports:
    @@ -483,7 +483,7 @@
             assert lines[1].endswith("xy.py .")
             assert lines[2] == "hello world"
     
    -    def test_looponfailingreport(self, testdir, linecomp):
    +    def test_looponfailreport(self, testdir, linecomp):
             modcol = testdir.getmodulecol("""
                 def test_fail():
                     assert 0
    @@ -492,10 +492,10 @@
             """)
             rep = TerminalReporter(modcol.config, file=linecomp.stringio)
             reports = [basic_run_report(x) for x in modcol.collect()]
    -        rep.pyevent_looponfailinginfo(event.LooponfailingInfo(reports, [modcol.config.topdir]))
    +        rep.pyevent_looponfailinfo(event.LooponfailingInfo(reports, [modcol.config.topdir]))
             linecomp.assert_contains_lines([
    -            "*test_looponfailingreport.py:2: assert 0",
    -            "*test_looponfailingreport.py:4: ValueError*",
    +            "*test_looponfailreport.py:2: assert 0",
    +            "*test_looponfailreport.py:4: ValueError*",
                 "*waiting*", 
                 "*%s*" % (modcol.config.topdir),
             ])
    
    Modified: py/trunk/py/test/testing/acceptance_test.py
    ==============================================================================
    --- py/trunk/py/test/testing/acceptance_test.py	(original)
    +++ py/trunk/py/test/testing/acceptance_test.py	Sat Mar 21 21:07:45 2009
    @@ -401,24 +401,24 @@
             if child.isalive(): 
                 child.wait()
     
    -    def test_simple_looponfailing_interaction(self, testdir):
    +    def test_simple_looponfail_interaction(self, testdir):
             spawn = self.getspawn(testdir.tmpdir)
             p1 = testdir.makepyfile("""
                 def test_1():
                     assert 1 == 0 
             """)
             p1.setmtime(p1.mtime() - 50.0)  
    -        child = spawn("%s %s --looponfailing %s" % (py.std.sys.executable, pytestpath, p1))
    +        child = spawn("%s %s --looponfail %s" % (py.std.sys.executable, pytestpath, p1))
             child.timeout = EXPECTTIMEOUT
             child.expect("assert 1 == 0")
    -        child.expect("test_simple_looponfailing_interaction.py:")
    +        child.expect("test_simple_looponfail_interaction.py:")
             child.expect("1 failed")
             child.expect("waiting for changes")
             p1.write(py.code.Source("""
                 def test_1():
                     assert 1 == 1
             """))
    -        child.expect("MODIFIED.*test_simple_looponfailing_interaction.py", timeout=4.0)
    +        child.expect("MODIFIED.*test_simple_looponfail_interaction.py", timeout=4.0)
             child.expect("1 passed", timeout=5.0)
             child.kill(15)
     
    
    
    From hpk at codespeak.net  Sat Mar 21 21:20:04 2009
    From: hpk at codespeak.net (hpk at codespeak.net)
    Date: Sat, 21 Mar 2009 21:20:04 +0100 (CET)
    Subject: [py-svn] r63197 - py/trunk/py/test/plugin
    Message-ID: <20090321202004.657B3168473@codespeak.net>
    
    Author: hpk
    Date: Sat Mar 21 21:20:02 2009
    New Revision: 63197
    
    Modified:
       py/trunk/py/test/plugin/pytest_default.py
       py/trunk/py/test/plugin/pytest_terminal.py
    Log:
    * don't think "showskipsummary" is a useful option
    * some typo fixes
    
    
    Modified: py/trunk/py/test/plugin/pytest_default.py
    ==============================================================================
    --- py/trunk/py/test/plugin/pytest_default.py	(original)
    +++ py/trunk/py/test/plugin/pytest_default.py	Sat Mar 21 21:20:02 2009
    @@ -33,7 +33,7 @@
             return Directory(path, parent=parent)
     
         def pytest_addoption(self, parser):
    -        group = parser.addgroup("general", "general test process options")
    +        group = parser.addgroup("general", "test collection and failure interaction options")
             group._addoption('-v', '--verbose', action="count", 
                        dest="verbose", default=0, help="increase verbosity."),
             group._addoption('-x', '--exitfirst',
    @@ -48,26 +48,19 @@
             group._addoption('-l', '--showlocals',
                        action="store_true", dest="showlocals", default=False,
                        help="show locals in tracebacks (disabled by default).")
    -        group._addoption('--showskipsummary',
    -                   action="store_true", dest="showskipsummary", default=False,
    -                   help="always show summary of skipped tests") 
    +        #group._addoption('--showskipsummary',
    +        #           action="store_true", dest="showskipsummary", default=False,
    +        #           help="always show summary of skipped tests") 
             group._addoption('--pdb',
                        action="store_true", dest="usepdb", default=False,
                        help="start pdb (the Python debugger) on errors.")
    -        group._addoption('--tb',
    +        group._addoption('--tb', metavar="style", 
                        action="store", dest="tbstyle", default='long',
                        type="choice", choices=['long', 'short', 'no'],
                        help="traceback verboseness (long/short/no).")
    -        group._addoption('--fulltrace',
    -                   action="store_true", dest="fulltrace", default=False,
    -                   help="don't cut any tracebacks (default is to cut).")
             group._addoption('-s', 
                        action="store_true", dest="nocapture", default=False,
                        help="disable catching of stdout/stderr during test run.")
    -        group._addoption('--iocapture', action="store", default="fd", metavar="method",
    -                   help="set iocapturing method: fd|sys|no.")
    -        group.addoption('--basetemp', dest="basetemp", default=None, metavar="dir",
    -                   help="temporary directory for this test run.")
             group.addoption('--boxed',
                        action="store_true", dest="boxed", default=False,
                        help="box each test run in a separate process") 
    @@ -89,6 +82,14 @@
             group._addoption('--nomagic',
                        action="store_true", dest="nomagic", default=False,
                        help="don't reinterpret asserts, no traceback cutting. ")
    +        group._addoption('--fulltrace',
    +                   action="store_true", dest="fulltrace", default=False,
    +                   help="don't cut any tracebacks (default is to cut).")
    +        group.addoption('--basetemp', dest="basetemp", default=None, metavar="dir",
    +                   help="base temporary directory for this test run.")
    +        group._addoption('--iocapture', action="store", default="fd", metavar="method",
    +                   type="choice", choices=['fd', 'sys', 'no'],
    +                   help="set iocapturing method: fd|sys|no.")
             group.addoption('--debug',
                        action="store_true", dest="debug", default=False,
                        help="generate and show debugging information.")
    
    Modified: py/trunk/py/test/plugin/pytest_terminal.py
    ==============================================================================
    --- py/trunk/py/test/plugin/pytest_terminal.py	(original)
    +++ py/trunk/py/test/plugin/pytest_terminal.py	Sat Mar 21 21:20:02 2009
    @@ -267,7 +267,7 @@
     
         def summary_skips(self):
             if 'skipped' in self.stats:
    -            if 'failed' not in self.stats or self.config.option.showskipsummary:
    +            if 'failed' not in self.stats: #  or self.config.option.showskipsummary:
                     fskips = folded_skips(self.stats['skipped'])
                     if fskips:
                         self.write_sep("_", "skipped test summary")
    @@ -557,7 +557,8 @@
                     import py; py.test.skip('skip me please!')
                 def test_interrupt_me():
                     raise KeyboardInterrupt   # simulating the user
    -        """, configargs=("--showskipsummary",) + ("-v",)*verbose)
    +        """, configargs=("-v",)*verbose)
    +        #""", configargs=("--showskipsummary",) + ("-v",)*verbose)
             rep = TerminalReporter(modcol.config, file=linecomp.stringio)
             modcol.config.bus.register(rep)
             bus = modcol.config.bus
    @@ -579,7 +580,7 @@
                 ">       assert 0",
                 "E       assert 0",
             ])
    -        assert "Skipped: 'skip me please!'" in text
    +        #assert "Skipped: 'skip me please!'" in text
             assert "_keyboard_interrupt.py:6: KeyboardInterrupt" in text
             see_details = "raise KeyboardInterrupt   # simulating the user" in text
             assert see_details == verbose
    
    
    From hpk at codespeak.net  Sun Mar 22 01:38:46 2009
    From: hpk at codespeak.net (hpk at codespeak.net)
    Date: Sun, 22 Mar 2009 01:38:46 +0100 (CET)
    Subject: [py-svn] r63200 - py/trunk/py/doc
    Message-ID: <20090322003846.99E0116841E@codespeak.net>
    
    Author: hpk
    Date: Sun Mar 22 01:38:43 2009
    New Revision: 63200
    
    Added:
       py/trunk/py/doc/test-dist.txt
       py/trunk/py/doc/test-examples.txt
       py/trunk/py/doc/test-ext.txt
       py/trunk/py/doc/test-features.txt
       py/trunk/py/doc/test-quickstart.txt
    Modified:
       py/trunk/py/doc/contact.txt
       py/trunk/py/doc/impl-test.txt
       py/trunk/py/doc/test-config.txt
       py/trunk/py/doc/test-plugins.txt
       py/trunk/py/doc/test.txt
    Log:
    new docs about testing 
    refactoring of documentation
    new entry page
    
    
    
    Modified: py/trunk/py/doc/contact.txt
    ==============================================================================
    --- py/trunk/py/doc/contact.txt	(original)
    +++ py/trunk/py/doc/contact.txt	Sun Mar 22 01:38:43 2009
    @@ -1,6 +1,8 @@
     py lib contact and communication 
     ===================================
     
    +- You may also subscribe to `tetamap`_, where Holger Krekel
    +  often posts about testing and py.test related news. 
     
     - **#pylib on irc.freenode.net**: you are welcome
       to lurk or ask questions in this IRC channel, it also tracks py lib commits. 
    @@ -20,6 +22,8 @@
     
     .. _`get an account`:  
     
    +.. _tetamap: http://tetamap.wordpress.com
    +
     
     get an account on codespeak  
     --------------------------- 
    
    Modified: py/trunk/py/doc/impl-test.txt
    ==============================================================================
    --- py/trunk/py/doc/impl-test.txt	(original)
    +++ py/trunk/py/doc/impl-test.txt	Sun Mar 22 01:38:43 2009
    @@ -131,28 +131,6 @@
         
         py.test --traceconfig
     
    -adding custom options
    -+++++++++++++++++++++++
    -
    -To register a project-specific command line option 
    -you may have the following code within a ``conftest.py`` file::
    -
    -    import py
    -    Option = py.test.config.Option
    -    option = py.test.config.addoptions("pypy options",
    -        Option('-V', '--view', action="store_true", dest="view", default=False,
    -               help="view translation tests' flow graphs with Pygame"),
    -    )
    -
    -and you can then access ``option.view`` like this:: 
    -
    -    if option.view:
    -        print "view this!"
    -
    -The option will be available if you type ``py.test -h``
    -Note that you may only register upper case short
    -options.  ``py.test`` reserves all lower 
    -case short options for its own cross-project usage. 
     
     customizing the collecting and running process 
     -----------------------------------------------
    @@ -245,7 +223,7 @@
     parameter sets but counting each of the calls as a separate
     tests. 
     
    -.. _`generative tests`: test.html#generative-tests
    +.. _`generative tests`: test-features.html#generative-tests
     
     The other extension possibility is about 
     specifying a custom test ``Item`` class which 
    
    Modified: py/trunk/py/doc/test-config.txt
    ==============================================================================
    --- py/trunk/py/doc/test-config.txt	(original)
    +++ py/trunk/py/doc/test-config.txt	Sun Mar 22 01:38:43 2009
    @@ -1,28 +1,23 @@
     Test configuration
     ========================
     
    -using / specifying plugins
    --------------------------------
    -
    -you can instruct py.test to use additional plugins by:
    -
    -* setting the PYTEST_PLUGINS environment variable
    -  to a comma-separated list of plugins
    -* XXX supplying "--plugins=NAME1,NAME2,..." at the command line 
    -* setting "pytest_plugins='name1', 'name2'" in 
    -  ``conftest.py`` files or in python test modules. 
    +test options and values 
    +-----------------------------
     
    -py.test will load all plugins along with their dependencies
    -(plugins may specify "pytest_plugins" as well). 
    +You can see all available command line options by running::
     
    -test option values 
    ------------------------------
    +    py.test -h 
     
    -py.test will lookup the value of an option "NAME" in this order: 
    +py.test will lookup values of options in this order:
     
     * option value supplied at command line 
     * content of environment variable ``PYTEST_OPTION_NAME=...``
     * ``name = ...`` setting in the nearest ``conftest.py`` file.
     
    -This means that you can specify default options per-run, 
    -per shell session or per project directory. 
    +The name of an option usually is the one you find 
    +in the longform of the option, i.e. the name 
    +behind the ``--`` double-dash. 
    +
    +IOW, you can set default values for options per project, per
    +home-directoray, per shell session or per test-run. 
    +
    
    Added: py/trunk/py/doc/test-dist.txt
    ==============================================================================
    --- (empty file)
    +++ py/trunk/py/doc/test-dist.txt	Sun Mar 22 01:38:43 2009
    @@ -0,0 +1,107 @@
    +.. _`distribute tests across machines`:
    +
    +``py.test`` can ad-hoc distribute test runs to multiple CPUs or remote
    +machines.  This allows to speed up development or to use special resources
    +of remote machines.  Before running tests remotely, ``py.test`` efficiently 
    +synchronizes your program source code to the remote place.  All test results 
    +are reported back and displayed to your local test session.  You may 
    +specify different Python versions and interpreters.
    +
    +Speed up test runs by sending tests to multiple CPUs
    +----------------------------------------------------------
    +
    +To send tests to multiple CPUs, type::
    +
    +    py.test -n NUM
    +
    +Especially for longer running tests or tests requiring 
    +a lot of IO this can lead to considerable speed ups. 
    +
    +Test on a different python interpreter 
    +----------------------------------------------------------
    +
    +To send tests to a python2.4 process, you may type::
    +
    +    py.test --tx popen//python=python2.4
    +
    +This will start a subprocess which is run with the "python2.4"
    +Python interpreter, found in your system binary lookup path. 
    +
    +.. For convenience you may prepend ``3*`` to create three sub processes.  
    +
    +
    +Sending tests to remote SSH accounts
    +------------------------------------
    +
    +Suppose you have a package ``mypkg`` which contains some 
    +tests that you can successfully run locally. And you
    +have a ssh-reachable machine ``myhost``.  Then    
    +you can ad-hoc distribute your tests by typing::
    +
    +    py.test -d --tx ssh=myhostpopen --rsyncdir mypkg mypkg
    +
    +This will synchronize your ``mypkg`` package directory 
    +to an remote ssh account and then locally collect tests 
    +and send them to remote places for execution.  
    +
    +You can specify multiple ``--rsyncdir`` directories 
    +to be sent to the remote side. 
    +
    +Sending tests to remote Socket Servers
    +----------------------------------------
    +
    +Download the single-module `socketserver.py`_ Python program 
    +and run it on the specified hosts like this::
    +
    +    python socketserver.py
    +
    +It will tell you that it starts listening.  You can now
    +on your home machine specify this new socket host
    +with something like this::
    +
    +    py.test -d --tx socket=192.168.1.102:8888 --rsyncdir mypkg mypkg
    +
    +no remote installation requirements 
    +++++++++++++++++++++++++++++++++++++++++++++
    +
    +Synchronisation and running of tests only requires
    +a bare Python installation on the remote side.   No
    +special software is installed - this is realized through the
    +*zero installation* principle that the underlying 
    +`py.execnet`_ mechanisms implements. 
    +
    +
    +.. _`socketserver.py`: ../execnet/script/socketserver.py
    +.. _`py.execnet`: execnet.html
    +
    +Differences from local tests
    +----------------------------
    +
    +* Test order is rather random (instead of in file order). 
    +* the test process may hang due to network problems 
    +* you may not reference files outside of rsynced directory structures
    +
    +Specifying test exec environments in a conftest.py
    +-------------------------------------------------------------
    +
    +Instead of specifying command line options, you can 
    +put options values in a ``conftest.py`` file like this::
    +
    +    pytest_option_tx = ['ssh=myhost//python=python2.5', 'popen//python=python2.5']
    +    pytest_option_dist = True
    +
    +Any commandline ``--tx`` specifictions  will add to the list of available execution
    +environments. 
    +
    +Specifying "rsync" dirs in a conftest.py
    +-------------------------------------------------------------
    +
    +In your ``mypkg/conftest.py`` you may specify directories to synchronise
    +or to exclude::
    +
    +    rsyncdirs = ['.', '../plugins']
    +    rsyncignore = ['_cache']
    +
    +These directory specifications are relative to the directory
    +where the ``conftest.py`` is found.
    +
    
    Added: py/trunk/py/doc/test-examples.txt
    ==============================================================================
    --- (empty file)
    +++ py/trunk/py/doc/test-examples.txt	Sun Mar 22 01:38:43 2009
    @@ -0,0 +1,54 @@
    +
    +Working Examples
    +================
    +
    +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 py/documentation/example/pytest/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. Did we mention that lazyness
    +is a virtue? 
    
    Added: py/trunk/py/doc/test-ext.txt
    ==============================================================================
    --- (empty file)
    +++ py/trunk/py/doc/test-ext.txt	Sun Mar 22 01:38:43 2009
    @@ -0,0 +1,61 @@
    +
    +
    +Learning by examples
    +------------------------
    +
    +XXX
    +
    +adding custom options
    ++++++++++++++++++++++++
    +
    +py.test supports adding of standard optparse_ Options. 
    +A plugin may implement the ``addoption`` hook for registering 
    +custom options:: 
    +
    +    class ConftestPlugin:
    +        def pytest_addoption(self, parser):
    +            parser.addoption("-M", "--myopt", action="store", 
    +                help="specify string to set myopt")
    +
    +        def pytest_configure(self, config):
    +            if config.option.myopt:
    +                # do action based on option value
    +
    +.. _optparse: http://docs.python.org/library/optparse.html
    +
    +Setting default values for test options
    +-----------------------------------------
    +
    +You can see all available command line options by running::
    +
    +    py.test -h 
    +
    +py.test will lookup values of options in this order:
    +
    +* option value supplied at command line 
    +* content of environment variable ``PYTEST_OPTION_NAME=...``
    +* ``name = ...`` setting in the nearest ``conftest.py`` file.
    +
    +The name of an option usually is the one you find 
    +in the longform of the option, i.e. the name 
    +behind the ``--`` double-dash. 
    +
    +IOW, you can set default values for options per project, per
    +home-directoray, per shell session or per test-run. 
    +
    +
    +
    +Plugin methods 
    +----------------------------------
    +
    +A Plugin class may implement the following attributes and methods: 
    +
    +XXX
    +
    +_`pytest event`: 
    +
    +Pytest Events 
    +-------------------
    +
    +XXX
    +
    
    Added: py/trunk/py/doc/test-features.txt
    ==============================================================================
    --- (empty file)
    +++ py/trunk/py/doc/test-features.txt	Sun Mar 22 01:38:43 2009
    @@ -0,0 +1,246 @@
    +
    +Basic Features of ``py.test`` 
    +=============================
    +
    +automatic collection of tests on all levels
    +-------------------------------------------
    +
    +The automated test collection process walks the current
    +directory (or the directory given as a command line argument)
    +and all its subdirectories and collects python modules with a
    +leading ``test_`` or trailing ``_test`` filename.  From each 
    +test module every function with a leading ``test_`` or class with 
    +a leading ``Test`` name is collected.  The collecting process can 
    +be customized at directory, module or class level.  (see 
    +`collection process`_ for some implementation details). 
    +
    +.. _`generative tests`: 
    +.. _`collection process`: impl-test.html#collection-process
    +
    +assert with the ``assert`` statement
    +------------------------------------
    +
    +``py.test`` allows to use the standard python
    +``assert statement`` for verifying expectations 
    +and values in Python tests.  For example, you can 
    +write the following in your tests:: 
    +
    +     assert hasattr(x, 'attribute') 
    +
    +to state that your object has a certain ``attribute``. In case this
    +assertion fails you will see the value of ``x``.  Intermediate
    +values are computed by executing the assert expression a second time. 
    +If you execute code with side effects, e.g. read from a file like this::
    +
    +        assert f.read() != '...'
    +
    +then you may get a warning from pytest if that assertions
    +first failed and then succeeded. 
    +
    +asserting expected exceptions 
    +----------------------------------------------
    +
    +In order to write assertions about exceptions, you use
    +one of two forms::
    +
    +    py.test.raises(Exception, func, *args, **kwargs) 
    +    py.test.raises(Exception, "func(*args, **kwargs)")
    +
    +both of which execute the given function with args and kwargs and
    +asserts that the given ``Exception`` is raised.  The reporter will
    +provide you with helpful output in case of failures such as *no
    +exception* or *wrong exception*.
    +
    +dynamically skipping tests 
    +----------------------------------------
    +
    +If you want to skip tests you can use ``py.test.skip`` within
    +test or setup functions.  Example::
    +
    +    py.test.skip("message")
    +
    +You can also use a helper to skip on a failing import::
    +
    +    docutils = py.test.importorskip("docutils")
    +
    +or to skip if a library does not have the right version::
    +
    +    docutils = py.test.importorskip("docutils", minversion="0.3")
    +
    +The version will be read from the module's ``__version__`` attribute. 
    +
    +
    +generative tests: yielding more tests
    +------------------------------------- 
    +
    +*Generative tests* are test methods that are *generator functions* which
    +``yield`` callables and their arguments.  This is most useful for running a
    +test function multiple times against different parameters.  Example::
    +
    +    def test_generative(): 
    +        for x in (42,17,49): 
    +            yield check, x 
    +    
    +    def check(arg): 
    +        assert arg % 7 == 0   # second generated tests fails!
    +
    +Note that ``test_generative()`` will cause three tests 
    +to get run, notably ``check(42)``, ``check(17)`` and ``check(49)``
    +of which the middle one will obviously fail. 
    +
    +To make it easier to distinguish the generated tests it is possible to specify an explicit name for them, like for example::
    +
    +    def test_generative(): 
    +        for x in (42,17,49): 
    +            yield "case %d" % x, check, x 
    +
    +
    +.. _`selection by keyword`: 
    +
    +selecting/unselecting tests by keyword 
    +---------------------------------------------
    +
    +Pytest's keyword mechanism provides a powerful way to 
    +group and selectively run tests in your test code base. 
    +You can selectively run tests by specifiying a keyword 
    +on the command line.  Examples:
    +
    +    py.test -k test_simple   
    +    py.test -k "-test_simple"
    +
    +will run all tests matching (or not matching) the
    +"test_simple" keyword.  Note that you need to quote 
    +the keyword if "-" is recognized as an indicator 
    +for a commandline option.  Lastly, you may use 
    +
    +    py.test. -k "test_simple:"
    +
    +which will run all tests after the expression has *matched once*, i.e. 
    +all tests that are seen after a test that matches the "test_simple" 
    +keyword. 
    +
    +By default, all filename parts and
    +class/function names of a test function are put into the set
    +of keywords for a given test.  You may specify additional 
    +kewords like this::
    +
    +    @py.test.mark(webtest=True)
    +    def test_send_http():
    +        ... 
    +
    +testing with multiple python versions / executables 
    +---------------------------------------------------
    +
    +With ``--tx EXECUTABLE`` you can specify a python
    +executable (e.g. ``python2.2``) with which the tests 
    +will be executed. 
    +
    +
    +testing starts immediately 
    +--------------------------
    +
    +Testing starts as soon as the first ``test item`` 
    +is collected.  The collection process is iterative 
    +and does not need to complete before your first 
    +test items are executed. 
    +
    +support for modules containing tests
    +--------------------------------------
    +
    +As ``py.test`` 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
    +----------------------------------
    +
    +By default, ``py.test`` catches text written to stdout/stderr during
    +the execution of each individual test. This output will only be
    +displayed however if the test fails; you will not see it
    +otherwise. This allows you to put debugging print statements in your
    +code without being overwhelmed by all the output that might be
    +generated by tests that do not fail.
    +
    +Each failing test that produced output during the running of the test
    +will have its output displayed in the ``recorded stdout`` section.
    +
    +The catching of stdout/stderr output can be disabled using the 
    +``--nocapture`` option to the ``py.test`` tool.  Any output will 
    +in this case be displayed as soon as it is generated.
    +
    +test execution order
    +--------------------------------
    +
    +Tests usually run in the order in which they appear in the files. 
    +However, tests should not rely on running one after another, as
    +this prevents more advanced usages: running tests
    +distributedly or selectively, or in "looponfailing" mode,
    +will cause them to run in random order. 
    +
    +useful tracebacks, recursion detection 
    +--------------------------------------
    +
    +A lot of care is taken to present nice tracebacks in case of test
    +failure. Try::
    +
    +    py.test py/doc/example/pytest/failure_demo.py
    +
    +to see a variety of 17 tracebacks, each tailored to a different
    +failure situation.
    +
    +``py.test`` uses the same order for presenting tracebacks as Python
    +itself: the oldest function call is shown first, and the most recent call is
    +shown last. A ``py.test`` reported traceback starts with your
    +failing test function.  If the maximum recursion depth has been
    +exceeded during the running of a test, for instance because of
    +infinite recursion, ``py.test`` will indicate where in the
    +code the recursion was taking place.  You can  inhibit
    +traceback "cutting" magic by supplying ``--fulltrace``. 
    +
    +There is also the possibility of using ``--tb=short`` to get regular CPython
    +tracebacks. Or you can use ``--tb=no`` to not show any tracebacks at all.
    +
    +no inheritance requirement 
    +--------------------------
    +
    +Test classes are recognized by their leading ``Test`` name.  Unlike
    +``unitest.py``, you don't need to inherit from some base class to make
    +them be found by the test runner. Besides being easier, it also allows
    +you to write test classes that subclass from application level
    +classes.
    +
    +disabling a test class
    +---------------------- 
    +
    +If you want to disable a complete test class you
    +can set the class-level attribute ``disabled``. 
    +For example, in order to avoid running some tests on Win32:: 
    +
    +    class TestPosixOnly: 
    +        disabled = sys.platform == 'win32'
    +    
    +        def test_xxx(self):
    +            ... 
    +
    +testing for deprecated APIs
    +------------------------------
    +
    +In your tests you can use ``py.test.deprecated_call(func, *args, **kwargs)``
    +to test that a particular function call triggers a DeprecationWarning. 
    +This is useful for testing phasing out of old APIs in your projects. 
    +
    +doctest support 
    +-------------------
    +
    +If you want to integrate doctests, ``py.test`` now by default
    +picks up files matching the ``test_*.txt`` or ``*_test.txt`` 
    +patterns and processes them as text files containing doctests. 
    +This is an experimental feature and likely to change
    +its implementation. 
    +
    +
    +.. _`reStructured Text`: http://docutils.sourceforge.net
    +.. _`Python debugger`: http://docs.python.org/lib/module-pdb.html
    +
    +
    +.. _nose: http://somethingaboutorange.com/mrl/projects/nose/
    
    Modified: py/trunk/py/doc/test-plugins.txt
    ==============================================================================
    --- py/trunk/py/doc/test-plugins.txt	(original)
    +++ py/trunk/py/doc/test-plugins.txt	Sun Mar 22 01:38:43 2009
    @@ -1,67 +1,56 @@
     
    -pytest plugins
    -==================
    +Many of py.test's features are implemented as a plugin. 
     
    -specifying plugins for directories or test modules 
    ----------------------------------------------------------
    -
    -py.test loads and configures plugins at tool startup and whenever 
    -it encounters new confest or test modules which 
    -contain a ``pytest_plugins`` definition.  At tool 
    -startup the ``PYTEST_PLUGINS`` environment variable 
    -is considered as well. 
    -
    -Example
    -++++++++++
    -
    -If you create a ``conftest.py`` file with the following content:: 
    +Available plugins
    +-----------------------
     
    -    pytest_plugins = "pytest_plugin1", MyLocalPluginClass
    +py.test has a number of default plugins.  You can see which 
    +ones by specifying ``--trace=config``. 
     
    -then test execution within that directory can make use 
    -of the according instantiated plugins:
    +* adding reporting facilities, examples:
    +  pytest_terminal: default reporter for writing info to terminals 
    +  pytest_resultlog: log test results in machine-readable form to a file 
    +  pytest_eventlog: log all internal pytest events to a file 
     
    -* the module ``pytest_plugin1`` will be imported and 
    -  and its contained `Plugin1`` class instantiated. 
    -  A plugin module can put its dependencies into 
    -  a "pytest_plugins" attribute at module level as well. 
    +* marking and reporting test specially 
    +  pytest_xfail: "expected to fail" test marker 
     
    -* the ``MyLocalPluginClass`` will be instantiated 
    -  and added to the pluginmanager. 
    +* funcargs for advanced 
    +  pytest_tmpdir: provide temporary directories to test functions 
    +  pytest_plugintester: generic apichecks, support for functional plugin tests 
    +  pytest_pytester: support for testing py.test runs
     
    +* extending test execution, e.g. 
    +  pytest_apigen: tracing values of function/method calls when running tests
     
    -Plugin methods 
    -----------------------------------
     
    -A Plugin class may implement the following attributes and methods: 
    +Loading plugins and specifying dependencies 
    +---------------------------------------------------------
     
    -* pytest_cmdlineoptions: a list of optparse-style py.test.config.Option objects 
    -* pytest_configure(self, config): called after command line options have been parsed 
    -* pytest_unconfigure(self, config): called before the test process quits 
    -* pytest_event(self, event): called for each `pytest event`_ 
    +py.test loads and configures plugins at tool startup:
     
    -XXX reference APIcheck'ed full documentation
    +* by reading the ``PYTEST_PLUGINS`` environment variable 
    +  and importing the comma-separated list of plugin names. 
     
    -_`pytest event`: 
    +* by loading all plugins specified via one or more ``-p name`` 
    +  command line options. 
     
    -Pytest Events 
    --------------------
    +* by loading all plugins specified via a ``pytest_plugins``
    +  variable in ``conftest.py`` files or test modules. 
     
    -XXX Various reporting events. 
    +example: ensure a plugin is loaded 
    +++++++++++++++++++++++++++++++++++++
     
    -Example plugins
    ------------------------
    +If you create a ``conftest.py`` file with the following content:: 
     
    -XXX here are a few existing plugins: 
    +    pytest_plugins = "pytest_myextension",
     
    -* adding reporting facilities, e.g. 
    -  pytest_terminal: default reporter for writing info to terminals 
    -  pytest_resultlog: log test results in machine-readable form to a file 
    -  pytest_eventlog: log all internal pytest events to a file 
    -  pytest_xfail: "expected to fail" test marker 
    -  pytest_tmpdir: provide temporary directories to test functions 
    -  pytest_plugintester: generic apichecks, support for functional plugin tests 
    -  pytest_pytester: support for testing py.test runs
    +then all tests in that directory and below it will run with
    +an instantiated "pytest_myextension".  Here is how instantiation
    +takes place:
    +
    +* the module ``pytest_extension`` will be imported and 
    +  and its contained `ExtensionPlugin`` class will 
    +  be instantiated.  A plugin module may specify its 
    +  dependencies via another ``pytest_plugins`` definition. 
     
    -* extending test execution, e.g. 
    -  pytest_apigen: tracing values of function/method calls when running tests
    
    Added: py/trunk/py/doc/test-quickstart.txt
    ==============================================================================
    --- (empty file)
    +++ py/trunk/py/doc/test-quickstart.txt	Sun Mar 22 01:38:43 2009
    @@ -0,0 +1,58 @@
    +.. _`setuptools installation`: http://pypi.python.org/pypi/setuptools
    +
    +
    +Installing py.test
    +-------------------------------
    +
    +This document assumes basic python knowledge.  If you have a
    +`setuptools installation`_, install ``py.test`` by typing::
    +
    +    easy_install -U py 
    +
    +For alternative installation methods please see the download_ page.  
    +
    +You should now have a ``py.test`` command line tool and can
    +look at its documented cmdline options via this command::
    +
    +    py.test -h  
    +
    +Writing and running a test
    +---------------------------
    +
    +``py.test`` is the command line tool to run tests.  
    +Let's write a first test module by putting the following
    +test function into a ``test_sample.py`` file::
    +
    +    # content of test_sample.py 
    +    def test_answer():
    +        assert 42 == 43 
    +
    +Now you can run the test by passing it as an argument::
    +
    +  py.test test_sample.py
    +
    +What does happen here?  ``py.test`` looks for functions and
    +methods in the module that start with ``test_``.  It then
    +executes those tests.  Assertions about test outcomes are
    +done via the standard ``assert`` statement.
    +
    +You can also use ``py.test`` to run all tests in a directory structure by
    +invoking it without any arguments::
    +
    +  py.test
    +
    +This will automatically collect and run any Python module whose filenames 
    +start with ``test_`` or ends with ``_test`` from the directory and any
    +subdirectories, starting with the current directory, and run them. Each 
    +Python test module is inspected for test methods starting with ``test_``. 
    +
    +.. Organising your tests 
    +.. ---------------------------
    +
    +Please refer to `features`_ for a walk through the basic features. 
    +
    +
    +.. _download: download.html
    +.. _features: test-features.html
    +
    +
    
    Modified: py/trunk/py/doc/test.txt
    ==============================================================================
    --- py/trunk/py/doc/test.txt	(original)
    +++ py/trunk/py/doc/test.txt	Sun Mar 22 01:38:43 2009
    @@ -1,651 +1,22 @@
    -================================
    -The ``py.test`` tool and library 
    -================================
    +*py.test* is a tool for:
     
    -.. contents::
    -.. sectnum::
    +* rapidly writing unit- and functional tests in Python
    +* writing tests for non-python code and data
    +* receiving useful reports on test failures 
    +* distributing tests to multiple CPUs and remote environments
     
    +quickstart_: for getting started immediately.
     
    -This document is about the *usage* of the ``py.test`` testing tool. There is
    -also document describing the `implementation and the extending of py.test`_.
    +features_: a walk through basic features and usage. 
     
    -.. _`implementation and the extending of py.test`: impl-test.html
    +plugins_: using available plugins. 
     
    -starting point: ``py.test`` command line tool 
    -=============================================
    +extend_: writing plugins and advanced configuration. 
     
    -We presume you have done an installation as per the
    -download_ page after which you should be able to execute the
    -'py.test' tool from a command line shell. 
    +`distributed testing`_ how to distribute test runs to other machines and platforms. 
     
    -``py.test`` is the command line tool to run tests. You can supply it
    -with a Python test file (or directory) by passing it as an argument::
    -
    -  py.test test_sample.py
    -
    -``py.test`` looks for any functions and methods in the module that
    -start with with ``test_`` and will then run those methods.  Assertions
    -about test outcomes are done via the standard ``assert`` statement.
    -
    -This means you can write tests without any boilerplate::
    -
    -    # content of test_sample.py 
    -    def test_answer():
    -        assert 42 == 43 
    -
    -You may have test functions and test methods, there is no
    -need to subclass or to put tests into a class. 
    -You can also use ``py.test`` to run all tests in a directory structure by
    -invoking it without any arguments::
    -
    -  py.test
    -
    -This will automatically collect and run any Python module whose filenames 
    -start with ``test_`` or ends with ``_test`` from the directory and any
    -subdirectories, starting with the current directory, and run them. Each 
    -Python test module is inspected for test methods starting with ``test_``. 
    -
    -.. _download: download.html
    -.. _features: 
    -
    -Basic Features of ``py.test`` 
    -=============================
    -
    -assert with the ``assert`` statement
    -------------------------------------
    -
    -Writing assertions is very simple and this is one of py.test's
    -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``. In case this
    -assertion fails the test ``reporter`` will provide you with a very
    -helpful analysis and a clean traceback.
    -
    -
    -how to write assertions about exceptions 
    -----------------------------------------
    -
    -In order to write assertions about exceptions, you use
    -one of two forms::
    -
    -    py.test.raises(Exception, func, *args, **kwargs) 
    -    py.test.raises(Exception, "func(*args, **kwargs)")
    -
    -both of which execute the given function with args and kwargs and
    -asserts that the given ``Exception`` is raised.  The reporter will
    -provide you with helpful output in case of failures such as *no
    -exception* or *wrong exception*.
    -
    -Skipping tests 
    -----------------------------------------
    -
    -If you want to skip tests you can use ``py.test.skip`` within
    -test or setup functions.  Example::
    -
    -    py.test.skip("message")
    -
    -You can also use a helper to skip on a failing import::
    -
    -    docutils = py.test.importorskip("docutils")
    -
    -or to skip if the library does not have the right version::
    -
    -    docutils = py.test.importorskip("docutils", minversion="0.3")
    -
    -automatic collection of tests on all levels
    --------------------------------------------
    -
    -The automated test collection process walks the current
    -directory (or the directory given as a command line argument)
    -and all its subdirectories and collects python modules with a
    -leading ``test_`` or trailing ``_test`` filename.  From each 
    -test module every function with a leading ``test_`` or class with 
    -a leading ``Test`` name is collected.  The collecting process can 
    -be customized at directory, module or class level.  (see 
    -`collection process`_ for some implementation details). 
    -
    -.. _`generative tests`: 
    -.. _`collection process`: impl-test.html#collection-process
    -
    -generative tests: yielding more tests
    -------------------------------------- 
    -
    -*Generative tests* are test methods that are *generator functions* which
    -``yield`` callables and their arguments.  This is most useful for running a
    -test function multiple times against different parameters.
    -Example::
    -
    -    def test_generative(): 
    -        for x in (42,17,49): 
    -            yield check, x 
    -    
    -    def check(arg): 
    -        assert arg % 7 == 0   # second generated tests fails!
    -
    -Note that ``test_generative()`` will cause three tests 
    -to get run, notably ``check(42)``, ``check(17)`` and ``check(49)``
    -of which the middle one will obviously fail. 
    -
    -To make it easier to distinguish the generated tests it is possible to specify an explicit name for them, like for example::
    -
    -    def test_generative(): 
    -        for x in (42,17,49): 
    -            yield "case %d" % x, check, x 
    -
    -
    -.. _`selection by keyword`: 
    -
    -selecting/unselecting tests by keyword 
    ----------------------------------------------
    -
    -Pytest's keyword mechanism provides a powerful way to 
    -group and selectively run tests in your test code base. 
    -You can selectively run tests by specifiying a keyword 
    -on the command line.  Examples:
    -
    -    py.test -k test_simple   
    -    py.test -k "-test_simple"
    -
    -will run all tests matching (or not matching) the
    -"test_simple" keyword.  Note that you need to quote 
    -the keyword if "-" is recognized as an indicator 
    -for a commandline option.  Lastly, you may use 
    -
    -    py.test. -k "test_simple:"
    -
    -which will run all tests after the expression has *matched once*, i.e. 
    -all tests that are seen after a test that matches the "test_simple" 
    -keyword. 
    -
    -By default, all filename parts and
    -class/function names of a test function are put into the set
    -of keywords for a given test.  You may specify additional 
    -kewords like this::
    -
    -    @py.test.mark(webtest=True)
    -    def test_send_http():
    -        ... 
    -
    -testing with multiple python versions / executables 
    ----------------------------------------------------
    -
    -With ``--exec=EXECUTABLE`` you can specify a python
    -executable (e.g. ``python2.2``) with which the tests 
    -will be executed. 
    -
    -
    -testing starts immediately 
    ---------------------------
    -
    -Testing starts as soon as the first ``test item`` 
    -is collected.  The collection process is iterative 
    -and does not need to complete before your first 
    -test items are executed. 
    -
    -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
    -----------------------------------
    -
    -By default, ``py.test`` catches text written to stdout/stderr during
    -the execution of each individual test. This output will only be
    -displayed however if the test fails; you will not see it
    -otherwise. This allows you to put debugging print statements in your
    -code without being overwhelmed by all the output that might be
    -generated by tests that do not fail.
    -
    -Each failing test that produced output during the running of the test
    -will have its output displayed in the ``recorded stdout`` section.
    -
    -The catching of stdout/stderr output can be disabled using the 
    -``--nocapture`` option to the ``py.test`` tool.  Any output will 
    -in this case be displayed as soon as it is generated.
    -
    -test execution order
    ---------------------------------
    -
    -Tests usually run in the order in which they appear in the files. 
    -However, tests should not rely on running one after another, as
    -this prevents more advanced usages: running tests
    -distributedly or selectively, or in "looponfailing" mode,
    -will cause them to run in random order. 
    -
    -useful tracebacks, recursion detection 
    ---------------------------------------
    -
    -A lot of care is taken to present nice tracebacks in case of test
    -failure. Try::
    -
    -    py.test py/documentation/example/pytest/failure_demo.py
    -
    -to see a variety of 17 tracebacks, each tailored to a different
    -failure situation.
    -
    -``py.test`` uses the same order for presenting tracebacks as Python
    -itself: the oldest function call is shown first, and the most recent call is
    -shown last. A ``py.test`` reported traceback starts with your
    -failing test function.  If the maximum recursion depth has been
    -exceeded during the running of a test, for instance because of
    -infinite recursion, ``py.test`` will indicate where in the
    -code the recursion was taking place.  You can  inhibit
    -traceback "cutting" magic by supplying ``--fulltrace``. 
    -
    -There is also the possibility of using ``--tb=short`` to get regular CPython
    -tracebacks. Or you can use ``--tb=no`` to not show any tracebacks at all.
    -
    -no inheritance requirement 
    ---------------------------
    -
    -Test classes are recognized by their leading ``Test`` name.  Unlike
    -``unitest.py``, you don't need to inherit from some base class to make
    -them be found by the test runner. Besides being easier, it also allows
    -you to write test classes that subclass from application level
    -classes.
    -
    -disabling a test class
    ----------------------- 
    -
    -If you want to disable a complete test class 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):
    -            ... 
    -
    -testing for deprecated APIs
    -------------------------------
    -
    -In your tests you can use ``py.test.deprecated_call(func, *args, **kwargs)``
    -to test that a particular function call triggers a DeprecationWarning. 
    -This is useful for testing phasing out of old APIs in your projects. 
    -
    -Managing test state across test modules, classes and methods 
    -------------------------------------------------------------
    -
    -Often you want to create some files, database connections or 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.  Again, ``py.test`` will detect these hooks in
    -modules on a name basis. The following module-level hooks will
    -automatically be called by the session::
    -
    -    def setup_module(module):
    -        """ setup up any state specific to the execution
    -            of the given module. 
    -        """
    -
    -    def teardown_module(module):
    -        """ teardown any state that was previously setup 
    -            with a setup_module method. 
    -        """
    -
    -The following hooks are available for test classes::
    -
    -    def setup_class(cls): 
    -        """ setup up any state specific to the execution
    -            of the given class (which usually contains tests). 
    -        """
    -
    -    def teardown_class(cls): 
    -        """ teardown any state that was previously setup 
    -            with a call to setup_class.
    -        """
    -
    -    def setup_method(self, method):
    -        """ setup up any state 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 state that was previously setup 
    -            with a setup_method call. 
    -        """
    -
    -The last two hooks, ``setup_method`` and ``teardown_method``, are
    -equivalent to ``setUp`` and ``tearDown`` in the Python standard
    -library's ``unitest`` module.
    -
    -All setup/teardown methods are optional.  You could have a
    -``setup_module`` but no ``teardown_module`` and the other way round.
    -
    -Note that while the test session guarantees that for every ``setup`` a
    -corresponding ``teardown`` will be invoked (if it exists) it does
    -*not* guarantee that any ``setup`` is called only happens once. For
    -example, the session might decide to call the ``setup_module`` /
    -``teardown_module`` pair more than once during the execution of a test
    -module.
    -
    -Experimental doctest support 
    -------------------------------------------------------------
    -
    -If you want to integrate doctests, ``py.test`` now by default
    -picks up files matching the ``test_*.txt`` or ``*_test.txt`` 
    -patterns and processes them as text files containing doctests. 
    -This is an experimental feature and likely to change
    -its implementation. 
    -
    -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 py/documentation/example/pytest/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. Did we mention that lazyness
    -is a virtue? 
    -
    -Some ``py.test`` command-line options
    -=====================================
    -
    -Regular options
    ----------------
    -
    -``-v, --verbose``
    -    Increase verbosity. This shows a test per line while running and also
    -    shows the traceback after interrupting the test run with Ctrl-C.
    -
    -
    -``-x, --exitfirst``
    -    exit instantly on the first error or the first failed test.
    -
    -
    -``-s, --nocapture``
    -    disable catching of sys.stdout/stderr output.
    -
    -
    -``-k KEYWORD``
    -    only run test items matching the given keyword expression. You can also add
    -    use ``-k -KEYWORD`` to exlude tests from being run. The keyword is matched
    -    against filename, test class name, method name.
    -
    -
    -``-l, --showlocals``
    -    show locals in tracebacks: for every frame in the traceback, show the values
    -    of the local variables.
    -
    -
    -``--pdb``
    -    drop into pdb (the `Python debugger`_) on exceptions. If the debugger is
    -    quitted, the next test is run. This implies ``-s``.
    -
    -
    -``--tb=TBSTYLE``
    -    traceback verboseness: ``long`` is the default, ``short`` are the normal
    -    Python tracebacks, ``no`` omits tracebacks completely.
    -
    -
    -``--fulltrace``
    -    Don't cut any tracebacks. The default is to leave out frames if an infinite
    -    recursion is detected.
    -
    -
    -``--nomagic``
    -    Refrain from using magic as much as possible. This can be useful if you are
    -    suspicious that ``py.test`` somehow interferes with your program in
    -    unintended ways (if this is the case, please contact us!).
    -
    -
    -``--collectonly``
    -    Only collect tests, don't execute them.
    -
    -
    -``--traceconfig``
    -    trace considerations of conftest.py files. Useful when you have various
    -    conftest.py files around and are unsure about their interaction.
    -
    -``-f, --looponfailing``
    -    Loop on failing test set. This is a feature you can use when you are trying
    -    to fix a number of failing tests: First all the tests are being run. If a
    -    number of tests are failing, these are run repeatedly afterwards. Every
    -    repetition is started once a file below the directory that you started
    -    testing for is changed. If one of the previously failing tests now passes,
    -    it is removed from the test set.
    -
    -``--exec=EXECUTABLE``
    -    Python executable to run the tests with. Useful for testing on different
    -    versions of Python.
    -
    -
    -
    -experimental options
    ---------------------
    -
    -**Note**: these options could change in the future.
    -
    -
    -``-d, --dist``
    -    ad-hoc `distribute tests across machines`_ (requires conftest settings)
    -
    -
    -``-w, --startserver``
    -    starts local web server for displaying test progress.
    -
    -
    -``-r, --runbrowser``
    -    Run browser (implies --startserver).
    -
    -
    -``--boxed``
    -    Use boxed tests: run each test in an external process. Very useful for testing
    -    things that occasionally segfault (since normally the segfault then would
    -    stop the whole test process).
    -
    -``--rest``
    -    `reStructured Text`_ output reporting.
    -
    -
    -.. _`reStructured Text`: http://docutils.sourceforge.net
    -.. _`Python debugger`: http://docs.python.org/lib/module-pdb.html
    -
    -
    -.. _`distribute tests across machines`:
    -
    -
    -Automated Distributed Testing
    -==================================
    -
    -If you have a project with a large number of tests, and you have 
    -machines accessible through SSH, ``py.test`` can distribute
    -tests across the machines.  It does not require any particular
    -installation on the remote machine sides as it uses `py.execnet`_ 
    -mechanisms to distribute execution.  Using distributed testing 
    -can speed up your development process considerably and it
    -may also be useful where you need to use a remote server
    -that has more resources (e.g. RAM/diskspace) than your
    -local machine. 
    -
    -*WARNING*: support for distributed testing is experimental, 
    -its mechanics and configuration options may change without 
    -prior notice.  Particularly, not all reporting features 
    -of the in-process py.test have been integrated into
    -the distributed testing approach. 
    -
    -Requirements
    -------------
    -
    -Local requirements: 
    -
    -* ssh client
    -* python
    -
    -requirements for remote machines:
    -
    -* ssh daemon running
    -* ssh keys setup to allow login without a password
    -* python 
    -* unix like machine (reliance on ``os.fork``)
    -
    -How to use it
    ------------------------
    -
    -When you issue ``py.test -d`` then your computer becomes
    -the distributor of tests ("master") and will start collecting
    -and distributing tests to several machines.  The machines
    -need to be specified in a ``conftest.py`` file.  
    -
    -At start up, the master connects to each node using `py.execnet.SshGateway`_ 
    -and *rsyncs* all specified python packages to all nodes. 
    -Then the master collects all of the tests and immediately sends test item
    -descriptions to its connected nodes. Each node has a local queue of tests 
    -to run and begins to execute the tests, following the setup and teardown 
    -semantics.   The test are distributed at function and method level. 
    -When a test run on a node is completed it reports back the result
    -to the master. 
    -
    -The master can run one of three reporters to process the events 
    -from the testing nodes: command line, rest output and ajaxy web based. 
    -
    -.. _`py.execnet`: execnet.html
    -.. _`py.execnet.SshGateway`: execnet.html
    -
    -Differences from local tests
    -----------------------------
    -
    -* Test order is rather random (instead of in file order). 
    -* the test process may hang due to network problems 
    -* you may not reference files outside of rsynced directory structures
    -
    -Configuration
    --------------
    -
    -You must create a conftest.py in any parent directory above your tests.
    -
    -The options that you need to specify in that conftest.py file are:
    -
    -* `dist_hosts`: a required list of host specifications
    -* `dist_rsync_roots` - a list of relative locations to copy to the remote machines.
    -* `dist_rsync_ignore` - a list of relative locations to ignore for rsyncing 
    -* `dist_remotepython` - the remote python executable to run.
    -* `dist_nicelevel` - process priority of remote nodes. 
    -* `dist_boxed` - will run each single test in a separate process 
    -  (allowing to survive segfaults for example) 
    -* `dist_taskspernode` - Maximum number of tasks being queued to remote nodes 
    -
    -Sample configuration::
    -
    -    dist_hosts = ['localhost', 'user at someserver:/tmp/somedir']
    -    dist_rsync_roots = ['../pypy', '../py']
    -    dist_remotepython = 'python2.4'
    -    dist_nicelevel = 10 
    -    dist_boxed = False
    -    dist_maxwait = 100 
    -    dist_taskspernode = 10
    -
    -To use the browser-based reporter (with a nice AJAX interface) you have to tell
    -``py.test`` to run a small server locally using the ``-w`` or ``--startserver``
    -command line options. Afterwards you can point your browser to localhost:8000
    -to see the progress of the testing.
    -
    -Development Notes
    ------------------
    -
    -Changing the behavior of the web based reporter requires `pypy`_ since the
    -javascript is actually generated fom rpython source.
    -
    -.. _`pypy`: http://codespeak.net/pypy
    -
    -Future/Planned Features of py.test 
    -==================================
    -
    -integrating various test methods 
    --------------------------------------------
    -
    -There are various conftest.py's out there
    -that do html-reports, ad-hoc distribute tests
    -to windows machines or other fun stuff. 
    -These approaches should be offerred natively
    -by py.test at some point (requires refactorings). 
    -In addition, performing special checks such 
    -as w3c-conformance tests or ReST checks
    -should be offered from mainline py.test. 
    -
    -more distributed testing 
    ------------------------------------------
    -
    -We'd like to generalize and extend our ad-hoc 
    -distributed testing approach to allow for running
    -on multiple platforms simultanously and selectively. 
    -The web reporter should learn to deal with driving
    -complex multi-platform test runs and providing 
    -useful introspection and interactive debugging hooks. 
    -
    -
    -move to report event based architecture
    ---------------------------------------------
    -
    -To facilitate writing of custom reporters
    -py.test is to learn to generate reporting events
    -at all levels which a reporter can choose to 
    -interpret and present.  The distributed testing
    -approach already uses such an approach and 
    -we'd like to unify this with the default 
    -in-process py.test mode. 
    -
    -
    -see what other tools do currently (nose, etc.)
    -----------------------------------------------------
    -
    -There are various tools out there, among them 
    -the nose_ clone. It's about time to look again
    -at these and other tools, integrate interesting
    -features and maybe collaborate on some issues. 
    -
    -.. _nose: http://somethingaboutorange.com/mrl/projects/nose/
    +.. _quickstart: test-quickstart.html
    +.. _features: test-features.html
    +.. _plugins: test-plugins.html
    +.. _extend: test-ext.html
    +.. _`distributed testing`: test-dist.html
    
    
    From hpk at codespeak.net  Sun Mar 22 02:20:00 2009
    From: hpk at codespeak.net (hpk at codespeak.net)
    Date: Sun, 22 Mar 2009 02:20:00 +0100 (CET)
    Subject: [py-svn] r63201 - in py/trunk/py: doc test test/dist/testing
    	test/plugin test/testing
    Message-ID: <20090322012000.4EADA1683E0@codespeak.net>
    
    Author: hpk
    Date: Sun Mar 22 02:19:57 2009
    New Revision: 63201
    
    Modified:
       py/trunk/py/doc/test-dist.txt
       py/trunk/py/test/config.py
       py/trunk/py/test/dist/testing/test_functional_dsession.py
       py/trunk/py/test/dist/testing/test_nodemanage.py
       py/trunk/py/test/plugin/pytest_default.py
       py/trunk/py/test/testing/acceptance_test.py
       py/trunk/py/test/testing/test_config.py
    Log:
    allow to specify "3*" for host specs.
    
    
    Modified: py/trunk/py/doc/test-dist.txt
    ==============================================================================
    --- py/trunk/py/doc/test-dist.txt	(original)
    +++ py/trunk/py/doc/test-dist.txt	Sun Mar 22 02:19:57 2009
    @@ -27,7 +27,11 @@
     This will start a subprocess which is run with the "python2.4"
     Python interpreter, found in your system binary lookup path. 
     
    -.. For convenience you may prepend ``3*`` to create three sub processes.  
    +If you prefix the --tx option like this::
    +
    +    py.test --tx 3*popen//python=python2.4
    +
    +then three subprocesses would be created. 
     
     
     Sending tests to remote SSH accounts
    
    Modified: py/trunk/py/test/config.py
    ==============================================================================
    --- py/trunk/py/test/config.py	(original)
    +++ py/trunk/py/test/config.py	Sun Mar 22 02:19:57 2009
    @@ -255,15 +255,20 @@
         def getxspecs(self):
             config = self 
             if config.option.numprocesses:
    -            xspec = ['popen'] * config.option.numprocesses
    +            xspeclist = ['popen'] * config.option.numprocesses
             else:
    -            xspec = config.option.xspec
    -            if not xspec:
    -                xspec = config.getvalue("xspec")
    -        if xspec is None:
    +            xspeclist = []
    +            for xspec in config.getvalue("tx"):
    +                i = xspec.find("*")
    +                try:
    +                    num = int(xspec[:i])
    +                except ValueError:
    +                    xspeclist.append(xspec)
    +                else:
    +                    xspeclist.extend([xspec[i+1:]] * num)
    +        if not xspeclist:
                 raise config.Error("MISSING test execution (tx) nodes: please specify --tx")
    -        #print "option value for xspecs", xspec
    -        return [py.execnet.XSpec(x) for x in xspec]
    +        return [py.execnet.XSpec(x) for x in xspeclist]
     
         def getrsyncdirs(self):
             config = self 
    
    Modified: py/trunk/py/test/dist/testing/test_functional_dsession.py
    ==============================================================================
    --- py/trunk/py/test/dist/testing/test_functional_dsession.py	(original)
    +++ py/trunk/py/test/dist/testing/test_functional_dsession.py	Sun Mar 22 02:19:57 2009
    @@ -26,7 +26,7 @@
                     print "test_1: conftest.option.someopt", conftest.option.someopt
                     assert conftest.option.someopt 
             """))
    -        result = testdir.runpytest('-n1', p1, '--someopt')
    +        result = testdir.runpytest('-d', '--tx=popen', p1, '--someopt')
             assert result.ret == 0
             extra = result.stdout.fnmatch_lines([
                 "*1 passed*", 
    
    Modified: py/trunk/py/test/dist/testing/test_nodemanage.py
    ==============================================================================
    --- py/trunk/py/test/dist/testing/test_nodemanage.py	(original)
    +++ py/trunk/py/test/dist/testing/test_nodemanage.py	Sun Mar 22 02:19:57 2009
    @@ -117,39 +117,3 @@
             ev = sorter.getfirstnamed("itemtestreport")
             assert ev.passed 
     
    -class TestOptionsAndConfiguration:
    -    def test_getxspecs_numprocesses(self, testdir):
    -        config = testdir.parseconfig("-n3")
    -        xspecs = config.getxspecs()
    -        assert len(xspecs) == 3
    -
    -    def test_getxspecs(self, testdir):
    -        testdir.chdir() 
    -        config = testdir.parseconfig("--tx=popen", "--tx", "ssh=xyz")
    -        xspecs = config.getxspecs()
    -        assert len(xspecs) == 2
    -        print xspecs
    -        assert xspecs[0].popen 
    -        assert xspecs[1].ssh == "xyz"
    -
    -    def test_getconfigroots(self, testdir):
    -        config = testdir.parseconfig('--rsyncdir=' + str(testdir.tmpdir))
    -        roots = config.getrsyncdirs()
    -        assert len(roots) == 1 + 1 
    -        assert testdir.tmpdir in roots
    -
    -    def test_getconfigroots_with_conftest(self, testdir):
    -        testdir.chdir()
    -        p = py.path.local()
    -        for bn in 'x y z'.split():
    -            p.mkdir(bn)
    -        testdir.makeconftest("""
    -            rsyncdirs= 'x', 
    -        """)
    -        config = testdir.parseconfig(testdir.tmpdir, '--rsyncdir=y', '--rsyncdir=z')
    -        roots = config.getrsyncdirs()
    -        assert len(roots) == 3 + 1 
    -        assert py.path.local('y') in roots 
    -        assert py.path.local('z') in roots 
    -        assert testdir.tmpdir.join('x') in roots 
    -
    
    Modified: py/trunk/py/test/plugin/pytest_default.py
    ==============================================================================
    --- py/trunk/py/test/plugin/pytest_default.py	(original)
    +++ py/trunk/py/test/plugin/pytest_default.py	Sun Mar 22 02:19:57 2009
    @@ -103,7 +103,7 @@
                        help="number of local test processes. conflicts with --dist.")
             group.addoption('--rsyncdir', action="append", default=[], metavar="dir1", 
                        help="add local directory for rsync to remote test nodes.")
    -        group._addoption('--tx', dest="xspec", action="append", 
    +        group._addoption('--tx', dest="tx", action="append", default=[],
                        help=("add a test environment, specified in XSpec syntax. examples: "
                              "--tx popen//python=python2.5 --tx socket=192.168.1.102"))
             #group._addoption('--rest',
    
    Modified: py/trunk/py/test/testing/acceptance_test.py
    ==============================================================================
    --- py/trunk/py/test/testing/acceptance_test.py	(original)
    +++ py/trunk/py/test/testing/acceptance_test.py	Sun Mar 22 02:19:57 2009
    @@ -287,7 +287,7 @@
                 """, 
             )
             testdir.makeconftest("""
    -            pytest_option_xspec = 'popen popen popen'.split()
    +            pytest_option_tx = 'popen popen popen'.split()
             """)
             result = testdir.runpytest(p1, '-d')
             result.stdout.fnmatch_lines([
    @@ -319,7 +319,7 @@
                         os.kill(os.getpid(), 15)
                 """
             )
    -        result = testdir.runpytest(p1, '-d', '-n 3')
    +        result = testdir.runpytest(p1, '-d', '--tx=3*popen')
             result.stdout.fnmatch_lines([
                 "*popen*Python*",
                 "*popen*Python*",
    
    Modified: py/trunk/py/test/testing/test_config.py
    ==============================================================================
    --- py/trunk/py/test/testing/test_config.py	(original)
    +++ py/trunk/py/test/testing/test_config.py	Sun Mar 22 02:19:57 2009
    @@ -218,6 +218,48 @@
                 assert col.config is config 
     
     
    +class TestOptionsAndConfiguration:
    +    def test_getxspecs_numprocesses(self, testdir):
    +        config = testdir.parseconfig("-n3")
    +        xspecs = config.getxspecs()
    +        assert len(xspecs) == 3
    +
    +    def test_getxspecs(self, testdir):
    +        testdir.chdir() 
    +        config = testdir.parseconfig("--tx=popen", "--tx", "ssh=xyz")
    +        xspecs = config.getxspecs()
    +        assert len(xspecs) == 2
    +        print xspecs
    +        assert xspecs[0].popen 
    +        assert xspecs[1].ssh == "xyz"
    +
    +    def test_xspecs_multiplied(self, testdir):
    +        testdir.chdir()
    +        xspecs = testdir.parseconfig("--tx=3*popen",).getxspecs()
    +        assert len(xspecs) == 3
    +        assert xspecs[1].popen 
    +
    +    def test_getrsyncdirs(self, testdir):
    +        config = testdir.parseconfig('--rsyncdir=' + str(testdir.tmpdir))
    +        roots = config.getrsyncdirs()
    +        assert len(roots) == 1 + 1 
    +        assert testdir.tmpdir in roots
    +
    +    def test_getrsyncdirs_with_conftest(self, testdir):
    +        testdir.chdir()
    +        p = py.path.local()
    +        for bn in 'x y z'.split():
    +            p.mkdir(bn)
    +        testdir.makeconftest("""
    +            rsyncdirs= 'x', 
    +        """)
    +        config = testdir.parseconfig(testdir.tmpdir, '--rsyncdir=y', '--rsyncdir=z')
    +        roots = config.getrsyncdirs()
    +        assert len(roots) == 3 + 1 
    +        assert py.path.local('y') in roots 
    +        assert py.path.local('z') in roots 
    +        assert testdir.tmpdir.join('x') in roots 
    +
     
     
     class TestOptionEffects:
    
    
    From hpk at codespeak.net  Sun Mar 22 02:50:21 2009
    From: hpk at codespeak.net (hpk at codespeak.net)
    Date: Sun, 22 Mar 2009 02:50:21 +0100 (CET)
    Subject: [py-svn] r63202 - in py/trunk/py/test/dist: . testing
    Message-ID: <20090322015021.F36FC168416@codespeak.net>
    
    Author: hpk
    Date: Sun Mar 22 02:50:16 2009
    New Revision: 63202
    
    Modified:
       py/trunk/py/test/dist/dsession.py
       py/trunk/py/test/dist/testing/test_dsession.py
    Log:
    prepare for allowing for items to be sent to multiple hosts
    
    
    Modified: py/trunk/py/test/dist/dsession.py
    ==============================================================================
    --- py/trunk/py/test/dist/dsession.py	(original)
    +++ py/trunk/py/test/dist/dsession.py	Sun Mar 22 02:50:16 2009
    @@ -26,8 +26,8 @@
             self.testsfailed = False
     
         def pyevent_itemtestreport(self, event):
    -        if event.colitem in self.dsession.item2node:
    -            self.dsession.removeitem(event.colitem)
    +        if event.colitem in self.dsession.item2nodes:
    +            self.dsession.removeitem(event.colitem, event.node)
             if event.failed:
                 self.testsfailed = True
     
    @@ -59,7 +59,7 @@
         def __init__(self, config):
             self.queue = Queue.Queue()
             self.node2pending = {}
    -        self.item2node = {}
    +        self.item2nodes = {}
             super(DSession, self).__init__(config=config)
     
         def pytest_configure(self, config):
    @@ -106,7 +106,7 @@
     
             # termination conditions
             if ((loopstate.testsfailed and self.config.option.exitfirst) or 
    -            (not self.item2node and not colitems and not self.queue.qsize())):
    +            (not self.item2nodes and not colitems and not self.queue.qsize())):
                 self.triggershutdown()
                 loopstate.shuttingdown = True
             elif not self.node2pending:
    @@ -166,7 +166,11 @@
                 # this happens if we didn't receive a testnodeready event yet
                 return []
             for item in pending:
    -            del self.item2node[item]
    +            l = self.item2nodes[item]
    +            l.remove(node)
    +            if not l:
    +                del self.item2nodes[item]
    +
             return pending
     
         def triggertesting(self, colitems):
    @@ -195,7 +199,7 @@
                         #assert item not in self.item2node, (
                         #    "sending same item %r to multiple "
                         #    "not implemented" %(item,))
    -                    self.item2node[item] = node
    +                    self.item2nodes.setdefault(item, []).append(node)
                         self.bus.notify("itemstart", item, node)
                     pending.extend(sending)
                     tosend[:] = tosend[room:]  # update inplace
    @@ -205,12 +209,14 @@
                 # we have some left, give it to the main loop
                 self.queueevent("rescheduleitems", event.RescheduleItems(tosend))
     
    -    def removeitem(self, item):
    -        if item not in self.item2node:
    -            raise AssertionError(item, self.item2node)
    -        node = self.item2node.pop(item)
    +    def removeitem(self, item, node):
    +        if item not in self.item2nodes:
    +            raise AssertionError(item, self.item2nodes)
    +        nodes = self.item2nodes[item]
    +        nodes.remove(node)
    +        if not nodes:
    +            del self.item2nodes[item]
             self.node2pending[node].remove(item)
    -        #self.config.bus.notify("removeitem", item, host.hostid)
     
         def handle_crashitem(self, item, node):
             longrepr = "!!! Node %r crashed during running of test %r" %(node, item)
    
    Modified: py/trunk/py/test/dist/testing/test_dsession.py
    ==============================================================================
    --- py/trunk/py/test/dist/testing/test_dsession.py	(original)
    +++ py/trunk/py/test/dist/testing/test_dsession.py	Sun Mar 22 02:50:16 2009
    @@ -6,9 +6,11 @@
     
     XSpec = py.execnet.XSpec
     
    -def run(item):
    +def run(item, node):
         runner = item._getrunner()
    -    return runner(item)
    +    rep = runner(item)
    +    rep.node = node
    +    return rep
     
     class MockNode:
         def __init__(self):
    @@ -27,31 +29,31 @@
     class TestDSession:
         def test_add_remove_node(self, testdir):
             item = testdir.getitem("def test_func(): pass")
    -        rep = run(item)
    -        session = DSession(item.config)
             node = MockNode()
    +        rep = run(item, node)
    +        session = DSession(item.config)
             assert not session.node2pending
             session.addnode(node)
             assert len(session.node2pending) == 1
             session.senditems([item])
             pending = session.removenode(node)
             assert pending == [item]
    -        assert item not in session.item2node
    +        assert item not in session.item2nodes
             l = session.removenode(node)
             assert not l 
     
         def test_senditems_removeitems(self, testdir):
             item = testdir.getitem("def test_func(): pass")
    -        rep = run(item)
    -        session = DSession(item.config)
             node = MockNode()
    +        rep = run(item, node)
    +        session = DSession(item.config)
             session.addnode(node)
             session.senditems([item])  
             assert session.node2pending[node] == [item]
    -        assert session.item2node[item] == node
    -        session.removeitem(item)
    +        assert session.item2nodes[item] == [node]
    +        session.removeitem(item, node)
             assert not session.node2pending[node] 
    -        assert not session.item2node
    +        assert not session.item2nodes
     
         def test_triggertesting_collect(self, testdir):
             modcol = testdir.getmodulecol("""
    @@ -117,7 +119,7 @@
             session.queueevent("anonymous", event.NOP())
             session.loop_once(loopstate)
             assert node.sent == [[item]]
    -        session.queueevent("itemtestreport", run(item))
    +        session.queueevent("itemtestreport", run(item, node))
             session.loop_once(loopstate)
             assert loopstate.shuttingdown 
             assert not loopstate.testsfailed 
    @@ -155,10 +157,10 @@
           
             # have one test pending for a node that goes down 
             session.senditems([item1, item2])
    -        node = session.item2node[item1]
    +        node = session.item2nodes[item1] [0]
             session.queueevent("testnodedown", node, None)
             evrec = EventRecorder(session.bus)
    -        print session.item2node
    +        print session.item2nodes
             loopstate = session._initloopstate([])
             session.loop_once(loopstate)
     
    @@ -200,7 +202,7 @@
             session.loop_once(loopstate)
     
             assert node.sent == [[item]]
    -        ev = run(item)
    +        ev = run(item, node)
             session.queueevent("itemtestreport", ev)
             session.loop_once(loopstate)
             assert loopstate.shuttingdown  
    @@ -236,8 +238,8 @@
             session.triggertesting(items)
     
             # run tests ourselves and produce reports 
    -        ev1 = run(items[0])
    -        ev2 = run(items[1])
    +        ev1 = run(items[0], node)
    +        ev2 = run(items[1], node)
             session.queueevent("itemtestreport", ev1) # a failing one
             session.queueevent("itemtestreport", ev2)
             # now call the loop
    @@ -254,7 +256,7 @@
             loopstate = session._initloopstate([])
             loopstate.shuttingdown = True
             evrec = EventRecorder(session.bus)
    -        session.queueevent("itemtestreport", run(item))
    +        session.queueevent("itemtestreport", run(item, node))
             session.loop_once(loopstate)
             assert not evrec.getfirstnamed("testnodedown")
             session.queueevent("testnodedown", node, None)
    @@ -297,7 +299,7 @@
             node = MockNode()
             session.addnode(node)
             session.senditems([item])
    -        session.queueevent("itemtestreport", run(item))
    +        session.queueevent("itemtestreport", run(item, node))
             loopstate = session._initloopstate([])
             session.loop_once(loopstate)
             assert node._shutdown is True
    @@ -322,7 +324,9 @@
             item1, item2 = colreport.result
             session.senditems([item1])
             # node2pending will become empty when the loop sees the report 
    -        session.queueevent("itemtestreport", run(item1)) 
    +        rep = run(item1, node)
    +
    +        session.queueevent("itemtestreport", run(item1, node)) 
     
             # but we have a collection pending
             session.queueevent("collectionreport", colreport) 
    
    
    From hpk at codespeak.net  Sun Mar 22 03:13:36 2009
    From: hpk at codespeak.net (hpk at codespeak.net)
    Date: Sun, 22 Mar 2009 03:13:36 +0100 (CET)
    Subject: [py-svn] r63203 - in py/trunk/py/test/dist: . testing
    Message-ID: <20090322021336.A70F4168412@codespeak.net>
    
    Author: hpk
    Date: Sun Mar 22 03:13:36 2009
    New Revision: 63203
    
    Modified:
       py/trunk/py/test/dist/dsession.py
       py/trunk/py/test/dist/testing/test_dsession.py
    Log:
    add a method that allows to send an item to multiple nodes
    
    
    Modified: py/trunk/py/test/dist/dsession.py
    ==============================================================================
    --- py/trunk/py/test/dist/dsession.py	(original)
    +++ py/trunk/py/test/dist/dsession.py	Sun Mar 22 03:13:36 2009
    @@ -170,7 +170,6 @@
                 l.remove(node)
                 if not l:
                     del self.item2nodes[item]
    -
             return pending
     
         def triggertesting(self, colitems):
    @@ -183,10 +182,29 @@
                     self.bus.notify("collectionstart", event.CollectionStart(next))
                     self.queueevent("collectionreport", basic_collect_report(next))
             self.senditems(senditems)
    +        #self.senditems_each(senditems)
     
         def queueevent(self, eventname, *args, **kwargs):
             self.queue.put((eventname, args, kwargs)) 
     
    +    def senditems_each(self, tosend):
    +        if not tosend:
    +            return 
    +        room = self.MAXITEMSPERHOST
    +        for node, pending in self.node2pending.items():
    +            room = min(self.MAXITEMSPERHOST - len(pending), room)
    +        sending = tosend[:room]
    +        for node, pending in self.node2pending.items():
    +            node.sendlist(sending)
    +            pending.extend(sending)
    +            for item in sending:
    +                self.item2nodes.setdefault(item, []).append(node)
    +                self.bus.notify("itemstart", item, node)
    +        tosend[:] = tosend[room:]  # update inplace
    +        if tosend:
    +            # we have some left, give it to the main loop
    +            self.queueevent("rescheduleitems", event.RescheduleItems(tosend))
    +
         def senditems(self, tosend):
             if not tosend:
                 return 
    @@ -221,6 +239,7 @@
         def handle_crashitem(self, item, node):
             longrepr = "!!! Node %r crashed during running of test %r" %(node, item)
             rep = event.ItemTestReport(item, when="???", excinfo=longrepr)
    +        rep.node = node
             self.bus.notify("itemtestreport", rep)
     
         def setup(self):
    
    Modified: py/trunk/py/test/dist/testing/test_dsession.py
    ==============================================================================
    --- py/trunk/py/test/dist/testing/test_dsession.py	(original)
    +++ py/trunk/py/test/dist/testing/test_dsession.py	Sun Mar 22 03:13:36 2009
    @@ -42,6 +42,24 @@
             l = session.removenode(node)
             assert not l 
     
    +    def test_send_remove_to_two_nodes(self, testdir):
    +        item = testdir.getitem("def test_func(): pass")
    +        node1 = MockNode()
    +        node2 = MockNode()
    +        session = DSession(item.config)
    +        session.addnode(node1)
    +        session.addnode(node2)
    +        session.senditems_each([item])
    +        assert session.node2pending[node1] == [item]
    +        assert session.node2pending[node2] == [item]
    +        assert node1 in session.item2nodes[item]
    +        assert node2 in session.item2nodes[item]
    +        session.removeitem(item, node1)
    +        assert session.item2nodes[item] == [node2]
    +        session.removeitem(item, node2)
    +        assert not session.node2pending[node1] 
    +        assert not session.item2nodes
    +
         def test_senditems_removeitems(self, testdir):
             item = testdir.getitem("def test_func(): pass")
             node = MockNode()
    
    
    From hpk at codespeak.net  Sun Mar 22 18:08:32 2009
    From: hpk at codespeak.net (hpk at codespeak.net)
    Date: Sun, 22 Mar 2009 18:08:32 +0100 (CET)
    Subject: [py-svn] r63208 - in py/trunk: example example/execnet
    	py/doc/example
    Message-ID: <20090322170832.26D6E16849C@codespeak.net>
    
    Author: hpk
    Date: Sun Mar 22 18:08:31 2009
    New Revision: 63208
    
    Added:
       py/trunk/example/
          - copied from r63191, py/trunk/py/doc/example/
       py/trunk/example/execnet/
       py/trunk/example/execnet/popen_read_multiple.py
    Removed:
       py/trunk/py/doc/example/
    Log:
    moving example to top-level dir. 
    
    
    
    Added: py/trunk/example/execnet/popen_read_multiple.py
    ==============================================================================
    --- (empty file)
    +++ py/trunk/example/execnet/popen_read_multiple.py	Sun Mar 22 18:08:31 2009
    @@ -0,0 +1,37 @@
    +"""
    +example
    +
    +reading results from possibly blocking code running in sub processes. 
    +"""
    +import py
    +
    +NUM_PROCESSES = 5
    +
    +channels = []
    +for i in range(NUM_PROCESSES):
    +    gw = py.execnet.PopenGateway() # or use SSH or socket gateways 
    +    channel = gw.remote_exec("""
    +        import time
    +        secs = channel.receive()
    +        time.sleep(secs)
    +        channel.send("waited %d secs" % secs)
    +    """)
    +    channels.append(channel)
    +    print "*** instantiated subprocess", gw
    +
    +mc = py.execnet.MultiChannel(channels)
    +queue = mc.make_receive_queue()
    +
    +print "***", "verifying that timeout on receiving results from blocked subprocesses works"
    +try:
    +    queue.get(timeout=1.0) 
    +except Exception:
    +    pass
    +
    +print "*** sending subprocesses some data to have them unblock"
    +mc.send_each(1) 
    +
    +print "*** receiving results asynchronously"
    +for i in range(NUM_PROCESSES):
    +    channel, result = queue.get(timeout=2.0)
    +    print "result", channel.gateway, result
    
    
    From hpk at codespeak.net  Sun Mar 22 18:41:38 2009
    From: hpk at codespeak.net (hpk at codespeak.net)
    Date: Sun, 22 Mar 2009 18:41:38 +0100 (CET)
    Subject: [py-svn] r63209 - in py/trunk/py/test: . dist dist/testing plugin
    	testing
    Message-ID: <20090322174138.F2E17168441@codespeak.net>
    
    Author: hpk
    Date: Sun Mar 22 18:41:36 2009
    New Revision: 63209
    
    Removed:
       py/trunk/py/test/dist/testing/test_functional_dsession.py
    Modified:
       py/trunk/py/test/config.py
       py/trunk/py/test/defaultconftest.py
       py/trunk/py/test/dist/dsession.py
       py/trunk/py/test/dist/nodemanage.py
       py/trunk/py/test/dist/testing/test_dsession.py
       py/trunk/py/test/dist/txnode.py
       py/trunk/py/test/plugin/pytest_default.py
       py/trunk/py/test/plugin/pytest_pytester.py
       py/trunk/py/test/plugin/pytest_restdoc.py
       py/trunk/py/test/testing/acceptance_test.py
       py/trunk/py/test/testing/test_config.py
       py/trunk/py/test/testing/test_pickling.py
    Log:
    polish command line options for distributed testing. 
    
    
    Modified: py/trunk/py/test/config.py
    ==============================================================================
    --- py/trunk/py/test/config.py	(original)
    +++ py/trunk/py/test/config.py	Sun Mar 22 18:41:36 2009
    @@ -253,21 +253,17 @@
                 raise self.Error("unknown io capturing: " + iocapture)
     
         def getxspecs(self):
    -        config = self 
    -        if config.option.numprocesses:
    -            xspeclist = ['popen'] * config.option.numprocesses
    -        else:
    -            xspeclist = []
    -            for xspec in config.getvalue("tx"):
    -                i = xspec.find("*")
    -                try:
    -                    num = int(xspec[:i])
    -                except ValueError:
    -                    xspeclist.append(xspec)
    -                else:
    -                    xspeclist.extend([xspec[i+1:]] * num)
    +        xspeclist = []
    +        for xspec in self.getvalue("tx"):
    +            i = xspec.find("*")
    +            try:
    +                num = int(xspec[:i])
    +            except ValueError:
    +                xspeclist.append(xspec)
    +            else:
    +                xspeclist.extend([xspec[i+1:]] * num)
             if not xspeclist:
    -            raise config.Error("MISSING test execution (tx) nodes: please specify --tx")
    +            raise self.Error("MISSING test execution (tx) nodes: please specify --tx")
             return [py.execnet.XSpec(x) for x in xspeclist]
     
         def getrsyncdirs(self):
    
    Modified: py/trunk/py/test/defaultconftest.py
    ==============================================================================
    --- py/trunk/py/test/defaultconftest.py	(original)
    +++ py/trunk/py/test/defaultconftest.py	Sun Mar 22 18:41:36 2009
    @@ -1,21 +1,14 @@
     import py
     
     Module = py.test.collect.Module
    -#DoctestFile = py.test.collect.DoctestFile
     Directory = py.test.collect.Directory
    +File = py.test.collect.File
    +
    +# python collectors 
     Class = py.test.collect.Class
     Generator = py.test.collect.Generator
     Function = py.test.collect.Function
     Instance = py.test.collect.Instance
     
    -
     pytest_plugins = "default terminal xfail tmpdir execnetcleanup monkeypatch".split()
     
    -# ===================================================
    -# settings in conftest only (for now) - for distribution
    -
    -if hasattr(py.std.os, 'nice'):
    -    dist_nicelevel = py.std.os.nice(0) # nice py.test works
    -else:
    -    dist_nicelevel = 0
    -
    
    Modified: py/trunk/py/test/dist/dsession.py
    ==============================================================================
    --- py/trunk/py/test/dist/dsession.py	(original)
    +++ py/trunk/py/test/dist/dsession.py	Sun Mar 22 18:41:36 2009
    @@ -62,19 +62,14 @@
             self.item2nodes = {}
             super(DSession, self).__init__(config=config)
     
    -    def pytest_configure(self, config):
    -        if self.config.getvalue("usepdb"):
    -            raise self.config.Error("--pdb does not work for distributed tests (yet).")
    +    def pytest_configure(self, __call__, config):
    +        __call__.execute()
             try:
    -            self.config.getxspecs()
    -        except self.config.Error:
    -            print "Please specify test environments for distribution of tests:"
    -            print "py.test --tx ssh=user at somehost --tx popen//python=python2.5"
    -            print "conftest.py: pytest_option_tx=['ssh=user at somehost','popen']"
    -            print "environment: PYTEST_OPTION_TX=ssh=@somehost,popen"
    -            print 
    -            #print "see also: http://codespeak.net/py/current/doc/test.html#automated-distributed-testing"
    -            raise SystemExit
    +            config.getxspecs()
    +        except config.Error:
    +            print
    +            raise config.Error("dist mode %r needs test execution environments, "
    +                               "none found." %(config.option.dist))
     
         def main(self, colitems=None):
             colitems = self.getinitialitems(colitems)
    @@ -126,6 +121,7 @@
                     loopstate.exitstatus = outcome.EXIT_TESTSFAILED
                 else:
                     loopstate.exitstatus = outcome.EXIT_OK
    +        #self.config.bus.unregister(loopstate)
     
         def _initloopstate(self, colitems):
             loopstate = LoopState(self, colitems)
    
    Modified: py/trunk/py/test/dist/nodemanage.py
    ==============================================================================
    --- py/trunk/py/test/dist/nodemanage.py	(original)
    +++ py/trunk/py/test/dist/nodemanage.py	Sun Mar 22 18:41:36 2009
    @@ -65,26 +65,12 @@
     
         def setup_nodes(self, putevent):
             self.rsync_roots()
    -        nice = self.config.getvalue("dist_nicelevel")
    -        if nice != 0:
    -            self.gwmanager.multi_exec("""
    -                import os
    -                if hasattr(os, 'nice'): 
    -                    os.nice(%r)
    -            """ % nice).waitclose()
    -
             self.trace_nodestatus()
    -        multigw = self.gwmanager.getgateways(inplacelocal=False, remote=True)
    -        multigw.remote_exec("""
    -            import os, sys
    -            sys.path.insert(0, os.getcwd())
    -        """).waitclose()
    -
             for gateway in self.gwmanager.gateways:
                 node = MasterNode(gateway, self.config, putevent)
                 self.nodes.append(node) 
     
         def teardown_nodes(self):
    -        # XXX teardown nodes? 
    +        # XXX do teardown nodes? 
             self.gwmanager.exit()
     
    
    Modified: py/trunk/py/test/dist/testing/test_dsession.py
    ==============================================================================
    --- py/trunk/py/test/dist/testing/test_dsession.py	(original)
    +++ py/trunk/py/test/dist/testing/test_dsession.py	Sun Mar 22 18:41:36 2009
    @@ -356,3 +356,30 @@
             session.loop_once(loopstate)
             assert loopstate.colitems == colreport.result
             assert loopstate.exitstatus is None, "loop did not care for colitems"
    +
    +    def test_dist_some_tests(self, testdir):
    +        from py.__.test.dist.testing.test_txnode import EventQueue
    +        p1 = testdir.makepyfile(test_one="""
    +            def test_1(): 
    +                pass
    +            def test_x():
    +                import py
    +                py.test.skip("aaa")
    +            def test_fail():
    +                assert 0
    +        """)
    +        config = testdir.parseconfig('-d', p1, '--tx=popen')
    +        dsession = DSession(config)
    +        eq = EventQueue(config.bus)
    +        dsession.main([config.getfsnode(p1)])
    +        ev, = eq.geteventargs("itemtestreport")
    +        assert ev.passed
    +        ev, = eq.geteventargs("itemtestreport")
    +        assert ev.skipped
    +        ev, = eq.geteventargs("itemtestreport")
    +        assert ev.failed
    +        # see that the node is really down 
    +        node, error = eq.geteventargs("testnodedown")
    +        assert node.gateway.spec.popen
    +        eq.geteventargs("testrunfinish")
    +
    
    Deleted: /py/trunk/py/test/dist/testing/test_functional_dsession.py
    ==============================================================================
    --- /py/trunk/py/test/dist/testing/test_functional_dsession.py	Sun Mar 22 18:41:36 2009
    +++ (empty file)
    @@ -1,94 +0,0 @@
    -import py
    -from py.__.test.dist.dsession import DSession
    -from test_txnode import EventQueue
    -
    -class TestAsyncFunctional:
    -    def test_conftest_options(self, testdir):
    -        p1 = testdir.tmpdir.ensure("dir", 'p1.py')
    -        p1.dirpath("__init__.py").write("")
    -        p1.dirpath("conftest.py").write(py.code.Source("""
    -            print "importing conftest", __file__
    -            import py
    -            Option = py.test.config.Option 
    -            option = py.test.config.addoptions("someopt", 
    -                Option('--someopt', action="store_true", dest="someopt", default=False))
    -            dist_rsync_roots = ['../dir']
    -            print "added options", option
    -            print "config file seen from conftest", py.test.config
    -        """))
    -        p1.write(py.code.Source("""
    -            import py, conftest
    -            def test_1(): 
    -                print "config from test_1", py.test.config
    -                print "conftest from test_1", conftest.__file__
    -                print "test_1: py.test.config.option.someopt", py.test.config.option.someopt
    -                print "test_1: conftest", conftest
    -                print "test_1: conftest.option.someopt", conftest.option.someopt
    -                assert conftest.option.someopt 
    -        """))
    -        result = testdir.runpytest('-d', '--tx=popen', p1, '--someopt')
    -        assert result.ret == 0
    -        extra = result.stdout.fnmatch_lines([
    -            "*1 passed*", 
    -        ])
    -
    -    def test_dist_some_tests(self, testdir):
    -        p1 = testdir.makepyfile(test_one="""
    -            def test_1(): 
    -                pass
    -            def test_x():
    -                import py
    -                py.test.skip("aaa")
    -            def test_fail():
    -                assert 0
    -        """)
    -        config = testdir.parseconfig('-d', p1, '--tx=popen')
    -        dsession = DSession(config)
    -        eq = EventQueue(config.bus)
    -        dsession.main([config.getfsnode(p1)])
    -        ev, = eq.geteventargs("itemtestreport")
    -        assert ev.passed
    -        ev, = eq.geteventargs("itemtestreport")
    -        assert ev.skipped
    -        ev, = eq.geteventargs("itemtestreport")
    -        assert ev.failed
    -        # see that the node is really down 
    -        node, error = eq.geteventargs("testnodedown")
    -        assert node.gateway.spec.popen
    -        eq.geteventargs("testrunfinish")
    -
    -    def test_distribution_rsyncdirs_example(self, testdir):
    -        source = testdir.mkdir("source")
    -        dest = testdir.mkdir("dest")
    -        subdir = source.mkdir("example_pkg")
    -        subdir.ensure("__init__.py")
    -        p = subdir.join("test_one.py")
    -        p.write("def test_5(): assert not __file__.startswith(%r)" % str(p))
    -        result = testdir.runpytest("-d", "--rsyncdir=%(subdir)s" % locals(), 
    -            "--tx=popen//chdir=%(dest)s" % locals(), p)
    -        assert result.ret == 0
    -        result.stdout.fnmatch_lines([
    -            "*1* *popen*platform*",
    -            #"RSyncStart: [G1]",
    -            #"RSyncFinished: [G1]",
    -            "*1 passed*"
    -        ])
    -        assert dest.join(subdir.basename).check(dir=1)
    -
    -    def test_nice_level(self, testdir):
    -        """ Tests if nice level behaviour is ok """
    -        import os
    -        if not hasattr(os, 'nice'):
    -            py.test.skip("no os.nice() available")
    -        testdir.makepyfile(conftest="""
    -                dist_nicelevel = 10
    -        """)
    -        p1 = testdir.makepyfile("""
    -            def test_nice():
    -                import os
    -                assert os.nice(0) == 10
    -        """)
    -        evrec = testdir.inline_run('-d', p1, '--tx=popen')
    -        ev = evrec.getfirstnamed('itemtestreport')
    -        assert ev.passed
    -
    
    Modified: py/trunk/py/test/dist/txnode.py
    ==============================================================================
    --- py/trunk/py/test/dist/txnode.py	(original)
    +++ py/trunk/py/test/dist/txnode.py	Sun Mar 22 18:41:36 2009
    @@ -70,6 +70,8 @@
     # setting up slave code 
     def install_slave(gateway, config):
         channel = gateway.remote_exec(source="""
    +        import os, sys 
    +        sys.path.insert(0, os.getcwd()) 
             from py.__.test.dist.mypickle import PickleChannel
             from py.__.test.dist.txnode import SlaveNode
             channel = PickleChannel(channel)
    
    Modified: py/trunk/py/test/plugin/pytest_default.py
    ==============================================================================
    --- py/trunk/py/test/plugin/pytest_default.py	(original)
    +++ py/trunk/py/test/plugin/pytest_default.py	Sun Mar 22 18:41:36 2009
    @@ -70,7 +70,7 @@
                              "and instantiate 'HelloPlugin' from the module."))
             group._addoption('-f', '--looponfail',
                        action="store_true", dest="looponfail", default=False,
    -                   help="run tests, loop on failing test set, until all pass. repeat forever.")
    +                   help="run tests, re-run failing test set until all pass.")
     
             group = parser.addgroup("test process debugging")
             group.addoption('--collectonly',
    @@ -94,33 +94,43 @@
                        action="store_true", dest="debug", default=False,
                        help="generate and show debugging information.")
     
    -        group = parser.addgroup("xplatform", "distributed/cross platform testing")
    -        group._addoption('-d', '--dist',
    -                   action="store_true", dest="dist", default=False,
    -                   help="ad-hoc distribute tests across machines (requires conftest settings)") 
    -        group._addoption('-n', dest="numprocesses", default=0, metavar="numprocesses", 
    +        group = parser.addgroup("dist", "distributed testing") #  see http://pytest.org/help/dist")
    +        group._addoption('--dist', metavar="distmode", 
    +                   action="store", choices=['load', 'each', 'no'], 
    +                   type="choice", dest="dist", default="no", 
    +                   help=("set mode for distributing tests to exec environments.\n\n"
    +                         "each: send each test to each available environment.\n\n"
    +                         "load: send each test to available environment.\n\n"
    +                         "(default) no: run tests inprocess, don't distribute."))
    +        group._addoption('--tx', dest="tx", action="append", default=[], metavar="xspec",
    +                   help=("add a test execution environment. some examples: "
    +                         "--tx popen//python=python2.5 --tx socket=192.168.1.102:8888 "
    +                         "--tx ssh=user at codespeak.net//chdir=testcache"))
    +        group._addoption('-d', 
    +                   action="store_true", dest="distload", default=False,
    +                   help="load-balance tests.  shortcut for '--dist=load'")
    +        group._addoption('-n', dest="numprocesses", metavar="numprocesses", 
                        action="store", type="int", 
    -                   help="number of local test processes. conflicts with --dist.")
    +                   help="shortcut for '--dist=load --tx=NUM*popen'")
             group.addoption('--rsyncdir', action="append", default=[], metavar="dir1", 
    -                   help="add local directory for rsync to remote test nodes.")
    -        group._addoption('--tx', dest="tx", action="append", default=[],
    -                   help=("add a test environment, specified in XSpec syntax. examples: "
    -                         "--tx popen//python=python2.5 --tx socket=192.168.1.102"))
    -        #group._addoption('--rest',
    -        #           action='store_true', dest="restreport", default=False,
    -        #           help="restructured text output reporting."),
    +                   help="add directory for rsyncing to remote tx nodes.")
     
         def pytest_configure(self, config):
    +        self.fixoptions(config)
             self.setsession(config)
             self.loadplugins(config)
    -        self.fixoptions(config)
     
         def fixoptions(self, config):
    +        if config.option.numprocesses:
    +            config.option.dist = "load"
    +            config.option.tx = ['popen'] * int(config.option.numprocesses)
    +        if config.option.distload:
    +            config.option.dist = "load"
             if config.getvalue("usepdb"):
                 if config.getvalue("looponfail"):
                     raise config.Error("--pdb incompatible with --looponfail.")
    -            if config.getvalue("dist"):
    -                raise config.Error("--pdb incomptaible with distributed testing.")
    +            if config.option.dist != "no":
    +                raise config.Error("--pdb incomptaible with distributing tests.")
     
         def loadplugins(self, config):
             for name in config.getvalue("plugin"):
    @@ -136,7 +146,7 @@
                 if val("looponfail"):
                     from py.__.test.looponfail.remote import LooponfailingSession
                     config.setsessionclass(LooponfailingSession)
    -            elif val("numprocesses") or val("dist"):
    +            elif val("dist") != "no":
                     from py.__.test.dist.dsession import  DSession
                     config.setsessionclass(DSession)
     
    @@ -153,10 +163,10 @@
                 return Exception
             return getattr(config._sessionclass, '__name__', None)
         assert x() == None
    -    assert x('--dist') == 'DSession'
    +    assert x('-d') == 'DSession'
    +    assert x('--dist=each') == 'DSession'
         assert x('-n3') == 'DSession'
         assert x('-f') == 'LooponfailingSession'
    -    assert x('--dist', '--collectonly') == 'Session'
     
     def test_generic(plugintester):
         plugintester.apicheck(DefaultPlugin)
    @@ -173,16 +183,48 @@
         assert config.option.plugin == ['default']
         config.pytestplugins.do_configure(config)
     
    -def test_conflict_options():
    -    def check_conflict_option(opts):
    -        print "testing if options conflict:", " ".join(opts)
    -        config = py.test.config._reparse(opts)
    -        py.test.raises(config.Error, 
    -            "config.pytestplugins.do_configure(config)")
    -    conflict_options = (
    -        '--looponfail --pdb',
    -        '--dist --pdb', 
    -    )
    -    for spec in conflict_options: 
    -        opts = spec.split()
    -        yield check_conflict_option, opts
    +
    +class TestDistOptions:
    +    def test_getxspecs(self, testdir):
    +        config = testdir.parseconfigure("--tx=popen", "--tx", "ssh=xyz")
    +        xspecs = config.getxspecs()
    +        assert len(xspecs) == 2
    +        print xspecs
    +        assert xspecs[0].popen 
    +        assert xspecs[1].ssh == "xyz"
    +
    +    def test_xspecs_multiplied(self, testdir):
    +        xspecs = testdir.parseconfigure("--tx=3*popen",).getxspecs()
    +        assert len(xspecs) == 3
    +        assert xspecs[1].popen 
    +
    +    def test_getrsyncdirs(self, testdir):
    +        config = testdir.parseconfigure('--rsyncdir=' + str(testdir.tmpdir))
    +        roots = config.getrsyncdirs()
    +        assert len(roots) == 1 + 1 
    +        assert testdir.tmpdir in roots
    +
    +    def test_getrsyncdirs_with_conftest(self, testdir):
    +        p = py.path.local()
    +        for bn in 'x y z'.split():
    +            p.mkdir(bn)
    +        testdir.makeconftest("""
    +            rsyncdirs= 'x', 
    +        """)
    +        config = testdir.parseconfigure(testdir.tmpdir, '--rsyncdir=y', '--rsyncdir=z')
    +        roots = config.getrsyncdirs()
    +        assert len(roots) == 3 + 1 
    +        assert py.path.local('y') in roots 
    +        assert py.path.local('z') in roots 
    +        assert testdir.tmpdir.join('x') in roots 
    +
    +def test_dist_options(testdir):
    +    py.test.raises(Exception, "testdir.parseconfigure('--pdb', '--looponfail')")
    +    py.test.raises(Exception, "testdir.parseconfigure('--pdb', '-n 3')")
    +    py.test.raises(Exception, "testdir.parseconfigure('--pdb', '-d')")
    +    config = testdir.parseconfigure("-n 2")
    +    assert config.option.dist == "load"
    +    assert config.option.tx == ['popen'] * 2
    +    
    +    config = testdir.parseconfigure("-d")
    +    assert config.option.dist == "load"
    
    Modified: py/trunk/py/test/plugin/pytest_pytester.py
    ==============================================================================
    --- py/trunk/py/test/plugin/pytest_pytester.py	(original)
    +++ py/trunk/py/test/plugin/pytest_pytester.py	Sun Mar 22 18:41:36 2009
    @@ -55,6 +55,7 @@
             self.tmpdir = tmpdir.mkdir(name)
             self.plugins = []
             self._syspathremove = []
    +        self.chdir() # always chdir
     
         def Config(self, pyplugins=None, topdir=None):
             if topdir is None:
    @@ -163,6 +164,11 @@
             config.parse(args)
             return config 
     
    +    def parseconfigure(self, *args):
    +        config = self.parseconfig(*args)
    +        config.pytestplugins.do_configure(config)
    +        return config
    +
         def getitem(self,  source, funcname="test_func"):
             modcol = self.getmodulecol(source)
             item = modcol.join(funcname) 
    
    Modified: py/trunk/py/test/plugin/pytest_restdoc.py
    ==============================================================================
    --- py/trunk/py/test/plugin/pytest_restdoc.py	(original)
    +++ py/trunk/py/test/plugin/pytest_restdoc.py	Sun Mar 22 18:41:36 2009
    @@ -6,12 +6,12 @@
             group.addoption('-R', '--urlcheck',
                    action="store_true", dest="urlcheck", default=False, 
                    help="urlopen() remote links found in ReST text files.") 
    -        group.addoption('--urlcheck-timeout', action="store", 
    +        group.addoption('--urltimeout', action="store", metavar="secs",
                 type="int", dest="urlcheck_timeout", default=5,
    -            help="timeout in seconds for urlcheck")
    +            help="timeout in seconds for remote urlchecks")
             group.addoption('--forcegen',
                    action="store_true", dest="forcegen", default=False,
    -               help="force generation of html files even if they appear up-to-date")
    +               help="force generation of html files.")
     
         def pytest_collect_file(self, path, parent):
             if path.ext == ".txt":
    
    Modified: py/trunk/py/test/testing/acceptance_test.py
    ==============================================================================
    --- py/trunk/py/test/testing/acceptance_test.py	(original)
    +++ py/trunk/py/test/testing/acceptance_test.py	Sun Mar 22 18:41:36 2009
    @@ -4,7 +4,7 @@
     pytestpath = pydir.join("bin", "py.test")
     EXPECTTIMEOUT=10.0
     
    -class TestPyTest:
    +class TestGeneralUsage:
         def test_config_error(self, testdir):
             testdir.makeconftest("""
                 class ConftestPlugin:
    @@ -251,8 +251,61 @@
                 "y* = 'xxxxxx*"
             ])
     
    +    def test_verbose_reporting(self, testdir):
    +        p1 = testdir.makepyfile("""
    +            import py
    +            def test_fail():
    +                raise ValueError()
    +            def test_pass():
    +                pass
    +            class TestClass:
    +                def test_skip(self):
    +                    py.test.skip("hello")
    +            def test_gen():
    +                def check(x):
    +                    assert x == 1
    +                yield check, 0
    +        """)
    +        result = testdir.runpytest(p1, '-v')
    +        result.stdout.fnmatch_lines([
    +            "*FAIL*test_verbose_reporting.py:2: test_fail*", 
    +            "*PASS*test_verbose_reporting.py:4: test_pass*",
    +            "*SKIP*test_verbose_reporting.py:7: TestClass.test_skip*",
    +            "*FAIL*test_verbose_reporting.py:10: test_gen*",
    +        ])
    +        assert result.ret == 1
    +
    +class TestDistribution:
    +    def test_dist_conftest_options(self, testdir):
    +        p1 = testdir.tmpdir.ensure("dir", 'p1.py')
    +        p1.dirpath("__init__.py").write("")
    +        p1.dirpath("conftest.py").write(py.code.Source("""
    +            print "importing conftest", __file__
    +            import py
    +            Option = py.test.config.Option 
    +            option = py.test.config.addoptions("someopt", 
    +                Option('--someopt', action="store_true", dest="someopt", default=False))
    +            dist_rsync_roots = ['../dir']
    +            print "added options", option
    +            print "config file seen from conftest", py.test.config
    +        """))
    +        p1.write(py.code.Source("""
    +            import py, conftest
    +            def test_1(): 
    +                print "config from test_1", py.test.config
    +                print "conftest from test_1", conftest.__file__
    +                print "test_1: py.test.config.option.someopt", py.test.config.option.someopt
    +                print "test_1: conftest", conftest
    +                print "test_1: conftest.option.someopt", conftest.option.someopt
    +                assert conftest.option.someopt 
    +        """))
    +        result = testdir.runpytest('-d', '--tx=popen', p1, '--someopt')
    +        assert result.ret == 0
    +        extra = result.stdout.fnmatch_lines([
    +            "*1 passed*", 
    +        ])
     
    -    def test_dist_testing(self, testdir):
    +    def test_manytests_to_one_popen(self, testdir):
             p1 = testdir.makepyfile("""
                     import py
                     def test_fail0():
    @@ -273,7 +326,7 @@
             ])
             assert result.ret == 1
     
    -    def test_dist_testing_conftest_specified(self, testdir):
    +    def test_dist_conftest_specified(self, testdir):
             p1 = testdir.makepyfile("""
                     import py
                     def test_fail0():
    @@ -329,44 +382,24 @@
             ])
             assert result.ret == 1
     
    -    def test_keyboard_interrupt(self, testdir):
    -        p1 = testdir.makepyfile("""
    -            import py
    -            def test_fail():
    -                raise ValueError()
    -            def test_inter():
    -                raise KeyboardInterrupt()
    -        """)
    -        result = testdir.runpytest(p1)
    +    def test_distribution_rsyncdirs_example(self, testdir):
    +        source = testdir.mkdir("source")
    +        dest = testdir.mkdir("dest")
    +        subdir = source.mkdir("example_pkg")
    +        subdir.ensure("__init__.py")
    +        p = subdir.join("test_one.py")
    +        p.write("def test_5(): assert not __file__.startswith(%r)" % str(p))
    +        result = testdir.runpytest("-d", "--rsyncdir=%(subdir)s" % locals(), 
    +            "--tx=popen//chdir=%(dest)s" % locals(), p)
    +        assert result.ret == 0
             result.stdout.fnmatch_lines([
    -            #"*test_inter() INTERRUPTED",
    -            "*KEYBOARD INTERRUPT*",
    -            "*1 failed*", 
    +            "*1* *popen*platform*",
    +            #"RSyncStart: [G1]",
    +            #"RSyncFinished: [G1]",
    +            "*1 passed*"
             ])
    +        assert dest.join(subdir.basename).check(dir=1)
     
    -    def test_verbose_reporting(self, testdir):
    -        p1 = testdir.makepyfile("""
    -            import py
    -            def test_fail():
    -                raise ValueError()
    -            def test_pass():
    -                pass
    -            class TestClass:
    -                def test_skip(self):
    -                    py.test.skip("hello")
    -            def test_gen():
    -                def check(x):
    -                    assert x == 1
    -                yield check, 0
    -        """)
    -        result = testdir.runpytest(p1, '-v')
    -        result.stdout.fnmatch_lines([
    -            "*FAIL*test_verbose_reporting.py:2: test_fail*", 
    -            "*PASS*test_verbose_reporting.py:4: test_pass*",
    -            "*SKIP*test_verbose_reporting.py:7: TestClass.test_skip*",
    -            "*FAIL*test_verbose_reporting.py:10: test_gen*",
    -        ])
    -        assert result.ret == 1
     
     class TestInteractive:
         def getspawn(self, tmpdir):
    @@ -443,4 +476,20 @@
                 "*popen-python2.4*FAIL*",
                 "*2 failed*"
             ])
    -        
    +       
    +class TestKeyboardInterrupt: 
    +    def test_raised_in_testfunction(self, testdir):
    +        p1 = testdir.makepyfile("""
    +            import py
    +            def test_fail():
    +                raise ValueError()
    +            def test_inter():
    +                raise KeyboardInterrupt()
    +        """)
    +        result = testdir.runpytest(p1)
    +        result.stdout.fnmatch_lines([
    +            #"*test_inter() INTERRUPTED",
    +            "*KEYBOARD INTERRUPT*",
    +            "*1 failed*", 
    +        ])
    +
    
    Modified: py/trunk/py/test/testing/test_config.py
    ==============================================================================
    --- py/trunk/py/test/testing/test_config.py	(original)
    +++ py/trunk/py/test/testing/test_config.py	Sun Mar 22 18:41:36 2009
    @@ -218,57 +218,13 @@
                 assert col.config is config 
     
     
    -class TestOptionsAndConfiguration:
    -    def test_getxspecs_numprocesses(self, testdir):
    -        config = testdir.parseconfig("-n3")
    -        xspecs = config.getxspecs()
    -        assert len(xspecs) == 3
    -
    -    def test_getxspecs(self, testdir):
    -        testdir.chdir() 
    -        config = testdir.parseconfig("--tx=popen", "--tx", "ssh=xyz")
    -        xspecs = config.getxspecs()
    -        assert len(xspecs) == 2
    -        print xspecs
    -        assert xspecs[0].popen 
    -        assert xspecs[1].ssh == "xyz"
    -
    -    def test_xspecs_multiplied(self, testdir):
    -        testdir.chdir()
    -        xspecs = testdir.parseconfig("--tx=3*popen",).getxspecs()
    -        assert len(xspecs) == 3
    -        assert xspecs[1].popen 
    -
    -    def test_getrsyncdirs(self, testdir):
    -        config = testdir.parseconfig('--rsyncdir=' + str(testdir.tmpdir))
    -        roots = config.getrsyncdirs()
    -        assert len(roots) == 1 + 1 
    -        assert testdir.tmpdir in roots
    -
    -    def test_getrsyncdirs_with_conftest(self, testdir):
    -        testdir.chdir()
    -        p = py.path.local()
    -        for bn in 'x y z'.split():
    -            p.mkdir(bn)
    -        testdir.makeconftest("""
    -            rsyncdirs= 'x', 
    -        """)
    -        config = testdir.parseconfig(testdir.tmpdir, '--rsyncdir=y', '--rsyncdir=z')
    -        roots = config.getrsyncdirs()
    -        assert len(roots) == 3 + 1 
    -        assert py.path.local('y') in roots 
    -        assert py.path.local('z') in roots 
    -        assert testdir.tmpdir.join('x') in roots 
    -
    -
    -
     class TestOptionEffects:
         def test_boxed_option_default(self, testdir):
             tmpdir = testdir.tmpdir.ensure("subdir", dir=1)
             config = py.test.config._reparse([tmpdir])
             config.initsession()
             assert not config.option.boxed
    -        config = py.test.config._reparse(['--dist', tmpdir])
    +        config = py.test.config._reparse(['-d', tmpdir])
             config.initsession()
             assert not config.option.boxed
     
    
    Modified: py/trunk/py/test/testing/test_pickling.py
    ==============================================================================
    --- py/trunk/py/test/testing/test_pickling.py	(original)
    +++ py/trunk/py/test/testing/test_pickling.py	Sun Mar 22 18:41:36 2009
    @@ -87,9 +87,7 @@
                 assert getattr(config2.option, name) == value
             assert config2.getvalue("x") == 1
     
    -    def test_config_rconfig(self, testdir):
    -        tmp = testdir.tmpdir
    -        tmp.ensure("__init__.py")
    +    def test_config_pickling_customoption(self, testdir):
             testdir.makeconftest("""
                 class ConftestPlugin:
                     def pytest_addoption(self, parser):
    @@ -97,6 +95,27 @@
                         group.addoption('-G', '--glong', action="store", default=42, 
                             type="int", dest="gdest", help="g value.")
             """)
    +        config = testdir.parseconfig("-G", "11")
    +        assert config.option.gdest == 11
    +        repr = config.__getstate__()
    +
    +        config = testdir.Config()
    +        py.test.raises(AttributeError, "config.option.gdest")
    +
    +        config2 = testdir.Config()
    +        config2.__setstate__(repr) 
    +        assert config2.option.gdest == 11
    +
    +    def test_config_pickling_and_conftest_deprecated(self, testdir):
    +        tmp = testdir.tmpdir.ensure("w1", "w2", dir=1)
    +        tmp.ensure("__init__.py")
    +        tmp.join("conftest.py").write(py.code.Source("""
    +            class ConftestPlugin:
    +                def pytest_addoption(self, parser):
    +                    group = parser.addgroup("testing group")
    +                    group.addoption('-G', '--glong', action="store", default=42, 
    +                        type="int", dest="gdest", help="g value.")
    +        """))
             config = testdir.parseconfig(tmp, "-G", "11")
             assert config.option.gdest == 11
             repr = config.__getstate__()
    @@ -106,10 +125,11 @@
     
             config2 = testdir.Config()
             config2.__setstate__(repr) 
    +        assert config2.option.gdest == 11
    +       
             option = config2.addoptions("testing group", 
                     config2.Option('-G', '--glong', action="store", default=42,
                            type="int", dest="gdest", help="g value."))
    -        assert config2.option.gdest == 11
             assert option.gdest == 11
     
         def test_config_picklability(self, testdir):
    
    
    From hpk at codespeak.net  Sun Mar 22 21:44:48 2009
    From: hpk at codespeak.net (hpk at codespeak.net)
    Date: Sun, 22 Mar 2009 21:44:48 +0100 (CET)
    Subject: [py-svn] r63210 - in py/trunk/py: . doc execnet execnet/testing
    	test/dist test/dist/testing test/plugin test/testing
    Message-ID: <20090322204448.57A851684DC@codespeak.net>
    
    Author: hpk
    Date: Sun Mar 22 21:44:45 2009
    New Revision: 63210
    
    Modified:
       py/trunk/py/conftest.py
       py/trunk/py/doc/test-dist.txt
       py/trunk/py/execnet/gateway.py
       py/trunk/py/execnet/testing/test_xspec.py
       py/trunk/py/execnet/xspec.py
       py/trunk/py/test/dist/dsession.py
       py/trunk/py/test/dist/nodemanage.py
       py/trunk/py/test/dist/testing/test_dsession.py
       py/trunk/py/test/dist/testing/test_nodemanage.py
       py/trunk/py/test/dist/testing/test_txnode.py
       py/trunk/py/test/dist/txnode.py
       py/trunk/py/test/plugin/pytest_terminal.py
       py/trunk/py/test/testing/acceptance_test.py
    Log:
    * implement "--dist=each" to distribute each to each available node
    * improved node-management and nice showing of results 
    * streamline command line options
    
    
    
    Modified: py/trunk/py/conftest.py
    ==============================================================================
    --- py/trunk/py/conftest.py	(original)
    +++ py/trunk/py/conftest.py	Sun Mar 22 21:44:45 2009
    @@ -12,11 +12,10 @@
             group = parser.addgroup("pylib", "py lib testing options")
             group.addoption('--sshhost', 
                    action="store", dest="sshhost", default=None,
    -               help=("target to run tests requiring ssh, e.g. "
    -                     "user at codespeak.net"))
    +               help=("ssh xspec for ssh functional tests. "))
             group.addoption('--gx', 
                    action="append", dest="gspecs", default=None,
    -               help=("add a global test environment, XSpec-syntax. ")), 
    +               help=("add a global test environment, XSpec-syntax. "))
             group.addoption('--runslowtests',
                    action="store_true", dest="runslowtests", default=False,
                    help=("run slow tests"))
    
    Modified: py/trunk/py/doc/test-dist.txt
    ==============================================================================
    --- py/trunk/py/doc/test-dist.txt	(original)
    +++ py/trunk/py/doc/test-dist.txt	Sun Mar 22 21:44:45 2009
    @@ -17,10 +17,10 @@
     Especially for longer running tests or tests requiring 
     a lot of IO this can lead to considerable speed ups. 
     
    -Test on a different python interpreter 
    +Test on a different python version
     ----------------------------------------------------------
     
    -To send tests to a python2.4 process, you may type::
    +To send tests to a python2.4 interpreter process, you may type::
     
         py.test --tx popen//python=python2.4
     
    @@ -65,6 +65,7 @@
     
         py.test -d --tx socket=192.168.1.102:8888 --rsyncdir mypkg mypkg
     
    +
     no remote installation requirements 
     ++++++++++++++++++++++++++++++++++++++++++++
     
    
    Modified: py/trunk/py/execnet/gateway.py
    ==============================================================================
    --- py/trunk/py/execnet/gateway.py	(original)
    +++ py/trunk/py/execnet/gateway.py	Sun Mar 22 21:44:45 2009
    @@ -244,6 +244,9 @@
         def _rinfo(self, update=False):
             """ return some sys/env information from remote. """
             if update or not hasattr(self, '_cache_rinfo'):
    +            class RInfo:
    +                def __init__(self, **kwargs):
    +                    self.__dict__.update(kwargs)
                 self._cache_rinfo = RInfo(**self.remote_exec("""
                     import sys, os
                     channel.send(dict(
    @@ -251,6 +254,7 @@
                         version_info = sys.version_info, 
                         platform = sys.platform,
                         cwd = os.getcwd(),
    +                    pid = os.getpid(),
                     ))
                 """).receive())
             return self._cache_rinfo
    @@ -368,10 +372,6 @@
             if self._requestqueue is not None:
                 self._requestqueue.put(None)
     
    -class RInfo:
    -    def __init__(self, **kwargs):
    -        self.__dict__.update(kwargs)
    -
     def getid(gw, cache={}):
         name = gw.__class__.__name__
         try:
    
    Modified: py/trunk/py/execnet/testing/test_xspec.py
    ==============================================================================
    --- py/trunk/py/execnet/testing/test_xspec.py	(original)
    +++ py/trunk/py/execnet/testing/test_xspec.py	Sun Mar 22 21:44:45 2009
    @@ -3,19 +3,21 @@
     XSpec = py.execnet.XSpec
     
     class TestXSpec:
    -    def test_attributes(self):
    +    def test_norm_attributes(self):
             spec = XSpec("socket=192.168.102.2:8888//python=c:/this/python2.5//chdir=d:\hello")
             assert spec.socket == "192.168.102.2:8888"
             assert spec.python == "c:/this/python2.5" 
             assert spec.chdir == "d:\hello"
    +        assert spec.nice is None
             assert not hasattr(spec, 'xyz')
     
             py.test.raises(AttributeError, "spec._hello")
     
    -        spec = XSpec("socket=192.168.102.2:8888//python=python2.5")
    +        spec = XSpec("socket=192.168.102.2:8888//python=python2.5//nice=3")
             assert spec.socket == "192.168.102.2:8888"
             assert spec.python == "python2.5"
             assert spec.chdir is None
    +        assert spec.nice == "3"
     
             spec = XSpec("ssh=user at host//chdir=/hello/this//python=/usr/bin/python2.5")
             assert spec.ssh == "user at host"
    @@ -53,6 +55,18 @@
             assert rinfo.cwd == py.std.os.getcwd()
             assert rinfo.version_info == py.std.sys.version_info
     
    +    def test_popen_nice(self):
    +        gw = py.execnet.makegateway("popen//nice=5")
    +        remotenice = gw.remote_exec("""
    +            import os
    +            if hasattr(os, 'nice'):
    +                channel.send(os.nice(0))
    +            else:
    +                channel.send(None)
    +        """).receive()
    +        if remotenice is not None:
    +            assert remotenice == 5
    +
         def test_popen_explicit(self):
             gw = py.execnet.makegateway("popen//python=%s" % py.std.sys.executable)
             assert gw.spec.python == py.std.sys.executable
    
    Modified: py/trunk/py/execnet/xspec.py
    ==============================================================================
    --- py/trunk/py/execnet/xspec.py	(original)
    +++ py/trunk/py/execnet/xspec.py	Sun Mar 22 21:44:45 2009
    @@ -10,7 +10,7 @@
             * if no "=value" is given, assume a boolean True value 
         """
         # XXX for now we are very restrictive about actually allowed key-values 
    -    popen = ssh = socket = python = chdir = None
    +    popen = ssh = socket = python = chdir = nice = None
     
         def __init__(self, string):
             self._spec = string
    @@ -56,14 +56,18 @@
             gw = py.execnet.SocketGateway(*hostport)
         gw.spec = spec 
         # XXX events
    -    if spec.chdir:
    -        gw.remote_exec("""
    +    if spec.chdir or spec.nice:
    +        channel = gw.remote_exec("""
                 import os
    -            path = %r 
    -            try:
    +            path, nice = channel.receive()
    +            if path:
    +                if not os.path.exists(path):
    +                    os.mkdir(path)
                     os.chdir(path)
    -            except OSError:
    -                os.mkdir(path)
    -                os.chdir(path)
    -        """ % spec.chdir).waitclose()
    +            if nice and hasattr(os, 'nice'):
    +                os.nice(nice)
    +        """)
    +        nice = spec.nice and int(spec.nice) or 0
    +        channel.send((spec.chdir, nice))
    +        channel.waitclose()
         return gw
    
    Modified: py/trunk/py/test/dist/dsession.py
    ==============================================================================
    --- py/trunk/py/test/dist/dsession.py	(original)
    +++ py/trunk/py/test/dist/dsession.py	Sun Mar 22 21:44:45 2009
    @@ -177,8 +177,11 @@
                 else:
                     self.bus.notify("collectionstart", event.CollectionStart(next))
                     self.queueevent("collectionreport", basic_collect_report(next))
    -        self.senditems(senditems)
    -        #self.senditems_each(senditems)
    +        if self.config.option.dist == "each":
    +            self.senditems_each(senditems)
    +        else:
    +            # XXX assert self.config.option.dist == "load"
    +            self.senditems_load(senditems)
     
         def queueevent(self, eventname, *args, **kwargs):
             self.queue.put((eventname, args, kwargs)) 
    @@ -201,7 +204,7 @@
                 # we have some left, give it to the main loop
                 self.queueevent("rescheduleitems", event.RescheduleItems(tosend))
     
    -    def senditems(self, tosend):
    +    def senditems_load(self, tosend):
             if not tosend:
                 return 
             for node, pending in self.node2pending.items():
    @@ -242,6 +245,8 @@
             """ setup any neccessary resources ahead of the test run. """
             self.nodemanager = NodeManager(self.config)
             self.nodemanager.setup_nodes(putevent=self.queue.put)
    +        if self.config.option.dist == "each":
    +            self.nodemanager.wait_nodesready(5.0)
     
         def teardown(self):
             """ teardown any resources after a test run. """ 
    
    Modified: py/trunk/py/test/dist/nodemanage.py
    ==============================================================================
    --- py/trunk/py/test/dist/nodemanage.py	(original)
    +++ py/trunk/py/test/dist/nodemanage.py	Sun Mar 22 21:44:45 2009
    @@ -1,6 +1,6 @@
     import py
     import sys, os
    -from py.__.test.dist.txnode import MasterNode
    +from py.__.test.dist.txnode import TXNode
     from py.__.execnet.gwmanage import GatewayManager
     
         
    @@ -12,19 +12,11 @@
             self.roots = self.config.getrsyncdirs()
             self.gwmanager = GatewayManager(specs)
             self.nodes = []
    +        self._nodesready = py.std.threading.Event()
     
         def trace(self, msg):
             self.config.bus.notify("trace", "nodemanage", msg)
     
    -    def trace_nodestatus(self):
    -        if self.config.option.debug:
    -            for ch, result in self.gwmanager.multi_exec("""
    -                import sys, os
    -                channel.send((sys.executable, os.getcwd(), sys.path))
    -            """).receive_each(withchannel=True):
    -                self.trace("spec %r, execuable %r, cwd %r, syspath %r" %(
    -                    ch.gateway.spec, result[0], result[1], result[2]))
    -
         def config_getignores(self):
             return self.config.getconftest_pathlist("rsyncignore")
     
    @@ -55,20 +47,33 @@
             # such that unpickling configs will 
             # pick it up as the right topdir 
             # (for other gateways this chdir is irrelevant)
    +        self.trace("making gateways")
             old = self.config.topdir.chdir()  
             try:
                 self.gwmanager.makegateways()
             finally:
                 old.chdir()
    -        self.trace_nodestatus()
    -
     
         def setup_nodes(self, putevent):
             self.rsync_roots()
    -        self.trace_nodestatus()
    +        self.trace("setting up nodes")
             for gateway in self.gwmanager.gateways:
    -            node = MasterNode(gateway, self.config, putevent)
    -            self.nodes.append(node) 
    +            node = TXNode(gateway, self.config, putevent, slaveready=self._slaveready)
    +            gateway.node = node  # to keep node alive 
    +            self.trace("started node %r" % node)
    +
    +    def _slaveready(self, node):
    +        #assert node.gateway == node.gateway
    +        #assert node.gateway.node == node
    +        self.nodes.append(node)
    +        self.trace("%s slave node ready %r" % (node.gateway.id, node))
    +        if len(self.nodes) == len(self.gwmanager.gateways):
    +            self._nodesready.set()
    +   
    +    def wait_nodesready(self, timeout=None):
    +        self._nodesready.wait(timeout)
    +        if not self._nodesready.isSet():
    +            raise IOError("nodes did not get ready for %r secs" % timeout)
     
         def teardown_nodes(self):
             # XXX do teardown nodes? 
    
    Modified: py/trunk/py/test/dist/testing/test_dsession.py
    ==============================================================================
    --- py/trunk/py/test/dist/testing/test_dsession.py	(original)
    +++ py/trunk/py/test/dist/testing/test_dsession.py	Sun Mar 22 21:44:45 2009
    @@ -35,14 +35,14 @@
             assert not session.node2pending
             session.addnode(node)
             assert len(session.node2pending) == 1
    -        session.senditems([item])
    +        session.senditems_load([item])
             pending = session.removenode(node)
             assert pending == [item]
             assert item not in session.item2nodes
             l = session.removenode(node)
             assert not l 
     
    -    def test_send_remove_to_two_nodes(self, testdir):
    +    def test_senditems_each_and_receive_with_two_nodes(self, testdir):
             item = testdir.getitem("def test_func(): pass")
             node1 = MockNode()
             node2 = MockNode()
    @@ -60,13 +60,13 @@
             assert not session.node2pending[node1] 
             assert not session.item2nodes
     
    -    def test_senditems_removeitems(self, testdir):
    +    def test_senditems_load_and_receive_one_node(self, testdir):
             item = testdir.getitem("def test_func(): pass")
             node = MockNode()
             rep = run(item, node)
             session = DSession(item.config)
             session.addnode(node)
    -        session.senditems([item])  
    +        session.senditems_load([item])  
             assert session.node2pending[node] == [item]
             assert session.item2nodes[item] == [node]
             session.removeitem(item, node)
    @@ -174,7 +174,7 @@
             session.addnode(node2)
           
             # have one test pending for a node that goes down 
    -        session.senditems([item1, item2])
    +        session.senditems_load([item1, item2])
             node = session.item2nodes[item1] [0]
             session.queueevent("testnodedown", node, None)
             evrec = EventRecorder(session.bus)
    @@ -316,7 +316,7 @@
     
             node = MockNode()
             session.addnode(node)
    -        session.senditems([item])
    +        session.senditems_load([item])
             session.queueevent("itemtestreport", run(item, node))
             loopstate = session._initloopstate([])
             session.loop_once(loopstate)
    @@ -340,7 +340,7 @@
     
             colreport = basic_collect_report(modcol)
             item1, item2 = colreport.result
    -        session.senditems([item1])
    +        session.senditems_load([item1])
             # node2pending will become empty when the loop sees the report 
             rep = run(item1, node)
     
    
    Modified: py/trunk/py/test/dist/testing/test_nodemanage.py
    ==============================================================================
    --- py/trunk/py/test/dist/testing/test_nodemanage.py	(original)
    +++ py/trunk/py/test/dist/testing/test_nodemanage.py	Sun Mar 22 21:44:45 2009
    @@ -28,6 +28,13 @@
             assert p.join("dir1").check()
             assert p.join("dir1", "file1").check()
     
    +    def test_popen_nodes_are_ready(self, testdir):
    +        nodemanager = NodeManager(testdir.parseconfig(
    +            "--tx", "3*popen"))
    +        
    +        nodemanager.setup_nodes([].append)
    +        nodemanager.wait_nodesready(timeout=2.0)
    +
         def test_popen_rsync_subdir(self, testdir, source, dest):
             dir1 = source.mkdir("dir1")
             dir2 = dir1.mkdir("dir2")
    
    Modified: py/trunk/py/test/dist/testing/test_txnode.py
    ==============================================================================
    --- py/trunk/py/test/dist/testing/test_txnode.py	(original)
    +++ py/trunk/py/test/dist/testing/test_txnode.py	Sun Mar 22 21:44:45 2009
    @@ -1,6 +1,6 @@
     
     import py
    -from py.__.test.dist.txnode import MasterNode
    +from py.__.test.dist.txnode import TXNode
     
     class EventQueue:
         def __init__(self, bus, queue=None):
    @@ -44,7 +44,7 @@
             self.queue = py.std.Queue.Queue()
             self.xspec = py.execnet.XSpec("popen")
             self.gateway = py.execnet.makegateway(self.xspec)
    -        self.node = MasterNode(self.gateway, self.config, putevent=self.queue.put)
    +        self.node = TXNode(self.gateway, self.config, putevent=self.queue.put)
             assert not self.node.channel.isclosed()
             return self.node 
     
    
    Modified: py/trunk/py/test/dist/txnode.py
    ==============================================================================
    --- py/trunk/py/test/dist/txnode.py	(original)
    +++ py/trunk/py/test/dist/txnode.py	Sun Mar 22 21:44:45 2009
    @@ -5,16 +5,22 @@
     from py.__.test import event
     from py.__.test.dist.mypickle import PickleChannel
     
    -class MasterNode(object):
    -    """ Install slave code, manage sending test tasks & receiving results """
    +
    +class TXNode(object):
    +    """ Represents a Test Execution environment in the controlling process. 
    +        - sets up a slave node through an execnet gateway 
    +        - manages sending of test-items and receival of results and events
    +        - creates events when the remote side crashes 
    +    """
         ENDMARK = -1
     
    -    def __init__(self, gateway, config, putevent):
    +    def __init__(self, gateway, config, putevent, slaveready=None):
             self.config = config 
             self.putevent = putevent 
             self.gateway = gateway
             self.channel = install_slave(gateway, config)
             self.channel.setcallback(self.callback, endmarker=self.ENDMARK)
    +        self._sendslaveready = slaveready
             self._down = False
     
         def notify(self, eventname, *args, **kwargs):
    @@ -39,6 +45,8 @@
                     return
                 eventname, args, kwargs = eventcall 
                 if eventname == "slaveready":
    +                if self._sendslaveready:
    +                    self._sendslaveready(self)
                     self.notify("testnodeready", self)
                 elif eventname == "slavefinished":
                     self._down = True
    
    Modified: py/trunk/py/test/plugin/pytest_terminal.py
    ==============================================================================
    --- py/trunk/py/test/plugin/pytest_terminal.py	(original)
    +++ py/trunk/py/test/plugin/pytest_terminal.py	Sun Mar 22 21:44:45 2009
    @@ -26,6 +26,7 @@
                 file = py.std.sys.stdout
             self._tw = py.io.TerminalWriter(file)
             self.currentfspath = None 
    +        self.gateway2info = {}
     
         def write_fspath_result(self, fspath, res):
             if fspath != self.currentfspath:
    @@ -96,10 +97,12 @@
             else:
                 d['extra'] = ""
             d['cwd'] = rinfo.cwd
    -        self.write_line("%(id)s %(spec)s -- platform %(platform)s, "
    +        infoline = ("%(id)s %(spec)s -- platform %(platform)s, "
                             "Python %(version)s "
                             "cwd: %(cwd)s"
                             "%(extra)s" % d)
    +        self.write_line(infoline)
    +        self.gateway2info[gateway] = infoline
     
         def pyevent_gwmanage_rsyncstart(self, source, gateways):
             targets = ", ".join([gw.id for gw in gateways])
    @@ -122,7 +125,6 @@
     
         def pyevent_testnodeready(self, node):
             self.write_line("%s node ready to receive tests" %(node.gateway.id,))
    -        
     
         def pyevent_testnodedown(self, node, error):
             if error:
    @@ -244,7 +246,11 @@
             if 'failed' in self.stats and self.config.option.tbstyle != "no":
                 self.write_sep("=", "FAILURES")
                 for ev in self.stats['failed']:
    -                self.write_sep("_")
    +                self.write_sep("_", "FAILURES")
    +                if hasattr(ev, 'node'):
    +                    self.write_line(self.gateway2info.get(
    +                        ev.node.gateway, "node %r (platinfo not found? strange)")
    +                            [:self._tw.fullwidth-1])
                     ev.toterminal(self._tw)
     
         def summary_stats(self):
    @@ -344,15 +350,6 @@
     from py.__.test.runner import basic_run_report
     
     class TestTerminal:
    -    @py.test.mark.xfail
    -    def test_testnodeready(self, testdir, linecomp):
    -        item = testdir.getitem("def test_func(): pass")
    -        rep = TerminalReporter(item.config, linecomp.stringio)
    -        XXX # rep.pyevent_testnodeready(maketestnodeready())
    -        linecomp.assert_contains_lines([
    -            "*INPROCESS* %s %s - Python %s" %(sys.platform, 
    -            sys.executable, repr_pythonversion(sys.version_info))
    -        ])
     
         def test_pass_skip_fail(self, testdir, linecomp):
             modcol = testdir.getmodulecol("""
    
    Modified: py/trunk/py/test/testing/acceptance_test.py
    ==============================================================================
    --- py/trunk/py/test/testing/acceptance_test.py	(original)
    +++ py/trunk/py/test/testing/acceptance_test.py	Sun Mar 22 21:44:45 2009
    @@ -400,6 +400,27 @@
             ])
             assert dest.join(subdir.basename).check(dir=1)
     
    +    def test_dist_each(self, testdir):
    +        interpreters = []
    +        for name in ("python2.4", "python2.5"):
    +            interp = py.path.local.sysfind(name)
    +            if interp is None:
    +                py.test.skip("%s not found" % name)
    +            interpreters.append(interp)
    +
    +        testdir.makepyfile(__init__="", test_one="""
    +            import sys
    +            def test_hello():
    +                print "%s...%s" % sys.version_info[:2]
    +                assert 0
    +        """)
    +        args = ["--dist=each"]
    +        args += ["--tx", "popen//python=%s" % interpreters[0]]
    +        args += ["--tx", "popen//python=%s" % interpreters[1]]
    +        result = testdir.runpytest(*args)
    +        result.stdout.fnmatch_lines(["2...4"])
    +        result.stdout.fnmatch_lines(["2...5"])
    +
     
     class TestInteractive:
         def getspawn(self, tmpdir):
    @@ -454,28 +475,6 @@
             child.expect("MODIFIED.*test_simple_looponfail_interaction.py", timeout=4.0)
             child.expect("1 passed", timeout=5.0)
             child.kill(15)
    -
    -    @py.test.mark.xfail("need new cmdline option")
    -    def test_dist_each(self, testdir):
    -        for name in ("python2.4", "python2.5"):
    -            if not py.path.local.sysfind(name):
    -                py.test.skip("%s not found" % name)
    -        testdir.makepyfile(__init__="", test_one="""
    -            import sys
    -            def test_hello():
    -                print sys.version_info[:2]
    -                assert 0
    -        """)
    -        result = testdir.runpytest("--dist-each", 
    -            "--tx=popen//python2.4", 
    -            "--tx=popen//python2.5", 
    -        )
    -        assert result.ret == 1
    -        result.stdout.fnmatch_lines([
    -            "*popen-python2.5*FAIL*", 
    -            "*popen-python2.4*FAIL*",
    -            "*2 failed*"
    -        ])
            
     class TestKeyboardInterrupt: 
         def test_raised_in_testfunction(self, testdir):
    
    
    From hpk at codespeak.net  Mon Mar 23 00:18:53 2009
    From: hpk at codespeak.net (hpk at codespeak.net)
    Date: Mon, 23 Mar 2009 00:18:53 +0100 (CET)
    Subject: [py-svn] r63212 - py/trunk/py/doc
    Message-ID: <20090322231853.01FFF1684C7@codespeak.net>
    
    Author: hpk
    Date: Mon Mar 23 00:18:49 2009
    New Revision: 63212
    
    Modified:
       py/trunk/py/doc/execnet.txt
    Log:
    completely revamp the execnet web page 
    
    
    Modified: py/trunk/py/doc/execnet.txt
    ==============================================================================
    --- py/trunk/py/doc/execnet.txt	(original)
    +++ py/trunk/py/doc/execnet.txt	Mon Mar 23 00:18:49 2009
    @@ -1,57 +1,27 @@
    -The py.execnet library 
    -======================
    -
    -.. contents::
    -.. sectnum::
    -
    -A new view on distributed execution
    ------------------------------------ 
    -
    -``py.execnet`` supports ad-hoc distribution of parts of
    -a program across process and network barriers.  *Ad-hoc*
    -means that the client side may completely control 
    -
    -* which parts of a program execute remotely and 
    -
    -* which data protocols are used between them 
    -
    -without requiring any prior manual installation 
    -of user program code on the remote side.  In fact,
    -not even a prior installation of any server code
    -is required, provided there is a way to get 
    -an input/output connection to a python interpreter
    -(for example via "ssh" and a "python" executable). 
    -
    -By comparison, traditional Remote Method Based (RMI) 
    -require prior installation and manual rather
    -heavy processes of setup, distribution and 
    -communication between program parts.  
     
     
    -What about Security? Are you completely nuts? 
    ----------------------------------------------
    +``py.execnet`` allows to:
     
    -We'll talk about that later :-) 
    +* instantiate local or remote Python Processes
    +* send code for execution in one or many processes 
    +* asynchronously send and receive data between processes through channels 
    +* completely avoid manual installation steps on remote places
     
    -Basic Features
    -==============
    -
    -With ''py.execnet'' you get the means 
    -
    -- to execute python code fragements in remote processes and 
    -- to interchange data between asynchronously executing code fragments 
    -
    -
    -Available Gateways
    ------------------------------------------
    +.. contents::
     
    -You may use one of the following connection methods:
    +Gateways: immediately spawn local or remote process
    +----------------------------------------------------
     
    -* :api:`py.execnet.PopenGateway` a subprocess on the local 
    -  machine.  Useful for jailing certain parts of a program
    -  or for making use of multiple processors. 
    +In order to send code to a remote place or a subprocess
    +you need to instantiate a so-called Gateway object.  
    +There are currently three Gateway classes:
    +
    +* :api:`py.execnet.PopenGateway` to open a subprocess 
    +  on the local machine.  Useful for making use 
    +  of multiple processors to to contain code execution
    +  in a separated environment. 
     
    -* :api:`py.execnet.SshGateway` a way to connect to 
    +* :api:`py.execnet.SshGateway` to connect to 
       a remote ssh server and distribute execution to it. 
     
     * :api:`py.execnet.SocketGateway` a way to connect to 
    @@ -59,13 +29,14 @@
       requires a manually started 
       :source:py/execnet/script/socketserver.py
       script.  You can run this "server script" without 
    -  having the py lib installed on that remote system. 
    +  having the py lib installed on the remote system
    +  and you can setup it up as permanent service. 
     
     
    -executing code remotely 
    --------------------------------------
    +remote_exec: execute source code remotely 
    +--------------------------------------------
     
    -All gateways offer remote code execution via this high level function:
    +All gateways offer remote code execution via this high level function::
     
         def remote_exec(source): 
             """return channel object for communicating with the asynchronously 
    @@ -76,7 +47,7 @@
     side and get both a local and a remote Channel_ object,
     which you can use to have the local and remote site
     communicate data in a structured way.   Here is 
    -an example: 
    +an example for reading the PID::
     
       >>> import py 
       >>> gw = py.execnet.PopenGateway()
    @@ -92,7 +63,7 @@
     .. _`channel-api`: 
     .. _`exchange data`: 
     
    -Bidirectionally exchange data between hosts 
    +Channels: bidirectionally exchange data between hosts 
     -------------------------------------------------------------
     
     A channel object allows to send and receive data between 
    @@ -131,44 +102,52 @@
             A remote side blocking on receive() on this channel 
             will get woken up and see an EOFError exception. 
     
    -Instantiating a gateway from a string-specification
    ----------------------------------------------------------
     
    -To specify Gateways with a String::
    +.. _xspec:
     
    -    >>> import py
    -    >>> gw = py.execnet.makegateway("popen")
    -    >>> ex = gw.remote_exec("import sys ; channel.send(sys.executable)").receive()
    -    >>> assert ex == py.std.sys.executable, (ex, py.std.sys.executable)
    -    >>>
     
    -current gateway types and specifications
    -+++++++++++++++++++++++++++++++++++++++++++++++
    +XSpec: string specification for gateway type and configuration
    +-------------------------------------------------------------------
     
    -+------------------------+-------------------------------------------+
    -| ssh host               | ssh:host:pythonexecutable:path            |
    -+------------------------+-------------------------------------------+
    -| local subprocess       | popen:python_executable:path              |
    -+------------------------+-------------------------------------------+
    -| remote socket process  | socket:host:port:python_executable:path   |
    -+------------------------+-------------------------------------------+
    +``py.execnet`` supports a simple extensible format for 
    +specifying and configuring Gateways for remote execution.  
    +You can use a string speficiation to make a new gateway, 
    +for example a new SshGateway::
     
    -examples of valid specifications
    -++++++++++++++++++++++++++++++++++++++
    +    gateway = py.execnet.makegateway("ssh=myhost")
     
    -``ssh:wyvern:python2.4`` signifies a connection to a Python process on the machine reached via "ssh wyvern", current dir will be the 'pyexecnet-cache' subdirectory. 
    +Let's look at some examples for valid specifications. 
    +Specification for an ssh connection to `wyvern`, running on python2.4 in the (newly created) 'mycache'  subdirectory::
     
    -``socket:192.168.1.4`` signifies a connection to a Python Socket server process to the given IP on the default port 8888; current dir will be the 'pyexecnet-cache' directory. 
    +    ssh=wyvern//python=python2.4//chdir=mycache
     
    -``popen:python2.5`` signifies a connection to a python2.5 subprocess; current dir will be the current dir of the instantiator. 
    +Specification of a python2.5 subprocess; with a low CPU priority ("nice" level). Current dir will be the current dir of the instantiator (that's true for all 'popen' specifications unless they specify 'chdir')::
     
    -``popen::pytest-cache1`` signifies a connection to a subprocess using ``sys.executable``; current dir will be the `pytest-cache1`. 
    +    popen//python=2.5//nice=20
     
    +Specification of a Python Socket server process that listens on 192.168.1.4:8888; current dir will be the 'pyexecnet-cache' sub directory which is used a default for all remote processes::
     
    -Examples for execnet usage
    --------------------------------------------
    +    socket=192.168.1.4:8888
     
    -Example: compare cwd() of Popen Gateways
    +More generally, a specification string has this general format::
    +
    +    key1=value1//key2=value2//key3=value3
    +
    +If you omit a value, a boolean true value is assumed.  Currently
    +the following key/values are supported: 
    +
    +* ``popen`` for a PopenGateway
    +* ``ssh=host`` for a SshGateway
    +* ``socket=address:port`` for a SocketGateway 
    +* ``python=executable`` for specifying Python executables
    +* ``chdir=path`` change remote working dir to given relative or absolute path
    +* ``nice=value`` decrease remote nice level if platforms supports it 
    +  
    +
    +Examples fo py.execnet usage 
    +-------------------------------------
    +
    +compare cwd() of Popen Gateways
     ++++++++++++++++++++++++++++++++++++++++
     
     A PopenGateway has the same working directory as the instantiatior::
    @@ -180,10 +159,10 @@
         >>> assert res == os.getcwd()
         >>> gw.exit()
     
    -Example: multichannels 
    -++++++++++++++++++++++++++++++++++++++++
    +synchronously receive results from two sub processes 
    +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     
    -MultiChannels manage 1-n execution and communication:
    +Use MultiChannels for receiving multiple results from remote code::
     
         >>> import py
         >>> ch1 = py.execnet.PopenGateway().remote_exec("channel.send(1)")
    @@ -193,20 +172,33 @@
         >>> assert len(l) == 2
         >>> assert 1 in l 
         >>> assert 2 in l 
    -
    -MultiGateways help with sending code to multiple remote places: 
    +   
    +assynchronously receive results from two sub processes 
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    +
    +Use ``MultiChannel.make_receive_queue()`` for asynchronously receiving 
    +multiple results from remote code.  This standard Queue provides 
    +``(channel, result)`` tuples which allows to determine where 
    +a result comes from::
     
         >>> import py
    -    >>> mgw = py.execnet.MultiGateway([py.execnet.PopenGateway() for x in range(2)])
    -    >>> mch = mgw.remote_exec("import os; channel.send(os.getcwd())")
    -    >>> res = mch.receive_each()
    -    >>> assert res == [os.getcwd()] * 2, res
    -    >>> mgw.exit()
    -
    +    >>> ch1 = py.execnet.PopenGateway().remote_exec("channel.send(1)")
    +    >>> ch2 = py.execnet.PopenGateway().remote_exec("channel.send(2)")
    +    >>> mch = py.execnet.MultiChannel([ch1, ch2])
    +    >>> queue = mch.make_receive_queue()
    +    >>> chan1, res1 = queue.get()  # you may also specify a timeout 
    +    >>> chan2, res2 = queue.get()
    +    >>> res1 + res2 
    +    3
    +    >>> assert chan1 in (ch1, ch2)
    +    >>> assert chan2 in (ch1, ch2)
    +    >>> assert chan1 != chan2
     
    -Example: receiving file contents from remote places
    +receive file contents from remote SSH account 
     ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    -problem: retrieving contents of remote files::
    +
    +Here is a small program that you can use to retrieve
    +contents of remote files::
     
         import py
         # open a gateway to a fresh child process 
    @@ -224,50 +216,20 @@
             # process content 
          
         # later you can exit / close down the gateway
    -    contentgateway.exit()
    +    gw.exit()
     
     
    -A more complicated "nested" Gateway Example 
    -........................................... 
    +Instantiate a socket server in a new subprocess 
    +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     
     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.  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 are 20 lines of code making the above triangle happen::
    -
    +child process, and starts a socket server within that process 
    +and then opens a second gateway to the freshly started
    +socketserver::
    +                
         import py 
    -    port = 7770
    -    socketserverbootstrap = py.code.Source(
    -        mypath.dirpath().dirpath('bin', 'socketserver.py').read(),
    -        """
    -        import socket
    -        sock = bind_and_listen(("localhost", %r)) 
    -        channel.send("ok") 
    -        startserver(sock)
    -    """ % port) 
    -    # open a gateway to a fresh child process
    -    proxygw = py.execnet.PopenGateway()
    -
    -    # execute asynchronously the above socketserverbootstrap on the other
    -    channel = proxygw.remote_exec(socketserverbootstrap)
    -
    -    # the other side should start the socket server now 
    -    assert channel.receive() == "ok" 
    -    gw = py.execnet.SocketGateway('localhost', cls.port)
    -    print "initialized socket gateway to port", cls.port
     
    +    popengw = py.execnet.PopenGateway()
    +    socketgw = py.execnet.SocketGateway.new_remote(popengw, ("127.0.0.1", 0))
     
    +    print socketgw._rinfo() # print some info about the remote environment
    
    
    From hpk at codespeak.net  Mon Mar 23 01:20:01 2009
    From: hpk at codespeak.net (hpk at codespeak.net)
    Date: Mon, 23 Mar 2009 01:20:01 +0100 (CET)
    Subject: [py-svn] r63213 - py/trunk/py/doc
    Message-ID: <20090323002001.5826A1684C9@codespeak.net>
    
    Author: hpk
    Date: Mon Mar 23 01:19:59 2009
    New Revision: 63213
    
    Modified:
       py/trunk/py/doc/execnet.txt
    Log:
    fix typo
    
    
    Modified: py/trunk/py/doc/execnet.txt
    ==============================================================================
    --- py/trunk/py/doc/execnet.txt	(original)
    +++ py/trunk/py/doc/execnet.txt	Mon Mar 23 01:19:59 2009
    @@ -173,7 +173,7 @@
         >>> assert 1 in l 
         >>> assert 2 in l 
        
    -assynchronously receive results from two sub processes 
    +asynchronously receive results from two sub processes 
     +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     
     Use ``MultiChannel.make_receive_queue()`` for asynchronously receiving 
    
    
    From hpk at codespeak.net  Mon Mar 23 01:38:17 2009
    From: hpk at codespeak.net (hpk at codespeak.net)
    Date: Mon, 23 Mar 2009 01:38:17 +0100 (CET)
    Subject: [py-svn] r63214 - py/trunk
    Message-ID: <20090323003817.DB43D1684D1@codespeak.net>
    
    Author: hpk
    Date: Mon Mar 23 01:38:17 2009
    New Revision: 63214
    
    Modified:
       py/trunk/setup.py
    Log:
    re-gen setup.py with up-to-date list of files. 
    
    
    
    Modified: py/trunk/setup.py
    ==============================================================================
    --- py/trunk/setup.py	(original)
    +++ py/trunk/setup.py	Mon Mar 23 01:38:17 2009
    @@ -1,7 +1,7 @@
     """
         setup file for 'py' package based on:
     
    -        https://codespeak.net/svn/py/trunk, revision=63163
    +        https://codespeak.net/svn/py/trunk, revision=63208
     
         autogenerated by gensetup.py
     """
    @@ -18,9 +18,9 @@
     
     - `py.test`_: cross-project testing tool with many advanced features
     - `py.execnet`_: ad-hoc code distribution to SSH, Socket and local sub processes
    +- `py.magic.greenlet`_: micro-threads on standard CPython ("stackless-light") and PyPy
     - `py.path`_: path abstractions over local and subversion files 
     - `py.code`_: dynamic code compile and traceback printing support
    -- `py.magic.greenlet`_: micro-threads on standard CPython ("stackless-light") and PyPy
     
     The py lib and its tools should work well on Linux, Win32, 
     OSX, Python versions 2.3-2.6.  For questions please go to
    @@ -56,7 +56,7 @@
                                               'py.svnwcrevert = py.cmdline:pysvnwcrevert',
                                               'py.test = py.cmdline:pytest',
                                               'py.which = py.cmdline:pywhich']},
    -        classifiers=['Development Status :: 4 - Beta',
    +        classifiers=['Development Status :: 3 - Alpha',
                          'Intended Audience :: Developers',
                          'License :: OSI Approved :: MIT License',
                          'Operating System :: POSIX',
    @@ -106,8 +106,8 @@
                       'py.rest',
                       'py.rest.testing',
                       'py.test',
    -                  'py.test.dsession',
    -                  'py.test.dsession.testing',
    +                  'py.test.dist',
    +                  'py.test.dist.testing',
                       'py.test.looponfail',
                       'py.test.looponfail.testing',
                       'py.test.plugin',
    @@ -122,6 +122,13 @@
                       'py.xmlobj.testing'],
             package_data={'py': ['',
                                  '',
    +                             '',
    +                             '',
    +                             '',
    +                             '',
    +                             '',
    +                             '',
    +                             '',
                                  'LICENSE',
                                  'bin/_findpy.py',
                                  'bin/_genscripts.py',
    @@ -169,12 +176,6 @@
                                  'doc/contact.txt',
                                  'doc/download.txt',
                                  'doc/draft_pyfs',
    -                             'doc/example/genhtml.py',
    -                             'doc/example/genhtmlcss.py',
    -                             'doc/example/genxml.py',
    -                             'doc/example/pytest/failure_demo.py',
    -                             'doc/example/pytest/test_failures.py',
    -                             'doc/example/pytest/test_setup_flow_example.py',
                                  'doc/execnet.txt',
                                  'doc/future.txt',
                                  'doc/greenlet.txt',
    @@ -189,7 +190,12 @@
                                  'doc/release-0.9.2.txt',
                                  'doc/style.css',
                                  'doc/test-config.txt',
    +                             'doc/test-dist.txt',
    +                             'doc/test-examples.txt',
    +                             'doc/test-ext.txt',
    +                             'doc/test-features.txt',
                                  'doc/test-plugins.txt',
    +                             'doc/test-quickstart.txt',
                                  'doc/test.txt',
                                  'doc/why_py.txt',
                                  'doc/xml.txt',
    @@ -214,4 +220,4 @@
     
     if __name__ == '__main__':
         main()
    -        
    +        
    \ No newline at end of file
    
    
    From hpk at codespeak.net  Mon Mar 23 01:46:23 2009
    From: hpk at codespeak.net (hpk at codespeak.net)
    Date: Mon, 23 Mar 2009 01:46:23 +0100 (CET)
    Subject: [py-svn] r63215 - py/trunk/py/doc
    Message-ID: <20090323004623.76D3F1684D4@codespeak.net>
    
    Author: hpk
    Date: Mon Mar 23 01:46:22 2009
    New Revision: 63215
    
    Modified:
       py/trunk/py/doc/test-dist.txt
       py/trunk/py/doc/test-features.txt
    Log:
    updating test distribution docs. 
    
    
    Modified: py/trunk/py/doc/test-dist.txt
    ==============================================================================
    --- py/trunk/py/doc/test-dist.txt	(original)
    +++ py/trunk/py/doc/test-dist.txt	Mon Mar 23 01:46:22 2009
    @@ -7,6 +7,11 @@
     are reported back and displayed to your local test session.  You may 
     specify different Python versions and interpreters.
     
    +Synchronisation and running of tests only requires
    +a bare Python installation on the remote side.   No
    +special software is installed - this is realized 
    +by use of the **zero installation** `py.execnet`_ mechanisms.
    +
     Speed up test runs by sending tests to multiple CPUs
     ----------------------------------------------------------
     
    @@ -17,25 +22,27 @@
     Especially for longer running tests or tests requiring 
     a lot of IO this can lead to considerable speed ups. 
     
    -Test on a different python version
    -----------------------------------------------------------
     
    -To send tests to a python2.4 interpreter process, you may type::
    +Running tests in a Python subprocess 
    +----------------------------------------
    +
    +To instantiate a python2.4 sub process and send tests to itinterpreter process, you may type::
     
    -    py.test --tx popen//python=python2.4
    +    py.test -d --tx popen//python=python2.4
     
     This will start a subprocess which is run with the "python2.4"
     Python interpreter, found in your system binary lookup path. 
     
    -If you prefix the --tx option like this::
    +If you prefix the --tx option value like this::
     
    -    py.test --tx 3*popen//python=python2.4
    +    --tx 3*popen//python=python2.4
     
    -then three subprocesses would be created. 
    +then three subprocesses would be created and tests
    +will be load-balanced across these three processes. 
     
     
     Sending tests to remote SSH accounts
    -------------------------------------
    +-----------------------------------------------
     
     Suppose you have a package ``mypkg`` which contains some 
     tests that you can successfully run locally. And you
    @@ -51,40 +58,39 @@
     You can specify multiple ``--rsyncdir`` directories 
     to be sent to the remote side. 
     
    +
     Sending tests to remote Socket Servers
     ----------------------------------------
     
     Download the single-module `socketserver.py`_ Python program 
    -and run it on the specified hosts like this::
    +and run it like this::
     
         python socketserver.py
     
    -It will tell you that it starts listening.  You can now
    -on your home machine specify this new socket host
    -with something like this::
    +It will tell you that it starts listening on the default
    +port.  You can now on your home machine specify this 
    +new socket host with something like this::
     
         py.test -d --tx socket=192.168.1.102:8888 --rsyncdir mypkg mypkg
     
     
    -no remote installation requirements 
    -++++++++++++++++++++++++++++++++++++++++++++
    +Running tests on many platforms at once 
    +-------------------------------------------------------------
     
    -Synchronisation and running of tests only requires
    -a bare Python installation on the remote side.   No
    -special software is installed - this is realized through the
    -*zero installation* principle that the underlying 
    -`py.execnet`_ mechanisms implements. 
    +The basic command to run tests on multiple platforms is::
     
    +    py.test --dist=each --tx=spec1 --tx=spec2 
     
    -.. _`socketserver.py`: ../execnet/script/socketserver.py
    -.. _`py.execnet`: execnet.html
    +If you specify a windows host, an OSX host and a Linux
    +environment this command will send each tests to all 
    +platforms - and report back failures from all platforms
    +at once.   The provided specifications strings 
    +use the `xspec syntax`_. 
     
    -Differences from local tests
    -----------------------------
    +.. _`xspec syntax`: execnet.html#xspec
     
    -* Test order is rather random (instead of in file order). 
    -* the test process may hang due to network problems 
    -* you may not reference files outside of rsynced directory structures
    +.. _`socketserver.py`: ../execnet/script/socketserver.py
    +.. _`py.execnet`: execnet.html
     
     Specifying test exec environments in a conftest.py
     -------------------------------------------------------------
    @@ -110,3 +116,4 @@
     These directory specifications are relative to the directory
     where the ``conftest.py`` is found.
     
    +
    
    Modified: py/trunk/py/doc/test-features.txt
    ==============================================================================
    --- py/trunk/py/doc/test-features.txt	(original)
    +++ py/trunk/py/doc/test-features.txt	Mon Mar 23 01:46:22 2009
    @@ -1,7 +1,4 @@
     
    -Basic Features of ``py.test`` 
    -=============================
    -
     automatic collection of tests on all levels
     -------------------------------------------
     
    
    
    From nopmop at codespeak.net  Mon Mar 23 01:49:32 2009
    From: nopmop at codespeak.net (nopmop at codespeak.net)
    Date: Mon, 23 Mar 2009 01:49:32 +0100 (CET)
    Subject: [py-svn] r63216 - py/trunk/py/doc
    Message-ID: <20090323004932.172A6168445@codespeak.net>
    
    Author: nopmop
    Date: Mon Mar 23 01:49:31 2009
    New Revision: 63216
    
    Modified:
       py/trunk/py/doc/execnet.txt
    Log:
    72: the code fragement -> fragment
    114: You can use a string speficiation -> specification
    147: Examples fo -> of
    
    Modified: py/trunk/py/doc/execnet.txt
    ==============================================================================
    --- py/trunk/py/doc/execnet.txt	(original)
    +++ py/trunk/py/doc/execnet.txt	Mon Mar 23 01:49:31 2009
    @@ -69,7 +69,7 @@
     A channel object allows to send and receive data between 
     two asynchronously running programs.  When calling
     `remote_exec` you will get a channel object back and 
    -the code fragement running on the other side will 
    +the code fragment running on the other side will 
     see a channel object in its global namespace. 
     
     Here is the interface of channel objects::
    @@ -111,7 +111,7 @@
     
     ``py.execnet`` supports a simple extensible format for 
     specifying and configuring Gateways for remote execution.  
    -You can use a string speficiation to make a new gateway, 
    +You can use a string spefication to make a new gateway, 
     for example a new SshGateway::
     
         gateway = py.execnet.makegateway("ssh=myhost")
    @@ -144,7 +144,7 @@
     * ``nice=value`` decrease remote nice level if platforms supports it 
       
     
    -Examples fo py.execnet usage 
    +Examples of py.execnet usage 
     -------------------------------------
     
     compare cwd() of Popen Gateways
    
    
    From hpk at codespeak.net  Mon Mar 23 01:59:19 2009
    From: hpk at codespeak.net (hpk at codespeak.net)
    Date: Mon, 23 Mar 2009 01:59:19 +0100 (CET)
    Subject: [py-svn] r63217 - in py/trunk: . py/doc
    Message-ID: <20090323005919.66A381684C0@codespeak.net>
    
    Author: hpk
    Date: Mon Mar 23 01:59:18 2009
    New Revision: 63217
    
    Removed:
       py/trunk/py/doc/apigen_refactorings.txt
    Modified:
       py/trunk/MANIFEST
       py/trunk/README.txt
       py/trunk/setup.py
    Log:
    * remove obsolete doc
    * regen setup.py 
    * update README.txt
    
    
    Modified: py/trunk/MANIFEST
    ==============================================================================
    --- py/trunk/MANIFEST	(original)
    +++ py/trunk/MANIFEST	Mon Mar 23 01:59:18 2009
    @@ -3,6 +3,13 @@
     MANIFEST
     README.txt
     TODO.txt
    +example/execnet/popen_read_multiple.py
    +example/genhtml.py
    +example/genhtmlcss.py
    +example/genxml.py
    +example/pytest/failure_demo.py
    +example/pytest/test_failures.py
    +example/pytest/test_setup_flow_example.py
     ez_setup.py
     py/LICENSE
     py/__init__.py
    @@ -105,12 +112,6 @@
     py/doc/contact.txt
     py/doc/download.txt
     py/doc/draft_pyfs
    -py/doc/example/genhtml.py
    -py/doc/example/genhtmlcss.py
    -py/doc/example/genxml.py
    -py/doc/example/pytest/failure_demo.py
    -py/doc/example/pytest/test_failures.py
    -py/doc/example/pytest/test_setup_flow_example.py
     py/doc/execnet.txt
     py/doc/future.txt
     py/doc/greenlet.txt
    @@ -125,7 +126,12 @@
     py/doc/release-0.9.2.txt
     py/doc/style.css
     py/doc/test-config.txt
    +py/doc/test-dist.txt
    +py/doc/test-examples.txt
    +py/doc/test-ext.txt
    +py/doc/test-features.txt
     py/doc/test-plugins.txt
    +py/doc/test-quickstart.txt
     py/doc/test.txt
     py/doc/why_py.txt
     py/doc/xml.txt
    @@ -292,17 +298,16 @@
     py/test/conftesthandle.py
     py/test/custompdb.py
     py/test/defaultconftest.py
    -py/test/dsession/__init__.py
    -py/test/dsession/dsession.py
    -py/test/dsession/mypickle.py
    -py/test/dsession/nodemanage.py
    -py/test/dsession/testing/__init__.py
    -py/test/dsession/testing/test_dsession.py
    -py/test/dsession/testing/test_functional_dsession.py
    -py/test/dsession/testing/test_mypickle.py
    -py/test/dsession/testing/test_nodemanage.py
    -py/test/dsession/testing/test_txnode.py
    -py/test/dsession/txnode.py
    +py/test/dist/__init__.py
    +py/test/dist/dsession.py
    +py/test/dist/mypickle.py
    +py/test/dist/nodemanage.py
    +py/test/dist/testing/__init__.py
    +py/test/dist/testing/test_dsession.py
    +py/test/dist/testing/test_mypickle.py
    +py/test/dist/testing/test_nodemanage.py
    +py/test/dist/testing/test_txnode.py
    +py/test/dist/txnode.py
     py/test/event.py
     py/test/looponfail/__init__.py
     py/test/looponfail/remote.py
    
    Modified: py/trunk/README.txt
    ==============================================================================
    --- py/trunk/README.txt	(original)
    +++ py/trunk/README.txt	Mon Mar 23 01:59:18 2009
    @@ -1,10 +1,11 @@
     The py lib is a Python development support library featuring 
     the following tools and modules:
     
    -* py.test: automated testing tool
    +* py.test: tool for distributed automated testing
     * py.execnet: ad-hoc distributed execution
    -* py.magic.greenlet: micro-threads
    +* py.code: dynamic code generation and introspection
     * py.path:  uniform local and svn path objects 
    +* py.magic.greenlet: micro-threads
     
     It includes code and contributions from several people, 
     listed in the LICENSE file. 
    
    Deleted: /py/trunk/py/doc/apigen_refactorings.txt
    ==============================================================================
    --- /py/trunk/py/doc/apigen_refactorings.txt	Mon Mar 23 01:59:18 2009
    +++ (empty file)
    @@ -1,35 +0,0 @@
    -
    -Proposed apigen refactorings
    -=============================
    -
    -First of all we would like to have some kind of a persistent storage
    -for apigen, so we could use it for different purposes (hint! hint! pdb)
    -than only web pages. This will resolve the issue of having separated
    -apigen "data" generation and web page generation.
    -
    -Apigen is very usefull feature, but we don't use it in general. Which
    -is bad. One of the reasons is above and the other one is that API of
    -apigen is not that well defined which makes it harder to use. So what
    -I think would be:
    -
    -* **py.apigen** tool, which will take tests and initpkg (or whatever
    -  means of collecting data) and will try to store it somewhere
    -  (not sure, plain text log as a first step?). Than next step
    -  would be to have tools for generating webpages out of it
    -  (py.webapi or so) and other tools which will integrate it to pdb,
    -  emacs (pick your random IDE here) or whatever.
    -
    -* Another option is to have py.test generate those data and have another
    -  tools using it.
    -
    -* Data storage. Text with a log comes in mind, but it's not very handy.
    -  Using any sort of SQL doesn't really counts, cause it makes pylib
    -  less standalone, especially that I wouldn't like to have write all
    -  those SQL myself, but rather use some kind of sql object relational
    -  mapper. Another format might be some kind of structured text
    -  (xml anyone?) or pickled stuff. Pickle has problems on his own,
    -  so I don't have any best solution at hand.
    -
    -* Accessing. These are all strings and simple types built on top of it.
    -  Probably would be good not to store all data in memory, because it might
    -  be huge in case we would like to have all past informations there.
    
    Modified: py/trunk/setup.py
    ==============================================================================
    --- py/trunk/setup.py	(original)
    +++ py/trunk/setup.py	Mon Mar 23 01:59:18 2009
    @@ -1,7 +1,7 @@
     """
         setup file for 'py' package based on:
     
    -        https://codespeak.net/svn/py/trunk, revision=63208
    +        https://codespeak.net/svn/py/trunk, revision=63215
     
         autogenerated by gensetup.py
     """
    @@ -169,7 +169,6 @@
                                  'compat/LICENSE',
                                  'compat/testing/test_doctest.txt',
                                  'compat/testing/test_doctest2.txt',
    -                             'doc/apigen_refactorings.txt',
                                  'doc/bin.txt',
                                  'doc/code.txt',
                                  'doc/coding-style.txt',
    
    
    From hpk at codespeak.net  Mon Mar 23 02:23:19 2009
    From: hpk at codespeak.net (hpk at codespeak.net)
    Date: Mon, 23 Mar 2009 02:23:19 +0100 (CET)
    Subject: [py-svn] r63218 - py/trunk/py/cmdline/testing
    Message-ID: <20090323012319.2F6A41684C0@codespeak.net>
    
    Author: hpk
    Date: Mon Mar 23 02:23:17 2009
    New Revision: 63218
    
    Modified:
       py/trunk/py/cmdline/testing/test_generic.py
    Log:
    temporary fix for the issue that rsync does not synchronize executable bits yet.
    
    
    Modified: py/trunk/py/cmdline/testing/test_generic.py
    ==============================================================================
    --- py/trunk/py/cmdline/testing/test_generic.py	(original)
    +++ py/trunk/py/cmdline/testing/test_generic.py	Mon Mar 23 02:23:17 2009
    @@ -24,6 +24,9 @@
                 cmd = script.basename
             else:
                 cmd = "%s" %(script, )
    +            # XXX distributed testing's rsync does not support
    +            # syncing executable bits 
    +            script.chmod(0777)
     
             if script.basename.startswith("py.lookup") or \
                script.basename.startswith("py.which"):
    
    
    From hpk at codespeak.net  Mon Mar 23 03:07:55 2009
    From: hpk at codespeak.net (hpk at codespeak.net)
    Date: Mon, 23 Mar 2009 03:07:55 +0100 (CET)
    Subject: [py-svn] r63219 - in py/trunk/py: execnet/testing test/dist
    Message-ID: <20090323020755.85EAA1684DD@codespeak.net>
    
    Author: hpk
    Date: Mon Mar 23 03:07:53 2009
    New Revision: 63219
    
    Modified:
       py/trunk/py/execnet/testing/test_gwmanage.py
       py/trunk/py/execnet/testing/test_xspec.py
       py/trunk/py/test/dist/dsession.py
    Log:
    fixing tests to care for underlying symlinked working directories
    
    
    Modified: py/trunk/py/execnet/testing/test_gwmanage.py
    ==============================================================================
    --- py/trunk/py/execnet/testing/test_gwmanage.py	(original)
    +++ py/trunk/py/execnet/testing/test_gwmanage.py	Mon Mar 23 03:07:53 2009
    @@ -77,7 +77,7 @@
             import os
             hm = GatewayManager(["popen//chdir=hello"] * 2)
             testdir.tmpdir.chdir()
    -        hellopath = testdir.tmpdir.mkdir("hello")
    +        hellopath = testdir.tmpdir.mkdir("hello").realpath()
             hm.makegateways()
             l = hm.multi_exec("import os ; channel.send(os.getcwd())").receive_each()
             paths = [x[1] for x in l]
    @@ -101,13 +101,13 @@
             hm.multi_chdir("hello", inplacelocal=False)
             l = hm.multi_exec("import os ; channel.send(os.getcwd())").receive_each()
             assert len(l) == 2
    -        assert l == [os.getcwd()] * 2
    +        curwd = os.path.realpath(os.getcwd())
    +        assert l == [curwd] * 2
     
             hm.multi_chdir("hello")
             l = hm.multi_exec("import os ; channel.send(os.getcwd())").receive_each()
             assert len(l) == 2
             assert l[0] == l[1]
    -        curwd = os.getcwd()
             assert l[0].startswith(curwd)
             assert l[0].endswith("hello")
     
    
    Modified: py/trunk/py/execnet/testing/test_xspec.py
    ==============================================================================
    --- py/trunk/py/execnet/testing/test_xspec.py	(original)
    +++ py/trunk/py/execnet/testing/test_xspec.py	Mon Mar 23 03:07:53 2009
    @@ -104,13 +104,13 @@
         def test_popen_chdir_absolute(self, testdir):
             gw = py.execnet.makegateway("popen//chdir=%s" % testdir.tmpdir)
             rinfo = gw._rinfo()
    -        assert rinfo.cwd == str(testdir.tmpdir)
    +        assert rinfo.cwd == str(testdir.tmpdir.realpath())
     
         def test_popen_chdir_newsub(self, testdir):
             testdir.chdir()
             gw = py.execnet.makegateway("popen//chdir=hello")
             rinfo = gw._rinfo()
    -        assert rinfo.cwd == str(testdir.tmpdir.join("hello"))
    +        assert rinfo.cwd == str(testdir.tmpdir.join("hello").realpath())
     
         def test_ssh(self, specssh):
             sshhost = specssh.ssh
    
    Modified: py/trunk/py/test/dist/dsession.py
    ==============================================================================
    --- py/trunk/py/test/dist/dsession.py	(original)
    +++ py/trunk/py/test/dist/dsession.py	Mon Mar 23 03:07:53 2009
    @@ -230,7 +230,8 @@
             if item not in self.item2nodes:
                 raise AssertionError(item, self.item2nodes)
             nodes = self.item2nodes[item]
    -        nodes.remove(node)
    +        if node in nodes: # the node might have gone down already
    +            nodes.remove(node)
             if not nodes:
                 del self.item2nodes[item]
             self.node2pending[node].remove(item)
    
    
    From hpk at codespeak.net  Mon Mar 23 03:11:33 2009
    From: hpk at codespeak.net (hpk at codespeak.net)
    Date: Mon, 23 Mar 2009 03:11:33 +0100 (CET)
    Subject: [py-svn] r63220 - py/extradoc/talk/pycon-us-2009/pytest-advanced
    Message-ID: <20090323021133.129D2168433@codespeak.net>
    
    Author: hpk
    Date: Mon Mar 23 03:11:32 2009
    New Revision: 63220
    
    Modified:
       py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.html
       py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt
    Log:
    adding some bits regarding a demo.
    
    
    Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.html
    ==============================================================================
    --- py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.html	(original)
    +++ py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.html	Mon Mar 23 03:11:32 2009
    @@ -11,34 +11,9 @@
     
    -

    Title: py.test - cross-platform and distributed testing -Presenter: Holger Krekel <holger@merlinux.eu>, Brian <brian@dorseys.org> -Tutorial format: interactive lecture -Recording: I give permission to record and publish my PyCon tutorial for free distribution. -Intended Audience: Python programmers -Maximum number of students: maybe 30 -Perequisites/knowledge: good knowledge of python programming, basic familiarity with automated testing -Requirements: Attendees are welcome to bring their laptops with Python installed (version 2.4 or higher). -Notes to reviewer: visting the beginner-oriented tutorial "rapid testing with minimal effort" is recommended, but not required.

    -
    -

    Tutorial Summary

    -

    Want to know more about advanced automated testing with Python? -Use a tool that allows you to ad-hoc distribute tests to multiple -CPUs for speed and to multiple platforms for compatibility checks? -With tons of debugging help in failure situations?

    -

    This tutorial provides in-depth information on advanced usages -of the popular py.test tool. We highlight its current feature set -including using and writing extensions for generating HTML pages, -testing Javascript or ReST documents. We showcase and discuss ways -of distributing tests across CPUs and platforms and will leave -time to discuss and tackle specific scenarios brought up -during the session.

    -

    The tutorial format will be an interactive lecture with plenty -of time for questions.

    -

    Terminology/Overview (20 minutes)

    -img/little_red_riding_hood_by_marikaz.jpg +img/little_red_riding_hood_by_marikaz.jpg

    http://marikaz.deviantart.com/ CC 3.0 AN-ND

    @@ -54,8 +29,8 @@

    Benefits of automated tests

      -
    • Quality of code
    • evolve codebase more easily
    • +
    • higher code quality
    • better collaboration
    • perfect fit for Python
    @@ -71,15 +46,15 @@

    Small Tests

    -
    img/small.png
    +
    img/small.png

    Medium Tests

    -
    img/medium.png
    +
    img/medium.png

    Large Tests

    -
    img/large.png
    +
    img/large.png

    Walkthrough Advanced Features (30 minutes)

    -img/new_color_in_dark_old_city_by_marikaz.jpg +img/new_color_in_dark_old_city_by_marikaz.jpg

    http://marikaz.deviantart.com/ CC 3.0 AN-ND

    @@ -117,6 +92,7 @@ def setup_method(self, method): self.tmpdir = self.root.mkdir(method.__name__)
    +

    XXX: Example confusing? Without pylib experience will students understand 'ensure'? What is setuptestfs?

    funcargs: per-function setup

    @@ -154,10 +130,10 @@

    Skipping Doctests / ReST

    -

    XXX check with code

    with 'pytest_restdoc' plugin:

    -.. >>> mypkg = py.test.importorskip("mypkg")
    +.. >>> import py
    +.. >>> if py.test.config.option.xyz: raise ValueError("skipchunk")
     >>> x = 3
     >>> print x
     3
    @@ -165,7 +141,7 @@
     

    Generative / Parametrized tests

    -

    creating three tests with "yield":

    +

    generate three tests with "yield":

     def check(x):
         assert x >= 0
    @@ -177,14 +153,14 @@
     

    Named Parametrized tests

    -

    creating three tests with "yield":

    +

    generate three named tests with "yield":

     def check(x):
         assert x >= 0
     
     def test_gen():
         for x in 1,2,3:
    -        yield "check_check, x
    +        yield "check %s" % x, check, x
     
    @@ -209,7 +185,7 @@

    Using Plugins and Extensions (40 minutes)

    -img/end_of_a_age_by_marikaz.jpg +img/end_of_a_age_by_marikaz.jpg

    http://marikaz.deviantart.com/ CC 3.0 AN-ND

    @@ -224,8 +200,8 @@
    • can be put into test directory or higher up
    • contains test configuration values
    • +
    • specifies which plugins to use
    • default values for command line options
    • -
    • specifies plugins to use
    • (old) can specify collection and test items
    @@ -273,6 +249,29 @@
  • DoctestFile collect doctests in a .txt file
  • +
    +

    Interactive demo "collection tree"

    +

    you can inspect at the test collection tree +tree without running tests:

    +
    +py.test --collectonly
    +
    +
    +
    +

    Collector nodes

    +

    Collector nodes:

    +
      +
    • collect() method provides a list of children
    • +
    • children may be collector nodes or runnable test items
    • +
    • Collection nodes are not tied to Directory/the file system
    • +
    +
    +
    +

    Test Item nodes

    +
      +
    • runtest() is called for executing a test
    • +
    +

    Specifying plugins

      @@ -281,8 +280,8 @@ anywhere in your import path
    -
    -

    Specifying plugins in a conftest

    +
    +

    Writing a local conftest plugin

    you can write a local conftest.py based plugin:

     class ConftestPlugin:
    @@ -304,7 +303,7 @@
     

    Break

    -img/flying_lady_by_marikaz.jpg +img/flying_lady_by_marikaz.jpg

    http://marikaz.deviantart.com/ CC 3.0 AN-ND

    @@ -349,18 +348,17 @@

    Distributed Testing (45 minutes)

    -img/rails_in_the_city_by_marikaz.jpg +img/rails_in_the_city_by_marikaz.jpg

    http://marikaz.deviantart.com/ CC 3.0 AN-ND

    Distributed Testing

      -
    • motivation/vision
    • -
    • run your tests on multiple CPUs/multicore
    • -
    • running tests on multiple machines at once
    • -
    • running tests on windows, driven by Unix
    • +
    • distribute test load to multiple CPUs/multicore
    • +
    • simultanously run tests on multiple machines
    • do's and dont's for cross-process testing
    +

    XXX insert bits from py/trunk/py/doc/test-dist.txt

    Questions

    Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Mon Mar 23 03:11:32 2009 @@ -20,8 +20,8 @@ Benefits of automated tests ============================== -- Quality of code - evolve codebase more easily +- higher code quality - better collaboration - perfect fit for Python @@ -99,7 +99,6 @@ XXX: Example confusing? Without pylib experience will students understand 'ensure'? What is setuptestfs? - funcargs: per-function setup =================================== @@ -150,7 +149,7 @@ Generative / Parametrized tests ================================= -creating three tests with "yield":: +generate three tests with "yield":: def check(x): assert x >= 0 @@ -162,16 +161,15 @@ Named Parametrized tests ================================= -creating three tests with "yield":: +generate three named tests with "yield":: def check(x): assert x >= 0 def test_gen(): for x in 1,2,3: - yield "check_check, x + yield "check %s" % x, check, x -XXX typo in yeild above? Selection/Reporting ============================ @@ -216,8 +214,8 @@ - can be put into test directory or higher up - contains test configuration values +- specifies which plugins to use - default values for command line options -- specifies plugins to use - (old) can specify collection and test items Customizing py.test @@ -254,7 +252,27 @@ - **Function** sets up and executes a python test function - **DoctestFile** collect doctests in a .txt file -XXX - will you be showing simple code examples or demos? There is are a lot of new abstractions here. +Interactive demo "collection tree" +==================================== + +you can inspect at the test collection tree +tree without running tests:: + + py.test --collectonly + +Collector nodes +==================================== + +Collector nodes: + +- ``collect()`` method provides a list of children +- children may be collector nodes or runnable test items +- Collection nodes are not tied to Directory/the file system + +Test Item nodes +==================================== + +- ``runtest()`` is called for executing a test Specifying plugins @@ -264,7 +282,7 @@ - plugins are always named "pytest_NAME" and can be anywhere in your import path -Specifying plugins in a conftest +Writing a local conftest plugin ==================================== you can write a local conftest.py based plugin:: @@ -329,7 +347,6 @@ pytest_pyfunc_call pytest_report* - Writing cross-project plugins ================================== @@ -349,13 +366,11 @@ Distributed Testing =================== -- motivation/vision -- run your tests on multiple CPUs/multicore -- running tests on multiple machines at once -- running tests on windows, driven by Unix +- distribute test load to multiple CPUs/multicore +- simultanously run tests on multiple machines - do's and dont's for cross-process testing -XXX - multiple python versions? +XXX insert bits from py/trunk/py/doc/test-dist.txt Questions ========= From hpk at codespeak.net Mon Mar 23 03:30:33 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 23 Mar 2009 03:30:33 +0100 (CET) Subject: [py-svn] r63221 - py/extradoc/talk/pycon-us-2009/pytest-advanced Message-ID: <20090323023033.073351684D4@codespeak.net> Author: hpk Date: Mon Mar 23 03:30:32 2009 New Revision: 63221 Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Log: a bit of streamlining of slides Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Mon Mar 23 03:30:32 2009 @@ -1,3 +1,6 @@ +.. include:: beamerdefs.txt +.. include:: + Terminology/Overview (20 minutes) ============================================================================ @@ -12,7 +15,7 @@ ======================================= - Benefits of automated tests -- small/medium/big tests aka unit/functional/integration +- small/medium/big tests aka unit/functional/acceptance - acceptance tests - benefits of automated testing - related testing tools @@ -31,7 +34,7 @@ - developer and "customer" tests - unit tests - functional tests -- integration / system tests +- acceptance tests Small Tests ============================== @@ -75,7 +78,7 @@ http://marikaz.deviantart.com/ CC 3.0 AN-ND -If you want to try yourself +Install py.test ============================ - svn checkout http://codespeak.net/svn/py/dist @@ -221,11 +224,12 @@ Customizing py.test =========================== -- modify/extend the test collection process -- add command line options -- register to reporting events (trunk/1.0) -- write conftest.py files - write local or global plugins +- write conftest.py files + +- add command line options +- modify/extend the test collection process +- register to reporting events - for debugging:: py.test --collectonly @@ -255,25 +259,11 @@ Interactive demo "collection tree" ==================================== -you can inspect at the test collection tree +you can always inspect the test collection tree tree without running tests:: py.test --collectonly -Collector nodes -==================================== - -Collector nodes: - -- ``collect()`` method provides a list of children -- children may be collector nodes or runnable test items -- Collection nodes are not tied to Directory/the file system - -Test Item nodes -==================================== - -- ``runtest()`` is called for executing a test - Specifying plugins ======================== From hpk at codespeak.net Mon Mar 23 11:01:19 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 23 Mar 2009 11:01:19 +0100 (CET) Subject: [py-svn] r63222 - py/trunk/py/doc Message-ID: <20090323100119.EAA5D1684E9@codespeak.net> Author: hpk Date: Mon Mar 23 11:01:15 2009 New Revision: 63222 Modified: py/trunk/py/doc/download.txt py/trunk/py/doc/test-dist.txt py/trunk/py/doc/test-features.txt Log: refactor test-features doc, make references to others Modified: py/trunk/py/doc/download.txt ============================================================================== --- py/trunk/py/doc/download.txt (original) +++ py/trunk/py/doc/download.txt Mon Mar 23 11:01:15 2009 @@ -1,5 +1,3 @@ - - "easy_install py" =================================================== Modified: py/trunk/py/doc/test-dist.txt ============================================================================== --- py/trunk/py/doc/test-dist.txt (original) +++ py/trunk/py/doc/test-dist.txt Mon Mar 23 11:01:15 2009 @@ -74,6 +74,8 @@ py.test -d --tx socket=192.168.1.102:8888 --rsyncdir mypkg mypkg +.. _`atonce`: + Running tests on many platforms at once ------------------------------------------------------------- Modified: py/trunk/py/doc/test-features.txt ============================================================================== --- py/trunk/py/doc/test-features.txt (original) +++ py/trunk/py/doc/test-features.txt Mon Mar 23 11:01:15 2009 @@ -1,137 +1,59 @@ -automatic collection of tests on all levels -------------------------------------------- +.. contents:: Basic features + :depth: 1 -The automated test collection process walks the current -directory (or the directory given as a command line argument) -and all its subdirectories and collects python modules with a -leading ``test_`` or trailing ``_test`` filename. From each -test module every function with a leading ``test_`` or class with -a leading ``Test`` name is collected. The collecting process can -be customized at directory, module or class level. (see -`collection process`_ for some implementation details). +py.test: cross-project general testing tool +================================================== + +py.test is a standalone-tool that collects and runs tests for +your Python application and modules. py.test works across +linux, windows and osx and on Python 2.3 - Python 2.6. + +It aims to support *unit-tests* and *functional tests* written +in Python and is used in projects that run more than 10000 +tests regularly. + +py.test presents a clean and powerful command line interface +and strives to generally make testing a fun effort. + +automatically collects and executes tests +=============================================== + +py.test discovers tests automatically by inspect specified +directories or files. By default, it collects all python +modules a leading ``test_`` or trailing ``_test`` filename. +From each test module every function with a leading ``test_`` +or class with a leading ``Test`` name is collected. .. _`generative tests`: .. _`collection process`: impl-test.html#collection-process -assert with the ``assert`` statement ------------------------------------- - -``py.test`` allows to use the standard python -``assert statement`` for verifying expectations -and values in Python tests. For example, you can -write the following in your tests:: - - assert hasattr(x, 'attribute') - -to state that your object has a certain ``attribute``. In case this -assertion fails you will see the value of ``x``. Intermediate -values are computed by executing the assert expression a second time. -If you execute code with side effects, e.g. read from a file like this:: - - assert f.read() != '...' - -then you may get a warning from pytest if that assertions -first failed and then succeeded. - -asserting expected exceptions ----------------------------------------------- - -In order to write assertions about exceptions, you use -one of two forms:: - - py.test.raises(Exception, func, *args, **kwargs) - py.test.raises(Exception, "func(*args, **kwargs)") - -both of which execute the given function with args and kwargs and -asserts that the given ``Exception`` is raised. The reporter will -provide you with helpful output in case of failures such as *no -exception* or *wrong exception*. - -dynamically skipping tests ----------------------------------------- - -If you want to skip tests you can use ``py.test.skip`` within -test or setup functions. Example:: - - py.test.skip("message") - -You can also use a helper to skip on a failing import:: - - docutils = py.test.importorskip("docutils") - -or to skip if a library does not have the right version:: - - docutils = py.test.importorskip("docutils", minversion="0.3") - -The version will be read from the module's ``__version__`` attribute. - - -generative tests: yielding more tests -------------------------------------- - -*Generative tests* are test methods that are *generator functions* which -``yield`` callables and their arguments. This is most useful for running a -test function multiple times against different parameters. Example:: - - def test_generative(): - for x in (42,17,49): - yield check, x - - def check(arg): - assert arg % 7 == 0 # second generated tests fails! - -Note that ``test_generative()`` will cause three tests -to get run, notably ``check(42)``, ``check(17)`` and ``check(49)`` -of which the middle one will obviously fail. - -To make it easier to distinguish the generated tests it is possible to specify an explicit name for them, like for example:: - - def test_generative(): - for x in (42,17,49): - yield "case %d" % x, check, x - - -.. _`selection by keyword`: - -selecting/unselecting tests by keyword ---------------------------------------------- - -Pytest's keyword mechanism provides a powerful way to -group and selectively run tests in your test code base. -You can selectively run tests by specifiying a keyword -on the command line. Examples: - - py.test -k test_simple - py.test -k "-test_simple" +load-balance tests to multiple CPUs +=================================== -will run all tests matching (or not matching) the -"test_simple" keyword. Note that you need to quote -the keyword if "-" is recognized as an indicator -for a commandline option. Lastly, you may use +For large test suites you can distribute your +tests to multiple CPUs by issuing for example:: - py.test. -k "test_simple:" + py.test -n 3 -which will run all tests after the expression has *matched once*, i.e. -all tests that are seen after a test that matches the "test_simple" -keyword. +Read more on `distributed testing`_. -By default, all filename parts and -class/function names of a test function are put into the set -of keywords for a given test. You may specify additional -kewords like this:: +.. _`distributed testing`: test-dist.html - @py.test.mark(webtest=True) - def test_send_http(): - ... +Distribute tests across machines +=================================== -testing with multiple python versions / executables ---------------------------------------------------- +py.test supports the sending of tests to +remote ssh-accounts or socket servers. +It can `ad-hoc run your test on multiple +platforms one a single test run`. Ad-hoc +means that there are **no installation +requirements whatsoever** on the remote side. -With ``--tx EXECUTABLE`` you can specify a python -executable (e.g. ``python2.2``) with which the tests -will be executed. +.. _`ad-hoc run your test on multiple platforms one a single test run`: test-dist.html#atonce +extensive debugging support +=================================== testing starts immediately -------------------------- @@ -174,6 +96,40 @@ distributedly or selectively, or in "looponfailing" mode, will cause them to run in random order. +assert with the ``assert`` statement +---------------------------------------- + +``py.test`` allows to use the standard python +``assert statement`` for verifying expectations +and values in Python tests. For example, you can +write the following in your tests:: + + assert hasattr(x, 'attribute') + +to state that your object has a certain ``attribute``. In case this +assertion fails you will see the value of ``x``. Intermediate +values are computed by executing the assert expression a second time. +If you execute code with side effects, e.g. read from a file like this:: + + assert f.read() != '...' + +then you may get a warning from pytest if that assertions +first failed and then succeeded. + +asserting expected exceptions +---------------------------------------- + +In order to write assertions about exceptions, you use +one of two forms:: + + py.test.raises(Exception, func, *args, **kwargs) + py.test.raises(Exception, "func(*args, **kwargs)") + +both of which execute the given function with args and kwargs and +asserts that the given ``Exception`` is raised. The reporter will +provide you with helpful output in case of failures such as *no +exception* or *wrong exception*. + useful tracebacks, recursion detection -------------------------------------- @@ -182,7 +138,7 @@ py.test py/doc/example/pytest/failure_demo.py -to see a variety of 17 tracebacks, each tailored to a different +to see a variety of tracebacks, each representing a different failure situation. ``py.test`` uses the same order for presenting tracebacks as Python @@ -206,6 +162,68 @@ you to write test classes that subclass from application level classes. +testing for deprecated APIs +------------------------------ + +In your tests you can use ``py.test.deprecated_call(func, *args, **kwargs)`` +to test that a particular function call triggers a DeprecationWarning. +This is useful for testing phasing out of old APIs in your projects. + + +advanced test selection / skipping +========================================================= + +dynamically skipping tests +------------------------------- + +If you want to skip tests you can use ``py.test.skip`` within +test or setup functions. Example:: + + py.test.skip("message") + +You can also use a helper to skip on a failing import:: + + docutils = py.test.importorskip("docutils") + +or to skip if a library does not have the right version:: + + docutils = py.test.importorskip("docutils", minversion="0.3") + +The version will be read from the module's ``__version__`` attribute. + +.. _`selection by keyword`: + +selecting/unselecting tests by keyword +--------------------------------------------- + +Pytest's keyword mechanism provides a powerful way to +group and selectively run tests in your test code base. +You can selectively run tests by specifiying a keyword +on the command line. Examples:: + + py.test -k test_simple + py.test -k "-test_simple" + +will run all tests matching (or not matching) the +"test_simple" keyword. Note that you need to quote +the keyword if "-" is recognized as an indicator +for a commandline option. Lastly, you may use:: + + py.test. -k "test_simple:" + +which will run all tests after the expression has *matched once*, i.e. +all tests that are seen after a test that matches the "test_simple" +keyword. + +By default, all filename parts and +class/function names of a test function are put into the set +of keywords for a given test. You may specify additional +kewords like this:: + + @py.test.mark(webtest=True) + def test_send_http(): + ... + disabling a test class ---------------------- @@ -219,22 +237,43 @@ def test_xxx(self): ... -testing for deprecated APIs ------------------------------- +generative tests: yielding parametrized tests +==================================================== -In your tests you can use ``py.test.deprecated_call(func, *args, **kwargs)`` -to test that a particular function call triggers a DeprecationWarning. -This is useful for testing phasing out of old APIs in your projects. +*Generative tests* are test methods that are *generator functions* which +``yield`` callables and their arguments. This is most useful for running a +test function multiple times against different parameters. Example:: + + def test_generative(): + for x in (42,17,49): + yield check, x + + def check(arg): + assert arg % 7 == 0 # second generated tests fails! + +Note that ``test_generative()`` will cause three tests +to get run, notably ``check(42)``, ``check(17)`` and ``check(49)`` +of which the middle one will obviously fail. + +To make it easier to distinguish the generated tests it is possible to specify an explicit name for them, like for example:: + + def test_generative(): + for x in (42,17,49): + yield "case %d" % x, check, x -doctest support -------------------- +extensible plugin system +========================================= -If you want to integrate doctests, ``py.test`` now by default -picks up files matching the ``test_*.txt`` or ``*_test.txt`` -patterns and processes them as text files containing doctests. -This is an experimental feature and likely to change -its implementation. +py.test itself consists of many plugins +and you can easily write new `py.test plugins`_ +for these purposes: + +* reporting extensions +* customizing collection and run of tests +* running non-python tests +* managing test state setup +.. _`py.test plugins`: test-plugins.html .. _`reStructured Text`: http://docutils.sourceforge.net .. _`Python debugger`: http://docs.python.org/lib/module-pdb.html From hpk at codespeak.net Mon Mar 23 11:05:50 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 23 Mar 2009 11:05:50 +0100 (CET) Subject: [py-svn] r63223 - py/trunk/py/doc Message-ID: <20090323100550.C15C01684E9@codespeak.net> Author: hpk Date: Mon Mar 23 11:05:50 2009 New Revision: 63223 Modified: py/trunk/py/doc/contact.txt Log: cleanup contact page Modified: py/trunk/py/doc/contact.txt ============================================================================== --- py/trunk/py/doc/contact.txt (original) +++ py/trunk/py/doc/contact.txt Mon Mar 23 11:05:50 2009 @@ -1,20 +1,18 @@ py lib contact and communication =================================== -- You may also subscribe to `tetamap`_, where Holger Krekel - often posts about testing and py.test related news. +- **#pylib on irc.freenode.net**: you are welcome to lurk or ask questions! -- **#pylib on irc.freenode.net**: you are welcome - to lurk or ask questions in this IRC channel, it also tracks py lib commits. +- `py-dev developers list`_ development mailing list. -- `py-dev developers list`_ development mailing list. Good for reporting bugs, feature questions, discussing issues. Usually sees between 1 and 10 posts per week. +- `tetamap`_: Holger Krekel's blog, often about testing and py.test related news. - `py-svn general commit mailing list`_ to follow all development commits. - `development bug/feature tracker`_ this roundup instance serves to file bugs and track issues. + (soon to be substitued by a google-code or other hosted one). -- `merlinux.eu`_ offers tutorials and commercial support for - py.test and the py lib in general. +- `merlinux.eu`_ offers teaching and consulting services. .. _`merlinux.eu`: http://merlinux.eu @@ -25,14 +23,15 @@ .. _tetamap: http://tetamap.wordpress.com -get an account on codespeak ---------------------------- - -codespeak_ is where the subversion repository is hosted. If you know -someone who is active on codespeak already or you are otherwise known in -the community (see also: FOAF_) you will get access. But even if -you are new to the python developer community please come to the IRC -or the mailing list and ask questions, get involved. +.. + get an account on codespeak + --------------------------- + + codespeak_ is where the subversion repository is hosted. If you know + someone who is active on codespeak already or you are otherwise known in + the community (see also: FOAF_) you will get access. But even if + you are new to the python developer community please come to the IRC + or the mailing list and ask questions, get involved. .. _FOAF: http://en.wikipedia.org/wiki/FOAF .. _`coding style`: coding-style.html From hpk at codespeak.net Mon Mar 23 11:06:32 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 23 Mar 2009 11:06:32 +0100 (CET) Subject: [py-svn] r63224 - py/trunk Message-ID: <20090323100632.D0C211684E9@codespeak.net> Author: hpk Date: Mon Mar 23 11:06:32 2009 New Revision: 63224 Modified: py/trunk/MANIFEST py/trunk/setup.py Log: regen setup Modified: py/trunk/MANIFEST ============================================================================== --- py/trunk/MANIFEST (original) +++ py/trunk/MANIFEST Mon Mar 23 11:06:32 2009 @@ -104,7 +104,6 @@ py/compat/textwrap.py py/conftest.py py/doc/__init__.py -py/doc/apigen_refactorings.txt py/doc/bin.txt py/doc/code.txt py/doc/coding-style.txt Modified: py/trunk/setup.py ============================================================================== --- py/trunk/setup.py (original) +++ py/trunk/setup.py Mon Mar 23 11:06:32 2009 @@ -1,7 +1,7 @@ """ setup file for 'py' package based on: - https://codespeak.net/svn/py/trunk, revision=63215 + https://codespeak.net/svn/py/trunk, revision=63221 autogenerated by gensetup.py """ From hpk at codespeak.net Mon Mar 23 15:01:15 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 23 Mar 2009 15:01:15 +0100 (CET) Subject: [py-svn] r63229 - py/trunk/py/doc Message-ID: <20090323140115.D5461168457@codespeak.net> Author: hpk Date: Mon Mar 23 15:01:13 2009 New Revision: 63229 Modified: py/trunk/py/doc/confrest.py Log: don'T generate apigen information for now Modified: py/trunk/py/doc/confrest.py ============================================================================== --- py/trunk/py/doc/confrest.py (original) +++ py/trunk/py/doc/confrest.py Mon Mar 23 15:01:13 2009 @@ -37,8 +37,8 @@ def fill_menubar(self): items = [ self.a_docref("index", "index.html"), - self.a_apigenref("api", "api/index.html"), - self.a_apigenref("source", "source/index.html"), + #self.a_apigenref("api", "api/index.html"), + #self.a_apigenref("source", "source/index.html"), self.a_docref("contact", "contact.html"), self.a_docref("download", "download.html"), ] From hpk at codespeak.net Mon Mar 23 16:01:17 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 23 Mar 2009 16:01:17 +0100 (CET) Subject: [py-svn] r63232 - in py/trunk/py/execnet: . testing Message-ID: <20090323150117.5BDED168491@codespeak.net> Author: hpk Date: Mon Mar 23 16:01:15 2009 New Revision: 63232 Modified: py/trunk/py/execnet/gateway.py py/trunk/py/execnet/testing/test_gateway.py Log: nicer repr for gateway._rinfo() informatio about remote location Modified: py/trunk/py/execnet/gateway.py ============================================================================== --- py/trunk/py/execnet/gateway.py (original) +++ py/trunk/py/execnet/gateway.py Mon Mar 23 16:01:15 2009 @@ -247,6 +247,10 @@ class RInfo: def __init__(self, **kwargs): self.__dict__.update(kwargs) + def __repr__(self): + info = ", ".join(["%s=%s" % item + for item in self.__dict__.items()]) + return "" % info self._cache_rinfo = RInfo(**self.remote_exec(""" import sys, os channel.send(dict( Modified: py/trunk/py/execnet/testing/test_gateway.py ============================================================================== --- py/trunk/py/execnet/testing/test_gateway.py (original) +++ py/trunk/py/execnet/testing/test_gateway.py Mon Mar 23 16:01:15 2009 @@ -446,6 +446,8 @@ assert rinfo.executable assert rinfo.cwd assert rinfo.version_info + s = repr(rinfo) + assert s.find(rinfo.cwd) != -1 old = self.gw.remote_exec(""" import os.path cwd = os.getcwd() From hpk at codespeak.net Mon Mar 23 16:17:49 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 23 Mar 2009 16:17:49 +0100 (CET) Subject: [py-svn] r63234 - in py: build trunk Message-ID: <20090323151749.99913168443@codespeak.net> Author: hpk Date: Mon Mar 23 16:17:49 2009 New Revision: 63234 Modified: py/build/gensetup.py py/trunk/setup.py Log: fix gensetup, regen setup Modified: py/build/gensetup.py ============================================================================== --- py/build/gensetup.py (original) +++ py/build/gensetup.py Mon Mar 23 16:17:49 2009 @@ -14,7 +14,7 @@ return " ".join(text.split()) class SetupWriter(object): - EXCLUDES = ("MANIFEST.in",) + EXCLUDES = ("MANIFEST.in", "contrib") def __init__(self, wcbasedir, pkg, setuptools=False): self.wcbasedir = wcbasedir @@ -182,9 +182,16 @@ def getpackages(self): packages = [] for p in self.allpaths: + if p.basename == "py": + continue if p.check(dir=1) and p.join('__init__.py').check(): modpath = p.relto(self.wcbasedir).replace(p.sep, '.') - packages.append(modpath) + for exclude in self.EXCLUDES: + if modpath.startswith(exclude): + print "EXCLUDING", modpath + break + else: + packages.append(modpath) return packages def getpackagedata(self): @@ -270,8 +277,13 @@ if p.check(dir=1): continue toadd = p.relto(self.wcbasedir) - if toadd and toadd not in self.EXCLUDES: - lines.append("%s" %(toadd)) + if toadd: + for exclude in self.EXCLUDES: + if toadd.startswith(exclude): + break + assert toadd.find(exclude) == -1, (toadd, exclude) + else: + lines.append("%s" %(toadd)) targetfile = self.basedir.join("MANIFEST") targetfile.write("\n".join(lines)) print "wrote", targetfile Modified: py/trunk/setup.py ============================================================================== --- py/trunk/setup.py (original) +++ py/trunk/setup.py Mon Mar 23 16:17:49 2009 @@ -1,7 +1,7 @@ """ setup file for 'py' package based on: - https://codespeak.net/svn/py/trunk, revision=63221 + https://codespeak.net/svn/py/trunk, revision=63230 autogenerated by gensetup.py """ @@ -67,12 +67,7 @@ 'Topic :: System :: Distributed Computing', 'Topic :: Utilities', 'Programming Language :: Python'], - packages=['contrib.pygreen', - 'contrib.pygreen.pipe', - 'contrib.pygreen.server', - 'contrib.pygreen.test', - 'py', - 'py.builtin', + packages=['py.builtin', 'py.builtin.testing', 'py.c-extension', 'py.cmdline', From hpk at codespeak.net Mon Mar 23 16:24:32 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 23 Mar 2009 16:24:32 +0100 (CET) Subject: [py-svn] r63235 - in py/trunk: . py Message-ID: <20090323152432.C88271684E8@codespeak.net> Author: hpk Date: Mon Mar 23 16:24:30 2009 New Revision: 63235 Modified: py/trunk/py/__init__.py py/trunk/setup.py Log: bumping version to 1.0.0b1 Modified: py/trunk/py/__init__.py ============================================================================== --- py/trunk/py/__init__.py (original) +++ py/trunk/py/__init__.py Mon Mar 23 16:24:30 2009 @@ -23,7 +23,7 @@ """ from initpkg import initpkg -version = "1.0.0a2" +version = "1.0.0b1" initpkg(__name__, description = "pylib and py.test: agile development and test support library", @@ -38,7 +38,7 @@ author_email = "holger at merlinux.eu, py-dev at codespeak.net", long_description = globals()['__doc__'], classifiers = [ - "Development Status :: 3 - Alpha", + "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: POSIX", Modified: py/trunk/setup.py ============================================================================== --- py/trunk/setup.py (original) +++ py/trunk/setup.py Mon Mar 23 16:24:30 2009 @@ -39,9 +39,9 @@ name='py', description='pylib and py.test: agile development and test support library', long_description = long_description, - version='1.0.0a2', + version='1.0.0b1', url='http://pylib.org', - download_url='http://codespeak.net/py/1.0.0a2/download.html', + download_url='http://codespeak.net/py/1.0.0b1/download.html', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel, Guido Wesdorp, Carl Friedrich Bolz, Armin Rigo, Maciej Fijalkowski & others', @@ -56,7 +56,7 @@ 'py.svnwcrevert = py.cmdline:pysvnwcrevert', 'py.test = py.cmdline:pytest', 'py.which = py.cmdline:pywhich']}, - classifiers=['Development Status :: 3 - Alpha', + classifiers=['Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: POSIX', @@ -122,8 +122,6 @@ '', '', '', - '', - '', 'LICENSE', 'bin/_findpy.py', 'bin/_genscripts.py', From hpk at codespeak.net Mon Mar 23 16:30:33 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 23 Mar 2009 16:30:33 +0100 (CET) Subject: [py-svn] r63238 - in py/trunk: . py py/misc Message-ID: <20090323153033.55B251684E2@codespeak.net> Author: hpk Date: Mon Mar 23 16:30:32 2009 New Revision: 63238 Removed: py/trunk/py/misc/conftest-socketgatewayrun.py Modified: py/trunk/py/__init__.py py/trunk/setup.py Log: rather going for some alpha releases until i am sure that packaging works well Modified: py/trunk/py/__init__.py ============================================================================== --- py/trunk/py/__init__.py (original) +++ py/trunk/py/__init__.py Mon Mar 23 16:30:32 2009 @@ -23,7 +23,7 @@ """ from initpkg import initpkg -version = "1.0.0b1" +version = "1.0.0a5" initpkg(__name__, description = "pylib and py.test: agile development and test support library", @@ -38,7 +38,7 @@ author_email = "holger at merlinux.eu, py-dev at codespeak.net", long_description = globals()['__doc__'], classifiers = [ - "Development Status :: 4 - Beta", + "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: POSIX", Deleted: /py/trunk/py/misc/conftest-socketgatewayrun.py ============================================================================== --- /py/trunk/py/misc/conftest-socketgatewayrun.py Mon Mar 23 16:30:32 2009 +++ (empty file) @@ -1,63 +0,0 @@ -""" - -Put this file as 'conftest.py' somewhere upwards from py-trunk, -modify the "socketserveradr" below to point to a windows/linux -host running "py/execnet/script/loop_socketserver.py" -and invoke e.g. from linux: - - py.test --session=MySession some_path_to_what_you_want_to_test - -This should ad-hoc distribute the running of tests to -the remote machine (including rsyncing your WC). - -""" -import py -from py.__.test.looponfail.remote import LooponfailingSession - -import os - -class MyRSync(py.execnet.RSync): - def filter(self, path): - if path.endswith('.pyc') or path.endswith('~'): - return False - dir, base = os.path.split(path) - # we may want to have revision info on the other side, - # so let's not exclude .svn directories - #if base == '.svn': - # return False - return True - -class MySession(LooponfailingSession): - socketserveradr = ('10.9.2.62', 8888) - socketserveradr = ('10.9.4.148', 8888) - - def _initslavegateway(self): - print "MASTER: initializing remote socket gateway" - gw = py.execnet.SocketGateway(*self.socketserveradr) - pkgname = 'py' # xxx flexibilize - channel = gw.remote_exec(""" - import os - topdir = os.path.join(os.environ['HOMEPATH'], 'pytestcache') - pkgdir = os.path.join(topdir, %r) - channel.send((topdir, pkgdir)) - """ % (pkgname,)) - remotetopdir, remotepkgdir = channel.receive() - sendpath = py.path.local(py.__file__).dirpath() - rsync = MyRSync(sendpath) - rsync.add_target(gw, remotepkgdir, delete=True) - rsync.send() - channel = gw.remote_exec(""" - import os, sys - path = %r # os.path.abspath - sys.path.insert(0, path) - os.chdir(path) - import py - channel.send((path, py.__file__)) - """ % remotetopdir) - topdir, remotepypath = channel.receive() - assert topdir == remotetopdir, (topdir, remotetopdir) - assert remotepypath.startswith(topdir), (remotepypath, topdir) - #print "remote side has rsynced pythonpath ready: %r" %(topdir,) - return gw, topdir - -dist_hosts = ['localhost', 'cobra', 'cobra'] Modified: py/trunk/setup.py ============================================================================== --- py/trunk/setup.py (original) +++ py/trunk/setup.py Mon Mar 23 16:30:32 2009 @@ -1,7 +1,7 @@ """ setup file for 'py' package based on: - https://codespeak.net/svn/py/trunk, revision=63230 + https://codespeak.net/svn/py/trunk, revision=63237 autogenerated by gensetup.py """ @@ -39,9 +39,9 @@ name='py', description='pylib and py.test: agile development and test support library', long_description = long_description, - version='1.0.0b1', + version='1.0.0a5', url='http://pylib.org', - download_url='http://codespeak.net/py/1.0.0b1/download.html', + download_url='http://codespeak.net/py/1.0.0a5/download.html', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel, Guido Wesdorp, Carl Friedrich Bolz, Armin Rigo, Maciej Fijalkowski & others', @@ -56,7 +56,7 @@ 'py.svnwcrevert = py.cmdline:pysvnwcrevert', 'py.test = py.cmdline:pytest', 'py.which = py.cmdline:pywhich']}, - classifiers=['Development Status :: 4 - Beta', + classifiers=['Development Status :: 3 - Alpha', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: POSIX', From hpk at codespeak.net Mon Mar 23 17:03:12 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 23 Mar 2009 17:03:12 +0100 (CET) Subject: [py-svn] r63241 - in py: build trunk trunk/py Message-ID: <20090323160312.51E5F1684E9@codespeak.net> Author: hpk Date: Mon Mar 23 17:03:10 2009 New Revision: 63241 Modified: py/build/gensetup.py py/trunk/MANIFEST py/trunk/py/__init__.py py/trunk/setup.py Log: regen setup, fix gensetup script Modified: py/build/gensetup.py ============================================================================== --- py/build/gensetup.py (original) +++ py/build/gensetup.py Mon Mar 23 17:03:10 2009 @@ -182,8 +182,8 @@ def getpackages(self): packages = [] for p in self.allpaths: - if p.basename == "py": - continue + #if p.basename == "py": + # continue if p.check(dir=1) and p.join('__init__.py').check(): modpath = p.relto(self.wcbasedir).replace(p.sep, '.') for exclude in self.EXCLUDES: @@ -201,7 +201,9 @@ if p.check(file=1) and (not p.dirpath("__init__.py").check() or p.ext != ".py"): if p.dirpath() != self.wcbasedir: - datafiles.append(p.relto(pkgbase)) + x = p.relto(pkgbase) + if x: + datafiles.append(p.relto(pkgbase)) return {'py': datafiles} def getdatafiles(self): Modified: py/trunk/MANIFEST ============================================================================== --- py/trunk/MANIFEST (original) +++ py/trunk/MANIFEST Mon Mar 23 17:03:10 2009 @@ -204,7 +204,6 @@ py/misc/cache.py py/misc/cmdline/__init__.py py/misc/cmdline/countloc.py -py/misc/conftest-socketgatewayrun.py py/misc/difftime.py py/misc/dynpkg.py py/misc/error.py Modified: py/trunk/py/__init__.py ============================================================================== --- py/trunk/py/__init__.py (original) +++ py/trunk/py/__init__.py Mon Mar 23 17:03:10 2009 @@ -23,7 +23,7 @@ """ from initpkg import initpkg -version = "1.0.0a5" +version = "1.0.0a6" initpkg(__name__, description = "pylib and py.test: agile development and test support library", Modified: py/trunk/setup.py ============================================================================== --- py/trunk/setup.py (original) +++ py/trunk/setup.py Mon Mar 23 17:03:10 2009 @@ -1,7 +1,7 @@ """ setup file for 'py' package based on: - https://codespeak.net/svn/py/trunk, revision=63237 + https://codespeak.net/svn/py/trunk, revision=63240 autogenerated by gensetup.py """ @@ -39,9 +39,9 @@ name='py', description='pylib and py.test: agile development and test support library', long_description = long_description, - version='1.0.0a5', + version='1.0.0a6', url='http://pylib.org', - download_url='http://codespeak.net/py/1.0.0a5/download.html', + download_url='http://codespeak.net/py/1.0.0a6/download.html', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel, Guido Wesdorp, Carl Friedrich Bolz, Armin Rigo, Maciej Fijalkowski & others', @@ -67,7 +67,8 @@ 'Topic :: System :: Distributed Computing', 'Topic :: Utilities', 'Programming Language :: Python'], - packages=['py.builtin', + packages=['py', + 'py.builtin', 'py.builtin.testing', 'py.c-extension', 'py.cmdline', @@ -115,14 +116,7 @@ 'py.tool.testing', 'py.xmlobj', 'py.xmlobj.testing'], - package_data={'py': ['', - '', - '', - '', - '', - '', - '', - 'LICENSE', + package_data={'py': ['LICENSE', 'bin/_findpy.py', 'bin/_genscripts.py', 'bin/gendoc.py', From hpk at codespeak.net Mon Mar 23 17:24:11 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 23 Mar 2009 17:24:11 +0100 (CET) Subject: [py-svn] r63242 - py/trunk/py/execnet/testing Message-ID: <20090323162411.27BAF16844B@codespeak.net> Author: hpk Date: Mon Mar 23 17:24:10 2009 New Revision: 63242 Modified: py/trunk/py/execnet/testing/test_gateway.py Log: fails on windows, unncessary to test anyway Modified: py/trunk/py/execnet/testing/test_gateway.py ============================================================================== --- py/trunk/py/execnet/testing/test_gateway.py (original) +++ py/trunk/py/execnet/testing/test_gateway.py Mon Mar 23 17:24:10 2009 @@ -447,7 +447,6 @@ assert rinfo.cwd assert rinfo.version_info s = repr(rinfo) - assert s.find(rinfo.cwd) != -1 old = self.gw.remote_exec(""" import os.path cwd = os.getcwd() From hpk at codespeak.net Mon Mar 23 17:27:52 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 23 Mar 2009 17:27:52 +0100 (CET) Subject: [py-svn] r63243 - py/dist Message-ID: <20090323162752.D9E25168456@codespeak.net> Author: hpk Date: Mon Mar 23 17:27:52 2009 New Revision: 63243 Added: py/dist/ - copied from r63242, py/trunk/ Log: copy trunk to dist From hpk at codespeak.net Mon Mar 23 17:40:18 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 23 Mar 2009 17:40:18 +0100 (CET) Subject: [py-svn] r63244 - py/trunk/py/doc Message-ID: <20090323164018.AF795168413@codespeak.net> Author: hpk Date: Mon Mar 23 17:40:18 2009 New Revision: 63244 Modified: py/trunk/py/doc/index.txt Log: update the main entry doc page Modified: py/trunk/py/doc/index.txt ============================================================================== --- py/trunk/py/doc/index.txt (original) +++ py/trunk/py/doc/index.txt Mon Mar 23 17:40:18 2009 @@ -1,35 +1,22 @@ -py lib documentation -================================================= +py lib: Main tools and APIs +----------------------------------- - The py lib is a development support library featuring - py.test, ad-hoc distributed execution, micro-threads - (greenlets) and uniform local path and svn abstractions. - It works on Linux, Windows and OSX, Python versions - 2.3, 2.4, 2.5 and 2.6. +`py.test`_ write and deploy unit- and functional tests to multiple machines. -`Download and Installation`_ +`py.execnet`_ rapidly deploy local or remote processes from your program. -`0.9.2 release announcement`_ +`py.magic.greenlet`_: instantiate thousands of micro-threads from your program. -Main tools and API ----------------------- +`py.path`_: use path objects to transparently access local and svn filesystems. -`py.test`_ introduces to the **py.test** testing utility. +`py.code`_: generate python code and use advanced introspection/traceback support. -`py.execnet`_ distributes programs across the net. -`py.magic.greenlet`_: micro-threads (lightweight in-process concurrent programming) - -`py.path`_: local and subversion Path and Filesystem access - -`py.code`_: High-level access/manipulation of Python code and traceback objects. - -`py lib scripts`_ describe the scripts contained in the ``py/bin`` directory. - - -support functionality +minor support functionality --------------------------------- +`py lib scripts`_ to make python development easier. + `py.xml`_ for generating in-memory xml/html object trees `py.io`_: Helper Classes for Capturing of Input/Output @@ -38,12 +25,6 @@ `miscellaneous features`_ describes some small but nice py lib features. -Background and Motivation information -------------------------------------------- - -`future`_ handles development visions and plans for the near future. - -`why what how py?`_, describing motivation and background of the py lib. .. _`download and installation`: download.html .. _`py-dev at codespeak net`: http://codespeak.net/mailman/listinfo/py-dev From hpk at codespeak.net Mon Mar 23 17:41:13 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 23 Mar 2009 17:41:13 +0100 (CET) Subject: [py-svn] r63245 - py/dist Message-ID: <20090323164113.3F4B3168413@codespeak.net> Author: hpk Date: Mon Mar 23 17:41:12 2009 New Revision: 63245 Added: py/dist/ - copied from r63244, py/trunk/ Log: re-copy to dist From hpk at codespeak.net Mon Mar 23 17:47:45 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 23 Mar 2009 17:47:45 +0100 (CET) Subject: [py-svn] r63246 - py/build Message-ID: <20090323164745.3138B168418@codespeak.net> Author: hpk Date: Mon Mar 23 17:47:44 2009 New Revision: 63246 Added: py/build/copy_trunk_to_dist Modified: py/build/makebdistegg.py py/build/makepyrelease.py py/build/test_update_website.py py/build/update_website.py Log: commit all the hackish changes to upload/makedoc scripts without further review Added: py/build/copy_trunk_to_dist ============================================================================== --- (empty file) +++ py/build/copy_trunk_to_dist Mon Mar 23 17:47:44 2009 @@ -0,0 +1,4 @@ + +BASE=svn+ssh://codespeak.net/svn/py + +python ~/user/arigo/hack/svnutil/svncpforce.py $BASE/trunk $BASE/dist Modified: py/build/makebdistegg.py ============================================================================== --- py/build/makebdistegg.py (original) +++ py/build/makebdistegg.py Mon Mar 23 17:47:44 2009 @@ -6,113 +6,91 @@ gensetuppath = mydir.join("gensetup.py") assert gensetuppath.check() -def trace(*args): - print >>sys.stderr, " ".join(map(str, args)) - -def remote_chdirtemp(): - #remote_tmpdir = winexec(""" - # import os, tempfile - # tmpdir = tempfile.mkdtemp() - # os.chdir(tmpdir) - # channel.send(tmpdir) - #""").receive() - remote_tmpdir = winexec(r""" - import os, tempfile, shutil - base = r"C:\tmp\makepyrelease" - path = tempfile.mkdtemp(dir=base) - #if os.path.exists(path): - # shutil.rmtree(path, ignore_errors=True) - #os.mkdir(path) - os.chdir(path) - channel.send(path) - """).receive() - trace("using remote tempdir", remote_tmpdir) - -def sendfile(path): - channel = winexec(""" - fn = channel.receive() - content = channel.receive() - open(fn, 'w').write(content) - """) - trace("sending", path) - channel.send(path.basename) - channel.send(path.read()) - -def remote_checkout(url): - channel = winexec(""" - import os - url = channel.receive() - errno = os.system("svn co " + url) - assert not errno - """) - channel.send(url) - trace("waiting for remote to checkout", url) - channel.waitclose() - -def remote_cd(path): - trace("changing remote curdir to", path) - winexec("import os ; os.chdir(%r)" % path).waitclose() - -def remote_makebdist_egg(python="python25"): - channel = winexec(r""" - import os - errno = os.system(r"C:\%s\python setup.py bdist_egg >log") - channel.send(open('log').read()) - assert not errno - """ % python) - log = channel.receive() - logpath = py.path.local("bdist_egg_%s.log" % python) - logpath.write(log) - trace("received log file in", logpath) - -def remote_getdist(): - channel = winexec(r""" - import py - for p in py.path.local("dist").listdir("*.egg"): - channel.send(p.basename) - channel.send(p.read()) - channel.send(None) - """) - while 1: - basename = channel.receive() - if basename is None: - break - print "receiving", basename - content = channel.receive() - py.path.local("dist").ensure(basename).write(content) - print "complete" - -def winexec(source): - return gw.remote_exec(source, stdout=sys.stdout, stderr=sys.stderr) - -def sysexec(cmd): - print "executing", cmd - os.system(cmd) - -def main(): - #errno = os.system("python setup.py sdist") - #assert not errno - #l = py.path.local("dist").listdir("*.gz") - #assert len(l) == 1 - #sdist = l[0] +class BDistEggMaker: + def __init__(self, xspec): + self.xspec = xspec + self.gw = py.execnet.makegateway(xspec) + self.trace("instantiated %r -> %s" %(self.xspec, self.gw)) + rinfo = self.gw._rinfo() + self.trace("remote", rinfo) + + def trace(self, *args): + print >>sys.stdout, " ".join(map(str, args)) + + def remote_checkout(self, url): + self.trace("checking out", url) + channel = self.winexec(""" + import os + url = channel.receive() + errno = os.system("svn co " + str(url)) + assert not errno + """) + channel.send(url) + self.trace("waiting for remote to checkout", url) + channel.waitclose() + + def remote_cd(self, path): + self.trace("changing remote curdir to", path) + self.winexec("import os ; os.chdir(%r)" % path).waitclose() + + def remote_makebdist_egg(self, python="python25"): + channel = self.winexec(r""" + import py + import os + errno = os.system(r"C:\%s\python setup.py bdist_egg >log") + channel.send(open('log').read()) + assert not errno + """ % python) + log = channel.receive() + logpath = py.path.local("bdist_egg_%s.log" % python) + logpath.write(log) + self.trace("received log file in", logpath) + + def remote_getdist(self): + channel = self.winexec(r""" + import py + for p in py.path.local("dist").listdir("*.egg"): + channel.send(p.basename) + channel.send(p.read()) + channel.send(None) + """) + while 1: + basename = channel.receive() + if basename is None: + break + self.trace("receiving", basename) + content = channel.receive() + py.path.local("dist").ensure(basename).write(content) + self.trace("complete") + + def winexec(self, source): + return self.gw.remote_exec(source, stdout=sys.stdout, stderr=sys.stderr) + + def main(self, wc): + wc = py.path.svnwc(wc) + if not wc.check(): + raise IOError("not found: %s" % wc) + assert wc.join("py").check(), wc + + self.remote_checkout(wc.info().url) + self.remote_cd(wc.basename) + for python in "python24", "python25": + self.remote_makebdist_egg(python) + self.remote_getdist() - wc = py.path.svnwc() - #trace("regenerating setup") + #self.trace("regenerating setup") #sysexec("python %s %s" %(gensetuppath, py.path.local())) #wc.commit("auto-commit for building new eggs") - trace("gateway", gw) - - remote_tmpdir = remote_chdirtemp() - remote_checkout(wc.info().url) - remote_cd(wc.basename) - for python in "python24", "python25": - remote_makebdist_egg(python) - remote_getdist() +def sysexec(cmd): + print "executing", cmd + os.system(cmd) if __name__ == '__main__': - gw = py.execnet.SocketGateway("10.9.2.62", 8888) - try: - main() - finally: - gw.exit() + xspec, basepath = sys.argv[1:] + pypath = py.path.local(basepath).join("py") + if not pypath.check(): + raise ValueError("not exists: %s" % pypath) + wc = py.path.svnwc(pypath.dirpath()) + maker = BDistEggMaker(xspec) + maker.main(wc) Modified: py/build/makepyrelease.py ============================================================================== --- py/build/makepyrelease.py (original) +++ py/build/makepyrelease.py Mon Mar 23 17:47:44 2009 @@ -160,7 +160,4 @@ #checksvnworks(unpacked) #pytest(unpacked) - pytest_remote('test at codespeak.net', py.__pkg__.download_url) - - - + #pytest_remote('test at codespeak.net', py.__pkg__.download_url) Modified: py/build/test_update_website.py ============================================================================== --- py/build/test_update_website.py (original) +++ py/build/test_update_website.py Mon Mar 23 17:47:44 2009 @@ -2,7 +2,7 @@ import sys mydir = py.magic.autopath().dirpath() -update_website = mydir.join('_update_website.py').pyimport() +update_website = mydir.join('update_website.py').pyimport() def test_rsync(): temp = py.test.ensuretemp('update_website_rsync') Modified: py/build/update_website.py ============================================================================== --- py/build/update_website.py (original) +++ py/build/update_website.py Mon Mar 23 17:47:44 2009 @@ -10,7 +10,7 @@ print "using py lib", py.__file__ import sys -def rsync(pkgpath, apidocspath, gateway, remotepath): +def rsync(pkgpath, gateway, remotepath): """ copy the code and docs to the remote host """ # copy to a temp dir first, even though both paths (normally) share the # same parent dir, that may contain other stuff that we don't want to @@ -23,32 +23,26 @@ rs.add_target(gateway, remotepath, delete=True) rs.send() -def run_tests(pkgpath, apigenpath, args='', captureouterr=False): - """ run the unit tests and build the docs """ +def gendoc(pkgpath, args='', captureouterr=False): + """ generate docs """ pypath = py.__pkg__.getpath() - pytestpath = pypath.join('bin/py.test') - # XXX this would need a Windows specific version if we want to allow - # running this script on that platform, but currently --apigen doesn't - # work there anyway... - apigenscript = pkgpath.join('apigen/apigen.py') # XXX be more general here? - if not apigenscript.check(file=True): - apigenscript = pypath.join('apigen/apigen.py') - cmd = ('APIGENPATH="%s" PYTHONPATH="%s:%s" python ' - '"%s" %s --apigen="%s" "%s"' % (apigenpath, pypath.dirpath(), - pkgpath.dirpath(), pytestpath, - args, apigenscript, - pkgpath)) + pytestpath = pypath.join('bin/gendoc.py') + cmd = "python py/bin/gendoc.py" if captureouterr: cmd += ' > /dev/null 2>&1' try: - output = py.process.cmdexec(cmd) + old = pypath.dirpath().chdir() + try: + output = py.process.cmdexec(cmd) + finally: + old.chdir() except py.error.Error, e: return e.err or str(e) return None -def main(pkgpath, apidocspath, rhost, rpath, args='', ignorefail=True): - print 'running tests' - errors = run_tests(pkgpath, apidocspath, args) +def main(pkgpath, rhost, rpath): + print 'running gendoc', pkgpath + errors = gendoc(pkgpath) if errors: print >>sys.stderr, \ 'Errors while running the unit tests: %s' % (errors,) @@ -57,34 +51,30 @@ 'regardless of failures, use --ignorefail') sys.exit(1) - print 'rsyncing' + print 'rsyncing to %s:%s' %(rhost, rpath) gateway = py.execnet.SshGateway(rhost) - errors = rsync(pkgpath, apidocspath, gateway, rpath) + errors = rsync(gateway, pkgpath, rpath) if errors: print >>sys.stderr, 'Errors while rsyncing: %s' sys.exit(1) if __name__ == '__main__': + host = "codespeak.net" + location = "/www/codespeak.net/htdocs/py" + baselocation = "%s:%s" % (host, location) + args = sys.argv[1:] - if '--help' in args or '-h' in args: - print 'usage: %s [options]' - print - print 'run the py lib tests and update the py lib website' - print 'options:' - print ' --ignorefail: ignore errors in the unit tests and always' - print ' try to rsync' - print ' --help: show this message' + if not args or '--help' in args or '-h' in args: + print "usage: update_website.py [directory]" print - print 'any additional arguments are passed on as-is to the py.test' - print 'child process' + print "generate py lib docs and send it to the website. " + print "the 'directory' must contain the 'py' package." + print "remote location will be %s/BASENAME_OF_DIRECTORY" % baselocation + sys.exit() - ignorefail = True - if '--ignorefail' in args: - args.remove('--ignorefail') - ignorefail = True - args = ' '.join(sys.argv[1:]) + + path = py.path.local(args[0]) + assert path.join("py").check() pkgpath = py.__pkg__.getpath() - apidocspath = pkgpath.dirpath().join('apigen') - main(pkgpath, apidocspath, 'codespeak.net', - 'rsynctests', args, ignorefail) + main(pkgpath, host, "%s/%s" % (baselocation, path.basename)) From hpk at codespeak.net Mon Mar 23 20:00:37 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 23 Mar 2009 20:00:37 +0100 (CET) Subject: [py-svn] r63251 - py/trunk/py/doc Message-ID: <20090323190037.7DCDD1684EA@codespeak.net> Author: hpk Date: Mon Mar 23 20:00:31 2009 New Revision: 63251 Modified: py/trunk/py/doc/test-dist.txt Log: fix reference Modified: py/trunk/py/doc/test-dist.txt ============================================================================== --- py/trunk/py/doc/test-dist.txt (original) +++ py/trunk/py/doc/test-dist.txt Mon Mar 23 20:00:31 2009 @@ -91,7 +91,7 @@ .. _`xspec syntax`: execnet.html#xspec -.. _`socketserver.py`: ../execnet/script/socketserver.py +.. _`socketserver.py`: http://codespeak.net/svn/py/dist/py/execnet/script/socketserver.py .. _`py.execnet`: execnet.html Specifying test exec environments in a conftest.py From hpk at codespeak.net Mon Mar 23 20:01:13 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 23 Mar 2009 20:01:13 +0100 (CET) Subject: [py-svn] r63252 - py/build Message-ID: <20090323190113.49E511684EA@codespeak.net> Author: hpk Date: Mon Mar 23 20:01:11 2009 New Revision: 63252 Added: py/build/sendpydoc.py Log: another script to build docs Added: py/build/sendpydoc.py ============================================================================== --- (empty file) +++ py/build/sendpydoc.py Mon Mar 23 20:01:11 2009 @@ -0,0 +1,56 @@ +import py +import os, sys + +def gendoc(pkgpath): + """ generate docs """ + pytestpath = pkgpath.join("bin", "py.test") + pydocpath = pkgpath.join("doc") + assert pytestpath.check() + assert pydocpath.check() + + cmd = "python %s" % pytestpath + cmd += " --forcegen py/doc/" + old = pkgpath.dirpath().chdir() + try: + os.system(cmd) + finally: + old.chdir() + return pydocpath + +def rsync(directory, rhost, rpath): + gw = py.execnet.SshGateway(rhost) + print "preparing rsync to %s, rpath %r" %(gw, rpath) + #tempdir = py.test.ensuretemp('update_website_rsync_temp') + #pkgpath.copy(tempdir.ensure(pkgpath.basename, dir=True)) + #apidocspath.copy(tempdir.ensure(apidocspath.basename, dir=True)) + + rs = py.execnet.RSync(directory) + rs.add_target(gw, rpath, delete=False) # True) + rs.send() + print "rsync completed", gw + +def main(pkgpath, rhost, rpath): + print "generating docs for", pkgpath + docbase = gendoc(pkgpath) + print "rsyncing %r to %s:%s" %(docbase, rhost, rpath) + rsync(docbase, rhost, rpath) + +if __name__ == '__main__': + rhost = "codespeak.net" + rbasepath = "/www/codespeak.net/htdocs/py" + + args = sys.argv[1:] + if not args or '--help' in args or '-h' in args: + print "usage: update_website.py [directory]" + print + print "generate py lib docs and send it to the website. " + print "the 'directory' must contain the 'py' package." + print "remote location will be %s/BASENAME_OF_DIRECTORY" % baselocation + + sys.exit() + + path = py.path.local(args[0]) + pkgpath = path.join("py") + assert pkgpath.check(), pkgpath + main(pkgpath, rhost, "%s/%s" % (rbasepath, path.basename)) + From hpk at codespeak.net Mon Mar 23 20:07:03 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 23 Mar 2009 20:07:03 +0100 (CET) Subject: [py-svn] r63253 - py/trunk/py/doc Message-ID: <20090323190703.AC9701684F6@codespeak.net> Author: hpk Date: Mon Mar 23 20:07:02 2009 New Revision: 63253 Modified: py/trunk/py/doc/execnet.txt Log: fix typo Modified: py/trunk/py/doc/execnet.txt ============================================================================== --- py/trunk/py/doc/execnet.txt (original) +++ py/trunk/py/doc/execnet.txt Mon Mar 23 20:07:02 2009 @@ -111,7 +111,7 @@ ``py.execnet`` supports a simple extensible format for specifying and configuring Gateways for remote execution. -You can use a string spefication to make a new gateway, +You can use a string specification to instantiate a new gateway, for example a new SshGateway:: gateway = py.execnet.makegateway("ssh=myhost") From hpk at codespeak.net Tue Mar 24 11:21:57 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 24 Mar 2009 11:21:57 +0100 (CET) Subject: [py-svn] r63271 - py/extradoc/talk/pycon-us-2009/pytest-advanced Message-ID: <20090324102157.3381816847D@codespeak.net> Author: hpk Date: Tue Mar 24 11:21:53 2009 New Revision: 63271 Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Log: save intermediate state (bah, i want to have an auto-committed home-directory) Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Tue Mar 24 11:21:53 2009 @@ -1,8 +1,22 @@ .. include:: beamerdefs.txt .. include:: -Terminology/Overview (20 minutes) -============================================================================ +================================================================= +Advanced cross platform testing +================================================================= + +my technical background +=========================== + +- programming since 20 years +- Python since around 2000 +- released projects: pypy, py.test/py lib, rlcompleter2 +- other: mailwitness, shpy, vadm, codespeak, ... +- merlinux GmbH since 2004 +- PyPy EU-project 2004-2007 + +my current background +======================== .. image:: img/little_red_riding_hood_by_marikaz.jpg :scale: 100 @@ -10,15 +24,26 @@ http://marikaz.deviantart.com/ CC 3.0 AN-ND +my testing background +======================= -Terminology/Overview -======================================= +- learned python 2001 +- became "test-driven developer" (TDD) + around 2002 when attending a Zope Sprint +- founded PyPy, based on TDD principles +- developed utest/stdtest, now py.test +- consulted for a few organisations on + (acceptance) testing -- Benefits of automated tests -- small/medium/big tests aka unit/functional/acceptance -- acceptance tests -- benefits of automated testing -- related testing tools +my view on Python and testing +================================== + +- Python does not have compile-time type security + like Java or C++ +- instead of declaring types, write tests! +- tests are more useful than declaring types +- improving the testing tool means improving + the useability of the language! Benefits of automated tests ============================== @@ -26,50 +51,100 @@ - evolve codebase more easily - higher code quality - better collaboration -- perfect fit for Python +- you can write larger apps! Some Test terminology ============================== -- developer and "customer" tests +- developer and customer tests - unit tests - functional tests - acceptance tests -Small Tests +you may discuss a long time +about categorizations ... + +But Python is about pragmatism +================================= + +Some Google developers presented +their view on testing at the Google +Automated Testing Conference (GTAC) 2008 +in Seattle. I liked it: + +let's talk about small, medium or large tests. + +Small Tests: one aspect ============================== .. image:: img/small.png :align: center :scale: 70 -Medium Tests +Medium Tests: two aspects ============================== .. image:: img/medium.png :align: center :scale: 70 -Large Tests +Large Tests: three/more ============================== .. image:: img/large.png :align: center :scale: 70 +The Automated Test question +======================================== -Related testing tools -========================= +what are automated there for? -- nosetests -- unittest.py -- zope3-test runner -- trial -- twill -- windmill +my current answer +======================================== + +to make sure that + +* function units react well to input. +* class instances co-operate nicely. +* components co-operate nicely +* **everything works in target execution contexts** + +The test tool question +======================================== + +what is the job of automated testing tools? + +my current answer +======================================== + +* make sure things work out **in the end** +* report nicely when things don't work out + +*in the end* +======================================== + +- certain servers +- certain Python Interpreters +- certain managed systems (GAE, Azura, "cloud"...) +- web browsers, IE, Mozilla, Safari, Mobile .... + +A vision for automated testing +======================================== -Walkthrough Advanced Features (30 minutes) +... integrate with actual deployment! + +Installing py.test, then break +======================================== + +- install py.test + +- svn checkout http://codespeak.net/svn/py/dist + +- run "python setup.py" with "install" or "develop" + +pytest advanced features (30 minutes) ============================================================================ .. image:: img/new_color_in_dark_old_city_by_marikaz.jpg @@ -78,29 +153,55 @@ http://marikaz.deviantart.com/ CC 3.0 AN-ND -Install py.test -============================ +organising your tests +========================================== -- svn checkout http://codespeak.net/svn/py/dist +- tests belong to the code +- keep test state setup separate -- run "python setup.py" with "install" or "develop" - -per-class/instance set up of test state +Python test function viewed abstractly +========================================== +:: + from app.pkg import SomeClass + def test_something(): + inst1 = SomeClass("somevalue") + ... + assert "things are ok" + +observations ========================================== -example:: +* test configuration mixes with code instantiation +* importing 'app' may fail because it misses dependencies +* the "app.pkg.SomeClass" reference may change - class LocalSetup: - def setup_class(cls): - cls.root = py.test.ensuretemp(cls.__name__) - cls.root.ensure(dir=1) - setuptestfs(cls.root) +setting up state close to code +========================================== +:: + from app.pkg import SomeClass + class TestGroup: def setup_method(self, method): - self.tmpdir = self.root.mkdir(method.__name__) + self.teststate = SomeClass() + + def test_something(self): + ... use self.teststate ... + assert "things are ok" + +observations +========================================== + +* test configuration mixes with code instantiation +* importing 'app' may fail because it misses dependencies +* the "app.pkg.SomeClass" reference may change +* TestGroup functions share the same setup + +the test state setup question +========================================== + +what is the goal goals test state setup? -XXX: Example confusing? Without pylib experience will students understand 'ensure'? What is setuptestfs? funcargs: per-function setup =================================== @@ -367,3 +468,13 @@ Q&A (15 minutes) + +Other Python testing tools +========================= + +- unittest.py +- nosetests +- windmill +- "pytest" from logilab +- project specific: zope-test runner, trial, ... +- ... From hpk at codespeak.net Tue Mar 24 11:46:08 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 24 Mar 2009 11:46:08 +0100 (CET) Subject: [py-svn] r63272 - py/extradoc/talk/pycon-us-2009/pytest-advanced Message-ID: <20090324104608.D4D70168481@codespeak.net> Author: hpk Date: Tue Mar 24 11:46:08 2009 New Revision: 63272 Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Log: more slides, new funcarg API Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Tue Mar 24 11:46:08 2009 @@ -195,12 +195,70 @@ * test configuration mixes with code instantiation * importing 'app' may fail because it misses dependencies * the "app.pkg.SomeClass" reference may change -* TestGroup functions share the same setup +* functions tend to get grouped by setup + the test state setup question ========================================== -what is the goal goals test state setup? +how to setup and communicate test state? + + +my current answers +=========================== + +* test tool drives test state setup +* separate test state setup from test code + +meet py.test "funcargs" +========================================== + +:: + def pytest_configure(self, config): + config.register_funcarg("mysetup", MySetup(config)) + +observations +========================================== + +* called after command line parsing +* allows to send test setup to test functions! + +class MySetup instanciation +========================================== + +class MySetup: + def __init__(self, config): + self.config = config + + def makepyfuncarg(self, pyfuncitem): + # pyfuncitem points into test collection + ... + return arginstance + + + +- can come from application! +- will get its hook getfuncitem( + + + +goals for test suite organisation +==================================== + +* group your tests by aspects they test +* continously re-group your test to match your app + +make good use of names for Test Classes and test functions + +basic "funcarg" mechanism +========================================== + +configuration +* think about setup of yourthink a about + + +use setup_class +(let's think of ways to get rid of it) funcargs: per-function setup From hpk at codespeak.net Tue Mar 24 12:23:10 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 24 Mar 2009 12:23:10 +0100 (CET) Subject: [py-svn] r63275 - py/extradoc/talk/pycon-us-2009/pytest-advanced Message-ID: <20090324112310.1B5FC1684F8@codespeak.net> Author: hpk Date: Tue Mar 24 12:23:08 2009 New Revision: 63275 Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Log: flesh out funcargs Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Tue Mar 24 12:23:08 2009 @@ -215,7 +215,7 @@ :: def pytest_configure(self, config): - config.register_funcarg("mysetup", MySetup(config)) + config.register_funcarg("mysetup", MyTestSetup(config)) observations ========================================== @@ -223,27 +223,45 @@ * called after command line parsing * allows to send test setup to test functions! -class MySetup instanciation +class MyTestSetup instanciation ========================================== -class MySetup: +class MyTestSetup: def __init__(self, config): self.config = config - def makepyfuncarg(self, pyfuncitem): - # pyfuncitem points into test collection + def makearg(self, pyfuncitem): + # pyfuncitem is a "collected test function" ... return arginstance + + +how does a test function look like? +==================================== +def test_something(self, mysetup): + app1 = mysetup.make_some_app_instance() + ... + assert result is ok + +Observations +================ + +* test configuration clearly separated from test code +* 'MySetup' can control dependencies, raise "skips" +* internal "app.pkg.SomeClass" references may change + without affecting the test code +Using funcargs means +======================== -- can come from application! -- will get its hook getfuncitem( - - +* freedom to setup your test state +* freedom to *logically* group your tests in test classes +* refactoring code places causes changes + often only in single places -goals for test suite organisation -==================================== + app1 = mysetup.make_some_app_instance() + ... * group your tests by aspects they test * continously re-group your test to match your app From hpk at codespeak.net Tue Mar 24 22:54:54 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 24 Mar 2009 22:54:54 +0100 (CET) Subject: [py-svn] r63299 - py/extradoc/talk/pycon-us-2009/pytest-advanced Message-ID: <20090324215454.8F18B16851B@codespeak.net> Author: hpk Date: Tue Mar 24 22:54:49 2009 New Revision: 63299 Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Log: * finish funcarg block * kind of finish first block * start a bit on fleshing out second Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Tue Mar 24 22:54:49 2009 @@ -207,36 +207,43 @@ my current answers =========================== -* test tool drives test state setup +* test tool is to control test state setup * separate test state setup from test code -meet py.test "funcargs" +meet py.test "funcargs" +========================================== + +goal: cleanly separate test configuration +and boilerplate application setup from +the actual code in the test function. + +basic funcarg mechanism ========================================== :: - def pytest_configure(self, config): - config.register_funcarg("mysetup", MyTestSetup(config)) + + def pytest_configure(config): + config.register_funcarg("myarg", myargmaker) + + def myargmaker(pyfuncitem): + return pyfuncitem.name + + def test_somefunction(myarg): + assert myarg == 'test_somefunction' observations -========================================== +===================== -* called after command line parsing -* allows to send test setup to test functions! +* funcarg makers are registered after command line parsing +* funcarg makers have meta-access to "collected function item" +* test code is free of boilerplate config or setup references class MyTestSetup instanciation ========================================== -class MyTestSetup: - def __init__(self, config): - self.config = config - - def makearg(self, pyfuncitem): - # pyfuncitem is a "collected test function" - ... - return arginstance - + def make(self, pyfuncitem): -how does a test function look like? +how could a test function look like? ==================================== def test_something(self, mysetup): @@ -252,113 +259,22 @@ * internal "app.pkg.SomeClass" references may change without affecting the test code -Using funcargs means +Using funcargs ======================== * freedom to setup your test state * freedom to *logically* group your tests in test classes -* refactoring code places causes changes - often only in single places - - app1 = mysetup.make_some_app_instance() - ... - -* group your tests by aspects they test -* continously re-group your test to match your app - -make good use of names for Test Classes and test functions +* change test setup in once place if app mechanics change -basic "funcarg" mechanism +Exercise ========================================== -configuration -* think about setup of yourthink a about - - -use setup_class -(let's think of ways to get rid of it) - - -funcargs: per-function setup -=================================== - -XXX find example:: - - def test_pypkpath(): - datadir = py.test.ensuretemp("pypkgdir") - pkg = datadir.ensure('pkg1', dir=1) - pkg.ensure("__init__.py") - pkg.ensure("subdir/__init__.py") - assert pkg.pypkgpath() == pkg - subinit = pkg.join('subdir', '__init__.py') - assert subinit.pypkgpath() == pkg - - -- example of test module -- working with failures, tracebacks -- generative tests -- skipping chunks within doctests -- looponfailing: run large test set until all tests pass - -Skipping tests -=================== - -use ``py.test.skip``:: - - def test_something_on_windows(): - if sys.platform == "win32": - py.test.skip("win32 required") - ... - -don't use py.test.skip() in classic setup functions -because it makes distribution to other platforms -harder. - -Skipping Doctests / ReST -====================================== - -with 'pytest_restdoc' plugin:: - - .. >>> import py - .. >>> if py.test.config.option.xyz: raise ValueError("skipchunk") - >>> x = 3 - >>> print x - 3 - - -Generative / Parametrized tests -================================= - -generate three tests with "yield":: - - def check(x): - assert x >= 0 - - def test_gen(): - for x in 1,2,3: - yield check, x - -Named Parametrized tests -================================= - -generate three named tests with "yield":: - - def check(x): - assert x >= 0 - - def test_gen(): - for x in 1,2,3: - yield "check %s" % x, check, x - - -Selection/Reporting -============================ - -- ``-l | --showlocals``: show local variables in traceback -- ``--pdb``: jump into pdb for failures -- ``-k KEYWORD``: select tests to run -- ``-x | --exitfirst``: stop on first test failure - +* install py.test +* write a new package "mypkg" +* add mypkg/__init__ and mypkg/test_ospath.py +* add a test_path function that needs a "url" argument +* register the argument maker for it +* run your tests Test Driven Development - at its best! ======================================= @@ -370,10 +286,8 @@ - checks for file changes and re-runs failing tests - if all failures fixed, reruns whole test suite -great for refactoring! - -Using Plugins and Extensions (40 minutes) +Using Plugins and Extensions ========================================= .. image:: img/end_of_a_age_by_marikaz.jpg @@ -382,20 +296,20 @@ http://marikaz.deviantart.com/ CC 3.0 AN-ND - -History +Historic view ========================== - 0.9.x uses conftest's for extension and configuration - 1.0 uses "plugins" for extending and conftest.py for configuration +- but "smooth" transition because of existing test code base Conftest.py =============== - can be put into test directory or higher up - contains test configuration values -- specifies which plugins to use -- default values for command line options +- can specifies plugins to use +- can provide default values for command line options - (old) can specify collection and test items Customizing py.test @@ -459,7 +373,15 @@ parser.addoption("--myworld", action="store_true") ... -Plugin Examples +Exercise +========================================== + +* port "url" funcarg to conftest +* add an option for specifying url on the command line +* look at "py.test -h" + + +other Plugin Examples ========================================= - integrate collection/run of traditional unit-tests @@ -478,6 +400,7 @@ http://marikaz.deviantart.com/ CC 3.0 AN-ND + Writing Plugins (30 minutes) ============================================================================ @@ -500,8 +423,6 @@ Collection Hooks ================= -.. XXX - pytest_collect_file pytest_collect_module ... @@ -521,6 +442,14 @@ - make a NamePlugin class available - release or copy to somewhere importable +Exercise +================================== + +* port conftest plugin to global "pytest_myapp" plugin. +* put pytest_myapp somewhere where it's importable (or release it :) +* put "pytest_plugins = 'pytest_myapp'" into your test module + + Distributed Testing (45 minutes) ==================================== @@ -530,8 +459,9 @@ http://marikaz.deviantart.com/ CC 3.0 AN-ND -Distributed Testing -=================== +Defining execution environments +================================= + - distribute test load to multiple CPUs/multicore - simultanously run tests on multiple machines From hpk at codespeak.net Tue Mar 24 23:00:08 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 24 Mar 2009 23:00:08 +0100 (CET) Subject: [py-svn] r63300 - in py/trunk/py: . misc/testing test test/plugin test/testing Message-ID: <20090324220008.DDA71168526@codespeak.net> Author: hpk Date: Tue Mar 24 23:00:07 2009 New Revision: 63300 Modified: py/trunk/py/_com.py py/trunk/py/misc/testing/test_com.py py/trunk/py/test/config.py py/trunk/py/test/plugin/pytest_monkeypatch.py py/trunk/py/test/plugin/pytest_pytester.py py/trunk/py/test/pycollect.py py/trunk/py/test/testing/test_config.py Log: * refining pyfuncarg setup, now there is explicit registration! * porting monkeypatch and pytester funcargs to the new method * fixing a kind-of-a-bug with MultiCalls Modified: py/trunk/py/_com.py ============================================================================== --- py/trunk/py/_com.py (original) +++ py/trunk/py/_com.py Tue Mar 24 23:00:07 2009 @@ -29,7 +29,7 @@ NONEASRESULT = object() def __init__(self, methods, *args, **kwargs): - self.methods = methods + self.methods = methods[:] self.args = args self.kwargs = kwargs self.results = [] @@ -69,6 +69,7 @@ def exclude_other_results(self): self._ex1 = True + class PyPlugins: """ Manage Plugins: Load plugins and manage calls to plugins. @@ -79,7 +80,6 @@ if plugins is None: plugins = [] self._plugins = plugins - self._callbacks = [] def import_module(self, modspec): # XXX allow modspec to specify version / lookup Modified: py/trunk/py/misc/testing/test_com.py ============================================================================== --- py/trunk/py/misc/testing/test_com.py (original) +++ py/trunk/py/misc/testing/test_com.py Tue Mar 24 23:00:07 2009 @@ -6,6 +6,13 @@ pytest_plugins = "xfail" class TestMultiCall: + def test_uses_copy_of_methods(self): + l = [lambda: 42] + mc = MultiCall(l) + l[:] = [] + res = mc.execute() + return res == 42 + def test_call_passing(self): class P1: def m(self, __call__, x): Modified: py/trunk/py/test/config.py ============================================================================== --- py/trunk/py/test/config.py (original) +++ py/trunk/py/test/config.py Tue Mar 24 23:00:07 2009 @@ -41,6 +41,7 @@ self.pytestplugins = pytestplugins self._conftest = Conftest(onimport=self._onimportconftest) self._setupstate = SetupState() + self._funcarg2maker = {} def _onimportconftest(self, conftestmodule): self.trace("loaded conftestmodule %r" %(conftestmodule,)) @@ -285,7 +286,23 @@ roots.append(pydir) return roots - + def register_funcargmaker(self, argname, maker): + """ register a setup method for the given argument name. """ + self._funcarg2maker.setdefault(argname, []).append(maker) + + def _makefuncarg(self, argname, pyfuncitem): + makerlist = self._getmakerlist(argname) + mcall = py._com.MultiCall(makerlist, pyfuncitem) + return mcall.execute(firstresult=True) + + def _getmakerlist(self, argname): + makerlist = self._funcarg2maker.get(argname, None) + if makerlist is None: + msg = "funcarg %r not registered, available are: %s" % ( + argname, ", ".join(self._funcarg2maker.keys())) + raise KeyError(msg) + assert makerlist + return makerlist[:] # # helpers # Modified: py/trunk/py/test/plugin/pytest_monkeypatch.py ============================================================================== --- py/trunk/py/test/plugin/pytest_monkeypatch.py (original) +++ py/trunk/py/test/plugin/pytest_monkeypatch.py Tue Mar 24 23:00:07 2009 @@ -2,7 +2,10 @@ class MonkeypatchPlugin: """ setattr-monkeypatching with automatical reversal after test. """ - def pytest_pyfuncarg_monkeypatch(self, pyfuncitem): + def pytest_configure(self, config): + config.register_funcargmaker("monkeypatch", self.argmaker) + + def argmaker(self, pyfuncitem): monkeypatch = MonkeyPatch() pyfuncitem.addfinalizer(monkeypatch.finalize) return monkeypatch Modified: py/trunk/py/test/plugin/pytest_pytester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_pytester.py (original) +++ py/trunk/py/test/plugin/pytest_pytester.py Tue Mar 24 23:00:07 2009 @@ -7,21 +7,20 @@ from py.__.test.config import Config as pytestConfig class PytesterPlugin: - def pytest_pyfuncarg_linecomp(self, pyfuncitem): - return LineComp() + def pytest_configure(self, config): + config.register_funcargmaker("linecomp", lambda x: LineComp()) + config.register_funcargmaker("LineMatcher", lambda x: LineMatcher) + config.register_funcargmaker("EventRecorder", lambda x: EventRecorder) - def pytest_pyfuncarg_LineMatcher(self, pyfuncitem): - return LineMatcher + config.register_funcargmaker("testdir", self.maketestdir) + config.register_funcargmaker("eventrecorder", self.makeeventrecorder) - def pytest_pyfuncarg_testdir(self, pyfuncitem): + def maketestdir(self, pyfuncitem): tmptestdir = TmpTestdir(pyfuncitem) pyfuncitem.addfinalizer(tmptestdir.finalize) return tmptestdir - def pytest_pyfuncarg_EventRecorder(self, pyfuncitem): - return EventRecorder - - def pytest_pyfuncarg_eventrecorder(self, pyfuncitem): + def makeeventrecorder(self, pyfuncitem): evrec = EventRecorder(py._com.pyplugins) pyfuncitem.addfinalizer(lambda: evrec.pyplugins.unregister(evrec)) return evrec Modified: py/trunk/py/test/pycollect.py ============================================================================== --- py/trunk/py/test/pycollect.py (original) +++ py/trunk/py/test/pycollect.py Tue Mar 24 23:00:07 2009 @@ -375,14 +375,23 @@ return kwargs def lookup_onearg(self, argname): - value = self.config.pytestplugins.call_firstresult( - "pytest_pyfuncarg_" + argname, pyfuncitem=self) + try: + makerlist = self.config._getmakerlist(argname) + except KeyError: + makerlist = [] + l = self.config.pytestplugins.listattr("pytest_pyfuncarg_" + argname) + makerlist.extend(l) + mc = py._com.MultiCall(makerlist, self) + #print "mc.methods", mc.methods + value = mc.execute(firstresult=True) if value is not None: return value else: metainfo = self.repr_metainfo() #self.config.bus.notify("pyfuncarg_lookuperror", argname) - raise LookupError("funcargument %r not found for: %s" %(argname,metainfo.verboseline())) + msg = "funcargument %r not found for: %s" %(argname,metainfo.verboseline()) + msg += "\n list of makers: %r" %(l,) + raise LookupError(msg) def __eq__(self, other): try: Modified: py/trunk/py/test/testing/test_config.py ============================================================================== --- py/trunk/py/test/testing/test_config.py (original) +++ py/trunk/py/test/testing/test_config.py Tue Mar 24 23:00:07 2009 @@ -1,5 +1,33 @@ import py +class TestFuncArgsSetup: + def test_register_funcarg_simple(self, testdir): + item = testdir.getitem("def test_func(hello): pass") + def maker(pyfuncitem): + assert item == pyfuncitem + return 42 + item.config.register_funcargmaker("hello", maker) + arg = item.config._makefuncarg("hello", item) + assert arg == 42 + + def test_register_funcarg_two(self, testdir): + item = testdir.getitem("def test_func(hello): pass") + def maker1(pyfuncitem): + assert item == pyfuncitem + return 1 + def maker2(__call__, pyfuncitem): + assert item == pyfuncitem + res = __call__.execute(firstresult=True) + return res + 1 + item.config.register_funcargmaker("two", maker1) + item.config.register_funcargmaker("two", maker2) + arg = item.config._makefuncarg("two", item) + assert arg == 2 + + def test_register_funcarg_error(self, testdir): + item = testdir.getitem("def test_func(hello): pass") + config = item.config + py.test.raises(KeyError, 'item.config._makefuncarg("notexist", item)') class TestConfigCmdlineParsing: def test_config_cmdline_options(self, testdir): From hpk at codespeak.net Tue Mar 24 23:38:45 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 24 Mar 2009 23:38:45 +0100 (CET) Subject: [py-svn] r63301 - py/trunk/py/process Message-ID: <20090324223845.707CE16851D@codespeak.net> Author: hpk Date: Tue Mar 24 23:38:43 2009 New Revision: 63301 Modified: py/trunk/py/process/cmdexec.py Log: don't open the anyway unused stdin, this can (at last on python 2.4 on Mac) leave open pipes in the process Modified: py/trunk/py/process/cmdexec.py ============================================================================== --- py/trunk/py/process/cmdexec.py (original) +++ py/trunk/py/process/cmdexec.py Tue Mar 24 23:38:43 2009 @@ -32,7 +32,7 @@ import errno #print "execing", cmd - child = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE, + child = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE, close_fds=True) stdin, stdout, stderr = child.stdin, child.stdout, child.stderr From hpk at codespeak.net Tue Mar 24 23:40:05 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 24 Mar 2009 23:40:05 +0100 (CET) Subject: [py-svn] r63302 - py/trunk/py/execnet/testing Message-ID: <20090324224005.1D24116851D@codespeak.net> Author: hpk Date: Tue Mar 24 23:40:04 2009 New Revision: 63302 Modified: py/trunk/py/execnet/testing/test_xspec.py Log: avoiding a (what i think mostly) mac python setup issue Modified: py/trunk/py/execnet/testing/test_xspec.py ============================================================================== --- py/trunk/py/execnet/testing/test_xspec.py (original) +++ py/trunk/py/execnet/testing/test_xspec.py Tue Mar 24 23:40:04 2009 @@ -79,12 +79,14 @@ for trypath in ('python2.4', r'C:\Python24\python.exe'): cpython24 = py.path.local.sysfind(trypath) if cpython24 is not None: + cpython24 = cpython24.realpath() break else: py.test.skip("cpython2.4 not found") gw = py.execnet.makegateway("popen//python=%s" % cpython24) rinfo = gw._rinfo() - assert rinfo.executable == cpython24 + if py.std.sys.platform != "darwin": # it's confusing there + assert rinfo.executable == cpython24 assert rinfo.cwd == py.std.os.getcwd() assert rinfo.version_info[:2] == (2,4) From hpk at codespeak.net Tue Mar 24 23:55:40 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 24 Mar 2009 23:55:40 +0100 (CET) Subject: [py-svn] r63303 - py/extradoc/talk/pycon-us-2009/pytest-advanced Message-ID: <20090324225540.8654A168510@codespeak.net> Author: hpk Date: Tue Mar 24 23:55:37 2009 New Revision: 63303 Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Log: shifting things around a bit Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Tue Mar 24 23:55:37 2009 @@ -130,6 +130,12 @@ - certain managed systems (GAE, Azura, "cloud"...) - web browsers, IE, Mozilla, Safari, Mobile .... +sorry, but +======================================== + +we are living in a complex deployment world! + + A vision for automated testing ======================================== @@ -276,16 +282,6 @@ * register the argument maker for it * run your tests -Test Driven Development - at its best! -======================================= - -meet ``py.test --looponfailing``: - -- does a full test run -- keeps a set of failing tests -- checks for file changes and re-runs failing tests -- if all failures fixed, reruns whole test suite - Using Plugins and Extensions ========================================= @@ -303,57 +299,22 @@ - 1.0 uses "plugins" for extending and conftest.py for configuration - but "smooth" transition because of existing test code base -Conftest.py -=============== - -- can be put into test directory or higher up -- contains test configuration values -- can specifies plugins to use -- can provide default values for command line options -- (old) can specify collection and test items - Customizing py.test =========================== +- configuration values go to conftest.py files - write local or global plugins -- write conftest.py files - -- add command line options -- modify/extend the test collection process -- register to reporting events -- for debugging:: +- provide funcargs - py.test --collectonly - py.test --traceconfig -The collection tree -=========================== - -- items: runnable tests - ``item.runtest()`` -- collectors: return collectors or items - ``collector.collect()`` -- collection tree is built iteratively -- test collection starts from directories or files (via cmdline) -- test collection not limited to Python files! -- use ``py.test --collectonly`` for debugging - -Important Collection Objects -================================== - -- **Directory** collect files in directory -- **FSCollector** a Collector for a File -- **Module** collect python Classes and Functions -- **Class**/**Instance** collect test methods -- **Generator** collect generative tests -- **Function** sets up and executes a python test function -- **DoctestFile** collect doctests in a .txt file - -Interactive demo "collection tree" -==================================== +Conftest.py +=============== -you can always inspect the test collection tree -tree without running tests:: +- can be put into test directory or higher up +- contains test configuration values +- can specify plugins to use +- can provide default values for command line options - py.test --collectonly Specifying plugins @@ -411,6 +372,39 @@ - writing cross-project plugins + py.test --collectonly + py.test --traceconfig + +py.test collection of tests +============================= + +- items: runnable tests - ``item.runtest()`` +- collectors: return collectors or items - ``collector.collect()`` +- collection tree is built iteratively +- test collection starts from directories or files (via cmdline) +- test collection not limited to Python files! +- use ``py.test --collectonly`` for debugging + +Important Collection Objects +================================== + +- **Directory** collect files in directory +- **FSCollector** a Collector for a File +- **Module** collect python Classes and Functions +- **Class**/**Instance** collect test methods +- **Generator** collect generative tests +- **Function** sets up and executes a python test function +- **DoctestFile** collect doctests in a .txt file + +Interactive demo "collection tree" +==================================== + +you can always inspect the test collection tree +tree without running tests:: + + py.test --collectonly + + Hooks and Events =================== From hpk at codespeak.net Wed Mar 25 00:01:40 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 25 Mar 2009 00:01:40 +0100 (CET) Subject: [py-svn] r63304 - py/extradoc/talk/pycon-us-2009/pytest-advanced Message-ID: <20090324230140.434AF168525@codespeak.net> Author: hpk Date: Wed Mar 25 00:01:39 2009 New Revision: 63304 Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Log: some more shuffling Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Wed Mar 25 00:01:39 2009 @@ -130,10 +130,10 @@ - certain managed systems (GAE, Azura, "cloud"...) - web browsers, IE, Mozilla, Safari, Mobile .... -sorry, but +things are ======================================== -we are living in a complex deployment world! +changing all the time A vision for automated testing @@ -141,14 +141,6 @@ ... integrate with actual deployment! -Installing py.test, then break -======================================== - -- install py.test - -- svn checkout http://codespeak.net/svn/py/dist - -- run "python setup.py" with "install" or "develop" pytest advanced features (30 minutes) ============================================================================ @@ -272,10 +264,18 @@ * freedom to *logically* group your tests in test classes * change test setup in once place if app mechanics change +Hands on: Installing py.test +======================================== + +- install py.test + +- svn checkout http://codespeak.net/svn/py/dist + +- run "python setup.py" with "install" or "develop" + Exercise ========================================== -* install py.test * write a new package "mypkg" * add mypkg/__init__ and mypkg/test_ospath.py * add a test_path function that needs a "url" argument From briandorsey at codespeak.net Wed Mar 25 01:48:40 2009 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Wed, 25 Mar 2009 01:48:40 +0100 (CET) Subject: [py-svn] r63306 - py/extradoc/talk/pycon-us-2009/pytest-introduction Message-ID: <20090325004840.ED6B2168519@codespeak.net> Author: briandorsey Date: Wed Mar 25 01:48:38 2009 New Revision: 63306 Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt Log: small fixes from the practice run Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html (original) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html Wed Mar 25 01:48:38 2009 @@ -374,7 +374,7 @@
    • Full Test Driven Development
    • Stupidity Driven Testing
    • -
    • Happy path testing - to help write better APIs then SDT
    • +
    • Happy path testing
    @@ -554,9 +554,9 @@ def setup_method(self, method): pass - def test_one(): + def test_one(self): assert True - def test_two(tmpdir): + def test_two(self): assert 1 == 1

    TODO: write handout/exercise @@ -713,7 +713,7 @@ data = ['spam', 'SPAM', 'eggs & spam', 'fish','spammy', 'more spam'] for item in data: - yeild contains_spam, item + yield contains_spam, item

    Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt Wed Mar 25 01:48:38 2009 @@ -71,7 +71,7 @@ - Full Test Driven Development - Stupidity Driven Testing -- Happy path testing - to help write better APIs then SDT +- Happy path testing what you get with py.test @@ -287,9 +287,9 @@ def setup_method(self, method): pass - def test_one(): + def test_one(self): assert True - def test_two(tmpdir): + def test_two(self): assert 1 == 1 TODO: write handout/exercise @@ -477,7 +477,7 @@ data = ['spam', 'SPAM', 'eggs & spam', 'fish','spammy', 'more spam'] for item in data: - yeild contains_spam, item + yield contains_spam, item exercise 5 (~10 min) From briandorsey at codespeak.net Wed Mar 25 05:18:18 2009 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Wed, 25 Mar 2009 05:18:18 +0100 (CET) Subject: [py-svn] r63307 - in py/extradoc/talk/pycon-us-2009/pytest-introduction: . helpers Message-ID: <20090325041818.C314C1684CF@codespeak.net> Author: briandorsey Date: Wed Mar 25 05:18:15 2009 New Revision: 63307 Added: py/extradoc/talk/pycon-us-2009/pytest-introduction/helpers/ py/extradoc/talk/pycon-us-2009/pytest-introduction/helpers/build.sh - copied, changed from r63305, py/extradoc/talk/pycon-us-2009/pytest-introduction/build.sh py/extradoc/talk/pycon-us-2009/pytest-introduction/helpers/extract_examples.py py/extradoc/talk/pycon-us-2009/pytest-introduction/helpers/show.sh - copied unchanged from r63305, py/extradoc/talk/pycon-us-2009/pytest-introduction/show.sh py/extradoc/talk/pycon-us-2009/pytest-introduction/test_examples_auto.py Removed: py/extradoc/talk/pycon-us-2009/pytest-introduction/build.sh py/extradoc/talk/pycon-us-2009/pytest-introduction/show.sh Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt Log: moved utilities into 'helpers' added utility to extract all code from slides into test_examples_auto.py some fixes to code in the slides to make sure the examples run. Deleted: /py/extradoc/talk/pycon-us-2009/pytest-introduction/build.sh ============================================================================== --- /py/extradoc/talk/pycon-us-2009/pytest-introduction/build.sh Wed Mar 25 05:18:15 2009 +++ (empty file) @@ -1 +0,0 @@ -rst2s5.py pytest-introduction.txt pytest-introduction.html Copied: py/extradoc/talk/pycon-us-2009/pytest-introduction/helpers/build.sh (from r63305, py/extradoc/talk/pycon-us-2009/pytest-introduction/build.sh) ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-introduction/build.sh (original) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/helpers/build.sh Wed Mar 25 05:18:15 2009 @@ -1 +1,2 @@ rst2s5.py pytest-introduction.txt pytest-introduction.html +python helpers/extract_examples.py Added: py/extradoc/talk/pycon-us-2009/pytest-introduction/helpers/extract_examples.py ============================================================================== --- (empty file) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/helpers/extract_examples.py Wed Mar 25 05:18:15 2009 @@ -0,0 +1,40 @@ +from BeautifulSoup import BeautifulSoup +from pprint import pprint + +source = 'pytest-introduction.html' +target = 'test_examples_auto.py' +target_file = open('test_examples_auto.py', 'w') + +html = open(source).read() +soup = BeautifulSoup(html) + +pre_tags = soup('pre') + +def unquote(text): + items = [ + ('"', '"'), + ('@', '@'), + ] + for a, b in items: + text = text.replace(a, b) + return text + +for item in pre_tags: + code = item.string + + slide = item.parent + comments = [] + title = slide.find('h1').string + if title.lower().startswith('options'): + continue + comments.append(title) + comments.append('') + paragraphs = slide.findAll('p', attrs={'class':None}) + for item in paragraphs: + comments.extend(item.string.split('\n')) + + comments = ['# ' + item for item in comments] + target_file.write('\n'.join(comments)) + target_file.write('\n') + target_file.write(unquote(code)) + Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html (original) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html Wed Mar 25 05:18:15 2009 @@ -394,7 +394,7 @@ assert True class TestSomething(): - def test_the_thing(): + def test_something(self): assert True

    Use individual test functions or group tests in classes. Use whichever @@ -407,7 +407,7 @@ assert True class TestSomething(): - def test_the_thing(): + def test_something(self): assert True # search all test_* and *_test files @@ -419,13 +419,11 @@

    assert introspection

    -def test_something():
    -    assert True     # assertTrue()
    -    assert 1 == 1   # assertEqual()
    -    assert 1 == 2   # assertNotEqual()
    -    assert False    # assertFalse()
    -
    -    assert should_return_true()
    +def test_assert_introspection():
    +    assert True         # assertTrue()
    +    assert 1 == 1       # assertEqual()
    +    assert not 1 == 2   # assertNotEqual()
    +    assert not False    # assertFalse()
     

    This allows you to write test code which looks just like application code. For example, no need to pass functions and parameters separately into @@ -440,6 +438,8 @@ print "Useful debugging information." assert True +import py +@py.test.mark(xfail="Expected failure.") def test_something2(): print "Useful debugging information." assert False @@ -527,14 +527,16 @@ print "debug text" assert True def test_two(): - print "debug text" assert 1 == 1 +@py.test.mark(xfail="Expected failure.") def test_three(): - print "debug text" assert 1 == 2 -def test_four(): | def test_bonus(): - print "debug text" | py.test.raises( - assert False | ValueError, int, 'foo') +@py.test.mark(xfail="Expected failure.") +def test_four(): + print "debug text" + assert False +def test_bonus(): + py.test.raises(ValueError, int, 'foo')

    TODO: write handout/exercise

    @@ -675,20 +677,17 @@ import py def test_something_we_want_to_skip(): - some_code() py.test.skip("need to decide what it should do.") - more_code() assert something == True

    generative tests 1

    -def test_ensure_sufficient_spam():
    +def test_ensure_sufficient_spam1():
         assert 'spam' in 'spam'.lower()
         assert 'spam' in 'SPAM'.lower()
         assert 'spam' in 'eggs & spam'.lower()
    -    assert 'spam' in 'fish'.lower()
         assert 'spam' in 'spammy'.lower()
         assert 'spam' in 'more spam'.lower()
     
    @@ -696,9 +695,9 @@

    generative tests 2

    -def test_ensure_sufficient_spam():
    +def test_ensure_sufficient_spam2():
         data = ['spam', 'SPAM', 'eggs & spam',
    -            'fish','spammy', 'more spam']
    +            'spammy', 'more spam']
         for item in data:
             assert 'spam' in item.lower()
     
    @@ -709,9 +708,9 @@ def contains_spam(item): assert 'spam' in item.lower() -def test_ensure_sufficient_spam(): +def test_ensure_sufficient_spam3(): data = ['spam', 'SPAM', 'eggs & spam', - 'fish','spammy', 'more spam'] + 'spammy', 'more spam'] for item in data: yield contains_spam, item Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt Wed Mar 25 05:18:15 2009 @@ -94,7 +94,7 @@ assert True class TestSomething(): - def test_the_thing(): + def test_something(self): assert True .. class:: handout @@ -112,7 +112,7 @@ assert True class TestSomething(): - def test_the_thing(): + def test_something(self): assert True # search all test_* and *_test files @@ -129,13 +129,11 @@ :: - def test_something(): - assert True # assertTrue() - assert 1 == 1 # assertEqual() - assert 1 == 2 # assertNotEqual() - assert False # assertFalse() - - assert should_return_true() + def test_assert_introspection(): + assert True # assertTrue() + assert 1 == 1 # assertEqual() + assert not 1 == 2 # assertNotEqual() + assert not False # assertFalse() .. class:: handout @@ -156,6 +154,8 @@ print "Useful debugging information." assert True + import py + @py.test.mark(xfail="Expected failure.") def test_something2(): print "Useful debugging information." assert False @@ -257,14 +257,17 @@ print "debug text" assert True def test_two(): - print "debug text" assert 1 == 1 + @py.test.mark(xfail="Expected failure.") def test_three(): - print "debug text" assert 1 == 2 - def test_four(): | def test_bonus(): - print "debug text" | py.test.raises( - assert False | ValueError, int, 'foo') + @py.test.mark(xfail="Expected failure.") + def test_four(): + print "debug text" + assert False + def test_bonus(): + py.test.raises(ValueError, int, 'foo') + TODO: write handout/exercise @@ -433,9 +436,7 @@ import py def test_something_we_want_to_skip(): - some_code() py.test.skip("need to decide what it should do.") - more_code() assert something == True @@ -444,11 +445,10 @@ :: - def test_ensure_sufficient_spam(): + def test_ensure_sufficient_spam1(): assert 'spam' in 'spam'.lower() assert 'spam' in 'SPAM'.lower() assert 'spam' in 'eggs & spam'.lower() - assert 'spam' in 'fish'.lower() assert 'spam' in 'spammy'.lower() assert 'spam' in 'more spam'.lower() @@ -458,9 +458,9 @@ :: - def test_ensure_sufficient_spam(): + def test_ensure_sufficient_spam2(): data = ['spam', 'SPAM', 'eggs & spam', - 'fish','spammy', 'more spam'] + 'spammy', 'more spam'] for item in data: assert 'spam' in item.lower() @@ -473,9 +473,9 @@ def contains_spam(item): assert 'spam' in item.lower() - def test_ensure_sufficient_spam(): + def test_ensure_sufficient_spam3(): data = ['spam', 'SPAM', 'eggs & spam', - 'fish','spammy', 'more spam'] + 'spammy', 'more spam'] for item in data: yield contains_spam, item Deleted: /py/extradoc/talk/pycon-us-2009/pytest-introduction/show.sh ============================================================================== --- /py/extradoc/talk/pycon-us-2009/pytest-introduction/show.sh Wed Mar 25 05:18:15 2009 +++ (empty file) @@ -1 +0,0 @@ -/Applications/Plainview.app/Contents/MacOS/Plainview pytest-introduction.html 2>/dev/null Added: py/extradoc/talk/pycon-us-2009/pytest-introduction/test_examples_auto.py ============================================================================== --- (empty file) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/test_examples_auto.py Wed Mar 25 05:18:15 2009 @@ -0,0 +1,111 @@ +# function or class +# + +def test_something(): + assert True + +class TestSomething(): + def test_something(self): + assert True +# automatic test discovery +# + +def test_something(): + assert True + +class TestSomething(): + def test_something(self): + assert True + +# search all test_* and *_test files +# assert introspection +# + +def test_assert_introspection(): + assert True # assertTrue() + assert 1 == 1 # assertEqual() + assert not 1 == 2 # assertNotEqual() + assert not False # assertFalse() +# print() debugging +# +# stdout is captured for each test, and only printed if the test fails. You can +# leave your debugging print statments in place to help your future self. + +def test_something1(): + print "Useful debugging information." + assert True + +import py + at py.test.mark(xfail="Expected failure.") +def test_something2(): + print "Useful debugging information." + assert False +# exercise 2 (~10 min) +# +# TODO: write handout/exercise + +def test_one(): + print "debug text" + assert True +def test_two(): + assert 1 == 1 + at py.test.mark(xfail="Expected failure.") +def test_three(): + assert 1 == 2 + at py.test.mark(xfail="Expected failure.") +def test_four(): + print "debug text" + assert False +def test_bonus(): + py.test.raises(ValueError, int, 'foo') +# exercise 3 (~10 min) +# +# TODO: write handout/exercise +# TODO: use some built-in funcarg for the example + +class TestSomething(): + def setup_class(cls): + pass + def setup_method(self, method): + pass + + def test_one(self): + assert True + def test_two(self): + assert 1 == 1 +# skipping tests +# + +import py + +def test_something_we_want_to_skip(): + py.test.skip("need to decide what it should do.") + assert something == True +# generative tests 1 +# + +def test_ensure_sufficient_spam1(): + assert 'spam' in 'spam'.lower() + assert 'spam' in 'SPAM'.lower() + assert 'spam' in 'eggs & spam'.lower() + assert 'spam' in 'spammy'.lower() + assert 'spam' in 'more spam'.lower() +# generative tests 2 +# + +def test_ensure_sufficient_spam2(): + data = ['spam', 'SPAM', 'eggs & spam', + 'spammy', 'more spam'] + for item in data: + assert 'spam' in item.lower() +# generative tests 3 +# + +def contains_spam(item): + assert 'spam' in item.lower() + +def test_ensure_sufficient_spam3(): + data = ['spam', 'SPAM', 'eggs & spam', + 'spammy', 'more spam'] + for item in data: + yield contains_spam, item From briandorsey at codespeak.net Wed Mar 25 08:50:54 2009 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Wed, 25 Mar 2009 08:50:54 +0100 (CET) Subject: [py-svn] r63308 - py/extradoc/talk/pycon-us-2009/pytest-introduction Message-ID: <20090325075054.99132168505@codespeak.net> Author: briandorsey Date: Wed Mar 25 08:50:52 2009 New Revision: 63308 Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt py/extradoc/talk/pycon-us-2009/pytest-introduction/test_examples_auto.py Log: slight cleanup of the last slide placeholder for Holger's funcarg info Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html (original) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html Wed Mar 25 08:50:52 2009 @@ -434,11 +434,12 @@

    stdout is captured for each test, and only printed if the test fails. You can leave your debugging print statments in place to help your future self.

    +import py
    +
     def test_something1():
         print "Useful debugging information."
         assert True
     
    -import py
     @py.test.mark(xfail="Expected failure.")
     def test_something2():
         print "Useful debugging information."
    @@ -736,11 +737,14 @@
     

    wrap up & questions

    Wrapping up and questions (20 minutes)

    +

    where to go from here:

      -
    • where to go from here
    • -
    • quesitons and student topics
    • +
    • http://pytest.org
    • +
    • irc.freenode.org #pylib
    +

    Quesitons?

    TODO: split

    +

    TODO:

    Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt Wed Mar 25 08:50:52 2009 @@ -150,11 +150,12 @@ :: + import py + def test_something1(): print "Useful debugging information." assert True - import py @py.test.mark(xfail="Expected failure.") def test_something2(): print "Useful debugging information." @@ -487,17 +488,10 @@ - generative tests -using doctests +using funcargs ============== -*Using doctests (25 minutes)* - -- what are doctests -- two usage scenarios - docstrings & stand alone files -- how they work demo and examples. -- 10 minute work time - exercises - -TODO: split +TODO: content from holger wrap up & questions @@ -505,9 +499,19 @@ *Wrapping up and questions (20 minutes)* -- where to go from here -- quesitons and student topics +where to go from here: + +- http://pytest.org +- irc.freenode.org #pylib + +Quesitons? + + + + -TODO: split +x += +TODO: Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/test_examples_auto.py ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-introduction/test_examples_auto.py (original) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/test_examples_auto.py Wed Mar 25 08:50:52 2009 @@ -31,11 +31,12 @@ # stdout is captured for each test, and only printed if the test fails. You can # leave your debugging print statments in place to help your future self. +import py + def test_something1(): print "Useful debugging information." assert True -import py @py.test.mark(xfail="Expected failure.") def test_something2(): print "Useful debugging information." From hpk at codespeak.net Wed Mar 25 12:44:37 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 25 Mar 2009 12:44:37 +0100 (CET) Subject: [py-svn] r63318 - py/trunk/py/doc Message-ID: <20090325114437.26C3B168472@codespeak.net> Author: hpk Date: Wed Mar 25 12:44:35 2009 New Revision: 63318 Modified: py/trunk/py/doc/test-dist.txt Log: fix typo Modified: py/trunk/py/doc/test-dist.txt ============================================================================== --- py/trunk/py/doc/test-dist.txt (original) +++ py/trunk/py/doc/test-dist.txt Wed Mar 25 12:44:35 2009 @@ -26,7 +26,7 @@ Running tests in a Python subprocess ---------------------------------------- -To instantiate a python2.4 sub process and send tests to itinterpreter process, you may type:: +To instantiate a python2.4 sub process and send tests to it, you may type:: py.test -d --tx popen//python=python2.4 From hpk at codespeak.net Wed Mar 25 12:50:57 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 25 Mar 2009 12:50:57 +0100 (CET) Subject: [py-svn] r63319 - in py/trunk: . py py/test/plugin py/test/testing Message-ID: <20090325115057.49158168519@codespeak.net> Author: hpk Date: Wed Mar 25 12:50:57 2009 New Revision: 63319 Modified: py/trunk/py/__init__.py py/trunk/py/test/plugin/pytest_plugintester.py py/trunk/py/test/plugin/pytest_terminal.py py/trunk/py/test/testing/acceptance_test.py py/trunk/setup.py Log: * fixing verbose reporting to work the "old" way for non-dist settings * bump version number * comment out greenlet C-Extension for now Modified: py/trunk/py/__init__.py ============================================================================== --- py/trunk/py/__init__.py (original) +++ py/trunk/py/__init__.py Wed Mar 25 12:50:57 2009 @@ -23,7 +23,7 @@ """ from initpkg import initpkg -version = "1.0.0a6" +version = "1.0.0a7" initpkg(__name__, description = "pylib and py.test: agile development and test support library", Modified: py/trunk/py/test/plugin/pytest_plugintester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_plugintester.py (original) +++ py/trunk/py/test/plugin/pytest_plugintester.py Wed Mar 25 12:50:57 2009 @@ -108,16 +108,6 @@ """ called before test process is exited. """ - def pytest_event(self, event): - """ called for each internal py.test event. """ - - #def pytest_pyfuncarg_NAME(self, pyfuncitem, argname): - # """ provide (value, finalizer) for an open test function argument. - # - # the finalizer (if not None) will be called after the test - # function has been executed (i.e. pyfuncitem.execute() returns). - # """ - def pytest_pyfunc_call(self, pyfuncitem, args, kwargs): """ return True if we consumed/did the call to the python function item. """ @@ -139,24 +129,19 @@ def pytest_pymodule_makeitem(self, modcol, name, obj): """ return custom item/collector for a python object in a module, or None. """ - - # from pytest_terminal plugin - def pytest_report_teststatus(self, event): - """ return shortletter and verbose word. """ - - # from pytest_terminal plugin + # reporting hooks (invoked from pytest_terminal.py) def pytest_report_teststatus(self, event): """ return shortletter and verbose word. """ def pytest_terminal_summary(self, terminalreporter): """ add additional section in terminal summary reporting. """ - # events + # Events def pyevent(self, eventname, *args, **kwargs): """ called for each testing event. """ def pyevent_gateway_init(self, gateway): - """ called a gateway has been initialized. """ + """ called after a gateway has been initialized. """ def pyevent_gateway_exit(self, gateway): """ called when gateway is being exited. """ @@ -199,7 +184,6 @@ The gateway will have an 'id' attribute that is unique within the gateway manager context. """ - def pyevent_testnodeready(self, node): """ Node is ready to operate. """ Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Wed Mar 25 12:50:57 2009 @@ -143,11 +143,15 @@ if node: extra = "-> " + str(node.gateway.id) self.write_ensure_prefix(line, extra) - #else: - # # ensure that the path is printed before the 1st test of - # # a module starts running - # fspath = item.fspath - # self.write_fspath_result(fspath, "") + # in dist situations itemstart (currently only means we + # queued the item for testing, doesn't tell much + elif self.config.option.verbose and self.config.option.dist == "no": + # ensure that the path is printed before the 1st test of + # a module starts running + info = item.repr_metainfo() + line = info.verboseline(basedir=self.curdir) + " " + #self.write_fspath_result(fspath, "") + self.write_ensure_prefix(line, "") def pyevent_rescheduleitems(self, event): if self.config.option.debug: @@ -169,13 +173,15 @@ else: info = event.colitem.repr_metainfo() line = info.verboseline(basedir=self.curdir) + " " - #self.write_ensure_prefix(line, word, **markup) - self.ensure_newline() - if hasattr(event, 'node'): - self._tw.write("%s " % event.node.gateway.id) - self._tw.write(word, **markup) - self._tw.write(" " + line) - self.currentfspath = -2 + if not hasattr(event, 'node'): + self.write_ensure_prefix(line, word, **markup) + else: + self.ensure_newline() + if hasattr(event, 'node'): + self._tw.write("%s " % event.node.gateway.id) + self._tw.write(word, **markup) + self._tw.write(" " + line) + self.currentfspath = -2 def pyevent_collectionreport(self, event): if not event.passed: @@ -400,9 +406,9 @@ rep.config.bus.notify("itemtestreport", basic_run_report(item)) linecomp.assert_contains_lines([ - "*PASS*test_pass_skip_fail_verbose.py:2: *test_ok*", - "*SKIP*test_pass_skip_fail_verbose.py:4: *test_skip*", - "*FAIL*test_pass_skip_fail_verbose.py:6: *test_func*", + "*test_pass_skip_fail_verbose.py:2: *test_ok*PASS*", + "*test_pass_skip_fail_verbose.py:4: *test_skip*SKIP*", + "*test_pass_skip_fail_verbose.py:6: *test_func*FAIL*", ]) rep.config.bus.notify("testrunfinish", event.TestrunFinish()) linecomp.assert_contains_lines([ Modified: py/trunk/py/test/testing/acceptance_test.py ============================================================================== --- py/trunk/py/test/testing/acceptance_test.py (original) +++ py/trunk/py/test/testing/acceptance_test.py Wed Mar 25 12:50:57 2009 @@ -268,10 +268,15 @@ """) result = testdir.runpytest(p1, '-v') result.stdout.fnmatch_lines([ + "*test_verbose_reporting.py:2: test_fail*FAIL*", + "*test_verbose_reporting.py:4: test_pass*PASS*", + "*test_verbose_reporting.py:7: TestClass.test_skip*SKIP*", + "*test_verbose_reporting.py:10: test_gen*FAIL*", + ]) + assert result.ret == 1 + result = testdir.runpytest(p1, '-v', '-n 1') + result.stdout.fnmatch_lines([ "*FAIL*test_verbose_reporting.py:2: test_fail*", - "*PASS*test_verbose_reporting.py:4: test_pass*", - "*SKIP*test_verbose_reporting.py:7: TestClass.test_skip*", - "*FAIL*test_verbose_reporting.py:10: test_gen*", ]) assert result.ret == 1 Modified: py/trunk/setup.py ============================================================================== --- py/trunk/setup.py (original) +++ py/trunk/setup.py Wed Mar 25 12:50:57 2009 @@ -1,7 +1,7 @@ """ setup file for 'py' package based on: - https://codespeak.net/svn/py/trunk, revision=63240 + https://codespeak.net/svn/py/trunk, revision=63254 autogenerated by gensetup.py """ @@ -34,20 +34,23 @@ """ +CEXTENSION = False def main(): setup( name='py', description='pylib and py.test: agile development and test support library', long_description = long_description, - version='1.0.0a6', + version='1.0.0a7', url='http://pylib.org', - download_url='http://codespeak.net/py/1.0.0a6/download.html', + download_url='http://codespeak.net/py/1.0.0a7/download.html', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel, Guido Wesdorp, Carl Friedrich Bolz, Armin Rigo, Maciej Fijalkowski & others', author_email='holger at merlinux.eu, py-dev at codespeak.net', - ext_modules = [Extension("py.c-extension.greenlet.greenlet", - ["py/c-extension/greenlet/greenlet.c"]),], + ext_modules = CEXTENSION and + [Extension("py.c-extension.greenlet.greenlet", + ["py/c-extension/greenlet/greenlet.c"]),] + or [], entry_points={'console_scripts': ['py.cleanup = py.cmdline:pycleanup', 'py.countloc = py.cmdline:pycountloc', From hpk at codespeak.net Wed Mar 25 12:56:52 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 25 Mar 2009 12:56:52 +0100 (CET) Subject: [py-svn] r63320 - in py/extradoc/talk/pycon-us-2009/pytest-advanced: . example Message-ID: <20090325115652.5F56B16851F@codespeak.net> Author: hpk Date: Wed Mar 25 12:56:49 2009 New Revision: 63320 Added: py/extradoc/talk/pycon-us-2009/pytest-advanced/example/ py/extradoc/talk/pycon-us-2009/pytest-advanced/example/test_funcarg.py (contents, props changed) Removed: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.html Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/author.latex py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Log: current status of slides Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/author.latex ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-advanced/author.latex (original) +++ py/extradoc/talk/pycon-us-2009/pytest-advanced/author.latex Wed Mar 25 12:56:49 2009 @@ -1,6 +1,6 @@ \definecolor{rrblitbackground}{rgb}{0.0, 0.0, 0.0} -\title[pytest advanced testing] {pytest advanced} +\title[pytest advanced testing tutorial] {pytest advanced testing tutorial} \author[H. Krekel]{Holger Krekel \\ http://merlinux.eu} \institute[Pycon US 2009, Chicago]{Pycon US 2009, Chicago} Added: py/extradoc/talk/pycon-us-2009/pytest-advanced/example/test_funcarg.py ============================================================================== --- (empty file) +++ py/extradoc/talk/pycon-us-2009/pytest-advanced/example/test_funcarg.py Wed Mar 25 12:56:49 2009 @@ -0,0 +1,9 @@ + +def pytest_configure(config): + config.register_funcargmaker("myarg", myargmaker) + +def myargmaker(pyfuncitem): + return pyfuncitem.name + +def test_somefunction(myarg2): + assert myarg == 'test_somefunction' Deleted: /py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.html ============================================================================== --- /py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.html Wed Mar 25 12:56:49 2009 +++ (empty file) @@ -1,369 +0,0 @@ - - - - - - - - - - -
    - - -
    -

    Terminology/Overview (20 minutes)

    -img/little_red_riding_hood_by_marikaz.jpg -

    http://marikaz.deviantart.com/ CC 3.0 AN-ND

    -
    -
    -

    Terminology/Overview

    -
      -
    • Benefits of automated tests
    • -
    • small/medium/big tests aka unit/functional/integration
    • -
    • acceptance tests
    • -
    • benefits of automated testing
    • -
    • related testing tools
    • -
    -
    -
    -

    Benefits of automated tests

    -
      -
    • evolve codebase more easily
    • -
    • higher code quality
    • -
    • better collaboration
    • -
    • perfect fit for Python
    • -
    -
    -
    -

    Some Test terminology

    -
      -
    • developer and "customer" tests
    • -
    • unit tests
    • -
    • functional tests
    • -
    • integration / system tests
    • -
    -
    -
    -

    Small Tests

    -
    img/small.png
    -
    -
    -

    Medium Tests

    -
    img/medium.png
    -
    -
    -

    Large Tests

    -
    img/large.png
    -
    - -
    -

    Walkthrough Advanced Features (30 minutes)

    -img/new_color_in_dark_old_city_by_marikaz.jpg -

    http://marikaz.deviantart.com/ CC 3.0 AN-ND

    -
    -
    -

    If you want to try yourself

    - -
    -
    -

    per-class/instance set up of test state

    -

    example:

    -
    -class LocalSetup:
    -    def setup_class(cls):
    -      cls.root = py.test.ensuretemp(cls.__name__)
    -      cls.root.ensure(dir=1)
    -      setuptestfs(cls.root)
    -
    -    def setup_method(self, method):
    -      self.tmpdir = self.root.mkdir(method.__name__)
    -
    -

    XXX: Example confusing? Without pylib experience will students understand 'ensure'? What is setuptestfs?

    -
    -
    -

    funcargs: per-function setup

    -

    XXX find example:

    -
    -def test_pypkpath():
    -  datadir = py.test.ensuretemp("pypkgdir")
    -  pkg = datadir.ensure('pkg1', dir=1)
    -  pkg.ensure("__init__.py")
    -  pkg.ensure("subdir/__init__.py")
    -  assert pkg.pypkgpath() == pkg
    -  subinit = pkg.join('subdir', '__init__.py')
    -  assert subinit.pypkgpath() == pkg
    -
    -
      -
    • example of test module
    • -
    • working with failures, tracebacks
    • -
    • generative tests
    • -
    • skipping chunks within doctests
    • -
    • looponfailing: run large test set until all tests pass
    • -
    -
    -
    -

    Skipping tests

    -

    use py.test.skip:

    -
    -def test_something_on_windows():
    -    if sys.platform == "win32":
    -        py.test.skip("win32 required")
    -     ...
    -
    -

    don't use py.test.skip() in classic setup functions -because it makes distribution to other platforms -harder.

    -
    -
    -

    Skipping Doctests / ReST

    -

    with 'pytest_restdoc' plugin:

    -
    -.. >>> import py
    -.. >>> if py.test.config.option.xyz: raise ValueError("skipchunk")
    ->>> x = 3
    ->>> print x
    -3
    -
    -
    -
    -

    Generative / Parametrized tests

    -

    generate three tests with "yield":

    -
    -def check(x):
    -    assert x >= 0
    -
    -def test_gen():
    -    for x in 1,2,3:
    -        yield check, x
    -
    -
    -
    -

    Named Parametrized tests

    -

    generate three named tests with "yield":

    -
    -def check(x):
    -    assert x >= 0
    -
    -def test_gen():
    -    for x in 1,2,3:
    -        yield "check %s" % x, check, x
    -
    -
    -
    -

    Selection/Reporting

    -
      -
    • -l | --showlocals: show local variables in traceback
    • -
    • --pdb: jump into pdb for failures
    • -
    • -k KEYWORD: select tests to run
    • -
    • -x | --exitfirst: stop on first test failure
    • -
    -
    -
    -

    Test Driven Development - at its best!

    -

    meet py.test --looponfailing:

    -
      -
    • does a full test run
    • -
    • keeps a set of failing tests
    • -
    • checks for file changes and re-runs failing tests
    • -
    • if all failures fixed, reruns whole test suite
    • -
    -

    great for refactoring!

    -
    -
    -

    Using Plugins and Extensions (40 minutes)

    -img/end_of_a_age_by_marikaz.jpg -

    http://marikaz.deviantart.com/ CC 3.0 AN-ND

    -
    -
    -

    History

    -
      -
    • 0.9.x uses conftest's for extension and configuration
    • -
    • 1.0 uses "plugins" for extending and conftest.py for configuration
    • -
    -
    -
    -

    Conftest.py

    -
      -
    • can be put into test directory or higher up
    • -
    • contains test configuration values
    • -
    • specifies which plugins to use
    • -
    • default values for command line options
    • -
    • (old) can specify collection and test items
    • -
    -
    -
    -

    Customizing py.test

    -
      -
    • modify/extend the test collection process

      -
    • -
    • add command line options

      -
    • -
    • register to reporting events (trunk/1.0)

      -
    • -
    • write conftest.py files

      -
    • -
    • write local or global plugins

      -
    • -
    • for debugging:

      -
      -py.test --collectonly
      -py.test --traceconfig
      -
      -
    • -
    -
    -
    -

    The collection tree

    -
      -
    • items: runnable tests - item.runtest()
    • -
    • collectors: return collectors or items - collector.collect()
    • -
    • collection tree is built iteratively
    • -
    • test collection starts from directories or files (via cmdline)
    • -
    • test collection not limited to Python files!
    • -
    • use py.test --collectonly for debugging
    • -
    -
    -
    -

    Important Collection Objects

    -
      -
    • Directory collect files in directory
    • -
    • FSCollector a Collector for a File
    • -
    • Module collect python Classes and Functions
    • -
    • Class/Instance collect test methods
    • -
    • Generator collect generative tests
    • -
    • Function sets up and executes a python test function
    • -
    • DoctestFile collect doctests in a .txt file
    • -
    -
    -
    -

    Interactive demo "collection tree"

    -

    you can inspect at the test collection tree -tree without running tests:

    -
    -py.test --collectonly
    -
    -
    -
    -

    Collector nodes

    -

    Collector nodes:

    -
      -
    • collect() method provides a list of children
    • -
    • children may be collector nodes or runnable test items
    • -
    • Collection nodes are not tied to Directory/the file system
    • -
    -
    -
    -

    Test Item nodes

    -
      -
    • runtest() is called for executing a test
    • -
    -
    -
    -

    Specifying plugins

    -
      -
    • -p|-plugin: load comma-separated list of plugins
    • -
    • plugins are always named "pytest_NAME" and can be -anywhere in your import path
    • -
    -
    -
    -

    Writing a local conftest plugin

    -

    you can write a local conftest.py based plugin:

    -
    -class ConftestPlugin:
    -    def pytest_addoption(self, parser):
    -        parser.addoption("--myworld", action="store_true")
    -    ...
    -
    -
    -
    -

    Plugin Examples

    -
      -
    • integrate collection/run of traditional unit-tests
    • -
    • run functions in their own tempdir
    • -
    • testing ReST documents
    • -
    • running Prolog tests (XXX conftest based)
    • -
    • running Javascript tests (conftest based)
    • -
    • html reporting for nightly runs (conftest-based)
    • -
    -
    -
    -

    Break

    -img/flying_lady_by_marikaz.jpg -

    http://marikaz.deviantart.com/ CC 3.0 AN-ND

    -
    -
    -

    Writing Plugins (30 minutes)

    -
      -
    • plugin hooks and events
    • -
    • event system for custom reporting
    • -
    • test collection hooks
    • -
    • test running hooks
    • -
    • writing cross-project plugins
    • -
    -
    -
    -

    Hooks and Events

    -
      -
    • Plugin hook methods interact with -configuration/collection/running of tests
    • -
    • Events signal Plugin hook methods interact with -configuration/collection/running of tests
    • -
    -
    -
    -

    Collection Hooks

    - -

    pytest_collect_file -pytest_collect_module -...

    -
    -
    -

    test run hooks

    - -

    pytest_pyfunc_call -pytest_report*

    -
    -
    -

    Writing cross-project plugins

    -
      -
    • put plugin into pytest_name.py or package
    • -
    • make a NamePlugin class available
    • -
    • release or copy to somewhere importable
    • -
    -
    -
    -

    Distributed Testing (45 minutes)

    -img/rails_in_the_city_by_marikaz.jpg -

    http://marikaz.deviantart.com/ CC 3.0 AN-ND

    -
    -
    -

    Distributed Testing

    -
      -
    • distribute test load to multiple CPUs/multicore
    • -
    • simultanously run tests on multiple machines
    • -
    • do's and dont's for cross-process testing
    • -
    -

    XXX insert bits from py/trunk/py/doc/test-dist.txt

    -
    -
    -

    Questions

    -

    Q&A (15 minutes)

    -
    -
    - - Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Wed Mar 25 12:56:49 2009 @@ -27,73 +27,23 @@ my testing background ======================= -- learned python 2001 -- became "test-driven developer" (TDD) - around 2002 when attending a Zope Sprint +- learned Python 2001 +- "test-driven developer" (TDD) since 2002 - founded PyPy, based on TDD principles - developed utest/stdtest, now py.test -- consulted for a few organisations on - (acceptance) testing +- consulted to help with testing -my view on Python and testing +How does Python compare to Java? ================================== -- Python does not have compile-time type security - like Java or C++ -- instead of declaring types, write tests! -- tests are more useful than declaring types -- improving the testing tool means improving - the useability of the language! +Python has no compile-time type security. -Benefits of automated tests -============================== - -- evolve codebase more easily -- higher code quality -- better collaboration -- you can write larger apps! - -Some Test terminology -============================== - -- developer and customer tests -- unit tests -- functional tests -- acceptance tests - -you may discuss a long time -about categorizations ... - -But Python is about pragmatism -================================= - -Some Google developers presented -their view on testing at the Google -Automated Testing Conference (GTAC) 2008 -in Seattle. I liked it: - -let's talk about small, medium or large tests. - -Small Tests: one aspect -============================== - -.. image:: img/small.png - :align: center - :scale: 70 -Medium Tests: two aspects -============================== +Testing to the rescue! +================================== -.. image:: img/medium.png - :align: center - :scale: 70 +automated tests are better than types. -Large Tests: three/more -============================== - -.. image:: img/large.png - :align: center - :scale: 70 The Automated Test question ======================================== @@ -106,8 +56,7 @@ to make sure that -* function units react well to input. -* class instances co-operate nicely. +* units react well to input. * components co-operate nicely * **everything works in target execution contexts** @@ -120,27 +69,70 @@ ======================================== * make sure things work out **in the end** -* report nicely when things don't work out +* be helpful when test scenarios fail + -*in the end* +how does "in the end look like"? ======================================== -- certain servers -- certain Python Interpreters -- certain managed systems (GAE, Azura, "cloud"...) -- web browsers, IE, Mozilla, Safari, Mobile .... +.. image:: img/rails_in_the_city_by_marikaz.jpg + :scale: 90 + :align: center + +http://marikaz.deviantart.com/ CC 3.0 AN-ND -things are +my current answer ======================================== -changing all the time +- operating systems, Python Interpreters +- cloud boxes, web browsers, .... + +generally speaking +============================================ +what is the vision of automated testing? -A vision for automated testing +my current answer ======================================== -... integrate with actual deployment! +merge with real-life deployment. + +Common Test terminology +============================== + +- developer and customer tests +- unit tests +- functional tests +- acceptance tests + +you may discuss a long time +about categorizations ... + +A pragmatic view on test types +================================= + +let's talk about small, medium or large tests. + +Small Tests: one aspect +============================== + +.. image:: img/small.png + :align: center + :scale: 70 + +Medium Tests: two aspects +============================== + +.. image:: img/medium.png + :align: center + :scale: 70 + +Large Tests: three/more +============================== +.. image:: img/large.png + :align: center + :scale: 70 pytest advanced features (30 minutes) ============================================================================ @@ -151,16 +143,10 @@ http://marikaz.deviantart.com/ CC 3.0 AN-ND -organising your tests -========================================== - -- tests belong to the code -- keep test state setup separate - - Python test function viewed abstractly ========================================== :: + from app.pkg import SomeClass def test_something(): inst1 = SomeClass("somevalue") @@ -171,98 +157,64 @@ ========================================== * test configuration mixes with code instantiation -* importing 'app' may fail because it misses dependencies +* importing 'app' may fail * the "app.pkg.SomeClass" reference may change setting up state close to code ========================================== :: + from app.pkg import SomeClass class TestGroup: def setup_method(self, method): - self.teststate = SomeClass() + self.inst1 = SomeClass("somevalue") def test_something(self): - ... use self.teststate ... + ... use self.inst1 ... assert "things are ok" -observations +observations ========================================== * test configuration mixes with code instantiation -* importing 'app' may fail because it misses dependencies +* importing 'app' may fail * the "app.pkg.SomeClass" reference may change -* functions tend to get grouped by setup - - -the test state setup question -========================================== - -how to setup and communicate test state? - - -my current answers -=========================== - -* test tool is to control test state setup -* separate test state setup from test code +* functions tend to get grouped by setup methods +* multiple methods can reuse the same setup meet py.test "funcargs" ========================================== -goal: cleanly separate test configuration -and boilerplate application setup from -the actual code in the test function. +goals: + +* fully separate test setup from test code +* setup app bootstraps in one place +* allow to group tests in classes or files logically basic funcarg mechanism ========================================== :: - def pytest_configure(config): - config.register_funcarg("myarg", myargmaker) + def pytest_configure(config): + config.register_funcargmaker( + "myarg", myargmaker) - def myargmaker(pyfuncitem): - return pyfuncitem.name + def myargmaker(pyfuncitem): + return pyfuncitem.name - def test_somefunction(myarg): - assert myarg == 'test_somefunction' + def test_somefunction(myarg): + assert myarg == 'test_somefunction' observations ===================== -* funcarg makers are registered after command line parsing -* funcarg makers have meta-access to "collected function item" -* test code is free of boilerplate config or setup references +funcargs: -class MyTestSetup instanciation -========================================== - - def make(self, pyfuncitem): - -how could a test function look like? -==================================== - -def test_something(self, mysetup): - app1 = mysetup.make_some_app_instance() - ... - assert result is ok - -Observations -================ - -* test configuration clearly separated from test code -* 'MySetup' can control dependencies, raise "skips" -* internal "app.pkg.SomeClass" references may change - without affecting the test code - -Using funcargs -======================== - -* freedom to setup your test state -* freedom to *logically* group your tests in test classes -* change test setup in once place if app mechanics change +* test functions receive value by using a name +* makers are registered after command line parsing +* makers have meta-access to "collected function item" Hands on: Installing py.test ======================================== @@ -280,7 +232,7 @@ * add mypkg/__init__ and mypkg/test_ospath.py * add a test_path function that needs a "url" argument * register the argument maker for it -* run your tests +* run your test Using Plugins and Extensions @@ -306,7 +258,6 @@ - write local or global plugins - provide funcargs - Conftest.py =============== @@ -315,8 +266,6 @@ - can specify plugins to use - can provide default values for command line options - - Specifying plugins ======================== @@ -348,9 +297,9 @@ - integrate collection/run of traditional unit-tests - run functions in their own tempdir - testing ReST documents -- running Prolog tests (XXX conftest based) -- running Javascript tests (conftest based) -- html reporting for nightly runs (conftest-based) +- running Prolog tests (old: conftest based) +- running Javascript tests (old: conftest based) +- html reporting for nightly runs (old: conftest-based) Break ===== @@ -365,38 +314,35 @@ Writing Plugins (30 minutes) ============================================================================ +- test collection objects - plugin hooks and events - event system for custom reporting - test collection hooks - test running hooks -- writing cross-project plugins +py.test collection objects +============================= - py.test --collectonly - py.test --traceconfig +* collectors, ``collect()`` returns list of "colitems" +* items, implement ``runtest()`` for running a test -py.test collection of tests -============================= +test collection tree +======================== -- items: runnable tests - ``item.runtest()`` -- collectors: return collectors or items - ``collector.collect()`` - collection tree is built iteratively - test collection starts from directories or files (via cmdline) -- test collection not limited to Python files! -- use ``py.test --collectonly`` for debugging -Important Collection Objects +py.test.collect.* objects ================================== -- **Directory** collect files in directory -- **FSCollector** a Collector for a File -- **Module** collect python Classes and Functions -- **Class**/**Instance** collect test methods -- **Generator** collect generative tests -- **Function** sets up and executes a python test function -- **DoctestFile** collect doctests in a .txt file +- **Directory** +- **File** +- **Module** +- **Class**/**Instance** +- **Generator** +- **Function** -Interactive demo "collection tree" +demo "collection tree" ==================================== you can always inspect the test collection tree @@ -404,36 +350,83 @@ py.test --collectonly - Hooks and Events =================== - Plugin hook methods interact with configuration/collection/running of tests -- Events signal Plugin hook methods interact with - configuration/collection/running of tests +- Events are called for notification purposes + +Configuration Hooks +====================== +:: + + def pytest_addoption(self, parser): + """ called before commandline parsing. """ + + def pytest_configure(self, config): + """ called after command line options have been parsed. " + + def pytest_unconfigure(self, config): + """ called before test process is exited. """ Collection Hooks -================= +====================== +:: + def pytest_collect_file(self, path, parent): + """ return Collection node or None. """ + + def pytest_collect_directory(self, path, parent): + """ return Collection node or None. """ -pytest_collect_file -pytest_collect_module -... + def pytest_collect_recurse(self, path, parent): + """ return True/False to cause/prevent recursion. """ -test run hooks -================= +Python collection hooks +===================================== +:: + def pytest_pymodule_makeitem(self, modcol, name, obj): + """ return custom item/collector for a python object in a module, or None. """ + +function test run hooks +========================== +:: + def pytest_pyfunc_call(self, pyfuncitem, args, kwargs): + """ return True if we consumed/did the call to the python function item. """ + + def pytest_item_makereport(self, item, excinfo, when, outerr): + """ return ItemTestReport event for the given test outcome. """ + +used by: pytest_xfail.py + +Reporting hooks +========================== +:: + def pytest_report_teststatus(self, event): + """ return shortletter and verbose word. """ + + def pytest_terminal_summary(self, terminalreporter): + """ add additional section in terminal summary reporting. """ + +Event methods +=============== -.. XXX +are only called, no interaction. -pytest_pyfunc_call -pytest_report* +see ``py/test/plugin/pytest_terminal.py`` +for a full selection. + +Warning +=============== + +naming changes might take place before 1.0 final Writing cross-project plugins ================================== - put plugin into pytest_name.py or package -- make a NamePlugin class available +- make a "NamePlugin" class available - release or copy to somewhere importable Exercise @@ -453,28 +446,128 @@ http://marikaz.deviantart.com/ CC 3.0 AN-ND -Defining execution environments + +the basic idea +==================================== + +collect tests locally, +run tests in separated test execution processes. + +test distribution modes +==================================== + +--dist=load # load-balance tests to exec environments +--dist=each # send each test to each exec environment + +send test to one other python interpreter +================================================== + +:: + py.test --dist=each --tx popen//python=python2.4 + +send test to three different interpreters +================================================== + +:: + py.test --dist=each \ + --tx popen//python=python2.4 \ + --tx=popen//python=python2.5 \ + --tx=popen//python=python2.6 + +Example: Speed up test runs +==================================== + +To send tests to multiple CPUs, type:: + + py.test --dist=load --tx popen --tx popen --tx popen + +or in short:: + + py.test -n 3 + +Especially for longer running tests or tests requiring +a lot of IO this can lead to considerable speed ups! + +send tests to remote SSH accout +======================================== + +:: + py.test --d --tx ssh=myhostpopen --rsyncdir mypkg mypkg + +Sending tests to remote socket servers +======================================== + +Download and start + +http://codespeak.net/svn/py/dist/py/execnet/script/socketserver.py + +Assuming an IP address, you can now tell py.test to distribute: + + py.test -d --tx socket=192.168.1.102:8888 --rsyncdir mypkg mypkg + +Exercise: Move settings into conftest +======================================== + +mypkg/conftest.py:: + rsyncdirs = ['.'] + pytest_option_tx = ['ssh=myhost', 'socket=...'] + +mypkg/conftest.py:: + rsyncdirs = ['.'] + +Now the commands +======================================== + +:: + py.test --dist=each mypkg + + py.test --dist=load mypkg + +how does py.test do all this? +======================================== + +it uses py.execnet! + +``py.execnet`` +======================================== + +* instantiates local or remote Python Processes +* send code for execution in one or many processes +* asynchronously send and receive data between processes through channels +* completely avoid manual installation steps on remote places + +xspecs: Exec environment specs ================================= +general form:: -- distribute test load to multiple CPUs/multicore -- simultanously run tests on multiple machines -- do's and dont's for cross-process testing + key1=value1//key2=value2//key3=value3 -XXX insert bits from py/trunk/py/doc/test-dist.txt +xspec key meanings +================================= -Questions -========= +* ``popen`` for a PopenGateway +* ``ssh=host`` for a SshGateway +* ``socket=address:port`` for a SocketGateway +* ``python=executable`` for specifying Python executables +* ``chdir=path`` change remote working dir to given relative or absolute path +* ``nice=value`` decrease remote nice level if platforms supports it -Q&A (15 minutes) - +xspec examples +================================= -Other Python testing tools -========================= +:: + ssh=wyvern//python=python2.4//chdir=mycache + popen//python=2.5//nice=20 + socket=192.168.1.4:8888 -- unittest.py -- nosetests -- windmill -- "pytest" from logilab -- project specific: zope-test runner, trial, ... -- ... +Feedback round +================== + +How did you like the tutorial? + +Thanks for taking part! + +holger krekel at merlinux eu + + From briandorsey at codespeak.net Wed Mar 25 15:46:22 2009 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Wed, 25 Mar 2009 15:46:22 +0100 (CET) Subject: [py-svn] r63325 - py/extradoc/talk/pycon-us-2009/pytest-advanced Message-ID: <20090325144622.994CA168505@codespeak.net> Author: briandorsey Date: Wed Mar 25 15:46:20 2009 New Revision: 63325 Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Log: small typo fixes Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Wed Mar 25 15:46:20 2009 @@ -48,7 +48,7 @@ The Automated Test question ======================================== -what are automated there for? +what are automated tests there for? my current answer @@ -470,7 +470,7 @@ :: py.test --dist=each \ - --tx popen//python=python2.4 \ + --tx=popen//python=python2.4 \ --tx=popen//python=python2.5 \ --tx=popen//python=python2.6 From briandorsey at codespeak.net Wed Mar 25 23:54:01 2009 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Wed, 25 Mar 2009 23:54:01 +0100 (CET) Subject: [py-svn] r63327 - in py/extradoc/talk/pycon-us-2009/pytest-introduction: . helpers Message-ID: <20090325225401.609521684B2@codespeak.net> Author: briandorsey Date: Wed Mar 25 23:53:59 2009 New Revision: 63327 Removed: py/extradoc/talk/pycon-us-2009/pytest-introduction/TODO.txt Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/helpers/extract_examples.py py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt Log: various small changes Deleted: /py/extradoc/talk/pycon-us-2009/pytest-introduction/TODO.txt ============================================================================== --- /py/extradoc/talk/pycon-us-2009/pytest-introduction/TODO.txt Wed Mar 25 23:53:59 2009 +++ (empty file) @@ -1,15 +0,0 @@ -- finish outline -- reference /py/dist for install -- determine which demos & exercises to write -- placeholder: write demos -- run py lib tests on windows -- experiment with funcargs - -- read: http://us.pycon.org/2009/conference/helpforspeakers/ -================= -2009/03/10 - talk with hogler about funcargs -2009/03/03 - choose presentation tool -2009/03/03 - figure out how s5 and bruce deal with different resolutions -2009/03/03 - experiment with bruce -2009/03/03 - setup template for rst2s5 -2009/03/03 - install rst2s5 Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/helpers/extract_examples.py ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-introduction/helpers/extract_examples.py (original) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/helpers/extract_examples.py Wed Mar 25 23:53:59 2009 @@ -3,7 +3,7 @@ source = 'pytest-introduction.html' target = 'test_examples_auto.py' -target_file = open('test_examples_auto.py', 'w') +target_file = open('test_examples.py', 'w') html = open(source).read() soup = BeautifulSoup(html) Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html (original) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html Wed Mar 25 23:53:59 2009 @@ -320,17 +320,16 @@

    py.test - introduction

    -

    Brian Dorsey <brian@dorseys.org>

    -

    Holger Krekel <holger@merlinux.eu>

    -

    http://pytest.org

    -

    TODO: Add URL to help people to prepare for the tutorial (install/config/etc)

    -

    TODO: Here is a bunch of stuff to do to setup your environment.

    -
    -
    -

    presenters

    -

    Brian Dorsey <brian@dorseys.org>

    -

    Holger Krekel <holger@merlinux.eu>

    -

    TODO: fill in

    +

    If you have time before the tutorial starts, please install the latest pylib:

    + +

    Get a copy of this presentation & exercises

    +

    the plan

    @@ -342,10 +341,15 @@
  • break
  • options (w/exercise)
  • branching out (w/exercise)
  • -
  • using doctests (w/exercise)
  • +
  • using plugins and funcargs
  • wrap up & questions
  • +
    +

    presenters

    +

    Brian Dorsey <brian@dorseys.org> - enthusiastic user of py.test.

    +

    Holger Krekel <holger@merlinux.eu> - lead developer of py.test.

    +

    motivation

    why automated testing? (about 10 minutes)

    @@ -359,13 +363,14 @@

    (steal some from Titus: http://ivory.idyll.org/articles/nose-intro.html)

    -

    why test-driven development?

    +

    why test driven development?

    • make sure code does the right thing
    • make sure changes don't break expected behaviour
    • incremental work style
    • faster develpment
    • easier maintenance
    • +
    • better than static types :)
    @@ -431,8 +436,6 @@

    simplicity

    @@ -454,7 +460,6 @@

    and many more

    Many extra benefits included if you need them, ignore if not.

      -
    • doctests
    • setup / teardown of test runtime environment
    • testing on multiple platforms & python versions
    • distributed testing - across both processes and machines
    • @@ -479,7 +484,7 @@

    installation

    -

    Installation, basic test functions. (30 minutes)

    +

    Installation, hello world. (30 minutes)

    • installation
    • test functions
    • @@ -491,13 +496,16 @@

      exercise 1 (~20 min)

      +

      Get a copy of this presentation & exercises

      + -

      TODO: make sure we ask them to install the right versions of python and pylib -TODO: put a copy of this presentation somewhere online and on USB

      basic usage

      @@ -532,14 +540,9 @@ @py.test.mark(xfail="Expected failure.") def test_three(): assert 1 == 2 -@py.test.mark(xfail="Expected failure.") -def test_four(): - print "debug text" - assert False def test_bonus(): py.test.raises(ValueError, int, 'foo') -

      TODO: write handout/exercise

      exercise 3

      @@ -562,8 +565,6 @@ def test_two(self): assert 1 == 1 -

      TODO: write handout/exercise -TODO: use some built-in funcarg for the example

      break

      @@ -577,91 +578,93 @@ options: -h, --help show this help message and exit -misc: - --resultlog=RESULTLOG - path for machine-readable result log - --collectonly only collect tests, don't execute them. -... - -
      -general:
      +test collection and failure interaction options:
           -v, --verbose       increase verbosity.
           -x, --exitfirst     exit instantly on first error or failed test.
      -    -s, --nocapture     disable catching of sys.stdout/stderr output.
           -k KEYWORD          only run test items matching the given space separated
                               keywords.  precede a keyword with '-' to negate.
                               Terminate the expression with ':' to treat a match as
                               a signal to run all subsequent tests.
           -l, --showlocals    show locals in tracebacks (disabled by default).
      -    --showskipsummary   always show summary of skipped testse work time - exercise
           --pdb               start pdb (the Python debugger) on errors.
      -    --tb=TBSTYLE        traceback verboseness (long/short/no).
      -    --fulltrace         don't cut any tracebacks (default is to cut).
      -    --nomagic           refrain from using magic as much as possible.
      -    --traceconfig       trace considerations of conftest.py files.
      -    -f, --looponfailing
      -                        loop on failing test set.
      -    --exec=EXECUTABLE   python executable to run the tests with.
      -    -n NUMPROCESSES, --numprocesses=NUMPROCESSES
      -                        number of local test processes.
      -    --debug             turn on debugging information.
      -
      -experimental:
      -    -d, --dist          ad-hoc distribute tests across machines (requires
      -                        conftest settings)
      -    -w, --startserver   starts local web server for displaying test progress.
      -    -r, --runbrowser    run browser (implies --startserver).
      +    --tb=style          traceback verboseness (long/short/no).
      +    -s                  disable catching of stdout/stderr during test run.
      +    ...
      +
      +
      +test collection and failure interaction options:
           --boxed             box each test run in a separate process
      -    --rest              restructured text output reporting.
      +    -p PLUGIN           load the specified plugin after command line parsing.
      +                        Example: '-p hello' will trigger 'import pytest_hello'
      +                        and instantiate 'HelloPlugin' from the module.
      +    -f, --looponfail    run tests, re-run failing test set until all pass.
      +
      +test process debugging:
      +    --collectonly       only collect tests, don't execute them.
      +    --traceconfig       trace considerations of conftest.py files.
      +    --nomagic           don't reinterpret asserts, no traceback cutting.
      +    --fulltrace         don't cut any tracebacks (default is to cut).
      +    --basetemp=dir      base temporary directory for this test run.
      +    --iocapture=method  set iocapturing method: fd|sys|no.
      +    --debug             generate and show debugging information.
      +
      +distributed testing:
      +    --dist=distmode     set mode for distributing tests to exec environments.
      +                        each: send each test to each available environment.
      +                        load: send each test to available environment.
      +                        (default) no: run tests inprocess, don't distribute.
      +    --tx=xspec          add a test execution environment. some examples: --tx
      +                        popen//python=python2.5 --tx socket=192.168.1.102:8888
      +                        --tx ssh=user@codespeak.net//chdir=testcache
      +    -d                  load-balance tests.  shortcut for '--dist=load'
      +    -n numprocesses     shortcut for '--dist=load --tx=NUM*popen'
      +    --rsyncdir=dir1     add directory for rsyncing to remote tx nodes.
       

      selected options

      Options (25 minutes)

        -
      • --nocapture - disable catching of sys.stdout/stderr output.
      • -
      • --exitfirst (*) - exit instantly on first error or failed test.
      • +
      • -s - disable catching of stdout/stderr during test run.
      • +
      • -x/--exitfirst (*) - exit instantly on first error or failed test.
      • --showlocals (*) - show locals in tracebacks.
      • --pdb (*) - start pdb (the Python debugger) on errors.
      • -
      • --looponfailing (*) - loop on failing test set.
      • +
      • -f/--looponfail (*) - loop on failing test set.
      • -k (*) - only run test items matching the given keyword.
      • -
      • --exec (*) - python executable to run the tests with.
      • --tb/fulltrace - different options to control traceback generation.

      (*) more detail in advanced tutorial

      -
      -

      demo: --nocapture

      -

      TODO: write demo script

      +
      +

      demo: -s / --iocapture=no

      +

      test_trace_setup.py

      demo: --exitfirst

      -

      TODO: write demo script

      +

      test_examples.py

      -
      -

      demo: --showlocals

      -

      TODO: write demo script

      +
      +

      demo: --looponfail

      +

      test_examples.py

      demo: -k

      -

      TODO: write demo script

      +

      test_examples.py

      exercise 4 (~10 min)

        -
      • --nocapture - disable catching of sys.stdout/stderr output.
      • -
      • --exitfirst (*) - exit instantly on first error or failed test.
      • +
      • -s - disable catching of stdout/stderr during test run.
      • +
      • -x/--exitfirst (*) - exit instantly on first error or failed test.
      • --showlocals (*) - show locals in tracebacks.
      • --pdb (*) - start pdb (the Python debugger) on errors.
      • -
      • --looponfailing (*) - loop on failing test set.
      • +
      • -f/--looponfail (*) - loop on failing test set.
      • -k (*) - only run test items matching the given keyword.
      • -
      • --exec (*) - python executable to run the tests with.
      • --tb/fulltrace - different options to control traceback generation.

      Experiment with a few of these options, ask questions and play.

      If you're in the advanced class this afternoon. Think about other options you'd like. Maybe we can make a plugin.

      -

      TODO: check with holger - time to make a plugin in the advanced class?

      branching out

      @@ -678,8 +681,8 @@ import py def test_something_we_want_to_skip(): - py.test.skip("need to decide what it should do.") - assert something == True + py.test.skip("fix delayed.") + assert True
      @@ -723,16 +726,10 @@
    • generative tests
    -
    -

    using doctests

    -

    Using doctests (25 minutes)

    -
      -
    • what are doctests
    • -
    • two usage scenarios - docstrings & stand alone files
    • -
    • how they work demo and examples.
    • -
    • 10 minute work time - exercises
    • -
    -

    TODO: split

    +
    +

    using plugins and funcargs

    +

    Overview of using plugins and funcargs (25 minutes)

    +

    TODO: content from holger

    wrap up & questions

    @@ -743,8 +740,13 @@
  • irc.freenode.org #pylib
  • Quesitons?

    -

    TODO: split

    -

    TODO:

    +
    +
    +

    x

    +

    TODO: +- add simple demo early on - what it looks like to run py.test from the command line. +- give overview of what's going on when you run py.test - collection, running, etc +- add py.test.mark to -k slide - if it works

    Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt Wed Mar 25 23:53:59 2009 @@ -2,27 +2,15 @@ py.test - introduction ====================== -Brian Dorsey +If you have time before the tutorial starts, please install the latest pylib: -Holger Krekel +- svn checkout http://codespeak.net/svn/py/dist +- ... or download tar from: http://codespeak.net/py/dist/download.html +- run "python setup.py" with "install" or "develop" -http://pytest.org +Get a copy of this presentation & exercises -TODO: Add URL to help people to prepare for the tutorial (install/config/etc) - -.. class:: handout - - TODO: Here is a bunch of stuff to do to setup your environment. - - -presenters -========== - -Brian Dorsey - -Holger Krekel - -TODO: fill in +- http://codespeak.net/svn/py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.zip the plan @@ -35,10 +23,18 @@ - `break`_ - `options`_ (w/exercise) - `branching out`_ (w/exercise) -- `using doctests`_ (w/exercise) +- `using plugins and funcargs`_ - `wrap up & questions`_ +presenters +========== + +Brian Dorsey - enthusiastic user of py.test. + +Holger Krekel - lead developer of py.test. + + motivation ========== @@ -54,24 +50,29 @@ (steal some from Titus: http://ivory.idyll.org/articles/nose-intro.html) -why test-driven development? -============================ +why do automated testing? +========================= - make sure code does the right thing - make sure changes don't break expected behaviour - incremental work style - faster develpment - easier maintenance +- better than static types :) -TDD spectrum -============ +Test driven development +======================= + +- Write each test before the code. + + +Pragmatic +========= *"I don't do test-driven development; I do stupidity-driven testing. When I do something stupid, I write a test to make sure I don't do it again."* - Titus Brown -- Full Test Driven Development -- Stupidity Driven Testing -- Happy path testing +- I do Happy Path testing - kind of minimal TDD + SDD. what you get with py.test @@ -145,9 +146,6 @@ print() debugging ================= -stdout is captured for each test, and only printed if the test fails. You can -leave your debugging print statments in place to help your future self. - :: import py @@ -161,6 +159,12 @@ print "Useful debugging information." assert False +.. class:: handout + + stdout is captured for each test, and only printed if the test fails. You + can leave your debugging print statments in place to help your future + self. + simplicity ========== @@ -173,7 +177,6 @@ Many extra benefits included if you need them, ignore if not. -- doctests - setup / teardown of test runtime environment - testing on multiple platforms & python versions - distributed testing - across both processes and machines @@ -204,7 +207,7 @@ installation ============ -*Installation, basic test functions. (30 minutes)* +*Installation, hello world. (30 minutes)* - installation - test functions @@ -218,14 +221,16 @@ exercise 1 (~20 min) ===================== -- Install from: http://codespeak.net/py/dist/download.html -- Get a copy of this presentation & exercises +- svn checkout http://codespeak.net/svn/py/dist +- ... or download tar from: http://codespeak.net/py/dist/download.html +- run "python setup.py" with "install" or "develop" + +Get a copy of this presentation & exercises + +- http://codespeak.net/svn/py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.zip - run: py.test test_hello.py - find someone else to help successfully run test_hello.py -TODO: make sure we ask them to install the right versions of python and pylib -TODO: put a copy of this presentation somewhere online and on USB - basic usage =========== @@ -262,17 +267,10 @@ @py.test.mark(xfail="Expected failure.") def test_three(): assert 1 == 2 - @py.test.mark(xfail="Expected failure.") - def test_four(): - print "debug text" - assert False def test_bonus(): py.test.raises(ValueError, int, 'foo') -TODO: write handout/exercise - - exercise 3 ========== @@ -296,9 +294,6 @@ def test_two(self): assert 1 == 1 -TODO: write handout/exercise -TODO: use some built-in funcarg for the example - break ===== @@ -315,46 +310,51 @@ options: -h, --help show this help message and exit - misc: - --resultlog=RESULTLOG - path for machine-readable result log - --collectonly only collect tests, don't execute them. - ... - - -.. class:: handout - -:: - - general: + test collection and failure interaction options: -v, --verbose increase verbosity. -x, --exitfirst exit instantly on first error or failed test. - -s, --nocapture disable catching of sys.stdout/stderr output. -k KEYWORD only run test items matching the given space separated keywords. precede a keyword with '-' to negate. Terminate the expression with ':' to treat a match as a signal to run all subsequent tests. -l, --showlocals show locals in tracebacks (disabled by default). - --showskipsummary always show summary of skipped testse work time - exercise --pdb start pdb (the Python debugger) on errors. - --tb=TBSTYLE traceback verboseness (long/short/no). - --fulltrace don't cut any tracebacks (default is to cut). - --nomagic refrain from using magic as much as possible. - --traceconfig trace considerations of conftest.py files. - -f, --looponfailing - loop on failing test set. - --exec=EXECUTABLE python executable to run the tests with. - -n NUMPROCESSES, --numprocesses=NUMPROCESSES - number of local test processes. - --debug turn on debugging information. - - experimental: - -d, --dist ad-hoc distribute tests across machines (requires - conftest settings) - -w, --startserver starts local web server for displaying test progress. - -r, --runbrowser run browser (implies --startserver). + --tb=style traceback verboseness (long/short/no). + -s disable catching of stdout/stderr during test run. + ... + + +.. class:: handout + +:: + + test collection and failure interaction options: --boxed box each test run in a separate process - --rest restructured text output reporting. + -p PLUGIN load the specified plugin after command line parsing. + Example: '-p hello' will trigger 'import pytest_hello' + and instantiate 'HelloPlugin' from the module. + -f, --looponfail run tests, re-run failing test set until all pass. + + test process debugging: + --collectonly only collect tests, don't execute them. + --traceconfig trace considerations of conftest.py files. + --nomagic don't reinterpret asserts, no traceback cutting. + --fulltrace don't cut any tracebacks (default is to cut). + --basetemp=dir base temporary directory for this test run. + --iocapture=method set iocapturing method: fd|sys|no. + --debug generate and show debugging information. + + distributed testing: + --dist=distmode set mode for distributing tests to exec environments. + each: send each test to each available environment. + load: send each test to available environment. + (default) no: run tests inprocess, don't distribute. + --tx=xspec add a test execution environment. some examples: --tx + popen//python=python2.5 --tx socket=192.168.1.102:8888 + --tx ssh=user at codespeak.net//chdir=testcache + -d load-balance tests. shortcut for '--dist=load' + -n numprocesses shortcut for '--dist=load --tx=NUM*popen' + --rsyncdir=dir1 add directory for rsyncing to remote tx nodes. selected options @@ -362,51 +362,48 @@ *Options (25 minutes)* -- --nocapture - disable catching of sys.stdout/stderr output. -- --exitfirst (*) - exit instantly on first error or failed test. +- -s - disable catching of stdout/stderr during test run. +- -x/--exitfirst (*) - exit instantly on first error or failed test. - --showlocals (*) - show locals in tracebacks. - --pdb (*) - start pdb (the Python debugger) on errors. -- --looponfailing (*) - loop on failing test set. +- -f/--looponfail (*) - loop on failing test set. - -k (*) - only run test items matching the given keyword. -- --exec (*) - python executable to run the tests with. - --tb/fulltrace - different options to control traceback generation. (*) more detail in advanced tutorial -demo: --nocapture -================= - -TODO: write demo script +demo: -s / --iocapture=no +========================= +test_trace_setup.py demo: --exitfirst ================= -TODO: write demo script +test_examples.py -demo: --showlocals +demo: --looponfail ================== -TODO: write demo script +test_examples.py demo: -k ======== -TODO: write demo script +test_examples.py exercise 4 (~10 min) ===================== -- --nocapture - disable catching of sys.stdout/stderr output. -- --exitfirst (*) - exit instantly on first error or failed test. +- -s - disable catching of stdout/stderr during test run. +- -x/--exitfirst (*) - exit instantly on first error or failed test. - --showlocals (*) - show locals in tracebacks. - --pdb (*) - start pdb (the Python debugger) on errors. -- --looponfailing (*) - loop on failing test set. +- -f/--looponfail (*) - loop on failing test set. - -k (*) - only run test items matching the given keyword. -- --exec (*) - python executable to run the tests with. - --tb/fulltrace - different options to control traceback generation. .. class:: handout @@ -416,8 +413,6 @@ If you're in the advanced class this afternoon. Think about other options you'd like. Maybe we can make a plugin. - TODO: check with holger - time to make a plugin in the advanced class? - branching out ============= @@ -437,8 +432,8 @@ import py def test_something_we_want_to_skip(): - py.test.skip("need to decide what it should do.") - assert something == True + py.test.skip("fix delayed.") + assert True generative tests 1 @@ -488,8 +483,10 @@ - generative tests -using funcargs -============== +using plugins and funcargs +========================== + +*Overview of using plugins and funcargs (25 minutes)* TODO: content from holger @@ -515,3 +512,7 @@ = TODO: +- add simple demo early on - what it looks like to run py.test from the command line. +- give overview of what's going on when you run py.test - collection, running, etc +- add py.test.mark to -k slide - if it works +- can we get --nocapture back? From hpk at codespeak.net Thu Mar 26 01:48:28 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 26 Mar 2009 01:48:28 +0100 (CET) Subject: [py-svn] r63329 - in py/extradoc/talk/pycon-us-2009/pytest-advanced: . example Message-ID: <20090326004828.69B3E168453@codespeak.net> Author: hpk Date: Thu Mar 26 01:48:26 2009 New Revision: 63329 Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/example/test_funcarg.py py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Log: intermediate Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/example/test_funcarg.py ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-advanced/example/test_funcarg.py (original) +++ py/extradoc/talk/pycon-us-2009/pytest-advanced/example/test_funcarg.py Thu Mar 26 01:48:26 2009 @@ -1,9 +1,21 @@ +import py + +def pytest_addoption(parser): + parser.addoption("--hello", dest="hello", action="store", default=None) def pytest_configure(config): - config.register_funcargmaker("myarg", myargmaker) + config.register_funcargmaker("myarg", MyArg(config.option.hello).make) + +def pytest_unconfigure(config): + config.register_funcargmaker("myarg", MyArg(config.option.hello).make) -def myargmaker(pyfuncitem): - return pyfuncitem.name +class MyArg: + def __init__(self, hello): + self.hello = hello + def make(self, pyfuncitem): + if pyfuncitem.config.option.hello == "skip": + py.test.skip("could not configure ...") + return 42 -def test_somefunction(myarg2): - assert myarg == 'test_somefunction' +def test_somefunction(myarg): + assert myarg.hello == "some" Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Thu Mar 26 01:48:26 2009 @@ -195,24 +195,35 @@ basic funcarg mechanism ========================================== -:: +- provide a "funcarg" maker +- and call it for each test function that uses it - def pytest_configure(config): - config.register_funcargmaker( - "myarg", myargmaker) - def myargmaker(pyfuncitem): - return pyfuncitem.name +Example test function +========================================== +:: def test_somefunction(myarg): assert myarg == 'test_somefunction' + +Example conftest.py +========================================== +:: + + class ConftestPlugin: + def pytest_configure(self, config): + config.register_funcargmaker("myarg", myargmaker) + + def myargmaker(self, pyfuncitem): + return pyfuncitem.name + observations ===================== funcargs: -* test functions receive value by using a name +* test functions receive value by specifying a name * makers are registered after command line parsing * makers have meta-access to "collected function item" From briandorsey at codespeak.net Thu Mar 26 04:33:45 2009 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Thu, 26 Mar 2009 04:33:45 +0100 (CET) Subject: [py-svn] r63332 - py/extradoc/talk/pycon-us-2009/pytest-introduction Message-ID: <20090326033345.41A141684F3@codespeak.net> Author: briandorsey Date: Thu Mar 26 04:33:42 2009 New Revision: 63332 Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt Log: various fixes and changes after walkthrough with Holger. Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html (original) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html Thu Mar 26 04:33:42 2009 @@ -341,7 +341,7 @@
  • break
  • options (w/exercise)
  • branching out (w/exercise)
  • -
  • using plugins and funcargs
  • +
  • using plugins
  • wrap up & questions
  • @@ -354,39 +354,35 @@

    motivation

    why automated testing? (about 10 minutes)

      -
    • what is unit testing and how does it compare to other types of testing
    • focusing mostly on unit testing (small tests) in this tutorial
    • -
    • why do automated testing? benefits, etc
    • -
    • existing python testing tools
    -

    TODO: split

    -

    (steal some from Titus: http://ivory.idyll.org/articles/nose-intro.html)

    -
    -

    why test driven development?

    +
    +

    why do automated testing?

      -
    • make sure code does the right thing
    • -
    • make sure changes don't break expected behaviour
    • -
    • incremental work style
    • -
    • faster develpment
    • -
    • easier maintenance
    • -
    • better than static types :)
    • +
    • make sure the code works as advertised
    • +
    • make sure changes don't break existing behaviour
    • +
    • make sure bugs don't come back silently
    • +
    • leads to faster development and easier maintenance
    -
    -

    TDD spectrum

    +
    +

    Test driven development

    +
      +
    • There is some religion around Test Driven Development.
    • +
    +
    +
    +

    Pragmatism

    "I don't do test-driven development; I do stupidity-driven testing. When I do something stupid, I write a test to make sure I don't do it again." - Titus Brown

      -
    • Full Test Driven Development
    • -
    • Stupidity Driven Testing
    • -
    • Happy path testing
    • +
    • I do Happy Path testing - kind of minimal TDD + SDD.

    what you get with py.test

    The fundamental features of py.test (about 10 minutes)

      -
    • a good tool for test-driven development!
    • no boilerplate: write tests rapidly
    • run all or subsets of tests
    • lots of useful information when a test fails
    • @@ -452,6 +448,13 @@ can leave your debugging print statments in place to help your future self.

    +
    +

    less boilerplate

    +

    py.test uses naming conventions to reduce boilerplate registration code.

    +
      +
    • TestSuite discovery story
    • +
    +

    simplicity

    All of these features simplfy writing your tests.

    @@ -726,10 +729,15 @@
  • generative tests
  • -
    -

    using plugins and funcargs

    -

    Overview of using plugins and funcargs (25 minutes)

    -

    TODO: content from holger

    +
    +

    using plugins

    +

    Overview of using plugins (25 minutes)

    +
      +
    • unittest
    • +
    • doctests
    • +
    • pocoo
    • +
    +

    TODO: split

    wrap up & questions

    @@ -746,7 +754,8 @@

    TODO: - add simple demo early on - what it looks like to run py.test from the command line. - give overview of what's going on when you run py.test - collection, running, etc -- add py.test.mark to -k slide - if it works

    +- add py.test.mark to -k slide - if it works +- can we get --nocapture back?

    Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt Thu Mar 26 04:33:42 2009 @@ -23,7 +23,7 @@ - `break`_ - `options`_ (w/exercise) - `branching out`_ (w/exercise) -- `using plugins and funcargs`_ +- `using plugins`_ - `wrap up & questions`_ @@ -35,40 +35,44 @@ Holger Krekel - lead developer of py.test. +You +=== + +- Who are you? + +.. class:: handout + + Python background + Testing background + python testing libraries: unitest, nose, py.test... + + motivation ========== *why automated testing? (about 10 minutes)* -- what is unit testing and how does it compare to other types of testing - focusing mostly on unit testing (small tests) in this tutorial -- why do automated testing? benefits, etc -- existing python testing tools - -TODO: split - -(steal some from Titus: http://ivory.idyll.org/articles/nose-intro.html) +TODO - add small medium large slides why do automated testing? ========================= -- make sure code does the right thing -- make sure changes don't break expected behaviour -- incremental work style -- faster develpment -- easier maintenance -- better than static types :) +- make sure the code works as advertised +- make sure changes don't break existing behaviour +- make sure bugs don't come back silently +- leads to faster development and easier maintenance Test driven development ======================= -- Write each test before the code. +- There is some religion around Test Driven Development. -Pragmatic -========= +Pragmatism +========== *"I don't do test-driven development; I do stupidity-driven testing. When I do something stupid, I write a test to make sure I don't do it again."* - Titus Brown @@ -78,34 +82,36 @@ what you get with py.test ========================= +TODO - title wording *The fundamental features of py.test (about 10 minutes)* -- a good tool for test-driven development! - no boilerplate: write tests rapidly - run all or subsets of tests - lots of useful information when a test fails -function or class -================= +how it starts +============= -:: +$ py.test - def test_something(): - assert True +(DEMO) - class TestSomething(): - def test_something(self): - assert True +automatic test discovery +======================== + +search all test_* and *_test files + .. class:: handout - Use individual test functions or group tests in classes. Use whichever - makes the most sense for each test or group of tests. + py.test searches for all modules which start with `test_` or end with + `_test`. Within those modules, it collects all functions and methods which + start with `test_`. -automatic test discovery -======================== +function or class +================= :: @@ -116,13 +122,10 @@ def test_something(self): assert True - # search all test_* and *_test files - .. class:: handout - py.test searches for all modules which start with `test_` or end with - `_test`. Within those modules, it collects all functions and methods which - start with `test_`. + Use individual test functions or group tests in classes. Use whichever + makes the most sense for each test or group of tests. assert introspection @@ -154,11 +157,13 @@ print "Useful debugging information." assert True - @py.test.mark(xfail="Expected failure.") + @py.test.mark.xfail("Expected failure.") def test_something2(): print "Useful debugging information." assert False +TODO: fix all xfail s + .. class:: handout stdout is captured for each test, and only printed if the test fails. You @@ -166,10 +171,18 @@ self. +less boilerplate +================ + +py.test uses naming conventions to reduce boilerplate registration code. + +- TestSuite discovery story + + simplicity ========== -All of these features simplfy writing your tests. +All of these features simplify writing your tests. and many more @@ -254,6 +267,9 @@ - working with failures - debug with print - bonus: exceptions +todo add slides on asserts and another on raises + + exercise 2 (~10 min) ===================== @@ -483,12 +499,16 @@ - generative tests -using plugins and funcargs -========================== +using plugins +============= + +*Overview of using plugins (25 minutes)* -*Overview of using plugins and funcargs (25 minutes)* +- unittest +- doctests +- pocoo -TODO: content from holger +TODO: split wrap up & questions From briandorsey at codespeak.net Thu Mar 26 04:34:53 2009 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Thu, 26 Mar 2009 04:34:53 +0100 (CET) Subject: [py-svn] r63333 - py/extradoc/talk/pycon-us-2009/pytest-introduction Message-ID: <20090326033453.07A181684F3@codespeak.net> Author: briandorsey Date: Thu Mar 26 04:34:52 2009 New Revision: 63333 Added: py/extradoc/talk/pycon-us-2009/pytest-introduction/test_doctest_example.txt py/extradoc/talk/pycon-us-2009/pytest-introduction/test_examples.py py/extradoc/talk/pycon-us-2009/pytest-introduction/test_hello.py py/extradoc/talk/pycon-us-2009/pytest-introduction/test_trace_setup.py Removed: py/extradoc/talk/pycon-us-2009/pytest-introduction/test_examples_auto.py Log: Added various example files. Added: py/extradoc/talk/pycon-us-2009/pytest-introduction/test_doctest_example.txt ============================================================================== --- (empty file) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/test_doctest_example.txt Thu Mar 26 04:34:52 2009 @@ -0,0 +1,2 @@ +>>> import os +>>> os.pathsep Added: py/extradoc/talk/pycon-us-2009/pytest-introduction/test_examples.py ============================================================================== --- (empty file) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/test_examples.py Thu Mar 26 04:34:52 2009 @@ -0,0 +1,103 @@ +# function or class +# + +def test_something(): + assert True + +class TestSomething(): + def test_something(self): + assert True +# automatic test discovery +# + +def test_something(): + assert True + +class TestSomething(): + def test_something(self): + assert True + +# search all test_* and *_test files +# assert introspection +# + +def test_assert_introspection(): + assert True # assertTrue() + assert 1 == 1 # assertEqual() + assert not 1 == 2 # assertNotEqual() + assert not False # assertFalse() +# print() debugging +# + +import py + +def test_something1(): + print "Useful debugging information." + assert True + + at py.test.mark(xfail="Expected failure.") +def test_something2(): + print "Useful debugging information." + assert False +# exercise 2 (~10 min) +# + +def test_one(): + print "debug text" + assert True +def test_two(): + assert 1 == 1 + at py.test.mark(xfail="Expected failure.") +def test_three(): + assert 1 == 2 +def test_bonus(): + py.test.raises(ValueError, int, 'foo') +# exercise 3 (~10 min) +# + +class TestSomething(): + def setup_class(cls): + pass + def setup_method(self, method): + pass + + def test_one(self): + assert True + def test_two(self): + assert 1 == 1 +# skipping tests +# + +import py + +def test_something_we_want_to_skip(): + py.test.skip("fix delayed.") + assert True +# generative tests 1 +# + +def test_ensure_sufficient_spam1(): + assert 'spam' in 'spam'.lower() + assert 'spam' in 'SPAM'.lower() + assert 'spam' in 'eggs & spam'.lower() + assert 'spam' in 'spammy'.lower() + assert 'spam' in 'more spam'.lower() +# generative tests 2 +# + +def test_ensure_sufficient_spam2(): + data = ['spam', 'SPAM', 'eggs & spam', + 'spammy', 'more spam'] + for item in data: + assert 'spam' in item.lower() +# generative tests 3 +# + +def contains_spam(item): + assert 'spam' in item.lower() + +def test_ensure_sufficient_spam3(): + data = ['spam', 'SPAM', 'eggs & spam', + 'spammy', 'more spam'] + for item in data: + yield contains_spam, item Deleted: /py/extradoc/talk/pycon-us-2009/pytest-introduction/test_examples_auto.py ============================================================================== --- /py/extradoc/talk/pycon-us-2009/pytest-introduction/test_examples_auto.py Thu Mar 26 04:34:52 2009 +++ (empty file) @@ -1,112 +0,0 @@ -# function or class -# - -def test_something(): - assert True - -class TestSomething(): - def test_something(self): - assert True -# automatic test discovery -# - -def test_something(): - assert True - -class TestSomething(): - def test_something(self): - assert True - -# search all test_* and *_test files -# assert introspection -# - -def test_assert_introspection(): - assert True # assertTrue() - assert 1 == 1 # assertEqual() - assert not 1 == 2 # assertNotEqual() - assert not False # assertFalse() -# print() debugging -# -# stdout is captured for each test, and only printed if the test fails. You can -# leave your debugging print statments in place to help your future self. - -import py - -def test_something1(): - print "Useful debugging information." - assert True - - at py.test.mark(xfail="Expected failure.") -def test_something2(): - print "Useful debugging information." - assert False -# exercise 2 (~10 min) -# -# TODO: write handout/exercise - -def test_one(): - print "debug text" - assert True -def test_two(): - assert 1 == 1 - at py.test.mark(xfail="Expected failure.") -def test_three(): - assert 1 == 2 - at py.test.mark(xfail="Expected failure.") -def test_four(): - print "debug text" - assert False -def test_bonus(): - py.test.raises(ValueError, int, 'foo') -# exercise 3 (~10 min) -# -# TODO: write handout/exercise -# TODO: use some built-in funcarg for the example - -class TestSomething(): - def setup_class(cls): - pass - def setup_method(self, method): - pass - - def test_one(self): - assert True - def test_two(self): - assert 1 == 1 -# skipping tests -# - -import py - -def test_something_we_want_to_skip(): - py.test.skip("need to decide what it should do.") - assert something == True -# generative tests 1 -# - -def test_ensure_sufficient_spam1(): - assert 'spam' in 'spam'.lower() - assert 'spam' in 'SPAM'.lower() - assert 'spam' in 'eggs & spam'.lower() - assert 'spam' in 'spammy'.lower() - assert 'spam' in 'more spam'.lower() -# generative tests 2 -# - -def test_ensure_sufficient_spam2(): - data = ['spam', 'SPAM', 'eggs & spam', - 'spammy', 'more spam'] - for item in data: - assert 'spam' in item.lower() -# generative tests 3 -# - -def contains_spam(item): - assert 'spam' in item.lower() - -def test_ensure_sufficient_spam3(): - data = ['spam', 'SPAM', 'eggs & spam', - 'spammy', 'more spam'] - for item in data: - yield contains_spam, item Added: py/extradoc/talk/pycon-us-2009/pytest-introduction/test_hello.py ============================================================================== --- (empty file) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/test_hello.py Thu Mar 26 04:34:52 2009 @@ -0,0 +1,3 @@ + +def test_hello(): + assert True Added: py/extradoc/talk/pycon-us-2009/pytest-introduction/test_trace_setup.py ============================================================================== --- (empty file) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/test_trace_setup.py Thu Mar 26 04:34:52 2009 @@ -0,0 +1,23 @@ +class TestSomething(): + name = "TestSomething" + + def setup_class(self): + print "**setup_class(%s)" % self.name + + def teardown_class(self): + print "**teardown_class(%s)" % self.name + + def setup_method(self, method): + print "**setup_method(%s)" % (self.name) + + def teardown_method(self, method): + print "**teardown_method(%s)" % (self.name) + + def test_true(self): + print "**test_true(%s)" % self.name + assert True + + def test_equals(self): + print "**test_equals(%s)" % self.name + assert 1 == 1 + From briandorsey at codespeak.net Thu Mar 26 04:54:14 2009 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Thu, 26 Mar 2009 04:54:14 +0100 (CET) Subject: [py-svn] r63334 - in py/extradoc/talk/pycon-us-2009/pytest-introduction: . img Message-ID: <20090326035414.E22461684BC@codespeak.net> Author: briandorsey Date: Thu Mar 26 04:54:09 2009 New Revision: 63334 Added: py/extradoc/talk/pycon-us-2009/pytest-introduction/img/ py/extradoc/talk/pycon-us-2009/pytest-introduction/img/large.png - copied unchanged from r63333, py/extradoc/talk/pycon-us-2009/pytest-advanced/img/large.png py/extradoc/talk/pycon-us-2009/pytest-introduction/img/medium.png - copied unchanged from r63333, py/extradoc/talk/pycon-us-2009/pytest-advanced/img/medium.png py/extradoc/talk/pycon-us-2009/pytest-introduction/img/small.png - copied unchanged from r63333, py/extradoc/talk/pycon-us-2009/pytest-advanced/img/small.png Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt py/extradoc/talk/pycon-us-2009/pytest-introduction/test_examples.py Log: more updates based on walkthrough with Holger Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html (original) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html Thu Mar 26 04:54:09 2009 @@ -334,8 +334,8 @@ -
    -

    presenters

    -

    Brian Dorsey <brian@dorseys.org> - enthusiastic user of py.test.

    -

    Holger Krekel <holger@merlinux.eu> - lead developer of py.test.

    -
    -
    -

    motivation

    -

    why automated testing? (about 10 minutes)

    -
      -
    • focusing mostly on unit testing (small tests) in this tutorial
    • -
    +
    +

    us

    +

    Brian Dorsey <brian@dorseys.org>

    +

    enthusiastic user of py.test.

    +

    Holger Krekel <holger@merlinux.eu>

    +

    lead developer of py.test.

    +
    +
    +

    you

    +

    Who are you?

    +

    Python background +Testing background +python testing libraries: unitest, nose, py.test...

    +
    +
    +

    motivation and terms

    +

    (about 10 minutes)

    +
    +
    +

    A pragmatic view on test types

    +

    let's talk about small, medium or large tests.

    +
    +
    +

    Small Tests: one aspect

    +
    img/small.png
    +
    +
    +

    Medium Tests: two aspects

    +
    img/medium.png
    +
    +
    +

    Large Tests: three/more

    +
    img/large.png

    why do automated testing?

    @@ -367,20 +389,20 @@
    -

    Test driven development

    +

    test driven development

    • There is some religion around Test Driven Development.
    -

    Pragmatism

    +

    pragmatism

    "I don't do test-driven development; I do stupidity-driven testing. When I do something stupid, I write a test to make sure I don't do it again." - Titus Brown

      -
    • I do Happy Path testing - kind of minimal TDD + SDD.
    • +
    • Brian does Happy Path testing - kind of minimal TDD + SDD.
    -
    -

    what you get with py.test

    +
    +

    py.test core concepts

    The fundamental features of py.test (about 10 minutes)

    • no boilerplate: write tests rapidly
    • @@ -388,6 +410,18 @@
    • lots of useful information when a test fails
    +
    +

    how it starts

    +

    $ py.test

    +

    (DEMO)

    +
    +
    +

    automatic test discovery

    +

    search all test_ and _test files

    +

    py.test searches for all modules which start with test_ or end with +_test. Within those modules, it collects all functions and methods which +start with test_.

    +

    function or class

    @@ -401,22 +435,6 @@
     

    Use individual test functions or group tests in classes. Use whichever makes the most sense for each test or group of tests.

    -
    -

    automatic test discovery

    -
    -def test_something():
    -    assert True
    -
    -class TestSomething():
    -    def test_something(self):
    -        assert True
    -
    -# search all test_* and *_test files
    -
    -

    py.test searches for all modules which start with test_ or end with -_test. Within those modules, it collects all functions and methods which -start with test_.

    -

    assert introspection

    @@ -439,7 +457,7 @@
         print "Useful debugging information."
         assert True
     
    -@py.test.mark(xfail="Expected failure.")
    +@py.test.mark.xfail("Expected failure.")
     def test_something2():
         print "Useful debugging information."
         assert False
    @@ -452,12 +470,13 @@
     

    less boilerplate

    py.test uses naming conventions to reduce boilerplate registration code.

      +
    • file, function, class, method
    • TestSuite discovery story

    simplicity

    -

    All of these features simplfy writing your tests.

    +

    All of these features simplify writing your tests.

    and many more

    @@ -531,6 +550,7 @@
  • working with failures - debug with print
  • bonus: exceptions
  • +

    todo add slides on asserts and another on raises

    exercise 2 (~10 min)

    @@ -540,7 +560,7 @@ assert True def test_two(): assert 1 == 1 -@py.test.mark(xfail="Expected failure.") +@py.test.mark.xfail("Expected failure.") def test_three(): assert 1 == 2 def test_bonus(): Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt Thu Mar 26 04:54:09 2009 @@ -16,8 +16,8 @@ the plan ======== -- `motivation`_ -- `what you get with py.test`_ +- `motivation and terms`_ +- `py.test core concepts`_ - `installation`_ (w/exercise) - `basic usage`_ (w/exercise) - `break`_ @@ -27,18 +27,23 @@ - `wrap up & questions`_ -presenters -========== +us +== + +Brian Dorsey + +enthusiastic user of py.test. -Brian Dorsey - enthusiastic user of py.test. -Holger Krekel - lead developer of py.test. +Holger Krekel +lead developer of py.test. -You + +you === -- Who are you? +Who are you? .. class:: handout @@ -47,14 +52,38 @@ python testing libraries: unitest, nose, py.test... -motivation -========== +motivation and terms +==================== + +*(about 10 minutes)* + + +A pragmatic view on test types +================================= + +let's talk about small, medium or large tests. + -*why automated testing? (about 10 minutes)* +Small Tests: one aspect +============================== -- focusing mostly on unit testing (small tests) in this tutorial +.. image:: img/small.png + :align: center + + +Medium Tests: two aspects +============================== + +.. image:: img/medium.png + :align: center + + +Large Tests: three/more +============================== + +.. image:: img/large.png + :align: center -TODO - add small medium large slides why do automated testing? ========================= @@ -65,24 +94,23 @@ - leads to faster development and easier maintenance -Test driven development +test driven development ======================= - There is some religion around Test Driven Development. -Pragmatism +pragmatism ========== *"I don't do test-driven development; I do stupidity-driven testing. When I do something stupid, I write a test to make sure I don't do it again."* - Titus Brown -- I do Happy Path testing - kind of minimal TDD + SDD. +- Brian does Happy Path testing - kind of minimal TDD + SDD. -what you get with py.test -========================= +py.test core concepts +===================== -TODO - title wording *The fundamental features of py.test (about 10 minutes)* - no boilerplate: write tests rapidly @@ -90,8 +118,8 @@ - lots of useful information when a test fails -how it starts -============= +lets go! +======== $ py.test @@ -101,7 +129,7 @@ automatic test discovery ======================== -search all test_* and *_test files +search all `test_` and `_test` files .. class:: handout @@ -162,7 +190,6 @@ print "Useful debugging information." assert False -TODO: fix all xfail s .. class:: handout @@ -176,6 +203,7 @@ py.test uses naming conventions to reduce boilerplate registration code. +- file, function, class, method - TestSuite discovery story @@ -280,7 +308,7 @@ assert True def test_two(): assert 1 == 1 - @py.test.mark(xfail="Expected failure.") + @py.test.mark.xfail("Expected failure.") def test_three(): assert 1 == 2 def test_bonus(): Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/test_examples.py ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-introduction/test_examples.py (original) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/test_examples.py Thu Mar 26 04:54:09 2009 @@ -7,17 +7,6 @@ class TestSomething(): def test_something(self): assert True -# automatic test discovery -# - -def test_something(): - assert True - -class TestSomething(): - def test_something(self): - assert True - -# search all test_* and *_test files # assert introspection # @@ -35,7 +24,7 @@ print "Useful debugging information." assert True - at py.test.mark(xfail="Expected failure.") + at py.test.mark.xfail("Expected failure.") def test_something2(): print "Useful debugging information." assert False @@ -47,7 +36,7 @@ assert True def test_two(): assert 1 == 1 - at py.test.mark(xfail="Expected failure.") + at py.test.mark.xfail("Expected failure.") def test_three(): assert 1 == 2 def test_bonus(): From hpk at codespeak.net Thu Mar 26 10:14:11 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 26 Mar 2009 10:14:11 +0100 (CET) Subject: [py-svn] r63337 - in py/trunk/py/test: . plugin testing Message-ID: <20090326091411.65580168488@codespeak.net> Author: hpk Date: Thu Mar 26 10:14:09 2009 New Revision: 63337 Modified: py/trunk/py/test/plugin/pytest_xfail.py py/trunk/py/test/pycollect.py py/trunk/py/test/testing/test_pycollect.py Log: provide more info for the pyfuncarg failing lookup improve docstring Modified: py/trunk/py/test/plugin/pytest_xfail.py ============================================================================== --- py/trunk/py/test/plugin/pytest_xfail.py (original) +++ py/trunk/py/test/plugin/pytest_xfail.py Thu Mar 26 10:14:09 2009 @@ -1,6 +1,6 @@ """ for marking and reporting "expected to fail" tests. - @py.test.mark(xfail="needs refactoring") + @py.test.mark.xfail("needs refactoring") def test_hello(): ... assert 0 Modified: py/trunk/py/test/pycollect.py ============================================================================== --- py/trunk/py/test/pycollect.py (original) +++ py/trunk/py/test/pycollect.py Thu Mar 26 10:14:09 2009 @@ -375,11 +375,12 @@ return kwargs def lookup_onearg(self, argname): + prefix = "pytest_pyfuncarg_" try: makerlist = self.config._getmakerlist(argname) except KeyError: makerlist = [] - l = self.config.pytestplugins.listattr("pytest_pyfuncarg_" + argname) + l = self.config.pytestplugins.listattr(prefix + argname) makerlist.extend(l) mc = py._com.MultiCall(makerlist, self) #print "mc.methods", mc.methods @@ -387,11 +388,22 @@ if value is not None: return value else: - metainfo = self.repr_metainfo() - #self.config.bus.notify("pyfuncarg_lookuperror", argname) - msg = "funcargument %r not found for: %s" %(argname,metainfo.verboseline()) - msg += "\n list of makers: %r" %(l,) - raise LookupError(msg) + self._raisefuncargerror(argname, prefix) + + def _raisefuncargerror(self, argname, prefix="pytest_pyfuncarg_"): + metainfo = self.repr_metainfo() + available = [] + plugins = self.config.pytestplugins._plugins.values() + plugins.extend(self.config.pytestplugins.pyplugins._plugins) + for plugin in plugins: + for name in vars(plugin.__class__): + if name.startswith(prefix): + name = name[len(prefix):] + if name not in available: + available.append(name) + msg = "funcargument %r not found for: %s" %(argname,metainfo.verboseline()) + msg += "\n available funcargs: %s" %(", ".join(available),) + raise LookupError(msg) def __eq__(self, other): try: Modified: py/trunk/py/test/testing/test_pycollect.py ============================================================================== --- py/trunk/py/test/testing/test_pycollect.py (original) +++ py/trunk/py/test/testing/test_pycollect.py Thu Mar 26 10:14:09 2009 @@ -250,8 +250,15 @@ assert not f1 != f1_b def test_pyfuncarg_lookupfails(self, testdir): + testdir.makeconftest(""" + class ConftestPlugin: + def pytest_pyfuncarg_something(self, pyfuncitem): + return 42 + """) item = testdir.getitem("def test_func(some): pass") - kw = py.test.raises(LookupError, "item.lookup_allargs()") + exc = py.test.raises(LookupError, "item.lookup_allargs()") + s = str(exc.value) + assert s.find("something") != -1 def test_pyfuncarg_lookup_default(self, testdir): item = testdir.getitem("def test_func(some, other=42): pass") From hpk at codespeak.net Thu Mar 26 10:16:30 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 26 Mar 2009 10:16:30 +0100 (CET) Subject: [py-svn] r63338 - in py/trunk/py: . execnet/testing test test/dist/testing test/plugin test/testing Message-ID: <20090326091630.AEAA8168494@codespeak.net> Author: hpk Date: Thu Mar 26 10:16:30 2009 New Revision: 63338 Modified: py/trunk/py/conftest.py py/trunk/py/execnet/testing/test_gwmanage.py py/trunk/py/test/dist/testing/test_nodemanage.py py/trunk/py/test/dist/testing/test_txnode.py py/trunk/py/test/plugin/pytest_iocapture.py py/trunk/py/test/plugin/pytest_plugintester.py py/trunk/py/test/plugin/pytest_restdoc.py py/trunk/py/test/plugin/pytest_tmpdir.py py/trunk/py/test/pycollect.py py/trunk/py/test/testing/test_pickling.py py/trunk/py/test/testing/test_pycollect.py py/trunk/py/test/testing/test_traceback.py Log: rename pyfuncarg to funcarg Modified: py/trunk/py/conftest.py ============================================================================== --- py/trunk/py/conftest.py (original) +++ py/trunk/py/conftest.py Thu Mar 26 10:16:30 2009 @@ -3,9 +3,9 @@ import py class PylibTestconfigPlugin: - def pytest_pyfuncarg_specssh(self, pyfuncitem): + def pytest_funcarg_specssh(self, pyfuncitem): return getspecssh(pyfuncitem.config) - def pytest_pyfuncarg_specsocket(self, pyfuncitem): + def pytest_funcarg_specsocket(self, pyfuncitem): return getsocketspec(pyfuncitem.config) def pytest_addoption(self, parser): Modified: py/trunk/py/execnet/testing/test_gwmanage.py ============================================================================== --- py/trunk/py/execnet/testing/test_gwmanage.py (original) +++ py/trunk/py/execnet/testing/test_gwmanage.py Thu Mar 26 10:16:30 2009 @@ -111,9 +111,9 @@ assert l[0].startswith(curwd) assert l[0].endswith("hello") -def pytest_pyfuncarg_source(pyfuncitem): +def pytest_funcarg_source(pyfuncitem): return py.test.ensuretemp(pyfuncitem.getmodpath()).mkdir("source") -def pytest_pyfuncarg_dest(pyfuncitem): +def pytest_funcarg_dest(pyfuncitem): return py.test.ensuretemp(pyfuncitem.getmodpath()).mkdir("dest") class TestHRSync: Modified: py/trunk/py/test/dist/testing/test_nodemanage.py ============================================================================== --- py/trunk/py/test/dist/testing/test_nodemanage.py (original) +++ py/trunk/py/test/dist/testing/test_nodemanage.py Thu Mar 26 10:16:30 2009 @@ -7,9 +7,9 @@ from py.__.test import event -def pytest_pyfuncarg_source(pyfuncitem): +def pytest_funcarg_source(pyfuncitem): return py.test.ensuretemp(pyfuncitem.getmodpath()).mkdir("source") -def pytest_pyfuncarg_dest(pyfuncitem): +def pytest_funcarg_dest(pyfuncitem): dest = py.test.ensuretemp(pyfuncitem.getmodpath()).mkdir("dest") return dest Modified: py/trunk/py/test/dist/testing/test_txnode.py ============================================================================== --- py/trunk/py/test/dist/testing/test_txnode.py (original) +++ py/trunk/py/test/dist/testing/test_txnode.py Thu Mar 26 10:16:30 2009 @@ -54,12 +54,12 @@ print "exiting:", gw gw.exit() -def pytest_pyfuncarg_mysetup(pyfuncitem): +def pytest_funcarg_mysetup(pyfuncitem): mysetup = MySetup(pyfuncitem) pyfuncitem.addfinalizer(mysetup.finalize) return mysetup -def pytest_pyfuncarg_testdir(__call__, pyfuncitem): +def pytest_funcarg_testdir(__call__, pyfuncitem): # decorate to make us always change to testdir testdir = __call__.execute(firstresult=True) testdir.chdir() Modified: py/trunk/py/test/plugin/pytest_iocapture.py ============================================================================== --- py/trunk/py/test/plugin/pytest_iocapture.py (original) +++ py/trunk/py/test/plugin/pytest_iocapture.py Thu Mar 26 10:16:30 2009 @@ -2,12 +2,12 @@ class IocapturePlugin: """ capture sys.stdout/sys.stderr / fd1/fd2. """ - def pytest_pyfuncarg_stdcapture(self, pyfuncitem): + def pytest_funcarg_stdcapture(self, pyfuncitem): capture = Capture(py.io.StdCapture) pyfuncitem.addfinalizer(capture.finalize) return capture - def pytest_pyfuncarg_stdcapturefd(self, pyfuncitem): + def pytest_funcarg_stdcapturefd(self, pyfuncitem): capture = Capture(py.io.StdCaptureFD) pyfuncitem.addfinalizer(capture.finalize) return capture Modified: py/trunk/py/test/plugin/pytest_plugintester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_plugintester.py (original) +++ py/trunk/py/test/plugin/pytest_plugintester.py Thu Mar 26 10:16:30 2009 @@ -5,7 +5,7 @@ class PlugintesterPlugin: """ test support code for testing pytest plugins. """ - def pytest_pyfuncarg_plugintester(self, pyfuncitem): + def pytest_funcarg_plugintester(self, pyfuncitem): pt = PluginTester(pyfuncitem) pyfuncitem.addfinalizer(pt.finalize) return pt @@ -46,7 +46,7 @@ getargs = py.std.inspect.getargs def isgenerichook(name): - return name.startswith("pytest_pyfuncarg_") + return name.startswith("pytest_funcarg_") while methods: name, method = methods.popitem() Modified: py/trunk/py/test/plugin/pytest_restdoc.py ============================================================================== --- py/trunk/py/test/plugin/pytest_restdoc.py (original) +++ py/trunk/py/test/plugin/pytest_restdoc.py Thu Mar 26 10:16:30 2009 @@ -328,7 +328,7 @@ "resolve_linkrole('source', 'py/foo/bar.py')") -def pytest_pyfuncarg_testdir(__call__, pyfuncitem): +def pytest_funcarg_testdir(__call__, pyfuncitem): testdir = __call__.execute(firstresult=True) testdir.makepyfile(confrest="from py.__.misc.rest import Project") testdir.plugins.append(RestdocPlugin()) Modified: py/trunk/py/test/plugin/pytest_tmpdir.py ============================================================================== --- py/trunk/py/test/plugin/pytest_tmpdir.py (original) +++ py/trunk/py/test/plugin/pytest_tmpdir.py Thu Mar 26 10:16:30 2009 @@ -13,7 +13,7 @@ """ provide temporary directories to test functions and methods. """ - def pytest_pyfuncarg_tmpdir(self, pyfuncitem): + def pytest_funcarg_tmpdir(self, pyfuncitem): name = pyfuncitem.name return pyfuncitem.config.mktemp(name, numbered=True) @@ -26,10 +26,10 @@ def test_generic(plugintester): plugintester.apicheck(TmpdirPlugin) -def test_pyfuncarg(testdir): +def test_funcarg(testdir): item = testdir.getitem("def test_func(tmpdir): pass") plugin = TmpdirPlugin() - p = plugin.pytest_pyfuncarg_tmpdir(item) + p = plugin.pytest_funcarg_tmpdir(item) assert p.check() bn = p.basename.strip("0123456789-") assert bn.endswith("test_func") Modified: py/trunk/py/test/pycollect.py ============================================================================== --- py/trunk/py/test/pycollect.py (original) +++ py/trunk/py/test/pycollect.py Thu Mar 26 10:16:30 2009 @@ -375,7 +375,7 @@ return kwargs def lookup_onearg(self, argname): - prefix = "pytest_pyfuncarg_" + prefix = "pytest_funcarg_" try: makerlist = self.config._getmakerlist(argname) except KeyError: @@ -390,7 +390,7 @@ else: self._raisefuncargerror(argname, prefix) - def _raisefuncargerror(self, argname, prefix="pytest_pyfuncarg_"): + def _raisefuncargerror(self, argname, prefix="pytest_funcarg_"): metainfo = self.repr_metainfo() available = [] plugins = self.config.pytestplugins._plugins.values() Modified: py/trunk/py/test/testing/test_pickling.py ============================================================================== --- py/trunk/py/test/testing/test_pickling.py (original) +++ py/trunk/py/test/testing/test_pickling.py Thu Mar 26 10:16:30 2009 @@ -1,6 +1,6 @@ import py -def pytest_pyfuncarg_pickletransport(pyfuncitem): +def pytest_funcarg_pickletransport(pyfuncitem): return ImmutablePickleTransport() def pytest_pyfunc_call(__call__, pyfuncitem, args, kwargs): Modified: py/trunk/py/test/testing/test_pycollect.py ============================================================================== --- py/trunk/py/test/testing/test_pycollect.py (original) +++ py/trunk/py/test/testing/test_pycollect.py Thu Mar 26 10:16:30 2009 @@ -249,10 +249,10 @@ assert f1 == f1_b assert not f1 != f1_b - def test_pyfuncarg_lookupfails(self, testdir): + def test_funcarg_lookupfails(self, testdir): testdir.makeconftest(""" class ConftestPlugin: - def pytest_pyfuncarg_something(self, pyfuncitem): + def pytest_funcarg_something(self, pyfuncitem): return 42 """) item = testdir.getitem("def test_func(some): pass") @@ -260,19 +260,19 @@ s = str(exc.value) assert s.find("something") != -1 - def test_pyfuncarg_lookup_default(self, testdir): + def test_funcarg_lookup_default(self, testdir): item = testdir.getitem("def test_func(some, other=42): pass") class Provider: - def pytest_pyfuncarg_some(self, pyfuncitem): + def pytest_funcarg_some(self, pyfuncitem): return pyfuncitem.name item.config.pytestplugins.register(Provider()) kw = item.lookup_allargs() assert len(kw) == 1 - def test_pyfuncarg_lookup_default_gets_overriden(self, testdir): + def test_funcarg_lookup_default_gets_overriden(self, testdir): item = testdir.getitem("def test_func(some=42, other=13): pass") class Provider: - def pytest_pyfuncarg_other(self, pyfuncitem): + def pytest_funcarg_other(self, pyfuncitem): return pyfuncitem.name item.config.pytestplugins.register(Provider()) kw = item.lookup_allargs() @@ -281,12 +281,12 @@ assert name == "other" assert value == item.name - def test_pyfuncarg_basic(self, testdir): + def test_funcarg_basic(self, testdir): item = testdir.getitem("def test_func(some, other): pass") class Provider: - def pytest_pyfuncarg_some(self, pyfuncitem): + def pytest_funcarg_some(self, pyfuncitem): return pyfuncitem.name - def pytest_pyfuncarg_other(self, pyfuncitem): + def pytest_funcarg_other(self, pyfuncitem): return 42 item.config.pytestplugins.register(Provider()) kw = item.lookup_allargs() @@ -294,11 +294,11 @@ assert kw['some'] == "test_func" assert kw['other'] == 42 - def test_pyfuncarg_addfinalizer(self, testdir): + def test_funcarg_addfinalizer(self, testdir): item = testdir.getitem("def test_func(some): pass") l = [] class Provider: - def pytest_pyfuncarg_some(self, pyfuncitem): + def pytest_funcarg_some(self, pyfuncitem): pyfuncitem.addfinalizer(lambda: l.append(42)) return 3 item.config.pytestplugins.register(Provider()) @@ -310,9 +310,9 @@ assert len(l) == 1 assert l[0] == 42 - def test_pyfuncarg_lookup_modulelevel(self, testdir): + def test_funcarg_lookup_modulelevel(self, testdir): modcol = testdir.getmodulecol(""" - def pytest_pyfuncarg_something(pyfuncitem): + def pytest_funcarg_something(pyfuncitem): return pyfuncitem.name class TestClass: Modified: py/trunk/py/test/testing/test_traceback.py ============================================================================== --- py/trunk/py/test/testing/test_traceback.py (original) +++ py/trunk/py/test/testing/test_traceback.py Thu Mar 26 10:16:30 2009 @@ -10,7 +10,7 @@ def test_traceback_argsetup(self, testdir): testdir.makeconftest(""" class ConftestPlugin: - def pytest_pyfuncarg_hello(self, pyfuncitem): + def pytest_funcarg_hello(self, pyfuncitem): raise ValueError("xyz") """) p = testdir.makepyfile("def test(hello): pass") From hpk at codespeak.net Thu Mar 26 10:26:11 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 26 Mar 2009 10:26:11 +0100 (CET) Subject: [py-svn] r63339 - in py/trunk/py/test: . plugin testing Message-ID: <20090326092611.981961684A9@codespeak.net> Author: hpk Date: Thu Mar 26 10:26:09 2009 New Revision: 63339 Modified: py/trunk/py/test/config.py py/trunk/py/test/plugin/pytest_monkeypatch.py py/trunk/py/test/plugin/pytest_pytester.py py/trunk/py/test/pycollect.py py/trunk/py/test/testing/test_config.py Log: undo rev 63000 so that there is only one method now for funcargs Modified: py/trunk/py/test/config.py ============================================================================== --- py/trunk/py/test/config.py (original) +++ py/trunk/py/test/config.py Thu Mar 26 10:26:09 2009 @@ -41,7 +41,6 @@ self.pytestplugins = pytestplugins self._conftest = Conftest(onimport=self._onimportconftest) self._setupstate = SetupState() - self._funcarg2maker = {} def _onimportconftest(self, conftestmodule): self.trace("loaded conftestmodule %r" %(conftestmodule,)) @@ -286,23 +285,7 @@ roots.append(pydir) return roots - def register_funcargmaker(self, argname, maker): - """ register a setup method for the given argument name. """ - self._funcarg2maker.setdefault(argname, []).append(maker) - - def _makefuncarg(self, argname, pyfuncitem): - makerlist = self._getmakerlist(argname) - mcall = py._com.MultiCall(makerlist, pyfuncitem) - return mcall.execute(firstresult=True) - - def _getmakerlist(self, argname): - makerlist = self._funcarg2maker.get(argname, None) - if makerlist is None: - msg = "funcarg %r not registered, available are: %s" % ( - argname, ", ".join(self._funcarg2maker.keys())) - raise KeyError(msg) - assert makerlist - return makerlist[:] + # # helpers # Modified: py/trunk/py/test/plugin/pytest_monkeypatch.py ============================================================================== --- py/trunk/py/test/plugin/pytest_monkeypatch.py (original) +++ py/trunk/py/test/plugin/pytest_monkeypatch.py Thu Mar 26 10:26:09 2009 @@ -2,10 +2,7 @@ class MonkeypatchPlugin: """ setattr-monkeypatching with automatical reversal after test. """ - def pytest_configure(self, config): - config.register_funcargmaker("monkeypatch", self.argmaker) - - def argmaker(self, pyfuncitem): + def pytest_funcarg_monkeypatch(self, pyfuncitem): monkeypatch = MonkeyPatch() pyfuncitem.addfinalizer(monkeypatch.finalize) return monkeypatch Modified: py/trunk/py/test/plugin/pytest_pytester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_pytester.py (original) +++ py/trunk/py/test/plugin/pytest_pytester.py Thu Mar 26 10:26:09 2009 @@ -7,20 +7,21 @@ from py.__.test.config import Config as pytestConfig class PytesterPlugin: - def pytest_configure(self, config): - config.register_funcargmaker("linecomp", lambda x: LineComp()) - config.register_funcargmaker("LineMatcher", lambda x: LineMatcher) - config.register_funcargmaker("EventRecorder", lambda x: EventRecorder) + def pytest_funcarg_linecomp(self, pyfuncitem): + return LineComp() - config.register_funcargmaker("testdir", self.maketestdir) - config.register_funcargmaker("eventrecorder", self.makeeventrecorder) + def pytest_funcarg_LineMatcher(self, pyfuncitem): + return LineMatcher - def maketestdir(self, pyfuncitem): + def pytest_funcarg_testdir(self, pyfuncitem): tmptestdir = TmpTestdir(pyfuncitem) pyfuncitem.addfinalizer(tmptestdir.finalize) return tmptestdir - def makeeventrecorder(self, pyfuncitem): + def pytest_funcarg_EventRecorder(self, pyfuncitem): + return EventRecorder + + def pytest_funcarg_eventrecorder(self, pyfuncitem): evrec = EventRecorder(py._com.pyplugins) pyfuncitem.addfinalizer(lambda: evrec.pyplugins.unregister(evrec)) return evrec Modified: py/trunk/py/test/pycollect.py ============================================================================== --- py/trunk/py/test/pycollect.py (original) +++ py/trunk/py/test/pycollect.py Thu Mar 26 10:26:09 2009 @@ -371,20 +371,13 @@ else: raise else: - pass # XXX lookup of arguments for yielded/generated tests as well + pass # XXX lookup of arguments for yielded/generated tests as well ? return kwargs def lookup_onearg(self, argname): prefix = "pytest_funcarg_" - try: - makerlist = self.config._getmakerlist(argname) - except KeyError: - makerlist = [] - l = self.config.pytestplugins.listattr(prefix + argname) - makerlist.extend(l) - mc = py._com.MultiCall(makerlist, self) - #print "mc.methods", mc.methods - value = mc.execute(firstresult=True) + #makerlist = self.config.pytestplugins.listattr(prefix + argname) + value = self.config.pytestplugins.call_firstresult(prefix + argname, pyfuncitem=self) if value is not None: return value else: Modified: py/trunk/py/test/testing/test_config.py ============================================================================== --- py/trunk/py/test/testing/test_config.py (original) +++ py/trunk/py/test/testing/test_config.py Thu Mar 26 10:26:09 2009 @@ -1,33 +1,5 @@ import py -class TestFuncArgsSetup: - def test_register_funcarg_simple(self, testdir): - item = testdir.getitem("def test_func(hello): pass") - def maker(pyfuncitem): - assert item == pyfuncitem - return 42 - item.config.register_funcargmaker("hello", maker) - arg = item.config._makefuncarg("hello", item) - assert arg == 42 - - def test_register_funcarg_two(self, testdir): - item = testdir.getitem("def test_func(hello): pass") - def maker1(pyfuncitem): - assert item == pyfuncitem - return 1 - def maker2(__call__, pyfuncitem): - assert item == pyfuncitem - res = __call__.execute(firstresult=True) - return res + 1 - item.config.register_funcargmaker("two", maker1) - item.config.register_funcargmaker("two", maker2) - arg = item.config._makefuncarg("two", item) - assert arg == 2 - - def test_register_funcarg_error(self, testdir): - item = testdir.getitem("def test_func(hello): pass") - config = item.config - py.test.raises(KeyError, 'item.config._makefuncarg("notexist", item)') class TestConfigCmdlineParsing: def test_config_cmdline_options(self, testdir): From hpk at codespeak.net Thu Mar 26 10:33:50 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 26 Mar 2009 10:33:50 +0100 (CET) Subject: [py-svn] r63340 - in py/trunk/py: . c-extension doc Message-ID: <20090326093350.DF39D1684BC@codespeak.net> Author: hpk Date: Thu Mar 26 10:33:50 2009 New Revision: 63340 Removed: py/trunk/py/c-extension/ py/trunk/py/doc/greenlet.txt Modified: py/trunk/py/__init__.py py/trunk/py/doc/index.txt Log: remove greenlet from py lib Modified: py/trunk/py/__init__.py ============================================================================== --- py/trunk/py/__init__.py (original) +++ py/trunk/py/__init__.py Thu Mar 26 10:33:50 2009 @@ -6,7 +6,6 @@ - `py.test`_: cross-project testing tool with many advanced features - `py.execnet`_: ad-hoc code distribution to SSH, Socket and local sub processes -- `py.magic.greenlet`_: micro-threads on standard CPython ("stackless-light") and PyPy - `py.path`_: path abstractions over local and subversion files - `py.code`_: dynamic code compile and traceback printing support @@ -16,14 +15,13 @@ .. _`py.test`: http://pylib.org/test.html .. _`py.execnet`: http://pylib.org/execnet.html -.. _`py.magic.greenlet`: http://pylib.org/greenlet.html .. _`py.path`: http://pylib.org/path.html .. _`py.code`: http://pylib.org/code.html """ from initpkg import initpkg -version = "1.0.0a7" +version = "1.0.0a8" initpkg(__name__, description = "pylib and py.test: agile development and test support library", Deleted: /py/trunk/py/doc/greenlet.txt ============================================================================== --- /py/trunk/py/doc/greenlet.txt Thu Mar 26 10:33:50 2009 +++ (empty file) @@ -1,315 +0,0 @@ -===================================================== -py.magic.greenlet: Lightweight concurrent programming -===================================================== - -.. contents:: -.. sectnum:: - -Motivation -========== - -The "greenlet" package is a spin-off of `Stackless`_, a version of CPython -that supports micro-threads called "tasklets". Tasklets run -pseudo-concurrently (typically in a single or a few OS-level threads) and -are synchronized with data exchanges on "channels". - -A "greenlet", on the other hand, is a still more primitive notion of -micro-thread with no implicit scheduling; coroutines, in other words. -This is useful when you want to -control exactly when your code runs. You can build custom scheduled -micro-threads on top of greenlet; however, it seems that greenlets are -useful on their own as a way to make advanced control flow structures. -For example, we can recreate generators; the difference with Python's own -generators is that our generators can call nested functions and the nested -functions can yield values too. (Additionally, you don't need a "yield" -keyword. See the example in :source:`py/c-extension/greenlet/test_generator.py`). - -Greenlets are provided as a C extension module for the regular unmodified -interpreter. - -.. _`Stackless`: http://www.stackless.com - -Example -------- - -Let's consider a system controlled by a terminal-like console, where the user -types commands. Assume that the input comes character by character. In such -a system, there will typically be a loop like the following one:: - - def process_commands(*args): - while True: - line = '' - while not line.endswith('\n'): - line += read_next_char() - if line == 'quit\n': - print "are you sure?" - if read_next_char() != 'y': - continue # ignore the command - process_command(line) - -Now assume that you want to plug this program into a GUI. Most GUI toolkits -are event-based. They will invoke a call-back for each character the user -presses. [Replace "GUI" with "XML expat parser" if that rings more bells to -you ``:-)``] In this setting, it is difficult to implement the -read_next_char() function needed by the code above. We have two incompatible -functions:: - - def event_keydown(key): - ?? - - def read_next_char(): - ?? should wait for the next event_keydown() call - -You might consider doing that with threads. Greenlets are an alternate -solution that don't have the related locking and shutdown problems. You -start the process_commands() function in its own, separate greenlet, and -then you exchange the keypresses with it as follows:: - - def event_keydown(key): - # jump into g_processor, sending it the key - g_processor.switch(key) - - def read_next_char(): - # g_self is g_processor in this simple example - g_self = greenlet.getcurrent() - # jump to the parent (main) greenlet, waiting for the next key - next_char = g_self.parent.switch() - return next_char - - g_processor = greenlet(process_commands) - g_processor.switch(*args) # input arguments to process_commands() - - gui.mainloop() - -In this example, the execution flow is: when read_next_char() is called, it -is part of the g_processor greenlet, so when it switches to its parent -greenlet, it resumes execution in the top-level main loop (the GUI). When -the GUI calls event_keydown(), it switches to g_processor, which means that -the execution jumps back wherever it was suspended in that greenlet -- in -this case, to the switch() instruction in read_next_char() -- and the ``key`` -argument in event_keydown() is passed as the return value of the switch() in -read_next_char(). - -Note that read_next_char() will be suspended and resumed with its call stack -preserved, so that it will itself return to different positions in -process_commands() depending on where it was originally called from. This -allows the logic of the program to be kept in a nice control-flow way; we -don't have to completely rewrite process_commands() to turn it into a state -machine. - - -Usage -===== - -Introduction ------------- - -A "greenlet" is a small independent pseudo-thread. Think about it as a -small stack of frames; the outermost (bottom) frame is the initial -function you called, and the innermost frame is the one in which the -greenlet is currently paused. You work with greenlets by creating a -number of such stacks and jumping execution between them. Jumps are never -implicit: a greenlet must choose to jump to another greenlet, which will -cause the former to suspend and the latter to resume where it was -suspended. Jumping between greenlets is called "switching". - -When you create a greenlet, it gets an initially empty stack; when you -first switch to it, it starts the run a specified function, which may call -other functions, switch out of the greenlet, etc. When eventually the -outermost function finishes its execution, the greenlet's stack becomes -empty again and the greenlet is "dead". Greenlets can also die of an -uncaught exception. - -For example:: - - from py.magic import greenlet - - def test1(): - print 12 - gr2.switch() - print 34 - - def test2(): - print 56 - gr1.switch() - print 78 - - gr1 = greenlet(test1) - gr2 = greenlet(test2) - gr1.switch() - -The last line jumps to test1, which prints 12, jumps to test2, prints 56, -jumps back into test1, prints 34; and then test1 finishes and gr1 dies. -At this point, the execution comes back to the original ``gr1.switch()`` -call. Note that 78 is never printed. - -Parents -------- - -Let's see where execution goes when a greenlet dies. Every greenlet has a -"parent" greenlet. The parent greenlet is initially the one in which the -greenlet was created (this can be changed at any time). The parent is -where execution continues when a greenlet dies. This way, greenlets are -organized in a tree. Top-level code that doesn't run in a user-created -greenlet runs in the implicit "main" greenlet, which is the root of the -tree. - -In the above example, both gr1 and gr2 have the main greenlet as a parent. -Whenever one of them dies, the execution comes back to "main". - -Uncaught exceptions are propagated into the parent, too. For example, if -the above test2() contained a typo, it would generate a NameError that -would kill gr2, and the exception would go back directly into "main". -The traceback would show test2, but not test1. Remember, switches are not -calls, but transfer of execution between parallel "stack containers", and -the "parent" defines which stack logically comes "below" the current one. - -Instantiation -------------- - -``py.magic.greenlet`` is the greenlet type, which supports the following -operations: - -``greenlet(run=None, parent=None)`` - Create a new greenlet object (without running it). ``run`` is the - callable to invoke, and ``parent`` is the parent greenlet, which - defaults to the current greenlet. - -``greenlet.getcurrent()`` - Returns the current greenlet (i.e. the one which called this - function). - -``greenlet.GreenletExit`` - This special exception does not propagate to the parent greenlet; it - can be used to kill a single greenlet. - -The ``greenlet`` type can be subclassed, too. A greenlet runs by calling -its ``run`` attribute, which is normally set when the greenlet is -created; but for subclasses it also makes sense to define a ``run`` method -instead of giving a ``run`` argument to the constructor. - -Switching ---------- - -Switches between greenlets occur when the method switch() of a greenlet is -called, in which case execution jumps to the greenlet whose switch() is -called, or when a greenlet dies, in which case execution jumps to the -parent greenlet. During a switch, an object or an exception is "sent" to -the target greenlet; this can be used as a convenient way to pass -information between greenlets. For example:: - - def test1(x, y): - z = gr2.switch(x+y) - print z - - def test2(u): - print u - gr1.switch(42) - - gr1 = greenlet(test1) - gr2 = greenlet(test2) - gr1.switch("hello", " world") - -This prints "hello world" and 42, with the same order of execution as the -previous example. Note that the arguments of test1() and test2() are not -provided when the greenlet is created, but only the first time someone -switches to it. - -Here are the precise rules for sending objects around: - -``g.switch(obj=None or *args)`` - Switches execution to the greenlet ``g``, sending it the given - ``obj``. As a special case, if ``g`` did not start yet, then it will - start to run now; in this case, any number of arguments can be - provided, and ``g.run(*args)`` is called. - -Dying greenlet - If a greenlet's ``run()`` finishes, its return value is the object - sent to its parent. If ``run()`` terminates with an exception, the - exception is propagated to its parent (unless it is a - ``greenlet.GreenletExit`` exception, in which case the exception - object is caught and *returned* to the parent). - -Apart from the cases described above, the target greenlet normally -receives the object as the return value of the call to ``switch()`` in -which it was previously suspended. Indeed, although a call to -``switch()`` does not return immediately, it will still return at some -point in the future, when some other greenlet switches back. When this -occurs, then execution resumes just after the ``switch()`` where it was -suspended, and the ``switch()`` itself appears to return the object that -was just sent. This means that ``x = g.switch(y)`` will send the object -``y`` to ``g``, and will later put the (unrelated) object that some -(unrelated) greenlet passes back to us into ``x``. - -Note that any attempt to switch to a dead greenlet actually goes to the -dead greenlet's parent, or its parent's parent, and so on. (The final -parent is the "main" greenlet, which is never dead.) - -Methods and attributes of greenlets ------------------------------------ - -``g.switch(obj=None or *args)`` - Switches execution to the greenlet ``g``. See above. - -``g.run`` - The callable that ``g`` will run when it starts. After ``g`` started, - this attribute no longer exists. - -``g.parent`` - The parent greenlet. This is writeable, but it is not allowed to - create cycles of parents. - -``g.gr_frame`` - The current top frame, or None. - -``g.dead`` - True if ``g`` is dead (i.e. it finished its execution). - -``bool(g)`` - True if ``g`` is active, False if it is dead or not yet started. - -``g.throw([typ, [val, [tb]]])`` - Switches execution to the greenlet ``g``, but immediately raises the - given exception in ``g``. If no argument is provided, the exception - defaults to ``greenlet.GreenletExit``. The normal exception - propagation rules apply, as described above. Note that calling this - method is almost equivalent to the following:: - - def raiser(): - raise typ, val, tb - g_raiser = greenlet(raiser, parent=g) - g_raiser.switch() - - except that this trick does not work for the - ``greenlet.GreenletExit`` exception, which would not propagate - from ``g_raiser`` to ``g``. - -Greenlets and Python threads ----------------------------- - -Greenlets can be combined with Python threads; in this case, each thread -contains an independent "main" greenlet with a tree of sub-greenlets. It -is not possible to mix or switch between greenlets belonging to different -threads. - -Garbage-collecting live greenlets ---------------------------------- - -If all the references to a greenlet object go away (including the -references from the parent attribute of other greenlets), then there is no -way to ever switch back to this greenlet. In this case, a GreenletExit -exception is generated into the greenlet. This is the only case where a -greenlet receives the execution asynchronously. This gives -``try:finally:`` blocks a chance to clean up resources held by the -greenlet. This feature also enables a programming style in which -greenlets are infinite loops waiting for data and processing it. Such -loops are automatically interrupted when the last reference to the -greenlet goes away. - -The greenlet is expected to either die or be resurrected by having a new -reference to it stored somewhere; just catching and ignoring the -GreenletExit is likely to lead to an infinite loop. - -Greenlets do not participate in garbage collection; cycles involving data -that is present in a greenlet's frames will not be detected. Storing -references to other greenlets cyclically may lead to leaks. Modified: py/trunk/py/doc/index.txt ============================================================================== --- py/trunk/py/doc/index.txt (original) +++ py/trunk/py/doc/index.txt Thu Mar 26 10:33:50 2009 @@ -5,13 +5,10 @@ `py.execnet`_ rapidly deploy local or remote processes from your program. -`py.magic.greenlet`_: instantiate thousands of micro-threads from your program. - `py.path`_: use path objects to transparently access local and svn filesystems. `py.code`_: generate python code and use advanced introspection/traceback support. - minor support functionality --------------------------------- @@ -29,7 +26,6 @@ .. _`download and installation`: download.html .. _`py-dev at codespeak net`: http://codespeak.net/mailman/listinfo/py-dev .. _`py.execnet`: execnet.html -.. _`py.magic.greenlet`: greenlet.html .. _`py.log`: log.html .. _`py.io`: io.html .. _`py.path`: path.html From hpk at codespeak.net Thu Mar 26 10:48:10 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 26 Mar 2009 10:48:10 +0100 (CET) Subject: [py-svn] r63341 - py/trunk Message-ID: <20090326094810.CA5E81684BC@codespeak.net> Author: hpk Date: Thu Mar 26 10:48:10 2009 New Revision: 63341 Modified: py/trunk/MANIFEST py/trunk/setup.py Log: regen setup Modified: py/trunk/MANIFEST ============================================================================== --- py/trunk/MANIFEST (original) +++ py/trunk/MANIFEST Thu Mar 26 10:48:10 2009 @@ -43,26 +43,6 @@ py/builtin/testing/test_reversed.py py/builtin/testing/test_set.py py/builtin/testing/test_sorted.py -py/c-extension/__init__.py -py/c-extension/greenlet/README.txt -py/c-extension/greenlet/dummy_greenlet.py -py/c-extension/greenlet/greenlet.c -py/c-extension/greenlet/greenlet.h -py/c-extension/greenlet/setup.py -py/c-extension/greenlet/slp_platformselect.h -py/c-extension/greenlet/switch_amd64_unix.h -py/c-extension/greenlet/switch_mips_unix.h -py/c-extension/greenlet/switch_ppc_macosx.h -py/c-extension/greenlet/switch_ppc_unix.h -py/c-extension/greenlet/switch_s390_unix.h -py/c-extension/greenlet/switch_sparc_sun_gcc.h -py/c-extension/greenlet/switch_x86_msvc.h -py/c-extension/greenlet/switch_x86_unix.h -py/c-extension/greenlet/test_generator.py -py/c-extension/greenlet/test_generator_nested.py -py/c-extension/greenlet/test_greenlet.py -py/c-extension/greenlet/test_remote.py -py/c-extension/greenlet/test_throw.py py/cmdline/__init__.py py/cmdline/pycleanup.py py/cmdline/pycountloc.py @@ -113,7 +93,6 @@ py/doc/draft_pyfs py/doc/execnet.txt py/doc/future.txt -py/doc/greenlet.txt py/doc/impl-test.txt py/doc/index.txt py/doc/io.txt Modified: py/trunk/setup.py ============================================================================== --- py/trunk/setup.py (original) +++ py/trunk/setup.py Thu Mar 26 10:48:10 2009 @@ -18,7 +18,6 @@ - `py.test`_: cross-project testing tool with many advanced features - `py.execnet`_: ad-hoc code distribution to SSH, Socket and local sub processes -- `py.magic.greenlet`_: micro-threads on standard CPython ("stackless-light") and PyPy - `py.path`_: path abstractions over local and subversion files - `py.code`_: dynamic code compile and traceback printing support @@ -28,7 +27,6 @@ .. _`py.test`: http://pylib.org/test.html .. _`py.execnet`: http://pylib.org/execnet.html -.. _`py.magic.greenlet`: http://pylib.org/greenlet.html .. _`py.path`: http://pylib.org/path.html .. _`py.code`: http://pylib.org/code.html @@ -40,9 +38,9 @@ name='py', description='pylib and py.test: agile development and test support library', long_description = long_description, - version='1.0.0a7', + version='1.0.0a8', url='http://pylib.org', - download_url='http://codespeak.net/py/1.0.0a7/download.html', + download_url='http://codespeak.net/py/1.0.0a8/download.html', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel, Guido Wesdorp, Carl Friedrich Bolz, Armin Rigo, Maciej Fijalkowski & others', @@ -73,7 +71,6 @@ packages=['py', 'py.builtin', 'py.builtin.testing', - 'py.c-extension', 'py.cmdline', 'py.cmdline.testing', 'py.code', @@ -137,25 +134,6 @@ 'bin/win32/py.svnwcrevert.cmd', 'bin/win32/py.test.cmd', 'bin/win32/py.which.cmd', - 'c-extension/greenlet/README.txt', - 'c-extension/greenlet/dummy_greenlet.py', - 'c-extension/greenlet/greenlet.c', - 'c-extension/greenlet/greenlet.h', - 'c-extension/greenlet/setup.py', - 'c-extension/greenlet/slp_platformselect.h', - 'c-extension/greenlet/switch_amd64_unix.h', - 'c-extension/greenlet/switch_mips_unix.h', - 'c-extension/greenlet/switch_ppc_macosx.h', - 'c-extension/greenlet/switch_ppc_unix.h', - 'c-extension/greenlet/switch_s390_unix.h', - 'c-extension/greenlet/switch_sparc_sun_gcc.h', - 'c-extension/greenlet/switch_x86_msvc.h', - 'c-extension/greenlet/switch_x86_unix.h', - 'c-extension/greenlet/test_generator.py', - 'c-extension/greenlet/test_generator_nested.py', - 'c-extension/greenlet/test_greenlet.py', - 'c-extension/greenlet/test_remote.py', - 'c-extension/greenlet/test_throw.py', 'compat/LICENSE', 'compat/testing/test_doctest.txt', 'compat/testing/test_doctest2.txt', @@ -167,7 +145,6 @@ 'doc/draft_pyfs', 'doc/execnet.txt', 'doc/future.txt', - 'doc/greenlet.txt', 'doc/impl-test.txt', 'doc/index.txt', 'doc/io.txt', From hpk at codespeak.net Thu Mar 26 11:12:12 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 26 Mar 2009 11:12:12 +0100 (CET) Subject: [py-svn] r63342 - in py/extradoc/talk/pycon-us-2009/pytest-advanced: . example/mypkg Message-ID: <20090326101212.99FB31684E4@codespeak.net> Author: hpk Date: Thu Mar 26 11:12:10 2009 New Revision: 63342 Added: py/extradoc/talk/pycon-us-2009/pytest-advanced/example/mypkg/ py/extradoc/talk/pycon-us-2009/pytest-advanced/example/mypkg/conftest.py (contents, props changed) py/extradoc/talk/pycon-us-2009/pytest-advanced/example/mypkg/test_url.py (contents, props changed) Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Log: trying to finalize Added: py/extradoc/talk/pycon-us-2009/pytest-advanced/example/mypkg/conftest.py ============================================================================== --- (empty file) +++ py/extradoc/talk/pycon-us-2009/pytest-advanced/example/mypkg/conftest.py Thu Mar 26 11:12:10 2009 @@ -0,0 +1,4 @@ + +class ConftestPlugin: + def pytest_funcarg_url(self, pyfuncitem): + return pyfuncitem.name Added: py/extradoc/talk/pycon-us-2009/pytest-advanced/example/mypkg/test_url.py ============================================================================== --- (empty file) +++ py/extradoc/talk/pycon-us-2009/pytest-advanced/example/mypkg/test_url.py Thu Mar 26 11:12:10 2009 @@ -0,0 +1,9 @@ + +def test_check(url): + assert url == "hello" + +def test_check_read(tmpdir, url): + tmpdir.join("hello").write("world") + #assert url == "hello" + assert 0 + Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Thu Mar 26 11:12:10 2009 @@ -33,7 +33,15 @@ - developed utest/stdtest, now py.test - consulted to help with testing -How does Python compare to Java? +What's your background? +========================= + +- what test tools do you use? +- work test-driven? +- have test-suites with >1000 tests? +- need/want to run tests on >1 platforms? + +Python ================================== Python has no compile-time type security. @@ -84,8 +92,10 @@ my current answer ======================================== -- operating systems, Python Interpreters -- cloud boxes, web browsers, .... +- various operating systems +- various Python Interpreters +- cloud environments +- web browsers etc. generally speaking ============================================ @@ -195,9 +205,8 @@ basic funcarg mechanism ========================================== -- provide a "funcarg" maker -- and call it for each test function that uses it - +- have a test function specify its needed setup +- lookup and call a function that provides setup Example test function ========================================== @@ -206,16 +215,12 @@ def test_somefunction(myarg): assert myarg == 'test_somefunction' - Example conftest.py ========================================== :: class ConftestPlugin: - def pytest_configure(self, config): - config.register_funcargmaker("myarg", myargmaker) - - def myargmaker(self, pyfuncitem): + def pytest_funcarg_myarg(self, pyfuncitem): return pyfuncitem.name observations @@ -227,6 +232,17 @@ * makers are registered after command line parsing * makers have meta-access to "collected function item" +notes on "named based lookup" +=================================== + +py.test often looks up names: + +- discover ``test_*.py`` test files +- discover ``test_`` functions or ``Test`` classes +- and now: discover test function arguments + +**automatic discovery avoids boilerplate** + Hands on: Installing py.test ======================================== @@ -240,11 +256,12 @@ ========================================== * write a new package "mypkg" -* add mypkg/__init__ and mypkg/test_ospath.py -* add a test_path function that needs a "url" argument -* register the argument maker for it +* add mypkg/__init__ and mypkg/test_url.py +* add a test_path function that needs an "url" argument +* write the provider in mypkg/conftest.py * run your test +Bonus: add more tests, play with failing. Using Plugins and Extensions ========================================= @@ -260,7 +277,7 @@ - 0.9.x uses conftest's for extension and configuration - 1.0 uses "plugins" for extending and conftest.py for configuration -- but "smooth" transition because of existing test code base +- we do "smooth" transition because of existing test code base Customizing py.test =========================== @@ -274,13 +291,13 @@ - can be put into test directory or higher up - contains test configuration values -- can specify plugins to use +- specifies plugins to use - can provide default values for command line options Specifying plugins ======================== -- ``-p|-plugin``: load comma-separated list of plugins +- ``-p NAME``: load comma-separated list of plugins - plugins are always named "pytest_NAME" and can be anywhere in your import path @@ -297,7 +314,6 @@ Exercise ========================================== -* port "url" funcarg to conftest * add an option for specifying url on the command line * look at "py.test -h" @@ -312,6 +328,11 @@ - running Javascript tests (old: conftest based) - html reporting for nightly runs (old: conftest-based) +if time permis ... +========================================= + +- let's play with "pytest_unittest" plugin + Break ===== @@ -343,62 +364,78 @@ - collection tree is built iteratively - test collection starts from directories or files (via cmdline) -py.test.collect.* objects -================================== +py.test.collect.* filesystem objects +====================================== - **Directory** - **File** + +always available Attributes +======================================= + +**parent** a reference to the collector that produced us + +**name**: a name that is unique within the scope of our parent + +**config**: test configuration + +Python object collectors +======================================= + +**obj** points to the underlying python object. + - **Module** - **Class**/**Instance** - **Generator** - **Function** -demo "collection tree" +Exercise "collection tree" ==================================== -you can always inspect the test collection tree -tree without running tests:: +inspecting the test collection tree:: py.test --collectonly Hooks and Events =================== -- Plugin hook methods interact with - configuration/collection/running of tests +- Plugin hook methods implement interaction + with configuration/collection/running of tests -- Events are called for notification purposes +- Events are called for notification purposes, + are not asked for return values. Configuration Hooks ====================== :: - def pytest_addoption(self, parser): - """ called before commandline parsing. """ + def pytest_addoption(self, parser): + """ called before commandline parsing. """ - def pytest_configure(self, config): - """ called after command line options have been parsed. " + def pytest_configure(self, config): + """ called after command line options have been parsed. " - def pytest_unconfigure(self, config): - """ called before test process is exited. """ + def pytest_unconfigure(self, config): + """ called before test process is exited. """ Collection Hooks ====================== :: - def pytest_collect_file(self, path, parent): - """ return Collection node or None. """ - def pytest_collect_directory(self, path, parent): - """ return Collection node or None. """ + def pytest_collect_file(self, path, parent): + """ return Collection node or None. """ - def pytest_collect_recurse(self, path, parent): - """ return True/False to cause/prevent recursion. """ + def pytest_collect_directory(self, path, parent): + """ return Collection node or None. """ + + def pytest_collect_recurse(self, path, parent): + """ return True/False to cause/prevent recursion. """ Python collection hooks ===================================== :: - def pytest_pymodule_makeitem(self, modcol, name, obj): - """ return custom item/collector for a python object in a module, or None. """ + def pytest_pymodule_makeitem(self, modcol, name, obj): + """ return custom item/collector for a python object in a module, or None. """ function test run hooks ========================== @@ -409,7 +446,6 @@ def pytest_item_makereport(self, item, excinfo, when, outerr): """ return ItemTestReport event for the given test outcome. """ -used by: pytest_xfail.py Reporting hooks ========================== @@ -426,7 +462,7 @@ are only called, no interaction. see ``py/test/plugin/pytest_terminal.py`` -for a full selection. +for a full selection of events. Warning =============== @@ -526,7 +562,7 @@ mypkg/conftest.py:: rsyncdirs = ['.'] -Now the commands +Distribution modes ======================================== :: @@ -572,6 +608,13 @@ popen//python=2.5//nice=20 socket=192.168.1.4:8888 +Exercise +================================= + +* Play with runing your tests in multiple interpreters or processes +* see if you can get access to your neighbour's PC + + Feedback round ================== From hpk at codespeak.net Thu Mar 26 11:13:32 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 26 Mar 2009 11:13:32 +0100 (CET) Subject: [py-svn] r63343 - py/dist Message-ID: <20090326101332.51F091684F9@codespeak.net> Author: hpk Date: Thu Mar 26 11:13:31 2009 New Revision: 63343 Added: py/dist/ - copied from r63342, py/trunk/ Log: stable snapshost From hpk at codespeak.net Thu Mar 26 11:16:43 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 26 Mar 2009 11:16:43 +0100 (CET) Subject: [py-svn] r63345 - py/trunk/py/test/plugin Message-ID: <20090326101643.768231684F3@codespeak.net> Author: hpk Date: Thu Mar 26 11:16:42 2009 New Revision: 63345 Modified: py/trunk/py/test/plugin/pytest_terminal.py Log: better naming Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Thu Mar 26 11:16:42 2009 @@ -124,7 +124,7 @@ self.write_line(msg) def pyevent_testnodeready(self, node): - self.write_line("%s node ready to receive tests" %(node.gateway.id,)) + self.write_line("%s txnode ready to receive tests" %(node.gateway.id,)) def pyevent_testnodedown(self, node, error): if error: From hpk at codespeak.net Thu Mar 26 12:15:15 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 26 Mar 2009 12:15:15 +0100 (CET) Subject: [py-svn] r63346 - in py/trunk: . py/doc Message-ID: <20090326111515.1B1E81684F3@codespeak.net> Author: hpk Date: Thu Mar 26 12:15:14 2009 New Revision: 63346 Modified: py/trunk/py/doc/test-plugins.txt py/trunk/setup.py Log: update docs Modified: py/trunk/py/doc/test-plugins.txt ============================================================================== --- py/trunk/py/doc/test-plugins.txt (original) +++ py/trunk/py/doc/test-plugins.txt Thu Mar 26 12:15:14 2009 @@ -1,28 +1,40 @@ Many of py.test's features are implemented as a plugin. -Available plugins +Default plugins ----------------------- -py.test has a number of default plugins. You can see which -ones by specifying ``--trace=config``. +You can find the source code of all default plugins in +http://codespeak.net/svn/py/trunk/py/test/plugin/ -* adding reporting facilities, examples: - pytest_terminal: default reporter for writing info to terminals - pytest_resultlog: log test results in machine-readable form to a file - pytest_eventlog: log all internal pytest events to a file - -* marking and reporting test specially - pytest_xfail: "expected to fail" test marker - -* funcargs for advanced - pytest_tmpdir: provide temporary directories to test functions - pytest_plugintester: generic apichecks, support for functional plugin tests - pytest_pytester: support for testing py.test runs +plugins that add reporting asepcts ++++++++++++++++++++++++++++++++++++++++ -* extending test execution, e.g. - pytest_apigen: tracing values of function/method calls when running tests +pytest_terminal: default reporter for writing info to terminals +pytest_resultlog: log test results in machine-readable form to a file + +pytest_eventlog: log all internal pytest events to a file + +plugins for adding new test types +++++++++++++++++++++++++++++++++++++++++++++++ + +pytest_unittest: run traditional unittest TestCase instances + +pytest_doctest: run doctests in python modules or .txt files + +pytest_restdoc: provide RestructuredText syntax and link checking + +plugins for python test functions +++++++++++++++++++++++++++++++++++++++++++++++ + +pytest_xfail: provides "expected to fail" test marker + +pytest_tmpdir: provide temporary directories to test functions + +pytest_plugintester: generic plugin apichecks, support for functional plugin tests + +pytest_apigen: tracing values of function/method calls when running tests Loading plugins and specifying dependencies --------------------------------------------------------- Modified: py/trunk/setup.py ============================================================================== --- py/trunk/setup.py (original) +++ py/trunk/setup.py Thu Mar 26 12:15:14 2009 @@ -1,7 +1,7 @@ """ setup file for 'py' package based on: - https://codespeak.net/svn/py/trunk, revision=63254 + https://codespeak.net/svn/py/trunk, revision=63342 autogenerated by gensetup.py """ From briandorsey at codespeak.net Thu Mar 26 12:31:08 2009 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Thu, 26 Mar 2009 12:31:08 +0100 (CET) Subject: [py-svn] r63347 - in py/extradoc/talk/pycon-us-2009/pytest-introduction: . helpers Message-ID: <20090326113108.52AF41684E4@codespeak.net> Author: briandorsey Date: Thu Mar 26 12:31:05 2009 New Revision: 63347 Added: py/extradoc/talk/pycon-us-2009/pytest-introduction/conftest.py Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/helpers/build.sh py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt py/extradoc/talk/pycon-us-2009/pytest-introduction/test_doctest_example.txt py/extradoc/talk/pycon-us-2009/pytest-introduction/test_examples.py Log: fleshed out plugin section added explanation slides for exercise 2 Added: py/extradoc/talk/pycon-us-2009/pytest-introduction/conftest.py ============================================================================== --- (empty file) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/conftest.py Thu Mar 26 12:31:05 2009 @@ -0,0 +1,2 @@ + +pytest_plugins = "doctest", "unittest" Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/helpers/build.sh ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-introduction/helpers/build.sh (original) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/helpers/build.sh Thu Mar 26 12:31:05 2009 @@ -1,2 +1,3 @@ rst2s5.py pytest-introduction.txt pytest-introduction.html python helpers/extract_examples.py +py.test test_examples.py Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html (original) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.html Thu Mar 26 12:31:05 2009 @@ -410,8 +410,8 @@
  • lots of useful information when a test fails
  • -
    -

    how it starts

    +
    +

    lets go!

    $ py.test

    (DEMO)

    @@ -550,7 +550,49 @@
  • working with failures - debug with print
  • bonus: exceptions
  • -

    todo add slides on asserts and another on raises

    +
    +
    +

    assert introspection

    +
    +def test_assert_introspection():
    +    assert True         # assertTrue()
    +    assert 1 == 1       # assertEqual()
    +    assert not 1 == 2   # assertNotEqual()
    +    assert not False    # assertFalse()
    +
    +

    This allows you to write test code which looks just like application code. +For example, no need to pass functions and parameters separately into +assertEquals.

    +
    +
    +

    print() debugging

    +
    +import py
    +
    +def test_something1():
    +    print "Useful debugging information."
    +    assert True
    +
    +@py.test.mark.xfail("Expected failure.")
    +def test_something2():
    +    print "Useful debugging information."
    +    assert False
    +
    +

    stdout is captured for each test, and only printed if the test fails. You +can leave your debugging print statments in place to help your future +self.

    +
    +
    +

    testing exceptions

    +
    +import py
    +
    +def test_raises_one():
    +    py.test.raises(ValueError, int, 'foo')
    +
    +def test_raises_two():
    +    py.test.raises(ValueError, "int('foo')")
    +

    exercise 2 (~10 min)

    @@ -563,8 +605,10 @@ @py.test.mark.xfail("Expected failure.") def test_three(): assert 1 == 2 -def test_bonus(): +def test_raises_one(): py.test.raises(ValueError, int, 'foo') +def test_raises_two(): + py.test.raises(ValueError, "int('foo')")
    @@ -757,7 +801,30 @@
  • doctests
  • pocoo
  • -

    TODO: split

    +
    +
    +

    enabling plugins - command line

    +

    $ py.test -p doctest -p unittest

    +
    +
    +

    enabling plugins - conftest.py

    +

    pytest_plugins = "doctest", "unittest"

    +
    +
    +

    plugins shipped with py.test

    +

    py/test/plugin

    +
    +
    +

    unittest plugin

    +

    (DEMO)

    +
    +
    +

    doctest plugin

    +

    (DEMO)

    +
    +
    +

    pocoo plugin

    +

    (DEMO)

    wrap up & questions

    @@ -769,14 +836,6 @@

    Quesitons?

    -
    -

    x

    -

    TODO: -- add simple demo early on - what it looks like to run py.test from the command line. -- give overview of what's going on when you run py.test - collection, running, etc -- add py.test.mark to -k slide - if it works -- can we get --nocapture back?

    -
    Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/pytest-introduction.txt Thu Mar 26 12:31:05 2009 @@ -295,8 +295,62 @@ - working with failures - debug with print - bonus: exceptions -todo add slides on asserts and another on raises +assert introspection +==================== + +:: + + def test_assert_introspection(): + assert True # assertTrue() + assert 1 == 1 # assertEqual() + assert not 1 == 2 # assertNotEqual() + assert not False # assertFalse() + +.. class:: handout + + This allows you to write test code which looks just like application code. + For example, no need to pass functions and parameters separately into + assertEquals. + + +print() debugging +================= + +:: + + import py + + def test_something1(): + print "Useful debugging information." + assert True + + @py.test.mark.xfail("Expected failure.") + def test_something2(): + print "Useful debugging information." + assert False + + +.. class:: handout + + stdout is captured for each test, and only printed if the test fails. You + can leave your debugging print statments in place to help your future + self. + + +testing exceptions +================== + +:: + + import py + + def test_raises_one(): + py.test.raises(ValueError, int, 'foo') + + def test_raises_two(): + py.test.raises(ValueError, "int('foo')") + exercise 2 (~10 min) ===================== @@ -311,8 +365,10 @@ @py.test.mark.xfail("Expected failure.") def test_three(): assert 1 == 2 - def test_bonus(): + def test_raises_one(): py.test.raises(ValueError, int, 'foo') + def test_raises_two(): + py.test.raises(ValueError, "int('foo')") exercise 3 @@ -536,7 +592,41 @@ - doctests - pocoo -TODO: split + +enabling plugins - command line +=============================== + +$ py.test -p doctest -p unittest + + +enabling plugins - conftest.py +============================== + +pytest_plugins = "doctest", "unittest" + + +plugins shipped with py.test +============================ + +py/test/plugin + + +unittest plugin +=============== + +(DEMO) + + +doctest plugin +============== + +(DEMO) + + +pocoo plugin +============ + +(DEMO) wrap up & questions @@ -554,13 +644,3 @@ - - -x -= - -TODO: -- add simple demo early on - what it looks like to run py.test from the command line. -- give overview of what's going on when you run py.test - collection, running, etc -- add py.test.mark to -k slide - if it works -- can we get --nocapture back? Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/test_doctest_example.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-introduction/test_doctest_example.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/test_doctest_example.txt Thu Mar 26 12:31:05 2009 @@ -1,2 +1,6 @@ ->>> import os ->>> os.pathsep +>>> import random +>>> l = range(5) +>>> print l +[0, 1, 2, 3, 4] +>>> random.choice(l) in l +True Modified: py/extradoc/talk/pycon-us-2009/pytest-introduction/test_examples.py ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-introduction/test_examples.py (original) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/test_examples.py Thu Mar 26 12:31:05 2009 @@ -28,6 +28,37 @@ def test_something2(): print "Useful debugging information." assert False +# assert introspection +# + +def test_assert_introspection(): + assert True # assertTrue() + assert 1 == 1 # assertEqual() + assert not 1 == 2 # assertNotEqual() + assert not False # assertFalse() +# print() debugging +# + +import py + +def test_something1(): + print "Useful debugging information." + assert True + + at py.test.mark.xfail("Expected failure.") +def test_something2(): + print "Useful debugging information." + assert False +# testing exceptions +# + +import py + +def test_raises_one(): + py.test.raises(ValueError, int, 'foo') + +def test_raises_two(): + py.test.raises(ValueError, "int('foo')") # exercise 2 (~10 min) # @@ -39,8 +70,10 @@ @py.test.mark.xfail("Expected failure.") def test_three(): assert 1 == 2 -def test_bonus(): +def test_raises_one(): py.test.raises(ValueError, int, 'foo') +def test_raises_two(): + py.test.raises(ValueError, "int('foo')") # exercise 3 (~10 min) # From briandorsey at codespeak.net Thu Mar 26 12:37:11 2009 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Thu, 26 Mar 2009 12:37:11 +0100 (CET) Subject: [py-svn] r63348 - py/extradoc/talk/pycon-us-2009/pytest-introduction Message-ID: <20090326113711.485651684AD@codespeak.net> Author: briandorsey Date: Thu Mar 26 12:37:10 2009 New Revision: 63348 Added: py/extradoc/talk/pycon-us-2009/pytest-introduction/test_unittest_example.py Log: added unittest example. Added: py/extradoc/talk/pycon-us-2009/pytest-introduction/test_unittest_example.py ============================================================================== --- (empty file) +++ py/extradoc/talk/pycon-us-2009/pytest-introduction/test_unittest_example.py Thu Mar 26 12:37:10 2009 @@ -0,0 +1,25 @@ +import random +import unittest + +class TestSequenceFunctions(unittest.TestCase): + + def setUp(self): + self.seq = range(10) + + def testshuffle(self): + # make sure the shuffled sequence does not lose any elements + random.shuffle(self.seq) + self.seq.sort() + self.assertEqual(self.seq, range(10)) + + def testchoice(self): + element = random.choice(self.seq) + self.assert_(element in self.seq) + + def testsample(self): + self.assertRaises(ValueError, random.sample, self.seq, 20) + for element in random.sample(self.seq, 5): + self.assert_(element in self.seq) + +if __name__ == '__main__': + unittest.main() From briandorsey at codespeak.net Thu Mar 26 12:38:55 2009 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Thu, 26 Mar 2009 12:38:55 +0100 (CET) Subject: [py-svn] r63349 - py/extradoc/talk/pycon-us-2009 Message-ID: <20090326113855.CEA1C1684E4@codespeak.net> Author: briandorsey Date: Thu Mar 26 12:38:55 2009 New Revision: 63349 Added: py/extradoc/talk/pycon-us-2009/make_zip.sh py/extradoc/talk/pycon-us-2009/pytest-introduction.zip (contents, props changed) Log: Added zipfile of the introduction presentation Added: py/extradoc/talk/pycon-us-2009/make_zip.sh ============================================================================== --- (empty file) +++ py/extradoc/talk/pycon-us-2009/make_zip.sh Thu Mar 26 12:38:55 2009 @@ -0,0 +1,8 @@ +rm pytest-introduction.zip +mkdir tempzip +svn export pytest-introduction tempzip/pytest-introduction +cd tempzip +zip -r pytest-introduction.zip pytest-introduction/ +cd .. +mv tempzip/pytest-introduction.zip . +rm -r tempzip Added: py/extradoc/talk/pycon-us-2009/pytest-introduction.zip ============================================================================== Binary file. No diff available. From hpk at codespeak.net Thu Mar 26 13:21:07 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 26 Mar 2009 13:21:07 +0100 (CET) Subject: [py-svn] r63353 - in py/trunk/py/test: . testing Message-ID: <20090326122107.EA1D4168509@codespeak.net> Author: hpk Date: Thu Mar 26 13:21:05 2009 New Revision: 63353 Modified: py/trunk/py/test/parseopt.py py/trunk/py/test/testing/test_parseopt.py Log: better command option handling Modified: py/trunk/py/test/parseopt.py ============================================================================== --- py/trunk/py/test/parseopt.py (original) +++ py/trunk/py/test/parseopt.py Thu Mar 26 13:21:05 2009 @@ -20,7 +20,7 @@ """ Parser for command line arguments. """ def __init__(self, usage=None, processopt=None): - self._anonymous = OptionGroup("misc", parser=self) + self._anonymous = OptionGroup("custom options", parser=self) self._groups = [self._anonymous] self._processopt = processopt self._usage = usage @@ -50,7 +50,9 @@ def parse(self, args): optparser = optparse.OptionParser(usage=self._usage) - for group in self._groups: + # make sure anaonymous group is at the end + groups = self._groups[1:] + [self._groups[0]] + for group in groups: if group.options: desc = group.description or group.name optgroup = optparse.OptionGroup(optparser, desc) Modified: py/trunk/py/test/testing/test_parseopt.py ============================================================================== --- py/trunk/py/test/testing/test_parseopt.py (original) +++ py/trunk/py/test/testing/test_parseopt.py Thu Mar 26 13:21:05 2009 @@ -38,7 +38,7 @@ def test_parser_addoption(self): parser = parseopt.Parser() - group = parser.getgroup("misc") + group = parser.getgroup("custom options") assert len(group.options) == 0 group.addoption("--option1", action="store_true") assert len(group.options) == 1 From hpk at codespeak.net Thu Mar 26 13:21:23 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 26 Mar 2009 13:21:23 +0100 (CET) Subject: [py-svn] r63354 - py/dist Message-ID: <20090326122123.5AE4C168509@codespeak.net> Author: hpk Date: Thu Mar 26 13:21:23 2009 New Revision: 63354 Added: py/dist/ - copied from r63353, py/trunk/ Log: stable From hpk at codespeak.net Thu Mar 26 13:22:05 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 26 Mar 2009 13:22:05 +0100 (CET) Subject: [py-svn] r63355 - in py/trunk: . py Message-ID: <20090326122205.8BA8A168509@codespeak.net> Author: hpk Date: Thu Mar 26 13:22:04 2009 New Revision: 63355 Modified: py/trunk/py/__init__.py py/trunk/setup.py Log: bump version Modified: py/trunk/py/__init__.py ============================================================================== --- py/trunk/py/__init__.py (original) +++ py/trunk/py/__init__.py Thu Mar 26 13:22:04 2009 @@ -21,7 +21,7 @@ """ from initpkg import initpkg -version = "1.0.0a8" +version = "1.0.0a9" initpkg(__name__, description = "pylib and py.test: agile development and test support library", Modified: py/trunk/setup.py ============================================================================== --- py/trunk/setup.py (original) +++ py/trunk/setup.py Thu Mar 26 13:22:04 2009 @@ -38,9 +38,9 @@ name='py', description='pylib and py.test: agile development and test support library', long_description = long_description, - version='1.0.0a8', + version='1.0.0a9', url='http://pylib.org', - download_url='http://codespeak.net/py/1.0.0a8/download.html', + download_url='http://codespeak.net/py/1.0.0a9/download.html', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel, Guido Wesdorp, Carl Friedrich Bolz, Armin Rigo, Maciej Fijalkowski & others', From hpk at codespeak.net Thu Mar 26 13:22:18 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 26 Mar 2009 13:22:18 +0100 (CET) Subject: [py-svn] r63356 - py/dist Message-ID: <20090326122218.7336B168528@codespeak.net> Author: hpk Date: Thu Mar 26 13:22:18 2009 New Revision: 63356 Added: py/dist/ - copied from r63355, py/trunk/ Log: recopy with correct version number From hpk at codespeak.net Thu Mar 26 13:49:58 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 26 Mar 2009 13:49:58 +0100 (CET) Subject: [py-svn] r63358 - in py/extradoc/talk/pycon-us-2009/pytest-advanced: . example/mypkg Message-ID: <20090326124958.9624816852A@codespeak.net> Author: hpk Date: Thu Mar 26 13:49:58 2009 New Revision: 63358 Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/example/mypkg/conftest.py py/extradoc/talk/pycon-us-2009/pytest-advanced/example/mypkg/test_url.py py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Log: fix slides Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/example/mypkg/conftest.py ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-advanced/example/mypkg/conftest.py (original) +++ py/extradoc/talk/pycon-us-2009/pytest-advanced/example/mypkg/conftest.py Thu Mar 26 13:49:58 2009 @@ -1,4 +1,8 @@ class ConftestPlugin: - def pytest_funcarg_url(self, pyfuncitem): + def pytest_addoption(self, parser): + group = parser.addgroup("my options") + group.addoption("--hello", dest="hello") + def pytest_funcarg__url(self, pyfuncitem): return pyfuncitem.name + Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/example/mypkg/test_url.py ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-advanced/example/mypkg/test_url.py (original) +++ py/extradoc/talk/pycon-us-2009/pytest-advanced/example/mypkg/test_url.py Thu Mar 26 13:49:58 2009 @@ -1,5 +1,5 @@ -def test_check(url): +def test_check(url2): assert url == "hello" def test_check_read(tmpdir, url): Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Thu Mar 26 13:49:58 2009 @@ -33,13 +33,14 @@ - developed utest/stdtest, now py.test - consulted to help with testing -What's your background? -========================= +What's **your** background? +============================== - what test tools do you use? - work test-driven? - have test-suites with >1000 tests? - need/want to run tests on >1 platforms? +- have "non-python" tests? Python ================================== @@ -50,37 +51,41 @@ Testing to the rescue! ================================== -automated tests are better than types. - +automated tests are better than declaring types. -The Automated Test question +The test tool question ======================================== -what are automated tests there for? - +what is the job of automated testing tools? my current answer ======================================== -to make sure that +* verify that my code changes work out +* be helpful when test scenarios fail -* units react well to input. -* components co-operate nicely -* **everything works in target execution contexts** +If failures are not helpful ... +======================================== -The test tool question +improve the test tool or + +write more (different) tests + +The Automated Test question ======================================== -what is the job of automated testing tools? +what are automated tests there for? my current answer ======================================== -* make sure things work out **in the end** -* be helpful when test scenarios fail +to make sure that +* units react well to input. +* components co-operate nicely +* code changes work out in the end -how does "in the end look like"? +What does "code changes work out" mean? ======================================== .. image:: img/rails_in_the_city_by_marikaz.jpg @@ -97,16 +102,6 @@ - cloud environments - web browsers etc. -generally speaking -============================================ - -what is the vision of automated testing? - -my current answer -======================================== - -merge with real-life deployment. - Common Test terminology ============================== @@ -114,6 +109,7 @@ - unit tests - functional tests - acceptance tests +- integration tests you may discuss a long time about categorizations ... @@ -137,13 +133,24 @@ :align: center :scale: 70 -Large Tests: three/more +Large Tests: end-to-end ============================== .. image:: img/large.png :align: center :scale: 70 +generally speaking +============================================ + +what is the vision of automated testing? + +my current answer +======================================== + +merge with real-life deployment. + + pytest advanced features (30 minutes) ============================================================================ @@ -190,8 +197,8 @@ * test configuration mixes with code instantiation * importing 'app' may fail * the "app.pkg.SomeClass" reference may change -* functions tend to get grouped by setup methods -* multiple methods can reuse the same setup +* **functions tend to group by setup methods** +* **multiple methods can reuse the same setup** meet py.test "funcargs" ========================================== @@ -220,7 +227,7 @@ :: class ConftestPlugin: - def pytest_funcarg_myarg(self, pyfuncitem): + def pytest_funcarg__myarg(self, pyfuncitem): return pyfuncitem.name observations @@ -261,7 +268,7 @@ * write the provider in mypkg/conftest.py * run your test -Bonus: add more tests, play with failing. +Bonus: add more tests, play with wrongly named args. Using Plugins and Extensions ========================================= @@ -328,7 +335,7 @@ - running Javascript tests (old: conftest based) - html reporting for nightly runs (old: conftest-based) -if time permis ... +if time permits ... ========================================= - let's play with "pytest_unittest" plugin @@ -446,7 +453,6 @@ def pytest_item_makereport(self, item, excinfo, when, outerr): """ return ItemTestReport event for the given test outcome. """ - Reporting hooks ========================== :: From hpk at codespeak.net Thu Mar 26 13:50:15 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 26 Mar 2009 13:50:15 +0100 (CET) Subject: [py-svn] r63359 - in py/trunk/py: . execnet/testing test test/dist/testing test/plugin test/testing Message-ID: <20090326125015.7DE3F16852A@codespeak.net> Author: hpk Date: Thu Mar 26 13:50:12 2009 New Revision: 63359 Modified: py/trunk/py/conftest.py py/trunk/py/execnet/testing/test_gwmanage.py py/trunk/py/test/dist/testing/test_nodemanage.py py/trunk/py/test/dist/testing/test_txnode.py py/trunk/py/test/plugin/pytest_iocapture.py py/trunk/py/test/plugin/pytest_monkeypatch.py py/trunk/py/test/plugin/pytest_plugintester.py py/trunk/py/test/plugin/pytest_pytester.py py/trunk/py/test/plugin/pytest_restdoc.py py/trunk/py/test/plugin/pytest_tmpdir.py py/trunk/py/test/pycollect.py py/trunk/py/test/testing/test_pickling.py py/trunk/py/test/testing/test_pycollect.py py/trunk/py/test/testing/test_traceback.py Log: change funcargs naming to use __ Modified: py/trunk/py/conftest.py ============================================================================== --- py/trunk/py/conftest.py (original) +++ py/trunk/py/conftest.py Thu Mar 26 13:50:12 2009 @@ -3,9 +3,9 @@ import py class PylibTestconfigPlugin: - def pytest_funcarg_specssh(self, pyfuncitem): + def pytest_funcarg__specssh(self, pyfuncitem): return getspecssh(pyfuncitem.config) - def pytest_funcarg_specsocket(self, pyfuncitem): + def pytest_funcarg__specsocket(self, pyfuncitem): return getsocketspec(pyfuncitem.config) def pytest_addoption(self, parser): Modified: py/trunk/py/execnet/testing/test_gwmanage.py ============================================================================== --- py/trunk/py/execnet/testing/test_gwmanage.py (original) +++ py/trunk/py/execnet/testing/test_gwmanage.py Thu Mar 26 13:50:12 2009 @@ -111,9 +111,9 @@ assert l[0].startswith(curwd) assert l[0].endswith("hello") -def pytest_funcarg_source(pyfuncitem): +def pytest_funcarg__source(pyfuncitem): return py.test.ensuretemp(pyfuncitem.getmodpath()).mkdir("source") -def pytest_funcarg_dest(pyfuncitem): +def pytest_funcarg__dest(pyfuncitem): return py.test.ensuretemp(pyfuncitem.getmodpath()).mkdir("dest") class TestHRSync: Modified: py/trunk/py/test/dist/testing/test_nodemanage.py ============================================================================== --- py/trunk/py/test/dist/testing/test_nodemanage.py (original) +++ py/trunk/py/test/dist/testing/test_nodemanage.py Thu Mar 26 13:50:12 2009 @@ -7,9 +7,9 @@ from py.__.test import event -def pytest_funcarg_source(pyfuncitem): +def pytest_funcarg__source(pyfuncitem): return py.test.ensuretemp(pyfuncitem.getmodpath()).mkdir("source") -def pytest_funcarg_dest(pyfuncitem): +def pytest_funcarg__dest(pyfuncitem): dest = py.test.ensuretemp(pyfuncitem.getmodpath()).mkdir("dest") return dest Modified: py/trunk/py/test/dist/testing/test_txnode.py ============================================================================== --- py/trunk/py/test/dist/testing/test_txnode.py (original) +++ py/trunk/py/test/dist/testing/test_txnode.py Thu Mar 26 13:50:12 2009 @@ -54,12 +54,12 @@ print "exiting:", gw gw.exit() -def pytest_funcarg_mysetup(pyfuncitem): +def pytest_funcarg__mysetup(pyfuncitem): mysetup = MySetup(pyfuncitem) pyfuncitem.addfinalizer(mysetup.finalize) return mysetup -def pytest_funcarg_testdir(__call__, pyfuncitem): +def pytest_funcarg__testdir(__call__, pyfuncitem): # decorate to make us always change to testdir testdir = __call__.execute(firstresult=True) testdir.chdir() Modified: py/trunk/py/test/plugin/pytest_iocapture.py ============================================================================== --- py/trunk/py/test/plugin/pytest_iocapture.py (original) +++ py/trunk/py/test/plugin/pytest_iocapture.py Thu Mar 26 13:50:12 2009 @@ -2,12 +2,12 @@ class IocapturePlugin: """ capture sys.stdout/sys.stderr / fd1/fd2. """ - def pytest_funcarg_stdcapture(self, pyfuncitem): + def pytest_funcarg__stdcapture(self, pyfuncitem): capture = Capture(py.io.StdCapture) pyfuncitem.addfinalizer(capture.finalize) return capture - def pytest_funcarg_stdcapturefd(self, pyfuncitem): + def pytest_funcarg__stdcapturefd(self, pyfuncitem): capture = Capture(py.io.StdCaptureFD) pyfuncitem.addfinalizer(capture.finalize) return capture Modified: py/trunk/py/test/plugin/pytest_monkeypatch.py ============================================================================== --- py/trunk/py/test/plugin/pytest_monkeypatch.py (original) +++ py/trunk/py/test/plugin/pytest_monkeypatch.py Thu Mar 26 13:50:12 2009 @@ -2,7 +2,7 @@ class MonkeypatchPlugin: """ setattr-monkeypatching with automatical reversal after test. """ - def pytest_funcarg_monkeypatch(self, pyfuncitem): + def pytest_funcarg__monkeypatch(self, pyfuncitem): monkeypatch = MonkeyPatch() pyfuncitem.addfinalizer(monkeypatch.finalize) return monkeypatch Modified: py/trunk/py/test/plugin/pytest_plugintester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_plugintester.py (original) +++ py/trunk/py/test/plugin/pytest_plugintester.py Thu Mar 26 13:50:12 2009 @@ -5,7 +5,7 @@ class PlugintesterPlugin: """ test support code for testing pytest plugins. """ - def pytest_funcarg_plugintester(self, pyfuncitem): + def pytest_funcarg__plugintester(self, pyfuncitem): pt = PluginTester(pyfuncitem) pyfuncitem.addfinalizer(pt.finalize) return pt @@ -46,7 +46,7 @@ getargs = py.std.inspect.getargs def isgenerichook(name): - return name.startswith("pytest_funcarg_") + return name.startswith("pytest_funcarg__") while methods: name, method = methods.popitem() Modified: py/trunk/py/test/plugin/pytest_pytester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_pytester.py (original) +++ py/trunk/py/test/plugin/pytest_pytester.py Thu Mar 26 13:50:12 2009 @@ -7,21 +7,21 @@ from py.__.test.config import Config as pytestConfig class PytesterPlugin: - def pytest_funcarg_linecomp(self, pyfuncitem): + def pytest_funcarg__linecomp(self, pyfuncitem): return LineComp() - def pytest_funcarg_LineMatcher(self, pyfuncitem): + def pytest_funcarg__LineMatcher(self, pyfuncitem): return LineMatcher - def pytest_funcarg_testdir(self, pyfuncitem): + def pytest_funcarg__testdir(self, pyfuncitem): tmptestdir = TmpTestdir(pyfuncitem) pyfuncitem.addfinalizer(tmptestdir.finalize) return tmptestdir - def pytest_funcarg_EventRecorder(self, pyfuncitem): + def pytest_funcarg__EventRecorder(self, pyfuncitem): return EventRecorder - def pytest_funcarg_eventrecorder(self, pyfuncitem): + def pytest_funcarg__eventrecorder(self, pyfuncitem): evrec = EventRecorder(py._com.pyplugins) pyfuncitem.addfinalizer(lambda: evrec.pyplugins.unregister(evrec)) return evrec Modified: py/trunk/py/test/plugin/pytest_restdoc.py ============================================================================== --- py/trunk/py/test/plugin/pytest_restdoc.py (original) +++ py/trunk/py/test/plugin/pytest_restdoc.py Thu Mar 26 13:50:12 2009 @@ -328,7 +328,7 @@ "resolve_linkrole('source', 'py/foo/bar.py')") -def pytest_funcarg_testdir(__call__, pyfuncitem): +def pytest_funcarg__testdir(__call__, pyfuncitem): testdir = __call__.execute(firstresult=True) testdir.makepyfile(confrest="from py.__.misc.rest import Project") testdir.plugins.append(RestdocPlugin()) Modified: py/trunk/py/test/plugin/pytest_tmpdir.py ============================================================================== --- py/trunk/py/test/plugin/pytest_tmpdir.py (original) +++ py/trunk/py/test/plugin/pytest_tmpdir.py Thu Mar 26 13:50:12 2009 @@ -13,7 +13,7 @@ """ provide temporary directories to test functions and methods. """ - def pytest_funcarg_tmpdir(self, pyfuncitem): + def pytest_funcarg__tmpdir(self, pyfuncitem): name = pyfuncitem.name return pyfuncitem.config.mktemp(name, numbered=True) @@ -29,7 +29,7 @@ def test_funcarg(testdir): item = testdir.getitem("def test_func(tmpdir): pass") plugin = TmpdirPlugin() - p = plugin.pytest_funcarg_tmpdir(item) + p = plugin.pytest_funcarg__tmpdir(item) assert p.check() bn = p.basename.strip("0123456789-") assert bn.endswith("test_func") Modified: py/trunk/py/test/pycollect.py ============================================================================== --- py/trunk/py/test/pycollect.py (original) +++ py/trunk/py/test/pycollect.py Thu Mar 26 13:50:12 2009 @@ -375,7 +375,7 @@ return kwargs def lookup_onearg(self, argname): - prefix = "pytest_funcarg_" + prefix = "pytest_funcarg__" #makerlist = self.config.pytestplugins.listattr(prefix + argname) value = self.config.pytestplugins.call_firstresult(prefix + argname, pyfuncitem=self) if value is not None: @@ -383,7 +383,7 @@ else: self._raisefuncargerror(argname, prefix) - def _raisefuncargerror(self, argname, prefix="pytest_funcarg_"): + def _raisefuncargerror(self, argname, prefix="pytest_funcarg__"): metainfo = self.repr_metainfo() available = [] plugins = self.config.pytestplugins._plugins.values() Modified: py/trunk/py/test/testing/test_pickling.py ============================================================================== --- py/trunk/py/test/testing/test_pickling.py (original) +++ py/trunk/py/test/testing/test_pickling.py Thu Mar 26 13:50:12 2009 @@ -1,6 +1,6 @@ import py -def pytest_funcarg_pickletransport(pyfuncitem): +def pytest_funcarg__pickletransport(pyfuncitem): return ImmutablePickleTransport() def pytest_pyfunc_call(__call__, pyfuncitem, args, kwargs): Modified: py/trunk/py/test/testing/test_pycollect.py ============================================================================== --- py/trunk/py/test/testing/test_pycollect.py (original) +++ py/trunk/py/test/testing/test_pycollect.py Thu Mar 26 13:50:12 2009 @@ -252,7 +252,7 @@ def test_funcarg_lookupfails(self, testdir): testdir.makeconftest(""" class ConftestPlugin: - def pytest_funcarg_something(self, pyfuncitem): + def pytest_funcarg__something(self, pyfuncitem): return 42 """) item = testdir.getitem("def test_func(some): pass") @@ -263,7 +263,7 @@ def test_funcarg_lookup_default(self, testdir): item = testdir.getitem("def test_func(some, other=42): pass") class Provider: - def pytest_funcarg_some(self, pyfuncitem): + def pytest_funcarg__some(self, pyfuncitem): return pyfuncitem.name item.config.pytestplugins.register(Provider()) kw = item.lookup_allargs() @@ -272,7 +272,7 @@ def test_funcarg_lookup_default_gets_overriden(self, testdir): item = testdir.getitem("def test_func(some=42, other=13): pass") class Provider: - def pytest_funcarg_other(self, pyfuncitem): + def pytest_funcarg__other(self, pyfuncitem): return pyfuncitem.name item.config.pytestplugins.register(Provider()) kw = item.lookup_allargs() @@ -284,9 +284,9 @@ def test_funcarg_basic(self, testdir): item = testdir.getitem("def test_func(some, other): pass") class Provider: - def pytest_funcarg_some(self, pyfuncitem): + def pytest_funcarg__some(self, pyfuncitem): return pyfuncitem.name - def pytest_funcarg_other(self, pyfuncitem): + def pytest_funcarg__other(self, pyfuncitem): return 42 item.config.pytestplugins.register(Provider()) kw = item.lookup_allargs() @@ -298,7 +298,7 @@ item = testdir.getitem("def test_func(some): pass") l = [] class Provider: - def pytest_funcarg_some(self, pyfuncitem): + def pytest_funcarg__some(self, pyfuncitem): pyfuncitem.addfinalizer(lambda: l.append(42)) return 3 item.config.pytestplugins.register(Provider()) @@ -312,7 +312,7 @@ def test_funcarg_lookup_modulelevel(self, testdir): modcol = testdir.getmodulecol(""" - def pytest_funcarg_something(pyfuncitem): + def pytest_funcarg__something(pyfuncitem): return pyfuncitem.name class TestClass: Modified: py/trunk/py/test/testing/test_traceback.py ============================================================================== --- py/trunk/py/test/testing/test_traceback.py (original) +++ py/trunk/py/test/testing/test_traceback.py Thu Mar 26 13:50:12 2009 @@ -10,7 +10,7 @@ def test_traceback_argsetup(self, testdir): testdir.makeconftest(""" class ConftestPlugin: - def pytest_funcarg_hello(self, pyfuncitem): + def pytest_funcarg__hello(self, pyfuncitem): raise ValueError("xyz") """) p = testdir.makepyfile("def test(hello): pass") From hpk at codespeak.net Thu Mar 26 13:51:48 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 26 Mar 2009 13:51:48 +0100 (CET) Subject: [py-svn] r63360 - in py/trunk: . py Message-ID: <20090326125148.3904316852E@codespeak.net> Author: hpk Date: Thu Mar 26 13:51:47 2009 New Revision: 63360 Modified: py/trunk/py/__init__.py py/trunk/setup.py Log: bumping to first beta Modified: py/trunk/py/__init__.py ============================================================================== --- py/trunk/py/__init__.py (original) +++ py/trunk/py/__init__.py Thu Mar 26 13:51:47 2009 @@ -21,7 +21,7 @@ """ from initpkg import initpkg -version = "1.0.0a9" +version = "1.0.0b1" initpkg(__name__, description = "pylib and py.test: agile development and test support library", @@ -36,7 +36,7 @@ author_email = "holger at merlinux.eu, py-dev at codespeak.net", long_description = globals()['__doc__'], classifiers = [ - "Development Status :: 3 - Alpha", + "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: POSIX", Modified: py/trunk/setup.py ============================================================================== --- py/trunk/setup.py (original) +++ py/trunk/setup.py Thu Mar 26 13:51:47 2009 @@ -38,9 +38,9 @@ name='py', description='pylib and py.test: agile development and test support library', long_description = long_description, - version='1.0.0a9', + version='1.0.0b1', url='http://pylib.org', - download_url='http://codespeak.net/py/1.0.0a9/download.html', + download_url='http://codespeak.net/py/1.0.0b1/download.html', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel, Guido Wesdorp, Carl Friedrich Bolz, Armin Rigo, Maciej Fijalkowski & others', @@ -57,7 +57,7 @@ 'py.svnwcrevert = py.cmdline:pysvnwcrevert', 'py.test = py.cmdline:pytest', 'py.which = py.cmdline:pywhich']}, - classifiers=['Development Status :: 3 - Alpha', + classifiers=['Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: POSIX', From hpk at codespeak.net Thu Mar 26 13:52:10 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 26 Mar 2009 13:52:10 +0100 (CET) Subject: [py-svn] r63361 - py/dist Message-ID: <20090326125210.792B116852E@codespeak.net> Author: hpk Date: Thu Mar 26 13:52:10 2009 New Revision: 63361 Added: py/dist/ - copied from r63360, py/trunk/ Log: recopy to dist From hpk at codespeak.net Thu Mar 26 14:56:06 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 26 Mar 2009 14:56:06 +0100 (CET) Subject: [py-svn] r63363 - py/extradoc/talk/pycon-us-2009/pytest-advanced Message-ID: <20090326135606.3E4ED168528@codespeak.net> Author: hpk Date: Thu Mar 26 14:56:00 2009 New Revision: 63363 Added: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.pdf Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Log: current set Added: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.pdf ============================================================================== Files (empty file) and py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.pdf Thu Mar 26 14:56:00 2009 differ Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Thu Mar 26 14:56:00 2009 @@ -114,6 +114,15 @@ you may discuss a long time about categorizations ... +py.test strives to test it all +================================= + +current focus: + +- unittesting +- functional +- integration tests + A pragmatic view on test types ================================= From hpk at codespeak.net Thu Mar 26 14:59:17 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 26 Mar 2009 14:59:17 +0100 (CET) Subject: [py-svn] r63364 - py/extradoc/talk/pycon-us-2009/pytest-advanced Message-ID: <20090326135917.9955E16852A@codespeak.net> Author: hpk Date: Thu Mar 26 14:59:16 2009 New Revision: 63364 Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.pdf py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Log: new set Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.pdf ============================================================================== Files py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.pdf (original) and py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.pdf Thu Mar 26 14:59:16 2009 differ Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Thu Mar 26 14:59:16 2009 @@ -5,6 +5,17 @@ Advanced cross platform testing ================================================================= +If you have time, please install +======================================== + +- svn checkout http://codespeak.net/svn/py/dist + +- run "python setup.py" with "install" or "develop" + +- if need be: easy_install "py" + +- slides: http://tinyurl.com/c76gve + my technical background =========================== @@ -259,15 +270,6 @@ **automatic discovery avoids boilerplate** -Hands on: Installing py.test -======================================== - -- install py.test - -- svn checkout http://codespeak.net/svn/py/dist - -- run "python setup.py" with "install" or "develop" - Exercise ========================================== From hpk at codespeak.net Sat Mar 28 11:44:19 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 28 Mar 2009 11:44:19 +0100 (CET) Subject: [py-svn] r63402 - py/extradoc/talk/pycon-us-2009/pytest-advanced Message-ID: <20090328104419.C9D001684D3@codespeak.net> Author: hpk Date: Sat Mar 28 11:44:16 2009 New Revision: 63402 Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.pdf py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Log: version as held Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.pdf ============================================================================== Files py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.pdf (original) and py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.pdf Sat Mar 28 11:44:16 2009 differ Modified: py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt ============================================================================== --- py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt (original) +++ py/extradoc/talk/pycon-us-2009/pytest-advanced/pytest-advanced.txt Sat Mar 28 11:44:16 2009 @@ -527,12 +527,14 @@ ================================================== :: + py.test --dist=each --tx popen//python=python2.4 send test to three different interpreters ================================================== :: + py.test --dist=each \ --tx=popen//python=python2.4 \ --tx=popen//python=python2.5 \ @@ -556,6 +558,7 @@ ======================================== :: + py.test --d --tx ssh=myhostpopen --rsyncdir mypkg mypkg Sending tests to remote socket servers @@ -565,7 +568,7 @@ http://codespeak.net/svn/py/dist/py/execnet/script/socketserver.py -Assuming an IP address, you can now tell py.test to distribute: +Assuming an IP address, you can now tell py.test to distribute: : py.test -d --tx socket=192.168.1.102:8888 --rsyncdir mypkg mypkg @@ -583,6 +586,7 @@ ======================================== :: + py.test --dist=each mypkg py.test --dist=load mypkg From briandorsey at codespeak.net Mon Mar 30 00:10:25 2009 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Mon, 30 Mar 2009 00:10:25 +0200 (CEST) Subject: [py-svn] r63427 - py/trunk/py/test/plugin Message-ID: <20090329221025.6F4C7168438@codespeak.net> Author: briandorsey Date: Mon Mar 30 00:10:24 2009 New Revision: 63427 Added: py/trunk/py/test/plugin/pytest_figleaf.py - copied unchanged from r63426, user/briandorsey/pytest_plugins/pytest_figleaf.py Log: added figleaf plugin to pylib From briandorsey at codespeak.net Mon Mar 30 00:40:47 2009 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Mon, 30 Mar 2009 00:40:47 +0200 (CEST) Subject: [py-svn] r63428 - py/trunk/py/test/plugin Message-ID: <20090329224047.6B0EE168430@codespeak.net> Author: briandorsey Date: Mon Mar 30 00:40:44 2009 New Revision: 63428 Modified: py/trunk/py/test/plugin/pytest_figleaf.py Log: added happy path tests for figleaf plugin Modified: py/trunk/py/test/plugin/pytest_figleaf.py ============================================================================== --- py/trunk/py/test/plugin/pytest_figleaf.py (original) +++ py/trunk/py/test/plugin/pytest_figleaf.py Mon Mar 30 00:40:44 2009 @@ -45,4 +45,21 @@ self.figleaf.annotate_html.report_as_html(coverage, str(reportdir), exclude, {}) +def test_generic(plugintester): + plugintester.apicheck(FigleafPlugin) +def test_functional(testdir): + testdir.plugins.append('figleaf') + testdir.makepyfile(""" + def f(): + x = 42 + def test_whatever(): + pass + """) + result = testdir.runpytest('-F') + assert result.ret == 0 + assert result.stdout.fnmatch_lines([ + '*figleaf html*' + ]) + print result.stdout.str() + assert 0 From briandorsey at codespeak.net Mon Mar 30 01:12:42 2009 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Mon, 30 Mar 2009 01:12:42 +0200 (CEST) Subject: [py-svn] r63429 - py/trunk/py/test/plugin Message-ID: <20090329231242.441A0168432@codespeak.net> Author: briandorsey Date: Mon Mar 30 01:12:41 2009 New Revision: 63429 Modified: py/trunk/py/test/plugin/pytest_figleaf.py Log: pytest_figleaf now filters out all coverage for files outside of the tested package Modified: py/trunk/py/test/plugin/pytest_figleaf.py ============================================================================== --- py/trunk/py/test/plugin/pytest_figleaf.py (original) +++ py/trunk/py/test/plugin/pytest_figleaf.py Mon Mar 30 01:12:41 2009 @@ -25,26 +25,36 @@ def pytest_terminal_summary(self, terminalreporter): if hasattr(self, 'figleaf'): - data_file = terminalreporter.config.getvalue('figleafdata') - data_file = py.path.local(data_file) + datafile = terminalreporter.config.getvalue('figleafdata') + datafile = py.path.local(datafile) tw = terminalreporter._tw tw.sep('-', 'figleaf') - tw.line('Writing figleaf data to %s' % (data_file)) + tw.line('Writing figleaf data to %s' % (datafile)) self.figleaf.stop() - self.figleaf.write_coverage(str(data_file)) - data = self.figleaf.read_coverage(str(data_file)) - d = {} - coverage = self.figleaf.combine_coverage(d, data) - # TODO exclude pylib - exclude = [] + self.figleaf.write_coverage(str(datafile)) + coverage = self.get_coverage(datafile, + terminalreporter.config.topdir) reportdir = terminalreporter.config.getvalue('figleafhtml') reportdir = py.path.local(reportdir) tw.line('Writing figleaf html to file://%s' % (reportdir)) self.figleaf.annotate_html.prepare_reportdir(str(reportdir)) + exclude = [] self.figleaf.annotate_html.report_as_html(coverage, str(reportdir), exclude, {}) + def get_coverage(self, datafile, topdir): + data = self.figleaf.read_coverage(str(datafile)) + d = {} + coverage = self.figleaf.combine_coverage(d, data) + for path in coverage.keys(): + if not py.path.local(path).relto(topdir): + del coverage[path] + + return coverage + + + def test_generic(plugintester): plugintester.apicheck(FigleafPlugin) @@ -62,4 +72,3 @@ '*figleaf html*' ]) print result.stdout.str() - assert 0 From jwhitlark at codespeak.net Mon Mar 30 01:57:58 2009 From: jwhitlark at codespeak.net (jwhitlark at codespeak.net) Date: Mon, 30 Mar 2009 01:57:58 +0200 (CEST) Subject: [py-svn] r63430 - py/trunk/py/test/plugin Message-ID: <20090329235758.404E1168435@codespeak.net> Author: jwhitlark Date: Mon Mar 30 01:57:55 2009 New Revision: 63430 Added: py/trunk/py/test/plugin/pytest_pylint.py Log: First cut of pylint plugin. Not in working state Added: py/trunk/py/test/plugin/pytest_pylint.py ============================================================================== --- (empty file) +++ py/trunk/py/test/plugin/pytest_pylint.py Mon Mar 30 01:57:55 2009 @@ -0,0 +1,57 @@ +"""pylint plugin + + +XXX: Currently in progress, NOT IN WORKING STATE. +""" +import py + +class PylintPlugin: + def pytest_addoption(self, parser): + group = parser.addgroup('pylint options') + group.addoption('--pylint', action='store_true', + default=False, dest='pylint', + help='Pylint coverate of test files.') + + def pytest_configure(self, config): + if config.getvalue('pylint'): + try: + from pylint import lint + self.lint = lint + except ImportError: + raise config.Error('Could not import pylint module') + print "trying to configure pytest" + + def pytest_collect_file(self, path, parent): + if path.ext == ".py": + if parent.config.getvalue('pylint'): + return PylintItem(path, parent, self.lint) + + def pytest_terminal_summary(self, terminalreporter): + if hasattr(self, 'lint'): + print 'placeholder for pylint output' + + + +class PylintItem(py.test.collect.Item): + def __init__(self, path, parent, lintlib): + name = self.__class__.__name__ + ":" + path.basename + super(PylintItem, self).__init__(name=name, parent=parent) + self.fspath = path + self.lint = lintlib + + def runtest(self): + # run lint here + capture = py.io.StdCaptureFD() + #pylib.org has docs on py.io.stdcaptureFD + self.linter = self.lint.PyLinter() #TODO: should this be in the PylintPlugin? + self.linter.check(str(self.fspath)) + out, err = capture.reset() + rating = out.strip().split('\n')[-1] + print ">>>", + print rating + +def test_generic(plugintester): + plugintester.apicheck(PylintPlugin) + +#def test_functional + From hpk at codespeak.net Tue Mar 31 19:58:02 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 31 Mar 2009 19:58:02 +0200 (CEST) Subject: [py-svn] r63461 - py/trunk/py/test/plugin Message-ID: <20090331175802.B33611683FE@codespeak.net> Author: hpk Date: Tue Mar 31 19:58:02 2009 New Revision: 63461 Modified: py/trunk/py/test/plugin/pytest_default.py py/trunk/py/test/plugin/pytest_doctest.py py/trunk/py/test/plugin/pytest_figleaf.py py/trunk/py/test/plugin/pytest_plugintester.py py/trunk/py/test/plugin/pytest_pytester.py py/trunk/py/test/plugin/pytest_terminal.py Log: a few fixes, now figleaf writes files correctly. also changed figleaf plugin to include only the files of the current working dir. Modified: py/trunk/py/test/plugin/pytest_default.py ============================================================================== --- py/trunk/py/test/plugin/pytest_default.py (original) +++ py/trunk/py/test/plugin/pytest_default.py Tue Mar 31 19:58:02 2009 @@ -5,7 +5,6 @@ def pytest_pyfunc_call(self, pyfuncitem, args, kwargs): pyfuncitem.obj(*args, **kwargs) - return def pytest_collect_file(self, path, parent): ext = path.ext Modified: py/trunk/py/test/plugin/pytest_doctest.py ============================================================================== --- py/trunk/py/test/plugin/pytest_doctest.py (original) +++ py/trunk/py/test/plugin/pytest_doctest.py Tue Mar 31 19:58:02 2009 @@ -2,7 +2,8 @@ class DoctestPlugin: def pytest_addoption(self, parser): - parser.addoption("--doctest-modules", + group = parser.addgroup("doctest options") + group.addoption("--doctest-modules", action="store_true", default=False, dest="doctestmodules") Modified: py/trunk/py/test/plugin/pytest_figleaf.py ============================================================================== --- py/trunk/py/test/plugin/pytest_figleaf.py (original) +++ py/trunk/py/test/plugin/pytest_figleaf.py Tue Mar 31 19:58:02 2009 @@ -5,7 +5,8 @@ group = parser.addgroup('figleaf options') group.addoption('-F', action='store_true', default=False, dest = 'figleaf', - help='trace coverage with figleaf and write HTML') + help=('trace coverage with figleaf and write HTML ' + 'for files below the current working dir')) group.addoption('--figleaf-data', action='store', default='.figleaf', dest='figleafdata', help='path coverage tracing file.') @@ -25,32 +26,31 @@ def pytest_terminal_summary(self, terminalreporter): if hasattr(self, 'figleaf'): - datafile = terminalreporter.config.getvalue('figleafdata') - datafile = py.path.local(datafile) + config = terminalreporter.config + datafile = py.path.local(config.getvalue('figleafdata')) tw = terminalreporter._tw tw.sep('-', 'figleaf') tw.line('Writing figleaf data to %s' % (datafile)) self.figleaf.stop() self.figleaf.write_coverage(str(datafile)) - coverage = self.get_coverage(datafile, - terminalreporter.config.topdir) + coverage = self.get_coverage(datafile, config) - reportdir = terminalreporter.config.getvalue('figleafhtml') - reportdir = py.path.local(reportdir) + reportdir = py.path.local(config.getvalue('figleafhtml')) tw.line('Writing figleaf html to file://%s' % (reportdir)) self.figleaf.annotate_html.prepare_reportdir(str(reportdir)) exclude = [] self.figleaf.annotate_html.report_as_html(coverage, str(reportdir), exclude, {}) - def get_coverage(self, datafile, topdir): + def get_coverage(self, datafile, config): + # basepath = config.topdir + basepath = py.path.local() data = self.figleaf.read_coverage(str(datafile)) d = {} coverage = self.figleaf.combine_coverage(d, data) for path in coverage.keys(): - if not py.path.local(path).relto(topdir): + if not py.path.local(path).relto(basepath): del coverage[path] - return coverage @@ -71,4 +71,4 @@ assert result.stdout.fnmatch_lines([ '*figleaf html*' ]) - print result.stdout.str() + #print result.stdout.str() Modified: py/trunk/py/test/plugin/pytest_plugintester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_plugintester.py (original) +++ py/trunk/py/test/plugin/pytest_plugintester.py Tue Mar 31 19:58:02 2009 @@ -29,6 +29,7 @@ # FSTester = self.pyfuncitem.config.pytestplugins.getpluginattr("pytester", "FSTester") from pytest_pytester import TmpTestdir crunner = TmpTestdir(self.pyfuncitem) + self.pyfuncitem.addfinalizer(crunner.finalize) # for colitem in self.pyfuncitem.listchain(): if isinstance(colitem, py.test.collect.Module) and \ Modified: py/trunk/py/test/plugin/pytest_pytester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_pytester.py (original) +++ py/trunk/py/test/plugin/pytest_pytester.py Tue Mar 31 19:58:02 2009 @@ -15,7 +15,6 @@ def pytest_funcarg__testdir(self, pyfuncitem): tmptestdir = TmpTestdir(pyfuncitem) - pyfuncitem.addfinalizer(tmptestdir.finalize) return tmptestdir def pytest_funcarg__EventRecorder(self, pyfuncitem): @@ -56,6 +55,11 @@ self.plugins = [] self._syspathremove = [] self.chdir() # always chdir + assert hasattr(self, '_olddir') + self.pyfuncitem.addfinalizer(self.finalize) + + def __repr__(self): + return "" % (self.tmpdir,) def Config(self, pyplugins=None, topdir=None): if topdir is None: @@ -69,7 +73,7 @@ self._olddir.chdir() def chdir(self): - old = self.testdir.chdir() + old = self.tmpdir.chdir() if not hasattr(self, '_olddir'): self._olddir = old @@ -110,9 +114,6 @@ def mkdir(self, name): return self.tmpdir.mkdir(name) - def chdir(self): - return self.tmpdir.chdir() - def genitems(self, colitems): return list(self.session.genitems(colitems)) Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Tue Mar 31 19:58:02 2009 @@ -29,6 +29,7 @@ self.gateway2info = {} def write_fspath_result(self, fspath, res): + fspath = self.curdir.bestrelpath(fspath) if fspath != self.currentfspath: self._tw.line() relpath = self.curdir.bestrelpath(fspath) @@ -138,6 +139,8 @@ def pyevent_itemstart(self, item, node=None): if self.config.option.debug: info = item.repr_metainfo() +node +n line = info.verboseline(basedir=self.curdir) + " " extra = "" if node: From hpk at codespeak.net Tue Mar 31 19:58:38 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 31 Mar 2009 19:58:38 +0200 (CEST) Subject: [py-svn] r63462 - py/trunk/py/test/plugin Message-ID: <20090331175838.67FD41683FE@codespeak.net> Author: hpk Date: Tue Mar 31 19:58:37 2009 New Revision: 63462 Modified: py/trunk/py/test/plugin/pytest_terminal.py Log: fix syntax error (ehem) Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Tue Mar 31 19:58:37 2009 @@ -139,8 +139,6 @@ def pyevent_itemstart(self, item, node=None): if self.config.option.debug: info = item.repr_metainfo() -node -n line = info.verboseline(basedir=self.curdir) + " " extra = "" if node: