initial copy and modifications from the original py/impl/test/dist and

looponfail code. Now works as its own "xdist" plugin.
This commit is contained in:
holger krekel
2010-01-13 15:55:01 +01:00
commit 6b75e0a740
27 changed files with 3237 additions and 0 deletions

1
testing/__init__.py Normal file
View File

@@ -0,0 +1 @@
#

119
testing/acceptance_test.py Normal file
View File

@@ -0,0 +1,119 @@
import py
class TestDistribution:
def test_manytests_to_one_popen(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")
""",
)
result = testdir.runpytest(p1, '-d', '--tx=popen', '--tx=popen')
result.stdout.fnmatch_lines([
"*0*popen*Python*",
"*1*popen*Python*",
"*2 failed, 1 passed, 1 skipped*",
])
assert result.ret == 1
def test_dist_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("""
option_tx = 'popen popen popen'.split()
""")
result = testdir.runpytest(p1, '-d')
result.stdout.fnmatch_lines([
"*0*popen*Python*",
"*1*popen*Python*",
"*2*popen*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")
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")
def test_crash():
import time
import os
time.sleep(0.5)
os.kill(os.getpid(), 15)
"""
)
result = testdir.runpytest(p1, '-d', '--tx=3*popen')
result.stdout.fnmatch_lines([
"*popen*Python*",
"*popen*Python*",
"*popen*Python*",
"*node down*",
"*3 failed, 1 passed, 1 skipped*"
])
assert result.ret == 1
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([
"*0* *popen*platform*",
#"RSyncStart: [G1]",
#"RSyncFinished: [G1]",
"*1 passed*"
])
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)
s = result.stdout.str()
assert "2.4" in s
assert "2.5" in s

40
testing/conftest.py Normal file
View File

@@ -0,0 +1,40 @@
import py
import execnet
pytest_plugins = "pytester"
option_report = 'skipped'
#rsyncdirs = ['.', '../xdist', py.path.local(execnet.__file__).dirpath()]
def pytest_addoption(parser):
parser.addoption('--gx',
action="append", dest="gspecs", default=None,
help=("add a global test environment, XSpec-syntax. "))
def pytest_funcarg__specssh(request):
return getspecssh(request.config)
def getgspecs(config):
return [execnet.XSpec(spec)
for spec in config.getvalueorskip("gspecs")]
# configuration information for tests
def getgspecs(config):
return [execnet.XSpec(spec)
for spec in config.getvalueorskip("gspecs")]
def getspecssh(config):
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
py.test.skip("need '--gx ssh=...'")
def getsocketspec(config):
xspecs = getgspecs(config)
for spec in xspecs:
if spec.socket:
return spec
py.test.skip("need '--gx socket=...'")

30
testing/test_boxed.py Normal file
View File

@@ -0,0 +1,30 @@
import py
@py.test.mark.skipif("not hasattr(os, 'fork')")
def test_functional_boxed(testdir):
p1 = testdir.makepyfile("""
import os
def test_function():
os.kill(os.getpid(), 15)
""")
result = testdir.runpytest(p1, "--boxed")
assert result.stdout.fnmatch_lines([
"*CRASHED*",
"*1 failed*"
])
class TestOptionEffects:
def test_boxed_option_default(self, testdir):
tmpdir = testdir.tmpdir.ensure("subdir", dir=1)
config = testdir.reparseconfig()
config.initsession()
assert not config.option.boxed
py.test.importorskip("execnet")
config = testdir.reparseconfig(['-d', tmpdir])
config.initsession()
assert not config.option.boxed
def test_is_not_boxed_by_default(self, testdir):
config = testdir.reparseconfig([testdir.tmpdir])
assert not config.option.boxed

View File

@@ -0,0 +1,37 @@
import py
def test_dist_conftest_options(testdir):
p1 = testdir.tmpdir.ensure("dir", 'p1.py')
p1.dirpath("__init__.py").write("")
p1.dirpath("conftest.py").write(py.code.Source("""
import py
from py.builtin import print_
print_("importing conftest", __file__)
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
from %s import conftest
from py.builtin import print_
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
""" % p1.dirpath().purebasename ))
result = testdir.runpytest('-d', '--tx=popen', p1, '--someopt')
assert result.ret == 0
result.stderr.fnmatch_lines([
"*Deprecation*pytest_addoptions*",
])
result.stdout.fnmatch_lines([
"*1 passed*",
])

505
testing/test_dsession.py Normal file
View File

@@ -0,0 +1,505 @@
from xdist.dsession import DSession
from py.impl.test import outcome
import py
import execnet
XSpec = execnet.XSpec
def run(item, node, excinfo=None):
runner = item.config.pluginmanager.getplugin("runner")
rep = runner.ItemTestReport(item=item,
excinfo=excinfo, when="call")
rep.node = node
return rep
class MockNode:
def __init__(self):
self.sent = []
def sendlist(self, items):
self.sent.append(items)
def shutdown(self):
self._shutdown=True
def dumpqueue(queue):
while queue.qsize():
print(queue.get())
class TestDSession:
def test_add_remove_node(self, testdir):
item = testdir.getitem("def test_func(): pass")
node = MockNode()
rep = run(item, node)
session = DSession(item.config)
assert not session.node2pending
session.addnode(node)
assert len(session.node2pending) == 1
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_senditems_each_and_receive_with_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_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_load([item])
assert session.node2pending[node] == [item]
assert session.item2nodes[item] == [node]
session.removeitem(item, node)
assert not session.node2pending[node]
assert not session.item2nodes
def test_triggertesting_collect(self, testdir):
modcol = testdir.getmodulecol("""
def test_func():
pass
""")
session = DSession(modcol.config)
session.triggertesting([modcol])
name, args, kwargs = session.queue.get(block=False)
assert name == 'pytest_collectreport'
report = kwargs['report']
assert len(report.result) == 1
def test_triggertesting_item(self, testdir):
item = testdir.getitem("def test_func(): pass")
session = DSession(item.config)
node1 = MockNode()
node2 = MockNode()
session.addnode(node1)
session.addnode(node2)
session.triggertesting([item] * (session.MAXITEMSPERHOST*2 + 1))
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 == "pytest_rescheduleitems"
assert kwargs['items'] == [item]
def test_keyboardinterrupt(self, testdir):
item = testdir.getitem("def test_func(): pass")
session = DSession(item.config)
def raise_(timeout=None): raise KeyboardInterrupt()
session.queue.get = raise_
exitstatus = session.loop([])
assert exitstatus == outcome.EXIT_INTERRUPTED
def test_internalerror(self, testdir):
item = testdir.getitem("def test_func(): pass")
session = DSession(item.config)
def raise_(): raise ValueError()
session.queue.get = raise_
exitstatus = session.loop([])
assert exitstatus == outcome.EXIT_INTERNALERROR
def test_rescheduleevent(self, testdir):
item = testdir.getitem("def test_func(): pass")
session = DSession(item.config)
node = MockNode()
session.addnode(node)
loopstate = session._initloopstate([])
session.queueevent("pytest_rescheduleitems", items=[item])
session.loop_once(loopstate)
# check that RescheduleEvents are not immediately
# rescheduled if there are no nodes
assert loopstate.dowork == False
session.queueevent(None)
session.loop_once(loopstate)
session.queueevent(None)
session.loop_once(loopstate)
assert node.sent == [[item]]
session.queueevent("pytest_runtest_logreport", report=run(item, node))
session.loop_once(loopstate)
assert loopstate.shuttingdown
assert not loopstate.testsfailed
def test_no_node_remaining_for_tests(self, testdir):
item = testdir.getitem("def test_func(): pass")
# setup a session with one node
session = DSession(item.config)
node = MockNode()
session.addnode(node)
# setup a HostDown event
session.queueevent("pytest_testnodedown", node=node, error=None)
loopstate = session._initloopstate([item])
loopstate.dowork = False
session.loop_once(loopstate)
dumpqueue(session.queue)
assert loopstate.exitstatus == outcome.EXIT_NOHOSTS
def test_removeitem_from_failing_teardown(self, testdir):
# teardown reports only come in when they signal a failure
# internal session-management should basically ignore them
# XXX probably it'S best to invent a new error hook for
# teardown/setup related failures
modcol = testdir.getmodulecol("""
def test_one():
pass
def teardown_function(function):
assert 0
""")
item1, = modcol.collect()
# setup a session with two nodes
session = DSession(item1.config)
node1, node2 = MockNode(), MockNode()
session.addnode(node1)
session.addnode(node2)
# have one test pending for a node that goes down
session.senditems_each([item1])
nodes = session.item2nodes[item1]
class rep:
failed = True
item = item1
node = nodes[0]
when = "call"
session.queueevent("pytest_runtest_logreport", report=rep)
reprec = testdir.getreportrecorder(session)
print(session.item2nodes)
loopstate = session._initloopstate([])
assert len(session.item2nodes[item1]) == 2
session.loop_once(loopstate)
assert len(session.item2nodes[item1]) == 1
rep.when = "teardown"
session.queueevent("pytest_runtest_logreport", report=rep)
session.loop_once(loopstate)
assert len(session.item2nodes[item1]) == 1
def test_testnodedown_causes_reschedule_pending(self, testdir):
modcol = testdir.getmodulecol("""
def test_crash():
assert 0
def test_fail():
x
""")
item1, item2 = modcol.collect()
# setup a session with two nodes
session = DSession(item1.config)
node1, node2 = MockNode(), MockNode()
session.addnode(node1)
session.addnode(node2)
# have one test pending for a node that goes down
session.senditems_load([item1, item2])
node = session.item2nodes[item1] [0]
item1.config.option.dist = "load"
session.queueevent("pytest_testnodedown", node=node, error="xyz")
reprec = testdir.getreportrecorder(session)
print(session.item2nodes)
loopstate = session._initloopstate([])
session.loop_once(loopstate)
assert loopstate.colitems == [item2] # do not reschedule crash item
rep = reprec.matchreport(names="pytest_runtest_logreport")
assert rep.failed
assert rep.item == item1
assert str(rep.longrepr).find("crashed") != -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 nodes
session = DSession(item.config)
node1 = MockNode()
session.queueevent("pytest_testnodeready", node=node1)
loopstate = session._initloopstate([item])
loopstate.dowork = False
assert len(session.node2pending) == 0
session.loop_once(loopstate)
assert len(session.node2pending) == 1
def runthrough(self, item, excinfo=None):
session = DSession(item.config)
node = MockNode()
session.addnode(node)
loopstate = session._initloopstate([item])
session.queueevent(None)
session.loop_once(loopstate)
assert node.sent == [[item]]
ev = run(item, node, excinfo=excinfo)
session.queueevent("pytest_runtest_logreport", report=ev)
session.loop_once(loopstate)
assert loopstate.shuttingdown
session.queueevent("pytest_testnodedown", node=node, error=None)
session.loop_once(loopstate)
dumpqueue(session.queue)
return session, loopstate.exitstatus
def test_exit_completed_tests_ok(self, testdir):
item = testdir.getitem("def test_func(): pass")
session, exitstatus = self.runthrough(item)
assert exitstatus == outcome.EXIT_OK
def test_exit_completed_tests_fail(self, testdir):
item = testdir.getitem("def test_func(): 0/0")
session, exitstatus = self.runthrough(item, excinfo="fail")
assert exitstatus == outcome.EXIT_TESTSFAILED
def test_exit_on_first_failing(self, testdir):
modcol = testdir.getmodulecol("""
def test_fail():
assert 0
def test_pass():
pass
""")
modcol.config.option.exitfirst = True
session = DSession(modcol.config)
node = MockNode()
session.addnode(node)
items = modcol.config.hook.pytest_make_collect_report(collector=modcol).result
# trigger testing - this sends tests to the node
session.triggertesting(items)
# run tests ourselves and produce reports
ev1 = run(items[0], node, "fail")
ev2 = run(items[1], node, None)
session.queueevent("pytest_runtest_logreport", report=ev1) # a failing one
session.queueevent("pytest_runtest_logreport", report=ev2)
# now call the loop
loopstate = session._initloopstate(items)
session.loop_once(loopstate)
assert loopstate.testsfailed
assert loopstate.shuttingdown
def test_shuttingdown_filters(self, testdir):
item = testdir.getitem("def test_func(): pass")
session = DSession(item.config)
node = MockNode()
session.addnode(node)
loopstate = session._initloopstate([])
loopstate.shuttingdown = True
reprec = testdir.getreportrecorder(session)
session.queueevent("pytest_runtest_logreport", report=run(item, node))
session.loop_once(loopstate)
assert not reprec.getcalls("pytest_testnodedown")
session.queueevent("pytest_testnodedown", node=node, error=None)
session.loop_once(loopstate)
assert reprec.getcall('pytest_testnodedown').node == node
def test_filteritems(self, testdir):
modcol = testdir.getmodulecol("""
def test_fail():
assert 0
def test_pass():
pass
""")
session = DSession(modcol.config)
modcol.config.option.keyword = "nothing"
dsel = session.filteritems([modcol])
assert dsel == [modcol]
items = modcol.collect()
hookrecorder = testdir.getreportrecorder(session).hookrecorder
remaining = session.filteritems(items)
assert remaining == []
event = hookrecorder.getcalls("pytest_deselected")[-1]
assert event.items == items
modcol.config.option.keyword = "test_fail"
remaining = session.filteritems(items)
assert remaining == [items[0]]
event = hookrecorder.getcalls("pytest_deselected")[-1]
assert event.items == [items[1]]
def test_testnodedown_shutdown_after_completion(self, testdir):
item = testdir.getitem("def test_func(): pass")
session = DSession(item.config)
node = MockNode()
session.addnode(node)
session.senditems_load([item])
session.queueevent("pytest_runtest_logreport", report=run(item, node))
loopstate = session._initloopstate([])
session.loop_once(loopstate)
assert node._shutdown is True
assert loopstate.exitstatus is None, "loop did not wait for testnodedown"
assert loopstate.shuttingdown
session.queueevent("pytest_testnodedown", node=node, error=None)
session.loop_once(loopstate)
assert loopstate.exitstatus == 0
def test_nopending_but_collection_remains(self, testdir):
modcol = testdir.getmodulecol("""
def test_fail():
assert 0
def test_pass():
pass
""")
session = DSession(modcol.config)
node = MockNode()
session.addnode(node)
colreport = modcol.config.hook.pytest_make_collect_report(collector=modcol)
item1, item2 = colreport.result
session.senditems_load([item1])
# node2pending will become empty when the loop sees the report
rep = run(item1, node)
session.queueevent("pytest_runtest_logreport", report=run(item1, node))
# but we have a collection pending
session.queueevent("pytest_collectreport", report=colreport)
loopstate = session._initloopstate([])
session.loop_once(loopstate)
assert loopstate.exitstatus is None, "loop did not care for collection report"
assert not loopstate.colitems
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):
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)
hookrecorder = testdir.getreportrecorder(config).hookrecorder
dsession.main([config.getnode(p1)])
rep = hookrecorder.popcall("pytest_runtest_logreport").report
assert rep.passed
rep = hookrecorder.popcall("pytest_runtest_logreport").report
assert rep.skipped
rep = hookrecorder.popcall("pytest_runtest_logreport").report
assert rep.failed
# see that the node is really down
node = hookrecorder.popcall("pytest_testnodedown").node
assert node.gateway.spec.popen
#XXX eq.geteventargs("pytest_sessionfinish")
def test_collected_function_causes_remote_skip(testdir):
sub = testdir.mkpydir("testing")
sub.join("test_module.py").write(py.code.Source("""
import py
path = py.path.local(%r)
if path.check():
path.remove()
else:
py.test.skip("remote skip")
def test_func():
pass
def test_func2():
pass
""" % str(sub.ensure("somefile"))))
result = testdir.runpytest('-v', '--dist=each', '--tx=popen')
result.stdout.fnmatch_lines([
"*2 skipped*"
])
def test_teardownfails_one_function(testdir):
p = testdir.makepyfile("""
def test_func():
pass
def teardown_function(function):
assert 0
""")
result = testdir.runpytest(p, '--dist=each', '--tx=popen')
result.stdout.fnmatch_lines([
"*def teardown_function(function):*",
"*1 passed*1 error*"
])
@py.test.mark.xfail
def test_terminate_on_hangingnode(testdir):
p = testdir.makeconftest("""
def pytest__teardown_final(session):
if session.nodeid == "my": # running on slave
import time
time.sleep(3)
""")
result = testdir.runpytest(p, '--dist=each', '--tx=popen//id=my')
assert result.duration < 2.0
result.stdout.fnmatch_lines([
"*killed*my*",
])
def test_session_hooks(testdir):
testdir.makeconftest("""
import sys
def pytest_sessionstart(session):
sys.pytestsessionhooks = session
def pytest_sessionfinish(session):
f = open(session.nodeid or "master", 'w')
f.write("xy")
f.close()
# let's fail on the slave
if session.nodeid:
raise ValueError(42)
""")
p = testdir.makepyfile("""
import sys
def test_hello():
assert hasattr(sys, 'pytestsessionhooks')
""")
result = testdir.runpytest(p, "--dist=each", "--tx=popen//id=my1")
result.stdout.fnmatch_lines([
"*ValueError*",
"*1 passed*",
])
assert result.ret
d = result.parseoutcomes()
assert d['passed'] == 1
assert testdir.tmpdir.join("my1").check()
assert testdir.tmpdir.join("master").check()
def test_funcarg_teardown_failure(testdir):
p = testdir.makepyfile("""
def pytest_funcarg__myarg(request):
def teardown(val):
raise ValueError(val)
return request.cached_setup(setup=lambda: 42, teardown=teardown,
scope="module")
def test_hello(myarg):
pass
""")
result = testdir.runpytest(p, "-n1")
assert result.ret
result.stdout.fnmatch_lines([
"*ValueError*42*",
"*1 passed*1 error*",
])

120
testing/test_gwmanage.py Normal file
View File

@@ -0,0 +1,120 @@
import py
import os
from xdist.gwmanage import GatewayManager, HostRSync
from py.impl.test.pluginmanager import HookRelay, Registry
from py.plugin import hookspec
import execnet
def pytest_funcarg__hookrecorder(request):
_pytest = request.getfuncargvalue('_pytest')
hook = request.getfuncargvalue('hook')
return _pytest.gethookrecorder(hook)
def pytest_funcarg__hook(request):
return HookRelay(hookspec, Registry())
class TestGatewayManagerPopen:
def test_popen_no_default_chdir(self, hook):
gm = GatewayManager(["popen"], hook)
assert gm.specs[0].chdir is None
def test_default_chdir(self, hook):
l = ["ssh=noco", "socket=xyz"]
for spec in GatewayManager(l, hook).specs:
assert spec.chdir == "pyexecnetcache"
for spec in GatewayManager(l, hook, defaultchdir="abc").specs:
assert spec.chdir == "abc"
def test_popen_makegateway_events(self, hook, hookrecorder, _pytest):
hm = GatewayManager(["popen"] * 2, hook)
hm.makegateways()
call = hookrecorder.popcall("pytest_gwmanage_newgateway")
assert call.gateway.spec == execnet.XSpec("popen")
assert call.gateway.id == "gw0"
assert call.platinfo.executable == call.gateway._rinfo().executable
call = hookrecorder.popcall("pytest_gwmanage_newgateway")
assert call.gateway.id == "gw1"
assert len(hm.group) == 2
hm.exit()
assert not len(hm.group)
def test_popens_rsync(self, hook, mysetup):
source = mysetup.source
hm = GatewayManager(["popen"] * 2, hook)
hm.makegateways()
assert len(hm.group) == 2
for gw in hm.group:
class pseudoexec:
args = []
def __init__(self, *args):
self.args.extend(args)
def waitclose(self):
pass
gw.remote_exec = pseudoexec
l = []
hm.rsync(source, notify=lambda *args: l.append(args))
assert not l
hm.exit()
assert not len(hm.group)
assert "sys.path.insert" in gw.remote_exec.args[0]
def test_rsync_popen_with_path(self, hook, mysetup):
source, dest = mysetup.source, mysetup.dest
hm = GatewayManager(["popen//chdir=%s" %dest] * 1, hook)
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.group['gw0'].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 test_rsync_same_popen_twice(self, hook, mysetup, hookrecorder):
source, dest = mysetup.source, mysetup.dest
hm = GatewayManager(["popen//chdir=%s" %dest] * 2, hook)
hm.makegateways()
source.ensure("dir1", "dir2", "hello")
hm.rsync(source)
call = hookrecorder.popcall("pytest_gwmanage_rsyncstart")
assert call.source == source
assert len(call.gateways) == 1
assert call.gateways[0] in hm.group
call = hookrecorder.popcall("pytest_gwmanage_rsyncfinish")
class pytest_funcarg__mysetup:
def __init__(self, request):
tmp = request.getfuncargvalue('tmpdir')
self.source = tmp.mkdir("source")
self.dest = tmp.mkdir("dest")
class TestHRSync:
def test_hrsync_filter(self, mysetup):
source, dest = mysetup.source, mysetup.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, mysetup):
source, dest = mysetup.source, mysetup.dest
gw = execnet.makegateway("popen//chdir=%s" % dest)
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

254
testing/test_mypickle.py Normal file
View File

@@ -0,0 +1,254 @@
import py
import sys
import execnet
Queue = py.builtin._tryimport('queue', 'Queue').Queue
from xdist.mypickle import ImmutablePickler, PickleChannel
from xdist.mypickle import UnpickleError, makekey
# first let's test some basic functionality
def pytest_generate_tests(metafunc):
if 'picklemod' in metafunc.funcargnames:
import pickle
metafunc.addcall(funcargs={'picklemod': pickle})
try:
import cPickle
except ImportError:
pass
else:
metafunc.addcall(funcargs={'picklemod': cPickle})
elif "obj" in metafunc.funcargnames and "proto" in metafunc.funcargnames:
a1 = A()
a2 = A()
a2.a1 = a1
for proto in (0,1,2, -1):
for obj in {1:2}, [1,2,3], a1, a2:
metafunc.addcall(funcargs=dict(obj=obj, proto=proto))
def test_underlying_basic_pickling_mechanisms(picklemod):
f1 = py.io.BytesIO()
f2 = py.io.BytesIO()
pickler1 = picklemod.Pickler(f1)
unpickler1 = picklemod.Unpickler(f2)
pickler2 = picklemod.Pickler(f2)
unpickler2 = picklemod.Unpickler(f1)
#pickler1.memo = unpickler1.memo = {}
#pickler2.memo = unpickler2.memo = {}
d = {}
pickler1.dump(d)
f1.seek(0)
d_other = unpickler2.load()
# translate unpickler2 memo to pickler2
pickler2.memo = dict([(id(obj), (int(x), obj))
for x, obj in unpickler2.memo.items()])
pickler2.dump(d_other)
f2.seek(0)
unpickler1.memo = dict([(makekey(x), y)
for x, y in pickler1.memo.values()])
d_back = unpickler1.load()
assert d is d_back
class A:
pass
def test_pickle_and_back_IS_same(obj, proto):
p1 = ImmutablePickler(uneven=False, protocol=proto)
p2 = ImmutablePickler(uneven=True, protocol=proto)
s1 = p1.dumps(obj)
d2 = p2.loads(s1)
s2 = p2.dumps(d2)
obj_back = p1.loads(s2)
assert obj is obj_back
def test_pickling_twice_before_unpickling():
p1 = ImmutablePickler(uneven=False)
p2 = ImmutablePickler(uneven=True)
a1 = A()
a2 = A()
a3 = A()
a3.a1 = a1
a2.a1 = a1
s1 = p1.dumps(a1)
a1.a3 = a3
s2 = p1.dumps(a2)
other_a1 = p2.loads(s1)
other_a2 = p2.loads(s2)
back_a1 = p1.loads(p2.dumps(other_a1))
other_a3 = p2.loads(p1.dumps(a3))
back_a3 = p1.loads(p2.dumps(other_a3))
back_a2 = p1.loads(p2.dumps(other_a2))
back_a1 = p1.loads(p2.dumps(other_a1))
assert back_a1 is a1
assert back_a2 is a2
def test_pickling_concurrently():
p1 = ImmutablePickler(uneven=False)
p2 = ImmutablePickler(uneven=True)
a1 = A()
a1.hasattr = 42
a2 = A()
s1 = p1.dumps(a1)
s2 = p2.dumps(a2)
other_a1 = p2.loads(s1)
other_a2 = p1.loads(s2)
a1_back = p1.loads(p2.dumps(other_a1))
def test_self_memoize():
p1 = ImmutablePickler(uneven=False)
a1 = A()
p1.selfmemoize(a1)
x = p1.loads(p1.dumps(a1))
assert x is a1
TESTTIMEOUT = 2.0
class TestPickleChannelFunctional:
def setup_class(cls):
cls.gw = execnet.PopenGateway()
cls.gw.remote_exec(
"import py ; py.path.local(%r).pyimport()" %(__file__)
)
cls.gw.remote_init_threads(5)
# we need the remote test code to import
# the same test module here
def test_popen_send_instance(self):
channel = self.gw.remote_exec("""
from xdist.mypickle import PickleChannel
channel = PickleChannel(channel)
from testing.test_mypickle import A
a1 = A()
a1.hello = 10
channel.send(a1)
a2 = channel.receive()
channel.send(a2 is a1)
""")
channel = PickleChannel(channel)
a_received = channel.receive()
assert isinstance(a_received, A)
assert a_received.hello == 10
channel.send(a_received)
remote_a2_is_a1 = channel.receive()
assert remote_a2_is_a1
def test_send_concurrent(self):
channel = self.gw.remote_exec("""
from xdist.mypickle import PickleChannel
channel = PickleChannel(channel)
from testing.test_mypickle import A
l = [A() for i in range(10)]
channel.send(l)
other_l = channel.receive()
channel.send((l, other_l))
channel.send(channel.receive())
channel.receive()
""")
channel = PickleChannel(channel)
l = [A() for i in range(10)]
channel.send(l)
other_l = channel.receive()
channel.send(other_l)
ret = channel.receive()
assert ret[0] is other_l
assert ret[1] is l
back = channel.receive()
assert other_l is other_l
channel.send(None)
#s1 = p1.dumps(a1)
#s2 = p2.dumps(a2)
#other_a1 = p2.loads(s1)
#other_a2 = p1.loads(s2)
#a1_back = p1.loads(p2.dumps(other_a1))
def test_popen_with_callback(self):
channel = self.gw.remote_exec("""
from xdist.mypickle import PickleChannel
channel = PickleChannel(channel)
from testing.test_mypickle import A
a1 = A()
a1.hello = 10
channel.send(a1)
a2 = channel.receive()
channel.send(a2 is a1)
""")
channel = PickleChannel(channel)
queue = Queue()
channel.setcallback(queue.put)
a_received = queue.get(timeout=TESTTIMEOUT)
assert isinstance(a_received, A)
assert a_received.hello == 10
channel.send(a_received)
#remote_a2_is_a1 = queue.get(timeout=TESTTIMEOUT)
#assert remote_a2_is_a1
def test_popen_with_callback_with_endmarker(self):
channel = self.gw.remote_exec("""
from xdist.mypickle import PickleChannel
channel = PickleChannel(channel)
from testing.test_mypickle import A
a1 = A()
a1.hello = 10
channel.send(a1)
a2 = channel.receive()
channel.send(a2 is a1)
""")
channel = PickleChannel(channel)
queue = Queue()
channel.setcallback(queue.put, endmarker=-1)
a_received = queue.get(timeout=TESTTIMEOUT)
assert isinstance(a_received, A)
assert a_received.hello == 10
channel.send(a_received)
remote_a2_is_a1 = queue.get(timeout=TESTTIMEOUT)
assert remote_a2_is_a1
endmarker = queue.get(timeout=TESTTIMEOUT)
assert endmarker == -1
def test_popen_with_callback_with_endmarker_and_unpickling_error(self):
channel = self.gw.remote_exec("""
from xdist.mypickle import PickleChannel
channel = PickleChannel(channel)
from testing.test_mypickle import A
a1 = A()
channel.send(a1)
channel.send(a1)
""")
channel = PickleChannel(channel)
queue = Queue()
a = channel.receive()
channel._ipickle._unpicklememo.clear()
channel.setcallback(queue.put, endmarker=-1)
next = queue.get(timeout=TESTTIMEOUT)
assert next == -1
error = channel._getremoteerror()
assert isinstance(error, UnpickleError)
def test_popen_with_various_methods(self):
channel = self.gw.remote_exec("""
from xdist.mypickle import PickleChannel
channel = PickleChannel(channel)
channel.receive()
""")
channel = PickleChannel(channel)
assert not channel.isclosed()
assert not channel._getremoteerror()
channel.send(2)
channel.waitclose(timeout=2)

127
testing/test_nodemanage.py Normal file
View File

@@ -0,0 +1,127 @@
import py
from xdist.nodemanage import NodeManager
class pytest_funcarg__mysetup:
def __init__(self, request):
basetemp = request.config.mktemp(
"mysetup-%s" % request.function.__name__,
numbered=True)
self.source = basetemp.mkdir("source")
self.dest = basetemp.mkdir("dest")
request.getfuncargvalue("_pytest")
class TestNodeManager:
@py.test.mark.xfail
def test_rsync_roots_no_roots(self, testdir, mysetup):
mysetup.source.ensure("dir1", "file1").write("hello")
config = testdir.reparseconfig([source])
nodemanager = NodeManager(config, ["popen//chdir=%s" % mysetup.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)
py.builtin.print_("remote curdir", p)
assert p == mysetup.dest.join(config.topdir.basename)
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=10.0)
def test_popen_rsync_subdir(self, testdir, mysetup):
source, dest = mysetup.source, mysetup.dest
dir1 = mysetup.source.mkdir("dir1")
dir2 = dir1.mkdir("dir2")
dir2.ensure("hello")
for rsyncroot in (dir1, source):
dest.remove()
nodemanager = NodeManager(testdir.parseconfig(
"--tx", "popen//chdir=%s" % dest,
"--rsyncdir", rsyncroot,
source,
))
assert nodemanager.config.topdir == source
nodemanager.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()
nodemanager.gwmanager.exit()
def test_init_rsync_roots(self, testdir, mysetup):
source, dest = mysetup.source, mysetup.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 = testdir.reparseconfig([source]).initsession()
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()
def test_rsyncignore(self, testdir, mysetup):
source, dest = mysetup.source, mysetup.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 = testdir.reparseconfig([source]).initsession()
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, testdir, mysetup):
source, dest = mysetup.source, mysetup.dest
specs = ["popen"] * 3
source.join("conftest.py").write("rsyncdirs = ['a']")
source.ensure('a', dir=1)
config = testdir.reparseconfig([source])
nodemanager = NodeManager(config, specs)
nodemanager.rsync_roots()
for gwspec in nodemanager.gwmanager.specs:
assert gwspec._samefilesystem()
assert not gwspec.chdir
def test_setup_DEBUG(self, mysetup, testdir):
source = mysetup.source
specs = ["popen"] * 2
source.join("conftest.py").write("rsyncdirs = ['a']")
source.ensure('a', dir=1)
config = testdir.reparseconfig([source, '--debug'])
assert config.option.debug
nodemanager = NodeManager(config, specs)
reprec = testdir.getreportrecorder(config).hookrecorder
nodemanager.setup_nodes(putevent=[].append)
for spec in nodemanager.gwmanager.specs:
l = reprec.getcalls("pytest_trace")
assert l
nodemanager.teardown_nodes()
def test_ssh_setup_nodes(self, specssh, testdir):
testdir.makepyfile(__init__="", test_x="""
def test_one():
pass
""")
reprec = testdir.inline_run("-d", "--rsyncdir=%s" % testdir.tmpdir,
"--tx", specssh, testdir.tmpdir)
rep, = reprec.getreports("pytest_runtest_logreport")
assert rep.passed

198
testing/test_pickling.py Normal file
View File

@@ -0,0 +1,198 @@
import py
import pickle
def setglobals(request):
oldconfig = py.test.config
print("setting py.test.config to None")
py.test.config = None
def resetglobals():
py.builtin.print_("setting py.test.config to", oldconfig)
py.test.config = oldconfig
request.addfinalizer(resetglobals)
def pytest_funcarg__testdir(request):
setglobals(request)
return request.getfuncargvalue("testdir")
class ImmutablePickleTransport:
def __init__(self, request):
from xdist.mypickle import ImmutablePickler
self.p1 = ImmutablePickler(uneven=0)
self.p2 = ImmutablePickler(uneven=1)
setglobals(request)
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
pytest_funcarg__pickletransport = ImmutablePickleTransport
class TestImmutablePickling:
def test_pickle_config(self, testdir, pickletransport):
config1 = testdir.parseconfig()
assert config1.topdir == testdir.tmpdir
testdir.chdir()
p2config = pickletransport.p1_to_p2(config1)
assert p2config.topdir.realpath() == config1.topdir.realpath()
config_back = pickletransport.p2_to_p1(p2config)
assert config_back is config1
def test_pickle_modcol(self, testdir, pickletransport):
modcol1 = testdir.getmodulecol("def test_one(): pass")
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")
item = modcol1.collect_by_name("test_one")
testdir.chdir()
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
class TestConfigPickling:
def test_config_getstate_setstate(self, testdir):
from py.impl.test.config import Config
testdir.makepyfile(__init__="", conftest="x=1; y=2")
hello = testdir.makepyfile(hello="")
tmp = testdir.tmpdir
testdir.chdir()
config1 = testdir.parseconfig(hello)
config2 = Config()
config2.__setstate__(config1.__getstate__())
assert config2.topdir == py.path.local()
config2_relpaths = [py.path.local(x).relto(config2.topdir)
for x in config2.args]
config1_relpaths = [py.path.local(x).relto(config1.topdir)
for x in config1.args]
assert config2_relpaths == config1_relpaths
for name, value in config1.option.__dict__.items():
assert getattr(config2.option, name) == value
assert config2.getvalue("x") == 1
def test_config_pickling_customoption(self, testdir):
testdir.makeconftest("""
def pytest_addoption(parser):
group = parser.getgroup("testing group")
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("""
def pytest_addoption(parser):
group = parser.getgroup("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__()
config = testdir.Config()
py.test.raises(AttributeError, "config.option.gdest")
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 option.gdest == 11
def test_config_picklability(self, testdir):
config = testdir.parseconfig()
s = pickle.dumps(config)
newconfig = pickle.loads(s)
assert hasattr(newconfig, "topdir")
assert newconfig.topdir == py.path.local()
def test_collector_implicit_config_pickling(self, testdir):
tmpdir = testdir.tmpdir
testdir.chdir()
testdir.makepyfile(hello="def test_x(): pass")
config = testdir.parseconfig(tmpdir)
col = config.getnode(config.topdir)
io = py.io.BytesIO()
pickler = pickle.Pickler(io)
pickler.dump(col)
io.seek(0)
unpickler = pickle.Unpickler(io)
col2 = unpickler.load()
assert col2.name == col.name
assert col2.listnames() == col.listnames()
def test_config_and_collector_pickling(self, testdir):
tmpdir = testdir.tmpdir
dir1 = tmpdir.ensure("somedir", dir=1)
config = testdir.parseconfig()
col = config.getnode(config.topdir)
col1 = col.join(dir1.basename)
assert col1.parent is col
io = py.io.BytesIO()
pickler = pickle.Pickler(io)
pickler.dump(col)
pickler.dump(col1)
pickler.dump(col)
io.seek(0)
unpickler = pickle.Unpickler(io)
topdir = tmpdir.ensure("newtopdir", dir=1)
topdir.ensure("somedir", dir=1)
old = topdir.chdir()
try:
newcol = unpickler.load()
newcol2 = unpickler.load()
newcol3 = unpickler.load()
assert newcol2.config is newcol.config
assert newcol2.parent == newcol
assert newcol2.config.topdir.realpath() == topdir.realpath()
assert newcol.fspath.realpath() == topdir.realpath()
assert newcol2.fspath.basename == dir1.basename
assert newcol2.fspath.relto(newcol2.config.topdir)
finally:
old.chdir()
def test_config__setstate__wired_correctly_in_childprocess(testdir):
execnet = py.test.importorskip("execnet")
from xdist.mypickle import PickleChannel
gw = execnet.makegateway()
channel = gw.remote_exec("""
import py
from xdist.mypickle import PickleChannel
channel = PickleChannel(channel)
config = channel.receive()
assert py.test.config == config
""")
channel = PickleChannel(channel)
config = testdir.parseconfig()
channel.send(config)
channel.waitclose() # this will potentially raise
gw.exit()

61
testing/test_plugin.py Normal file
View File

@@ -0,0 +1,61 @@
import py
import execnet
from xdist.nodemanage import NodeManager
def test_dist_incompatibility_messages(testdir):
Error = py.test.config.Error
py.test.raises(Error, "testdir.parseconfigure('--pdb', '--looponfail')")
result = testdir.runpytest("--pdb", "-n", "3")
assert result.ret != 0
assert "incompatible" in result.stderr.str()
result = testdir.runpytest("--pdb", "-d", "--tx", "popen")
assert result.ret != 0
assert "incompatible" in result.stderr.str()
def test_dist_options(testdir):
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"
class TestDistOptions:
def test_getxspecs(self, testdir):
config = testdir.parseconfigure("--tx=popen", "--tx", "ssh=xyz")
nodemanager = NodeManager(config)
xspecs = nodemanager._getxspecs()
assert len(xspecs) == 2
print(xspecs)
assert xspecs[0].popen
assert xspecs[1].ssh == "xyz"
def test_xspecs_multiplied(self, testdir):
config = testdir.parseconfigure("--tx=3*popen",)
xspecs = NodeManager(config)._getxspecs()
assert len(xspecs) == 3
assert xspecs[1].popen
def test_getrsyncdirs(self, testdir):
config = testdir.parseconfigure('--rsyncdir=' + str(testdir.tmpdir))
nm = NodeManager(config, specs=[execnet.XSpec("popen")])
roots = nm._getrsyncdirs()
assert len(roots) == 1 + 2 # pylib + xdist
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')
nm = NodeManager(config, specs=[execnet.XSpec("popen")])
roots = nm._getrsyncdirs()
assert len(roots) == 3 + 2 # pylib + xdist
assert py.path.local('y') in roots
assert py.path.local('z') in roots
assert testdir.tmpdir.join('x') in roots

151
testing/test_remote.py Normal file
View File

@@ -0,0 +1,151 @@
import py
py.test.importorskip("execnet")
from xdist.remote import LooponfailingSession, LoopState, RemoteControl
class TestRemoteControl:
def test_nofailures(self, testdir):
item = testdir.getitem("def test_func(): pass\n")
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.setup()
failures = control.runsession()
assert failures
control.setup()
item.fspath.write("def test_func(): assert 1\n")
pyc = item.fspath.new(ext=".pyc")
if pyc.check():
pyc.remove()
failures = control.runsession(failures)
assert not failures
def test_failure_change(self, testdir):
modcol = testdir.getitem("""
def test_func():
assert 0
""")
control = RemoteControl(modcol.config)
control.setup()
failures = control.runsession()
assert failures
control.setup()
modcol.fspath.write(py.code.Source("""
def test_func():
assert 1
def test_new():
assert 0
"""))
pyc = modcol.fspath.new(ext=".pyc")
if pyc.check():
pyc.remove()
failures = control.runsession(failures)
assert not failures
control.setup()
failures = control.runsession()
assert failures
assert str(failures).find("test_new") != -1
class TestLooponFailing:
def test_looponfail_from_fail_to_ok(self, testdir):
modcol = testdir.getmodulecol("""
def test_one():
x = 0
assert x == 1
def test_two():
assert 1
""")
session = LooponfailingSession(modcol.config)
loopstate = LoopState()
session.remotecontrol.setup()
session.loop_once(loopstate)
assert len(loopstate.colitems) == 1
modcol.fspath.write(py.code.Source("""
def test_one():
x = 15
assert x == 15
def test_two():
assert 1
"""))
assert session.statrecorder.check()
session.loop_once(loopstate)
assert not loopstate.colitems
def test_looponfail_from_one_to_two_tests(self, testdir):
modcol = testdir.getmodulecol("""
def test_one():
assert 0
""")
session = LooponfailingSession(modcol.config)
loopstate = LoopState()
session.remotecontrol.setup()
loopstate.colitems = []
session.loop_once(loopstate)
assert len(loopstate.colitems) == 1
modcol.fspath.write(py.code.Source("""
def test_one():
assert 1 # passes now
def test_two():
assert 0 # new and fails
"""))
assert session.statrecorder.check()
session.loop_once(loopstate)
assert len(loopstate.colitems) == 0
session.loop_once(loopstate)
assert len(loopstate.colitems) == 1
def test_looponfail_removed_test(self, testdir):
modcol = testdir.getmodulecol("""
def test_one():
assert 0
def test_two():
assert 0
""")
session = LooponfailingSession(modcol.config)
loopstate = LoopState()
session.remotecontrol.setup()
loopstate.colitems = []
session.loop_once(loopstate)
assert len(loopstate.colitems) == 2
modcol.fspath.write(py.code.Source("""
def test_xxx(): # renamed test
assert 0
def test_two():
assert 1 # pass now
"""))
assert session.statrecorder.check()
session.loop_once(loopstate)
assert len(loopstate.colitems) == 0
session.loop_once(loopstate)
assert len(loopstate.colitems) == 1
def test_looponfail_functional_fail_to_ok(self, testdir):
p = testdir.makepyfile("""
def test_one():
x = 0
assert x == 1
""")
child = testdir.spawn_pytest("-f %s" % p)
child.expect("def test_one")
child.expect("x == 1")
child.expect("1 failed")
child.expect("### LOOPONFAILING ####")
child.expect("waiting for changes")
p.write(py.code.Source("""
def test_one():
x = 1
assert x == 1
"""))
child.expect(".*1 passed.*")
child.kill(15)

148
testing/test_txnode.py Normal file
View File

@@ -0,0 +1,148 @@
import py
import execnet
from xdist.txnode import TXNode
queue = py.builtin._tryimport("queue", "Queue")
Queue = queue.Queue
class EventQueue:
def __init__(self, registry, queue=None):
if queue is None:
queue = Queue()
self.queue = queue
registry.register(self)
def geteventargs(self, eventname, timeout=2.0):
events = []
while 1:
try:
eventcall = self.queue.get(timeout=timeout)
except queue.Empty:
#print "node channel", self.node.channel
#print "remoteerror", self.node.channel._getremoteerror()
py.builtin.print_("seen events", events)
raise IOError("did not see %r events" % (eventname))
else:
name, args, kwargs = eventcall
assert isinstance(name, str)
if name == eventname:
if args:
return args
return kwargs
events.append(name)
if name == "pytest_internalerror":
py.builtin.print_(str(kwargs["excrepr"]))
class MySetup:
def __init__(self, request):
self.id = 0
self.request = request
def geteventargs(self, eventname, timeout=2.0):
eq = EventQueue(self.config.pluginmanager, self.queue)
return eq.geteventargs(eventname, timeout=timeout)
def makenode(self, config=None):
if config is None:
testdir = self.request.getfuncargvalue("testdir")
config = testdir.reparseconfig([])
self.config = config
self.queue = Queue()
self.xspec = execnet.XSpec("popen")
self.gateway = execnet.makegateway(self.xspec)
self.id += 1
self.gateway.id = str(self.id)
self.node = TXNode(self.gateway, self.config, putevent=self.queue.put)
assert not self.node.channel.isclosed()
return self.node
def xfinalize(self):
if hasattr(self, 'node'):
gw = self.node.gateway
py.builtin.print_("exiting:", gw)
gw.exit()
def pytest_funcarg__mysetup(request):
mysetup = MySetup(request)
#pyfuncitem.addfinalizer(mysetup.finalize)
return mysetup
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
kwargs = mysetup.geteventargs("pytest_testnodedown")
assert kwargs['node'] is node
assert "Not properly terminated" in str(kwargs['error'])
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(), 9)
""")
node = mysetup.makenode(item.config)
node.send(item)
kwargs = mysetup.geteventargs("pytest_testnodedown")
assert kwargs['node'] is node
assert "Not properly terminated" in str(kwargs['error'])
def test_node_down(self, mysetup):
node = mysetup.makenode()
node.shutdown()
kwargs = mysetup.geteventargs("pytest_testnodedown")
assert kwargs['node'] is node
assert not kwargs['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.getcalls(pytest_internalerror)
#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)
kwargs = mysetup.geteventargs("pytest_runtest_logreport")
rep = kwargs['report']
assert rep.passed
py.builtin.print_(rep)
assert rep.item == 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():
kwargs = mysetup.geteventargs("pytest_runtest_logreport")
report = kwargs['report']
assert getattr(report, outcome)
node.sendlist(items)
for outcome in "passed failed skipped".split():
rep = mysetup.geteventargs("pytest_runtest_logreport")['report']
assert getattr(rep, outcome)

61
testing/test_util.py Normal file
View File

@@ -0,0 +1,61 @@
import py
from xdist.util import StatRecorder
def test_filechange(tmpdir):
tmp = tmpdir
hello = tmp.ensure("hello.py")
sd = StatRecorder([tmp])
changed = sd.check()
assert not changed
hello.write("world")
changed = sd.check()
assert changed
tmp.ensure("new.py")
changed = sd.check()
assert changed
tmp.join("new.py").remove()
changed = sd.check()
assert changed
tmp.join("a", "b", "c.py").ensure()
changed = sd.check()
assert changed
tmp.join("a", "c.txt").ensure()
changed = sd.check()
assert changed
changed = sd.check()
assert not changed
tmp.join("a").remove()
changed = sd.check()
assert changed
def test_pycremoval(tmpdir):
tmp = tmpdir
hello = tmp.ensure("hello.py")
sd = StatRecorder([tmp])
changed = sd.check()
assert not changed
pycfile = hello + "c"
pycfile.ensure()
changed = sd.check()
assert not changed
hello.write("world")
changed = sd.check()
assert not pycfile.check()
def test_waitonchange(tmpdir, monkeypatch):
tmp = tmpdir
sd = StatRecorder([tmp])
l = [True, False]
monkeypatch.setattr(StatRecorder, 'check', lambda self: l.pop())
sd.waitonchange(checkinterval=0.2)
assert not l