adapt to new refactored py baseline.
remove pickling in distributed testing refactor internal mechanism enough to work for -n1 * * * fix handling of crashing items and slave down events
This commit is contained in:
@@ -2,6 +2,40 @@ import py
|
||||
import sys
|
||||
|
||||
class TestDistribution:
|
||||
def test_n1_pass(self, testdir):
|
||||
p1 = testdir.makepyfile("""
|
||||
def test_ok():
|
||||
pass
|
||||
""")
|
||||
result = testdir.runpytest(p1, "-n1")
|
||||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines([
|
||||
"*1 passed*",
|
||||
])
|
||||
|
||||
def test_n1_fail(self, testdir):
|
||||
p1 = testdir.makepyfile("""
|
||||
def test_fail():
|
||||
assert 0
|
||||
""")
|
||||
result = testdir.runpytest(p1, "-n1")
|
||||
assert result.ret == 1
|
||||
result.stdout.fnmatch_lines([
|
||||
"*1 failed*",
|
||||
])
|
||||
|
||||
def test_n1_skip(self, testdir):
|
||||
p1 = testdir.makepyfile("""
|
||||
def test_skip():
|
||||
import py
|
||||
py.test.skip("myreason")
|
||||
""")
|
||||
result = testdir.runpytest(p1, "-n1")
|
||||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines([
|
||||
"*1 skipped*",
|
||||
])
|
||||
|
||||
def test_manytests_to_one_popen(self, testdir):
|
||||
p1 = testdir.makepyfile("""
|
||||
import py
|
||||
@@ -23,6 +57,20 @@ class TestDistribution:
|
||||
])
|
||||
assert result.ret == 1
|
||||
|
||||
def test_n1_fail_minus_x(self, testdir):
|
||||
p1 = testdir.makepyfile("""
|
||||
def test_fail1():
|
||||
assert 0
|
||||
def test_fail2():
|
||||
assert 0
|
||||
""")
|
||||
result = testdir.runpytest(p1, "-x", "-v", "-n1")
|
||||
assert result.ret == 2
|
||||
result.stdout.fnmatch_lines([
|
||||
"*Interrupted: stopping*1*",
|
||||
"*1 failed*",
|
||||
])
|
||||
|
||||
def test_dist_conftest_specified(self, testdir):
|
||||
p1 = testdir.makepyfile("""
|
||||
import py
|
||||
@@ -48,7 +96,7 @@ class TestDistribution:
|
||||
])
|
||||
assert result.ret == 1
|
||||
|
||||
@py.test.mark.xfail("sys.platform.startswith('java')")
|
||||
@py.test.mark.xfail("sys.platform.startswith('java')", run=False)
|
||||
def test_dist_tests_with_crash(self, testdir):
|
||||
if not hasattr(py.std.os, 'kill'):
|
||||
py.test.skip("no os.kill")
|
||||
@@ -70,12 +118,11 @@ class TestDistribution:
|
||||
os.kill(os.getpid(), 15)
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest(p1, "-v", '-d', '--tx=3*popen')
|
||||
result = testdir.runpytest(p1, "-v", '-d', '-n1')
|
||||
result.stdout.fnmatch_lines([
|
||||
"*popen*Python*",
|
||||
"*popen*Python*",
|
||||
"*popen*Python*",
|
||||
"*node down*",
|
||||
"*test_ok*PASS*",
|
||||
"*node*down*",
|
||||
"*3 failed, 1 passed, 1 skipped*"
|
||||
])
|
||||
assert result.ret == 1
|
||||
@@ -86,8 +133,9 @@ class TestDistribution:
|
||||
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("-v", "-d", "--rsyncdir=%(subdir)s" % locals(),
|
||||
p.write("def test_5():\n assert not __file__.startswith(%r)" % str(p))
|
||||
result = testdir.runpytest("-v", "-d",
|
||||
"--rsyncdir=%(subdir)s" % locals(),
|
||||
"--tx=popen//chdir=%(dest)s" % locals(), p)
|
||||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines([
|
||||
|
||||
@@ -17,11 +17,9 @@ 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):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from xdist.dsession import DSession
|
||||
from xdist.dsession import DSession, LoadScheduling
|
||||
from py._test import session as outcome
|
||||
import py
|
||||
import execnet
|
||||
@@ -16,8 +16,8 @@ class MockNode:
|
||||
def __init__(self):
|
||||
self.sent = []
|
||||
|
||||
def send(self, item):
|
||||
self.sent.append(item)
|
||||
def send_runtest(self, nodeid):
|
||||
self.sent.append(nodeid)
|
||||
|
||||
def sendlist(self, items):
|
||||
self.sent.extend(items)
|
||||
@@ -29,23 +29,78 @@ 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
|
||||
class TestLoadScheduling:
|
||||
def test_schedule_load_simple(self):
|
||||
node1 = MockNode()
|
||||
node2 = MockNode()
|
||||
sched = LoadScheduling(2)
|
||||
sched.addnode(node1)
|
||||
sched.addnode(node2)
|
||||
collection = ["a.py::test_1", "a.py::test_2"]
|
||||
assert not sched.collection_is_completed()
|
||||
sched.addnode_collection(node1, collection)
|
||||
assert not sched.collection_is_completed()
|
||||
sched.addnode_collection(node2, collection)
|
||||
assert sched.collection_is_completed()
|
||||
assert sched.node2collection[node1] == collection
|
||||
assert sched.node2collection[node2] == collection
|
||||
sched.init_distribute()
|
||||
assert sched.pending
|
||||
sched.triggertesting()
|
||||
assert not sched.tests_finished()
|
||||
assert node1.sent == collection[:1]
|
||||
assert node2.sent == collection[1:]
|
||||
sched.remove_item(node1, collection[0])
|
||||
sched.remove_item(node2, collection[1])
|
||||
assert sched.tests_finished()
|
||||
assert not sched.pending
|
||||
|
||||
def test_senditems_each_and_receive_with_two_nodes(self, testdir):
|
||||
def test_triggertesting_chunksize(self):
|
||||
sched = LoadScheduling(2)
|
||||
node1 = MockNode()
|
||||
node2 = MockNode()
|
||||
sched.addnode(node1)
|
||||
sched.addnode(node2)
|
||||
sched.ITEM_CHUNKSIZE = 2
|
||||
col = ["xyz"] * (2*sched.ITEM_CHUNKSIZE +1)
|
||||
sched.addnode_collection(node1, col)
|
||||
sched.addnode_collection(node2, col)
|
||||
sched.init_distribute()
|
||||
sched.triggertesting()
|
||||
sent1 = node1.sent
|
||||
sent2 = node2.sent
|
||||
chunkitems = col[:sched.ITEM_CHUNKSIZE]
|
||||
assert sent1 == chunkitems
|
||||
assert sent2 == chunkitems
|
||||
assert sched.node2pending[node1] == sent1
|
||||
assert sched.node2pending[node2] == sent2
|
||||
assert len(sched.pending) == 1
|
||||
for node in (node1, node2):
|
||||
for i in range(sched.ITEM_CHUNKSIZE):
|
||||
sched.remove_item(node, "xyz")
|
||||
sched.triggertesting()
|
||||
assert not sched.pending
|
||||
|
||||
def test_add_remove_node(self):
|
||||
node = MockNode()
|
||||
sched = LoadScheduling(1)
|
||||
sched.addnode(node)
|
||||
collection = ["test_file.py::test_func"]
|
||||
sched.addnode_collection(node, collection)
|
||||
assert sched.collection_is_completed()
|
||||
sched.init_distribute()
|
||||
sched.triggertesting()
|
||||
assert not sched.pending
|
||||
sched.remove_node(node)
|
||||
assert sched.pending == collection
|
||||
|
||||
|
||||
class TestDSession:
|
||||
|
||||
#def test_collection_fails(self, testdir):
|
||||
# pass
|
||||
|
||||
def xxx_test_senditems_each_and_receive_with_two_nodes(self, testdir):
|
||||
item = testdir.getitem("def test_func(): pass")
|
||||
node1 = MockNode()
|
||||
node2 = MockNode()
|
||||
@@ -63,54 +118,6 @@ class TestDSession:
|
||||
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)
|
||||
reprec = testdir.getreportrecorder(session)
|
||||
items = session.collect_all_items([modcol])
|
||||
assert len(items) == 1
|
||||
calls= reprec.getcalls("pytest_collectreport")
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
assert len(call.report.result) == 1
|
||||
|
||||
def test_senditems_load(self, testdir, monkeypatch):
|
||||
item = testdir.getitem("def test_func(): pass")
|
||||
session = DSession(item.config)
|
||||
node1 = MockNode()
|
||||
node2 = MockNode()
|
||||
session.addnode(node1)
|
||||
session.addnode(node2)
|
||||
monkeypatch.setattr(session, 'ITEM_CHUNKSIZE', 3)
|
||||
session.senditems_load([item] * (2*session.ITEM_CHUNKSIZE +1))
|
||||
sent1 = node1.sent
|
||||
sent2 = node2.sent
|
||||
chunkitems = [item] * session.ITEM_CHUNKSIZE
|
||||
assert sent1 == chunkitems
|
||||
assert sent2 == chunkitems
|
||||
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)
|
||||
@@ -127,35 +134,6 @@ class TestDSession:
|
||||
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)
|
||||
# we need to do work because nothing is pending / we would not wake up
|
||||
assert loopstate.dowork == True
|
||||
|
||||
session.node2pending[node].append(item)
|
||||
session.queueevent("pytest_rescheduleitems", items=[item])
|
||||
session.loop_once(loopstate)
|
||||
# now we want to not directly trigger work again to avoid busy-wait
|
||||
assert loopstate.dowork == False
|
||||
|
||||
session.queueevent(None)
|
||||
session.loop_once(loopstate)
|
||||
session.queueevent(None)
|
||||
session.loop_once(loopstate)
|
||||
assert node.sent == [item, item]
|
||||
session.queueevent("pytest_runtest_logreport", report=run(item, node))
|
||||
session.loop_once(loopstate)
|
||||
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
|
||||
|
||||
@@ -1,254 +0,0 @@
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
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
|
||||
|
||||
|
||||
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()
|
||||
@@ -4,8 +4,8 @@ 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", "--looponfail")
|
||||
assert result.ret != 0
|
||||
result = testdir.runpytest("--pdb", "-n", "3")
|
||||
assert result.ret != 0
|
||||
assert "incompatible" in result.stderr.str()
|
||||
|
||||
198
testing/test_remote.py
Normal file
198
testing/test_remote.py
Normal file
@@ -0,0 +1,198 @@
|
||||
import py
|
||||
from xdist.remote import SlaveController
|
||||
from xdist.remote import serialize_report, unserialize_report
|
||||
import execnet
|
||||
queue = py.builtin._tryimport("queue", "Queue")
|
||||
from py.builtin import print_
|
||||
import marshal
|
||||
|
||||
def check_marshallable(d):
|
||||
try:
|
||||
marshal.dumps(d)
|
||||
except ValueError:
|
||||
py.std.pprint.pprint(d)
|
||||
raise ValueError("not marshallable")
|
||||
|
||||
class EventCall:
|
||||
def __init__(self, eventcall):
|
||||
self.name, self.kwargs = eventcall
|
||||
|
||||
def __str__(self):
|
||||
return "<EventCall %s(**%s)>" %(self.name, self.kwargs)
|
||||
|
||||
class SlaveSetup:
|
||||
use_callback = False
|
||||
|
||||
def __init__(self, request):
|
||||
self.testdir = testdir = request.getfuncargvalue("testdir")
|
||||
self.request = request
|
||||
self.events = queue.Queue()
|
||||
|
||||
def setup(self, ):
|
||||
self.testdir.chdir()
|
||||
#import os ; os.environ['EXECNET_DEBUG'] = "2"
|
||||
self.gateway = execnet.makegateway()
|
||||
self.config = config = self.testdir.parseconfig()
|
||||
putevent = self.use_callback and self.events.put or None
|
||||
self.slp = SlaveController(None, self.gateway, config, putevent)
|
||||
self.request.addfinalizer(self.slp.ensure_teardown)
|
||||
self.slp.setup()
|
||||
|
||||
def popevent(self, name=None):
|
||||
while 1:
|
||||
if self.use_callback:
|
||||
data = self.events.get(timeout=2)
|
||||
else:
|
||||
data = self.slp.channel.receive(timeout=2)
|
||||
ev = EventCall(data)
|
||||
if name is None or ev.name == name:
|
||||
return ev
|
||||
print("skipping %s" % (ev,))
|
||||
|
||||
def sendcommand(self, name, **kwargs):
|
||||
self.slp.sendcommand(name, **kwargs)
|
||||
|
||||
def pytest_funcarg__slave(request):
|
||||
return SlaveSetup(request)
|
||||
|
||||
def test_remoteinitconfig(testdir):
|
||||
from xdist.remote import remote_initconfig
|
||||
config1 = testdir.parseconfig()
|
||||
config2 = testdir.parseconfig("-x")
|
||||
cfg = remote_initconfig(config2, config1.option.__dict__, config1.args)
|
||||
assert cfg == config2
|
||||
assert cfg.option.__dict__ == config1.option.__dict__
|
||||
|
||||
class TestReportSerialization:
|
||||
def test_itemreport_outcomes(self, testdir):
|
||||
reprec = testdir.inline_runsource("""
|
||||
import py
|
||||
def test_pass(): pass
|
||||
def test_fail(): 0/0
|
||||
@py.test.mark.skipif("True")
|
||||
def test_skip(): pass
|
||||
def test_skip_imperative():
|
||||
py.test.skip("hello")
|
||||
@py.test.mark.xfail("True")
|
||||
def test_xfail(): 0/0
|
||||
def test_xfail_imperative():
|
||||
py.test.xfail("hello")
|
||||
""")
|
||||
reports = reprec.getreports("pytest_runtest_logreport")
|
||||
assert len(reports) == 6
|
||||
for rep in reports:
|
||||
d = serialize_report(rep)
|
||||
check_marshallable(d)
|
||||
newrep = unserialize_report(d)
|
||||
assert newrep.passed == rep.passed
|
||||
assert newrep.failed == rep.failed
|
||||
assert newrep.skipped == rep.skipped
|
||||
assert newrep.outcome == rep.outcome
|
||||
assert newrep.when == rep.when
|
||||
assert newrep.keywords == rep.keywords
|
||||
if rep.failed:
|
||||
assert newrep.longrepr == str(rep.longrepr)
|
||||
|
||||
def test_collectreport_passed(self, testdir):
|
||||
reprec = testdir.inline_runsource("def test_func(): pass")
|
||||
reports = reprec.getreports("pytest_collectreport")
|
||||
for rep in reports:
|
||||
d = serialize_report(rep)
|
||||
check_marshallable(d)
|
||||
newrep = unserialize_report(d)
|
||||
assert newrep.passed == rep.passed
|
||||
assert newrep.failed == rep.failed
|
||||
assert newrep.skipped == rep.skipped
|
||||
|
||||
def test_collectreport_fail(self, testdir):
|
||||
reprec = testdir.inline_runsource("qwe abc")
|
||||
reports = reprec.getreports("pytest_collectreport")
|
||||
assert reports
|
||||
for rep in reports:
|
||||
d = serialize_report(rep)
|
||||
check_marshallable(d)
|
||||
newrep = unserialize_report(d)
|
||||
assert newrep.passed == rep.passed
|
||||
assert newrep.failed == rep.failed
|
||||
assert newrep.skipped == rep.skipped
|
||||
if rep.failed:
|
||||
assert newrep.longrepr == str(rep.longrepr)
|
||||
|
||||
class TestSlaveInteractor:
|
||||
def test_basic_collect_and_runtests(self, slave):
|
||||
p = slave.testdir.makepyfile("""
|
||||
def test_func():
|
||||
pass
|
||||
""")
|
||||
slave.setup()
|
||||
ev = slave.popevent()
|
||||
assert ev.name == "slaveready"
|
||||
ev = slave.popevent()
|
||||
assert ev.name == "collectionstart"
|
||||
assert not ev.kwargs
|
||||
ev = slave.popevent("collectionfinish")
|
||||
assert ev.kwargs['topdir'] == slave.testdir.tmpdir
|
||||
ids = ev.kwargs['ids']
|
||||
assert len(ids) == 1
|
||||
slave.sendcommand("runtests", ids=ids)
|
||||
ev = slave.popevent("testreport")
|
||||
assert ev.name == "testreport"
|
||||
rep = unserialize_report(ev.kwargs['data'])
|
||||
assert rep.nodeid.endswith("::test_func")
|
||||
assert rep.passed
|
||||
assert rep.when == "call"
|
||||
slave.sendcommand("shutdown")
|
||||
ev = slave.popevent("slavefinished")
|
||||
assert 'slaveoutput' in ev.kwargs
|
||||
|
||||
def test_remote_collect_skip(self, slave):
|
||||
p = slave.testdir.makepyfile("""
|
||||
import py
|
||||
py.test.skip("hello")
|
||||
""")
|
||||
slave.setup()
|
||||
ev = slave.popevent("collectionstart")
|
||||
assert not ev.kwargs
|
||||
ev = slave.popevent()
|
||||
assert ev.name == "collectreport"
|
||||
rep = unserialize_report(ev.kwargs['data'])
|
||||
assert rep.skipped
|
||||
ev = slave.popevent("collectionfinish")
|
||||
print ev.kwargs
|
||||
assert not ev.kwargs['ids']
|
||||
|
||||
def test_remote_collect_fail(self, slave):
|
||||
p = slave.testdir.makepyfile("""aasd qwe""")
|
||||
slave.setup()
|
||||
ev = slave.popevent("collectionstart")
|
||||
assert not ev.kwargs
|
||||
ev = slave.popevent()
|
||||
assert ev.name == "collectreport"
|
||||
rep = unserialize_report(ev.kwargs['data'])
|
||||
assert rep.failed
|
||||
ev = slave.popevent("collectionfinish")
|
||||
print ev.kwargs
|
||||
assert not ev.kwargs['ids']
|
||||
|
||||
def test_happy_run_events_converted(self, testdir, slave):
|
||||
py.test.xfail("implement a simple test for event production")
|
||||
assert not slave.use_callback
|
||||
p = slave.testdir.makepyfile("""
|
||||
def test_func():
|
||||
pass
|
||||
""")
|
||||
slave.setup()
|
||||
hookrec = testdir.getreportrecorder(slave.config)
|
||||
for data in slave.slp.channel:
|
||||
slave.slp.process_from_remote(data)
|
||||
slave.slp.process_from_remote(slave.slp.ENDMARK)
|
||||
py.std.pprint.pprint(hookrec.hookrecorder.calls)
|
||||
hookrec.hookrecorder.contains([
|
||||
("pytest_collectstart", "collector.fspath == aaa"),
|
||||
("pytest_pycollect_makeitem", "name == 'test_func'"),
|
||||
("pytest_collectreport", "report.collector.fspath == aaa"),
|
||||
("pytest_collectstart", "collector.fspath == bbb"),
|
||||
("pytest_pycollect_makeitem", "name == 'test_func'"),
|
||||
("pytest_collectreport", "report.collector.fspath == bbb"),
|
||||
])
|
||||
|
||||
Reference in New Issue
Block a user