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:
1
testing/__init__.py
Normal file
1
testing/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
#
|
||||
119
testing/acceptance_test.py
Normal file
119
testing/acceptance_test.py
Normal 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
40
testing/conftest.py
Normal 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
30
testing/test_boxed.py
Normal 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
|
||||
|
||||
37
testing/test_deprecated.py
Normal file
37
testing/test_deprecated.py
Normal 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
505
testing/test_dsession.py
Normal 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
120
testing/test_gwmanage.py
Normal 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
254
testing/test_mypickle.py
Normal 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
127
testing/test_nodemanage.py
Normal 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
198
testing/test_pickling.py
Normal 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
61
testing/test_plugin.py
Normal 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
151
testing/test_remote.py
Normal 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
148
testing/test_txnode.py
Normal 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
61
testing/test_util.py
Normal 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
|
||||
Reference in New Issue
Block a user