diff --git a/py/execnet/gwmanage.py b/py/execnet/gwmanage.py index 0d7c0efa1..e32fb3c0a 100644 --- a/py/execnet/gwmanage.py +++ b/py/execnet/gwmanage.py @@ -22,7 +22,6 @@ from py.__.test import event 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 @@ class GatewaySpec(object): 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 @@ class GatewaySpec(object): 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 @@ class MultiChannel: 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 wait(self): + def receive(self): + return [x[1] for x in self.receive_items()] + + def waitclose(self): for ch in self._channels: ch.waitclose() @@ -91,8 +101,7 @@ class GatewayManager: 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 @@ class GatewayManager: 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 @@ class GatewayManager: 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(): diff --git a/py/execnet/testing/test_gwmanage.py b/py/execnet/testing/test_gwmanage.py index fb70de30a..30623db64 100644 --- a/py/execnet/testing/test_gwmanage.py +++ b/py/execnet/testing/test_gwmanage.py @@ -155,7 +155,11 @@ class TestGatewayManagerPopen: 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 @@ class TestGatewayManagerPopen: 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() - assert len(l) == 2 - assert l == [12, 12] - def test_multichannel_wait(self): + pc1 = pseudochannel() + pc2 = pseudochannel() + multichannel = MultiChannel([pc1, pc2]) + l = multichannel.receive_items() + assert len(l) == 2 + assert l == [(pc1, 12), (pc2, 12)] + + 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 diff --git a/py/test/config.py b/py/test/config.py index 880bdc47f..7e0572778 100644 --- a/py/test/config.py +++ b/py/test/config.py @@ -39,9 +39,17 @@ class Config(object): 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 @@ class Config(object): 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. diff --git a/py/test/dsession/dsession.py b/py/test/dsession/dsession.py index 058b13074..0958dcf62 100644 --- a/py/test/dsession/dsession.py +++ b/py/test/dsession/dsession.py @@ -44,9 +44,9 @@ class DSession(Session): 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 diff --git a/py/test/dsession/hostmanage.py b/py/test/dsession/hostmanage.py index 5d53c9f4b..5778b6cb5 100644 --- a/py/test/dsession/hostmanage.py +++ b/py/test/dsession/hostmanage.py @@ -8,19 +8,34 @@ def getconfighosts(config): 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 @@ class HostManager(object): 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 @@ class HostManager(object): # (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 @@ class HostManager(object): 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, diff --git a/py/test/dsession/testing/test_functional_dsession.py b/py/test/dsession/testing/test_functional_dsession.py index cd47828e4..22988500f 100644 --- a/py/test/dsession/testing/test_functional_dsession.py +++ b/py/test/dsession/testing/test_functional_dsession.py @@ -39,7 +39,6 @@ class TestAsyncFunctional: ]) 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 @@ class TestAsyncFunctional: 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 @@ class TestAsyncFunctional: 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 @@ class TestAsyncFunctional: 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 @@ class TestAsyncFunctional: 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 @@ class TestAsyncFunctional: 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 diff --git a/py/test/dsession/testing/test_hostmanage.py b/py/test/dsession/testing/test_hostmanage.py index f32aae590..30047eda7 100644 --- a/py/test/dsession/testing/test_hostmanage.py +++ b/py/test/dsession/testing/test_hostmanage.py @@ -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 @@ def pytest_pyfuncarg_dest(pyfuncitem): 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 @@ class TestHostManager: 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 @@ class TestHostManager: 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 @@ class TestHostManager: 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 @@ class TestHostManager: 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 @@ class TestHostManager: 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) + evrec = EventRecorder(config.bus, debug=True) + hm.setup_hosts(putevent=[].append) for host in hm.gwmanager.spec2gateway: - eventcall = queue.get(timeout=2.0) - name, args, kwargs = eventcall - assert name == "hostup" - 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 XXXtest_ssh_rsync_samehost_twice(self): - #XXX we have no easy way to have a temp directory remotely! + 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 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 @@ class TestHostManager: 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 + diff --git a/py/test/dsession/testing/test_masterslave.py b/py/test/dsession/testing/test_masterslave.py index 869c9b5ac..6fc61e97b 100644 --- a/py/test/dsession/testing/test_masterslave.py +++ b/py/test/dsession/testing/test_masterslave.py @@ -43,7 +43,7 @@ class MySetup: 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() diff --git a/py/test/plugin/pytest_default.py b/py/test/plugin/pytest_default.py index 235bc6c5c..bfd24116a 100644 --- a/py/test/plugin/pytest_default.py +++ b/py/test/plugin/pytest_default.py @@ -36,9 +36,6 @@ class DefaultPlugin: 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 @@ class DefaultPlugin: "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."), - 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', - action="store_true", dest="debug", default=False, - help="turn on debugging information."), - - group = parser.addgroup("experimental", "experimental options") - 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)." - ), + 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('--rest', - action='store_true', dest="restreport", default=False, - help="restructured text output reporting."), + group._addoption('-f', '--looponfailing', + action="store_true", dest="looponfailing", default=False, + 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="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', '--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) diff --git a/py/test/plugin/pytest_plugintester.py b/py/test/plugin/pytest_plugintester.py index 0b9b432d2..47e52ad23 100644 --- a/py/test/plugin/pytest_plugintester.py +++ b/py/test/plugin/pytest_plugintester.py @@ -155,6 +155,9 @@ class PytestPluginHooks: 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. """ diff --git a/py/test/plugin/pytest_pytester.py b/py/test/plugin/pytest_pytester.py index 2ec7050f3..a1235a784 100644 --- a/py/test/plugin/pytest_pytester.py +++ b/py/test/plugin/pytest_pytester.py @@ -248,10 +248,8 @@ class EventRecorder(object): 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 = [] diff --git a/py/test/plugin/pytest_terminal.py b/py/test/plugin/pytest_terminal.py index 5cd5e7373..a0e29edcc 100644 --- a/py/test/plugin/pytest_terminal.py +++ b/py/test/plugin/pytest_terminal.py @@ -3,11 +3,6 @@ import sys 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 @@ class TerminalReporter: 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 @@ class TerminalReporter: 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)) diff --git a/py/test/session.py b/py/test/session.py index 3ba4fa005..4bd4d1486 100644 --- a/py/test/session.py +++ b/py/test/session.py @@ -29,9 +29,9 @@ class Session(object): 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." diff --git a/py/test/testing/acceptance_test.py b/py/test/testing/acceptance_test.py index 0e8164b5c..7f82c9a08 100644 --- a/py/test/testing/acceptance_test.py +++ b/py/test/testing/acceptance_test.py @@ -241,13 +241,11 @@ class TestPyTest: 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 @@ class TestPyTest: 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 diff --git a/py/test/testing/test_config.py b/py/test/testing/test_config.py index aa013f25b..83e306e59 100644 --- a/py/test/testing/test_config.py +++ b/py/test/testing/test_config.py @@ -1,5 +1,6 @@ import py + class TestConfigCmdlineParsing: def test_config_cmdline_options(self, testdir): testdir.makepyfile(conftest=""" @@ -118,22 +119,18 @@ class TestConfigAPI: 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() diff --git a/py/test/testing/test_pytestplugin.py b/py/test/testing/test_pytestplugin.py index b955fc1d4..d5b560a37 100644 --- a/py/test/testing/test_pytestplugin.py +++ b/py/test/testing/test_pytestplugin.py @@ -100,7 +100,7 @@ class TestBootstrapping: 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()