[svn r62978] * 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

--HG--
branch : trunk
This commit is contained in:
hpk 2009-03-16 22:17:14 +01:00
parent 2aea0a73e2
commit b5a1f95856
16 changed files with 275 additions and 178 deletions

View File

@ -22,7 +22,6 @@ from py.__.test import event
class GatewaySpec(object): class GatewaySpec(object):
type = "ssh"
def __init__(self, spec, defaultjoinpath="pyexecnetcache"): def __init__(self, spec, defaultjoinpath="pyexecnetcache"):
if spec == "popen" or spec.startswith("popen:"): if spec == "popen" or spec.startswith("popen:"):
self.address = "popen" self.address = "popen"
@ -42,6 +41,7 @@ class GatewaySpec(object):
parts = spec.split(":", 1) parts = spec.split(":", 1)
self.address = parts.pop(0) self.address = parts.pop(0)
self.joinpath = parts and parts.pop(0) or "" self.joinpath = parts and parts.pop(0) or ""
self.type = "ssh"
if not self.joinpath and not self.inplacelocal(): if not self.joinpath and not self.inplacelocal():
self.joinpath = defaultjoinpath self.joinpath = defaultjoinpath
@ -60,8 +60,15 @@ class GatewaySpec(object):
elif self.type == "ssh": elif self.type == "ssh":
gw = py.execnet.SshGateway(self.address, remotepython=python) gw = py.execnet.SshGateway(self.address, remotepython=python)
if self.joinpath: if self.joinpath:
channel = gw.remote_exec("import os ; os.chdir(channel.receive())") channel = gw.remote_exec("""
channel.send(self.joinpath) import os
path = %r
try:
os.chdir(path)
except OSError:
os.mkdir(path)
os.chdir(path)
""" % self.joinpath)
if waitclose: if waitclose:
channel.waitclose() channel.waitclose()
else: else:
@ -74,13 +81,16 @@ class MultiChannel:
def __init__(self, channels): def __init__(self, channels):
self._channels = channels self._channels = channels
def receive(self): def receive_items(self):
values = [] items = []
for ch in self._channels: for ch in self._channels:
values.append(ch.receive()) items.append((ch, ch.receive()))
return values 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: for ch in self._channels:
ch.waitclose() ch.waitclose()
@ -91,8 +101,7 @@ class GatewayManager:
self.spec2gateway[GatewaySpec(spec)] = None self.spec2gateway[GatewaySpec(spec)] = None
def trace(self, msg): def trace(self, msg):
py._com.pyplugins.notify("trace_gatewaymanage", msg) py._com.pyplugins.notify("trace", "gatewaymanage", msg)
#print "trace", msg
def makegateways(self): def makegateways(self):
for spec, value in self.spec2gateway.items(): for spec, value in self.spec2gateway.items():
@ -101,6 +110,9 @@ class GatewayManager:
self.spec2gateway[spec] = spec.makegateway() self.spec2gateway[spec] = spec.makegateway()
def multi_exec(self, source, inplacelocal=True): 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) source = py.code.Source(source)
channels = [] channels = []
for spec, gw in self.spec2gateway.items(): for spec, gw in self.spec2gateway.items():
@ -109,10 +121,15 @@ class GatewayManager:
return MultiChannel(channels) return MultiChannel(channels)
def multi_chdir(self, basename, inplacelocal=True): 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, 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): def rsync(self, source, notify=None, verbose=False, ignores=None):
""" perform rsync to all remote hosts.
"""
rsync = HostRSync(source, verbose=verbose, ignores=ignores) rsync = HostRSync(source, verbose=verbose, ignores=ignores)
added = False added = False
for spec, gateway in self.spec2gateway.items(): for spec, gateway in self.spec2gateway.items():

View File

@ -155,7 +155,11 @@ class TestGatewayManagerPopen:
testdir.tmpdir.chdir() testdir.tmpdir.chdir()
hellopath = testdir.tmpdir.mkdir("hello") hellopath = testdir.tmpdir.mkdir("hello")
hm.makegateways() 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 assert l == [str(hellopath)] * 2
py.test.raises(Exception, 'hm.multi_chdir("world", inplacelocal=False)') py.test.raises(Exception, 'hm.multi_chdir("world", inplacelocal=False)')
worldpath = hellopath.mkdir("world") worldpath = hellopath.mkdir("world")
@ -188,22 +192,25 @@ class TestGatewayManagerPopen:
from py.__.execnet.gwmanage import MultiChannel from py.__.execnet.gwmanage import MultiChannel
class TestMultiChannel: class TestMultiChannel:
def test_multichannel_receive(self): def test_multichannel_receive_items(self):
class pseudochannel: class pseudochannel:
def receive(self): def receive(self):
return 12 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 = [] l = []
class pseudochannel: class pseudochannel:
def waitclose(self): def waitclose(self):
l.append(0) l.append(0)
multichannel = MultiChannel([pseudochannel(), pseudochannel()]) multichannel = MultiChannel([pseudochannel(), pseudochannel()])
multichannel.wait() multichannel.waitclose()
assert len(l) == 2 assert len(l) == 2

View File

@ -39,9 +39,17 @@ class Config(object):
assert isinstance(pytestplugins, py.test._PytestPlugins) assert isinstance(pytestplugins, py.test._PytestPlugins)
self.bus = pytestplugins.pyplugins self.bus = pytestplugins.pyplugins
self.pytestplugins = pytestplugins self.pytestplugins = pytestplugins
self._conftest = Conftest(onimport=self.pytestplugins.consider_conftest) self._conftest = Conftest(onimport=self._onimportconftest)
self._setupstate = SetupState() 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): def _processopt(self, opt):
if hasattr(opt, 'default') and opt.dest: if hasattr(opt, 'default') and opt.dest:
val = os.environ.get("PYTEST_OPTION_" + opt.dest.upper(), None) val = os.environ.get("PYTEST_OPTION_" + opt.dest.upper(), None)
@ -119,21 +127,18 @@ class Config(object):
col = Dir(pkgpath, config=self) col = Dir(pkgpath, config=self)
return col._getfsnode(path) 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 """ return a matching value, which needs to be sequence
of filenames that will be returned as a list of Path of filenames that will be returned as a list of Path
objects (they can be relative to the location objects (they can be relative to the location
where they were found). where they were found).
""" """
try: try:
return getattr(self.option, name) mod, relroots = self._conftest.rget_with_confmod(name, path)
except AttributeError: except KeyError:
try: return None
mod, relroots = self._conftest.rget_with_confmod(name, path) modpath = py.path.local(mod.__file__).dirpath()
except KeyError: return [modpath.join(x, abs=True) for x in relroots]
return None
modpath = py.path.local(mod.__file__).dirpath()
return [modpath.join(x, abs=True) for x in relroots]
def addoptions(self, groupname, *specs): def addoptions(self, groupname, *specs):
""" add a named group of options to the current testing session. """ add a named group of options to the current testing session.

View File

@ -44,9 +44,9 @@ class DSession(Session):
def fixoptions(self): def fixoptions(self):
""" check, fix and determine conflicting options. """ """ check, fix and determine conflicting options. """
option = self.config.option option = self.config.option
if option.runbrowser and not option.startserver: #if option.runbrowser and not option.startserver:
#print "--runbrowser implies --startserver" # #print "--runbrowser implies --startserver"
option.startserver = True # option.startserver = True
if self.config.getvalue("dist_boxed") and option.dist: if self.config.getvalue("dist_boxed") and option.dist:
option.boxed = True option.boxed = True
# conflicting options # conflicting options

View File

@ -8,19 +8,34 @@ def getconfighosts(config):
if config.option.numprocesses: if config.option.numprocesses:
hosts = ['localhost'] * config.option.numprocesses hosts = ['localhost'] * config.option.numprocesses
else: else:
hosts = config.getvalue("dist_hosts") hosts = config.option.hosts
assert hosts is not None if not hosts:
hosts = config.getvalue("hosts")
else:
hosts = hosts.split(",")
assert hosts is not None
return hosts 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): class HostManager(object):
def __init__(self, config, hosts=None): def __init__(self, config, hosts=None):
self.config = config 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: if hosts is None:
hosts = getconfighosts(self.config) hosts = getconfighosts(self.config)
self.roots = getconfigroots(config)
self.gwmanager = GatewayManager(hosts) self.gwmanager = GatewayManager(hosts)
def makegateways(self): def makegateways(self):
@ -29,6 +44,19 @@ class HostManager(object):
self.gwmanager.makegateways() self.gwmanager.makegateways()
finally: finally:
old.chdir() 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): def rsync_roots(self):
""" make sure that all remote gateways """ make sure that all remote gateways
@ -42,20 +70,23 @@ class HostManager(object):
# (for other gateways this chdir is irrelevant) # (for other gateways this chdir is irrelevant)
self.makegateways() self.makegateways()
options = { options = {
'ignores': self.config.getvalue_pathlist("dist_rsync_ignore"), 'ignores': self.config_getignores(),
'verbose': self.config.option.verbose 'verbose': 1, # self.config.option.verbose
} }
if self.roots: if self.roots:
# send each rsync root # send each rsync root
for root in self.roots: for root in self.roots:
self.gwmanager.rsync(root, **options) self.gwmanager.rsync(root, **options)
else: else:
# we transfer our topdir as the root # we transfer our topdir as the root
# but need to be careful regarding
self.gwmanager.rsync(self.config.topdir, **options) self.gwmanager.rsync(self.config.topdir, **options)
# and cd into it
self.gwmanager.multi_chdir(self.config.topdir.basename, inplacelocal=False) self.gwmanager.multi_chdir(self.config.topdir.basename, inplacelocal=False)
self.config.bus.notify("rsyncfinished", event.RsyncFinished()) self.config.bus.notify("rsyncfinished", event.RsyncFinished())
def trace(self, msg):
self.config.bus.notify("trace", "testhostmanage", msg)
def setup_hosts(self, putevent): def setup_hosts(self, putevent):
self.rsync_roots() self.rsync_roots()
nice = self.config.getvalue("dist_nicelevel") nice = self.config.getvalue("dist_nicelevel")
@ -64,8 +95,10 @@ class HostManager(object):
import os import os
if hasattr(os, 'nice'): if hasattr(os, 'nice'):
os.nice(%r) os.nice(%r)
""" % nice).wait() """ % nice).waitclose()
self.trace_hoststatus()
for host, gateway in self.gwmanager.spec2gateway.items(): for host, gateway in self.gwmanager.spec2gateway.items():
host.node = MasterNode(host, host.node = MasterNode(host,
gateway, gateway,

View File

@ -39,7 +39,6 @@ class TestAsyncFunctional:
]) ])
def test_dist_some_tests(self, testdir): def test_dist_some_tests(self, testdir):
testdir.makepyfile(conftest="dist_hosts=['localhost']\n")
p1 = testdir.makepyfile(test_one=""" p1 = testdir.makepyfile(test_one="""
def test_1(): def test_1():
pass pass
@ -49,7 +48,7 @@ class TestAsyncFunctional:
def test_fail(): def test_fail():
assert 0 assert 0
""") """)
config = testdir.parseconfig('-d', p1) config = testdir.parseconfig('-d', p1, '--hosts=popen')
dsession = DSession(config) dsession = DSession(config)
eq = EventQueue(config.bus) eq = EventQueue(config.bus)
dsession.main([config.getfsnode(p1)]) dsession.main([config.getfsnode(p1)])
@ -61,7 +60,7 @@ class TestAsyncFunctional:
assert ev.failed assert ev.failed
# see that the host is really down # see that the host is really down
ev, = eq.geteventargs("hostdown") ev, = eq.geteventargs("hostdown")
assert ev.host.address == "localhost" assert ev.host.address == "popen"
ev, = eq.geteventargs("testrunfinish") ev, = eq.geteventargs("testrunfinish")
def test_distribution_rsync_roots_example(self, testdir): def test_distribution_rsync_roots_example(self, testdir):
@ -70,8 +69,8 @@ class TestAsyncFunctional:
subdir = "sub_example_dist" subdir = "sub_example_dist"
sourcedir = self.tmpdir.mkdir("source") sourcedir = self.tmpdir.mkdir("source")
sourcedir.ensure(subdir, "conftest.py").write(py.code.Source(""" sourcedir.ensure(subdir, "conftest.py").write(py.code.Source("""
dist_hosts = ["localhost:%s"] hosts = ["popen:%s"]
dist_rsync_roots = ["%s", "../py"] rsyncdirs = ["%s", "../py"]
""" % (destdir, tmpdir.join(subdir), ))) """ % (destdir, tmpdir.join(subdir), )))
tmpdir.ensure(subdir, "__init__.py") tmpdir.ensure(subdir, "__init__.py")
tmpdir.ensure(subdir, "test_one.py").write(py.code.Source(""" tmpdir.ensure(subdir, "test_one.py").write(py.code.Source("""
@ -102,7 +101,6 @@ class TestAsyncFunctional:
if not hasattr(os, 'nice'): if not hasattr(os, 'nice'):
py.test.skip("no os.nice() available") py.test.skip("no os.nice() available")
testdir.makepyfile(conftest=""" testdir.makepyfile(conftest="""
dist_hosts=['localhost']
dist_nicelevel = 10 dist_nicelevel = 10
""") """)
p1 = testdir.makepyfile(""" p1 = testdir.makepyfile("""
@ -110,7 +108,7 @@ class TestAsyncFunctional:
import os import os
assert os.nice(0) == 10 assert os.nice(0) == 10
""") """)
evrec = testdir.inline_run('-d', p1) evrec = testdir.inline_run('-d', p1, '--hosts=popen')
ev = evrec.getreport('test_nice') ev = evrec.getreport('test_nice')
assert ev.passed assert ev.passed

View File

@ -3,7 +3,7 @@
""" """
import py 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.__.execnet.gwmanage import GatewaySpec as Host
from py.__.test import event from py.__.test import event
@ -15,12 +15,14 @@ def pytest_pyfuncarg_dest(pyfuncitem):
return dest return dest
class TestHostManager: class TestHostManager:
def gethostmanager(self, source, dist_hosts, dist_rsync_roots=None): def gethostmanager(self, source, hosts, rsyncdirs=None):
l = ["dist_hosts = %r" % dist_hosts] def opt(optname, l):
if dist_rsync_roots: return '%s=%s' % (optname, ",".join(map(str, l)))
l.append("dist_rsync_roots = %r" % dist_rsync_roots) args = [opt('--hosts', hosts)]
source.join("conftest.py").write("\n".join(l)) if rsyncdirs:
config = py.test.config._reparse([source]) 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 assert config.topdir == source
hm = HostManager(config) hm = HostManager(config)
assert hm.gwmanager.spec2gateway assert hm.gwmanager.spec2gateway
@ -34,7 +36,7 @@ class TestHostManager:
def test_hostmanager_rsync_roots_no_roots(self, source, dest): def test_hostmanager_rsync_roots_no_roots(self, source, dest):
source.ensure("dir1", "file1").write("hello") source.ensure("dir1", "file1").write("hello")
config = py.test.config._reparse([source]) 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 assert hm.config.topdir == source == config.topdir
hm.rsync_roots() 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()
@ -48,8 +50,8 @@ class TestHostManager:
dir2 = source.ensure("dir1", "dir2", dir=1) dir2 = source.ensure("dir1", "dir2", dir=1)
dir2.ensure("hello") dir2.ensure("hello")
hm = self.gethostmanager(source, hm = self.gethostmanager(source,
dist_hosts = ["localhost:%s" % dest], hosts = ["popen:%s" % dest],
dist_rsync_roots = ['dir1'] rsyncdirs = ['dir1']
) )
assert hm.config.topdir == source assert hm.config.topdir == source
hm.rsync_roots() hm.rsync_roots()
@ -61,8 +63,8 @@ class TestHostManager:
dir2 = source.ensure("dir1", "dir2", dir=1) dir2 = source.ensure("dir1", "dir2", dir=1)
dir2.ensure("hello") dir2.ensure("hello")
hm = self.gethostmanager(source, hm = self.gethostmanager(source,
dist_hosts = ["localhost:%s" % dest], hosts = ["popen:%s" % dest],
dist_rsync_roots = [str(source)] rsyncdirs = [str(source)]
) )
assert hm.config.topdir == source assert hm.config.topdir == source
hm.rsync_roots() hm.rsync_roots()
@ -77,37 +79,37 @@ class TestHostManager:
dir2.ensure("hello") dir2.ensure("hello")
source.ensure("bogusdir", "file") source.ensure("bogusdir", "file")
source.join("conftest.py").write(py.code.Source(""" source.join("conftest.py").write(py.code.Source("""
dist_rsync_roots = ['dir1/dir2'] rsyncdirs = ['dir1/dir2']
""")) """))
session = py.test.config._reparse([source]).initsession() session = py.test.config._reparse([source]).initsession()
hm = HostManager(session.config, hm = HostManager(session.config,
hosts=["localhost:" + str(dest)]) hosts=["popen:" + str(dest)])
hm.rsync_roots() hm.rsync_roots()
assert dest.join("dir2").check() assert dest.join("dir2").check()
assert not dest.join("dir1").check() assert not dest.join("dir1").check()
assert not dest.join("bogus").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) dir2 = source.ensure("dir1", "dir2", dir=1)
dir5 = source.ensure("dir5", "dir6", "bogus") dir5 = source.ensure("dir5", "dir6", "bogus")
dirf = source.ensure("dir5", "file") dirf = source.ensure("dir5", "file")
dir2.ensure("hello") dir2.ensure("hello")
source.join("conftest.py").write(py.code.Source(""" source.join("conftest.py").write(py.code.Source("""
dist_rsync_ignore = ['dir1/dir2', 'dir5/dir6'] rsyncdirs = ['dir1', 'dir5']
dist_rsync_roots = ['dir1', 'dir5'] rsyncignore = ['dir1/dir2', 'dir5/dir6']
""")) """))
session = py.test.config._reparse([source]).initsession() session = py.test.config._reparse([source]).initsession()
hm = HostManager(session.config, hm = HostManager(session.config,
hosts=["localhost:" + str(dest)]) hosts=["popen:" + str(dest)])
hm.rsync_roots() hm.rsync_roots()
assert dest.join("dir1").check() assert dest.join("dir1").check()
assert not dest.join("dir1", "dir2").check() assert not dest.join("dir1", "dir2").check()
assert dest.join("dir5","file").check() assert dest.join("dir5","file").check()
assert not dest.join("dir6").check() assert not dest.join("dir6").check()
def test_hostmanage_optimise_localhost(self, source, dest): def test_hostmanage_optimise_popen(self, source, dest):
hosts = ["localhost"] * 3 hosts = ["popen"] * 3
source.join("conftest.py").write("dist_rsync_roots = ['a']") source.join("conftest.py").write("rsyncdirs = ['a']")
source.ensure('a', dir=1) source.ensure('a', dir=1)
config = py.test.config._reparse([source]) config = py.test.config._reparse([source])
hm = HostManager(config, hosts=hosts) hm = HostManager(config, hosts=hosts)
@ -116,31 +118,35 @@ class TestHostManager:
assert gwspec.inplacelocal() assert gwspec.inplacelocal()
assert not gwspec.joinpath assert not gwspec.joinpath
def test_hostmanage_setup_hosts(self, source): def test_hostmanage_setup_hosts_DEBUG(self, source, EventRecorder):
hosts = ["localhost"] * 3 hosts = ["popen"] * 2
source.join("conftest.py").write("dist_rsync_roots = ['a']") source.join("conftest.py").write("rsyncdirs = ['a']")
source.ensure('a', dir=1) 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) hm = HostManager(config, hosts=hosts)
queue = py.std.Queue.Queue() evrec = EventRecorder(config.bus, debug=True)
hm.setup_hosts(putevent=queue.put) hm.setup_hosts(putevent=[].append)
for host in hm.gwmanager.spec2gateway: for host in hm.gwmanager.spec2gateway:
eventcall = queue.get(timeout=2.0) l = evrec.getnamed("trace")
name, args, kwargs = eventcall print evrec.events
assert name == "hostup" assert l
for host in hm.gwmanager.spec2gateway: hm.teardown_hosts()
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"
def XXXtest_ssh_rsync_samehost_twice(self): def test_hostmanage_simple_ssh_test(self, testdir):
#XXX we have no easy way to have a temp directory remotely! 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 option = py.test.config.option
if option.sshhost is None: if option.sshhost is None:
py.test.skip("no known ssh target, use -S to set one") py.test.skip("no known ssh target, use -S to set one")
host1 = Host("%s" % (option.sshhost, )) host1 = Host("%s" % (option.sshhost, ))
host2 = Host("%s" % (option.sshhost, )) host2 = Host("%s" % (option.sshhost, ))
hm = HostManager(config, hosts=[host1, host2]) hm = HostManager(config, hosts=[host1, host2])
@ -150,8 +156,35 @@ class TestHostManager:
assert 0 assert 0
def test_getconfighosts(): def test_getconfighosts_numprocesses():
config = py.test.config._reparse(['-n3']) config = py.test.config._reparse(['-n3'])
hosts = getconfighosts(config) hosts = getconfighosts(config)
assert len(hosts) == 3 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

View File

@ -43,7 +43,7 @@ class MySetup:
config = py.test.config._reparse([]) config = py.test.config._reparse([])
self.config = config self.config = config
self.queue = py.std.Queue.Queue() self.queue = py.std.Queue.Queue()
self.host = GatewaySpec("localhost") self.host = GatewaySpec("popen")
self.gateway = self.host.makegateway() self.gateway = self.host.makegateway()
self.node = MasterNode(self.host, self.gateway, self.config, putevent=self.queue.put) self.node = MasterNode(self.host, self.gateway, self.config, putevent=self.queue.put)
assert not self.node.channel.isclosed() assert not self.node.channel.isclosed()

View File

@ -36,9 +36,6 @@ class DefaultPlugin:
group._addoption('-x', '--exitfirst', group._addoption('-x', '--exitfirst',
action="store_true", dest="exitfirst", default=False, action="store_true", dest="exitfirst", default=False,
help="exit instantly on first error or failed test."), 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', group._addoption('-k',
action="store", dest="keyword", default='', action="store", dest="keyword", default='',
help="only run test items matching the given " help="only run test items matching the given "
@ -47,57 +44,70 @@ class DefaultPlugin:
"to run all subsequent tests. ") "to run all subsequent tests. ")
group._addoption('-l', '--showlocals', group._addoption('-l', '--showlocals',
action="store_true", dest="showlocals", default=False, 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', group._addoption('--showskipsummary',
action="store_true", dest="showskipsummary", default=False, action="store_true", dest="showskipsummary", default=False,
help="always show summary of skipped tests"), help="always show summary of skipped tests")
group._addoption('', '--pdb', group._addoption('--pdb',
action="store_true", dest="usepdb", default=False, action="store_true", dest="usepdb", default=False,
help="start pdb (the Python debugger) on errors."), help="start pdb (the Python debugger) on errors.")
group._addoption('', '--tb', group._addoption('--tb',
action="store", dest="tbstyle", default='long', action="store", dest="tbstyle", default='long',
type="choice", choices=['long', 'short', 'no'], type="choice", choices=['long', 'short', 'no'],
help="traceback verboseness (long/short/no)."), help="traceback verboseness (long/short/no).")
group._addoption('', '--fulltrace', group._addoption('--fulltrace',
action="store_true", dest="fulltrace", default=False, action="store_true", dest="fulltrace", default=False,
help="don't cut any tracebacks (default is to cut)."), help="don't cut any tracebacks (default is to cut).")
group._addoption('', '--nomagic', group._addoption('-s', '--nocapture',
action="store_true", dest="nomagic", default=False, action="store_true", dest="nocapture", default=False,
help="refrain from using magic as much as possible."), help="disable catching of sys.stdout/stderr output."),
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)."
),
group._addoption('--boxed', group._addoption('--boxed',
action="store_true", dest="boxed", default=False, 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('--rest', group._addoption('-f', '--looponfailing',
action='store_true', dest="restreport", default=False, action="store_true", dest="looponfailing", default=False,
help="restructured text output reporting."), 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): def pytest_configure(self, config):
self.setsession(config) self.setsession(config)

View File

@ -155,6 +155,9 @@ class PytestPluginHooks:
def pyevent(self, eventname, *args, **kwargs): def pyevent(self, eventname, *args, **kwargs):
""" called for each testing event. """ """ called for each testing event. """
def pyevent_trace(self, category, msg):
""" called for tracing events events. """
def pyevent_internalerror(self, event): def pyevent_internalerror(self, event):
""" called for internal errors. """ """ called for internal errors. """

View File

@ -248,10 +248,8 @@ class EventRecorder(object):
if name == "plugin_registered" and args == (self,): if name == "plugin_registered" and args == (self,):
return return
if self.debug: if self.debug:
print "[event] %s: %s **%s" %(name, ", ".join(map(str, args)), kwargs,) print "[event: %s]: %s **%s" %(name, ", ".join(map(str, args)), kwargs,)
if len(args) == 1: self.events.append((name,) + tuple(args))
event, = args
self.events.append((name, event))
def get(self, cls): def get(self, cls):
l = [] l = []

View File

@ -3,11 +3,6 @@ import sys
class TerminalPlugin(object): class TerminalPlugin(object):
""" Report a test run to a terminal. """ """ 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): def pytest_configure(self, config):
if config.option.collectonly: if config.option.collectonly:
self.reporter = CollectonlyReporter(config) self.reporter = CollectonlyReporter(config)
@ -115,6 +110,11 @@ class TerminalReporter:
if error: if error:
self.write_line("HostDown %s: %s" %(host, 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): def pyevent_itemstart(self, event):
if self.config.option.verbose: if self.config.option.verbose:
info = event.item.repr_metainfo() info = event.item.repr_metainfo()
@ -167,14 +167,15 @@ class TerminalReporter:
rev = py.__pkg__.getrev() rev = py.__pkg__.getrev()
self.write_line("using py lib: %s <rev %s>" % ( self.write_line("using py lib: %s <rev %s>" % (
py.path.local(py.__file__).dirpath(), rev)) py.path.local(py.__file__).dirpath(), rev))
plugins = [] if self.config.option.traceconfig:
for x in self.config.pytestplugins._plugins: plugins = []
if isinstance(x, str) and x.startswith("pytest_"): for x in self.config.pytestplugins._plugins:
plugins.append(x[7:]) if isinstance(x, str) and x.startswith("pytest_"):
else: plugins.append(x[7:])
plugins.append(str(x)) # XXX display conftest plugins more nicely else:
plugins = ", ".join(plugins) plugins.append(str(x)) # XXX display conftest plugins more nicely
self.write_line("active plugins: %s" %(plugins,)) plugins = ", ".join(plugins)
self.write_line("active plugins: %s" %(plugins,))
for i, testarg in py.builtin.enumerate(self.config.args): for i, testarg in py.builtin.enumerate(self.config.args):
self.write_line("test object %d: %s" %(i+1, testarg)) self.write_line("test object %d: %s" %(i+1, testarg))

View File

@ -29,9 +29,9 @@ class Session(object):
def fixoptions(self): def fixoptions(self):
""" check, fix and determine conflicting options. """ """ check, fix and determine conflicting options. """
option = self.config.option option = self.config.option
if option.runbrowser and not option.startserver: #if option.runbrowser and not option.startserver:
#print "--runbrowser implies --startserver" # #print "--runbrowser implies --startserver"
option.startserver = True # option.startserver = True
# conflicting options # conflicting options
if option.looponfailing and option.usepdb: if option.looponfailing and option.usepdb:
raise ValueError, "--looponfailing together with --pdb not supported." raise ValueError, "--looponfailing together with --pdb not supported."

View File

@ -241,13 +241,11 @@ class TestPyTest:
def test_skip(): def test_skip():
py.test.skip("hello") 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([ result.stdout.fnmatch_lines([
"HOSTUP: localhost*Python*", "HOSTUP: popen*Python*",
#"HOSTUP: localhost*Python*", #"HOSTUP: localhost*Python*",
#"HOSTUP: localhost*Python*", #"HOSTUP: localhost*Python*",
"*2 failed, 1 passed, 1 skipped*", "*2 failed, 1 passed, 1 skipped*",
@ -273,17 +271,14 @@ class TestPyTest:
import os import os
time.sleep(0.5) time.sleep(0.5)
os.kill(os.getpid(), 15) 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([ result.stdout.fnmatch_lines([
"*localhost*Python*", "*popen*Python*",
"*localhost*Python*", "*popen*Python*",
"*localhost*Python*", "*popen*Python*",
"HostDown*localhost*TERMINATED*", "HostDown*TERMINATED*",
"*3 failed, 1 passed, 1 skipped*" "*3 failed, 1 passed, 1 skipped*"
]) ])
assert result.ret == 1 assert result.ret == 1

View File

@ -1,5 +1,6 @@
import py import py
class TestConfigCmdlineParsing: class TestConfigCmdlineParsing:
def test_config_cmdline_options(self, testdir): def test_config_cmdline_options(self, testdir):
testdir.makepyfile(conftest=""" testdir.makepyfile(conftest="""
@ -118,22 +119,18 @@ class TestConfigAPI:
config = py.test.config._reparse([str(o)]) config = py.test.config._reparse([str(o)])
assert config.getvalue('x') == 1 assert config.getvalue('x') == 1
def test_getvalue_pathlist(self, tmpdir): def test_getconftest_pathlist(self, tmpdir):
somepath = tmpdir.join("x", "y", "z") somepath = tmpdir.join("x", "y", "z")
p = tmpdir.join("conftest.py") p = tmpdir.join("conftest.py")
p.write("pathlist = ['.', %r]" % str(somepath)) p.write("pathlist = ['.', %r]" % str(somepath))
config = py.test.config._reparse([p]) config = py.test.config._reparse([p])
assert config.getvalue_pathlist('notexist') is None assert config.getconftest_pathlist('notexist') is None
pl = config.getvalue_pathlist('pathlist') pl = config.getconftest_pathlist('pathlist')
print pl print pl
assert len(pl) == 2 assert len(pl) == 2
assert pl[0] == tmpdir assert pl[0] == tmpdir
assert pl[1] == somepath 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): def test_setsessionclass_and_initsession(self, testdir):
from py.__.test.config import Config from py.__.test.config import Config
config = Config() config = Config()

View File

@ -100,7 +100,7 @@ class TestBootstrapping:
def test_config_sets_conftesthandle_onimport(self, testdir): def test_config_sets_conftesthandle_onimport(self, testdir):
config = testdir.parseconfig([]) config = testdir.parseconfig([])
assert config._conftest._onimport == config.pytestplugins.consider_conftest assert config._conftest._onimport == config._onimportconftest
def test_consider_conftest_deps(self, testdir): def test_consider_conftest_deps(self, testdir):
mod = testdir.makepyfile("pytest_plugins='xyz'").pyimport() mod = testdir.makepyfile("pytest_plugins='xyz'").pyimport()