fixes issue79 - call hooks more systematically on slave nodes and also in the case of SIGINT
reorganize internal session code to share more code with the "normal" non-distributed session
This commit is contained in:
@@ -1,6 +1,9 @@
|
|||||||
1.2
|
1.2
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
|
- fix issue79: sessionfinish/teardown hooks more systematically
|
||||||
|
on the slave side
|
||||||
|
|
||||||
- introduce a new data input/output mechanism to allow the master side
|
- introduce a new data input/output mechanism to allow the master side
|
||||||
to send and receive data from a slave.
|
to send and receive data from a slave.
|
||||||
|
|
||||||
|
|||||||
@@ -152,3 +152,23 @@ class TestDistribution:
|
|||||||
"*1 passed*"
|
"*1 passed*"
|
||||||
])
|
])
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
|
|
||||||
|
def test_keyboardinterrupt_hooks_issue79(self, testdir):
|
||||||
|
testdir.makepyfile(__init__="", test_one="""
|
||||||
|
def test_hello():
|
||||||
|
raise KeyboardInterrupt()
|
||||||
|
""")
|
||||||
|
testdir.makeconftest("""
|
||||||
|
def pytest_sessionfinish(session):
|
||||||
|
if hasattr(session.config, 'slaveoutput'):
|
||||||
|
session.config.slaveoutput['s2'] = 42
|
||||||
|
def pytest_testnodedown(node, error):
|
||||||
|
assert node.slaveoutput['s2'] == 42
|
||||||
|
print "s2call-finished"
|
||||||
|
""")
|
||||||
|
args = ["-n1"]
|
||||||
|
result = testdir.runpytest(*args)
|
||||||
|
s = result.stdout.str()
|
||||||
|
assert result.ret
|
||||||
|
assert 'SIGINT' in s
|
||||||
|
assert 's2call' in s
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ class TestMasterSlaveConnection:
|
|||||||
node.send(123) # invalid item
|
node.send(123) # invalid item
|
||||||
kwargs = mysetup.geteventargs("pytest_testnodedown")
|
kwargs = mysetup.geteventargs("pytest_testnodedown")
|
||||||
assert kwargs['node'] is node
|
assert kwargs['node'] is node
|
||||||
assert isinstance(kwargs['error'], execnet.RemoteError)
|
#assert isinstance(kwargs['error'], execnet.RemoteError)
|
||||||
|
|
||||||
def test_crash_killed(self, testdir, mysetup):
|
def test_crash_killed(self, testdir, mysetup):
|
||||||
if not hasattr(py.std.os, 'kill'):
|
if not hasattr(py.std.os, 'kill'):
|
||||||
|
|||||||
@@ -132,13 +132,14 @@ class DSession(Session):
|
|||||||
call(**kwargs)
|
call(**kwargs)
|
||||||
|
|
||||||
# termination conditions
|
# termination conditions
|
||||||
if ((loopstate.testsfailed and self.config.option.exitfirst) or
|
if (not self.node2pending or
|
||||||
|
(loopstate.testsfailed and self.config.option.exitfirst) or
|
||||||
(not self.item2nodes and not colitems and not self.queue.qsize())):
|
(not self.item2nodes and not colitems and not self.queue.qsize())):
|
||||||
if self.config.option.exitfirst:
|
if self.config.option.exitfirst:
|
||||||
raise ExitFirstInterrupt()
|
raise ExitFirstInterrupt()
|
||||||
self.triggershutdown()
|
self.triggershutdown()
|
||||||
loopstate.shuttingdown = True
|
loopstate.shuttingdown = True
|
||||||
elif not self.node2pending:
|
if not self.node2pending:
|
||||||
loopstate.exitstatus = outcome.EXIT_NOHOSTS
|
loopstate.exitstatus = outcome.EXIT_NOHOSTS
|
||||||
|
|
||||||
def loop_once_shutdown(self, loopstate):
|
def loop_once_shutdown(self, loopstate):
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
import py
|
import py
|
||||||
from xdist.mypickle import PickleChannel
|
from xdist.mypickle import PickleChannel
|
||||||
from py._test import outcome
|
from py._test.session import Session
|
||||||
|
|
||||||
class TXNode(object):
|
class TXNode(object):
|
||||||
""" Represents a Test Execution environment in the controlling process.
|
""" Represents a Test Execution environment in the controlling process.
|
||||||
@@ -45,7 +45,7 @@ class TXNode(object):
|
|||||||
err = self.channel._getremoteerror()
|
err = self.channel._getremoteerror()
|
||||||
if not self._down:
|
if not self._down:
|
||||||
if not err or isinstance(err, EOFError):
|
if not err or isinstance(err, EOFError):
|
||||||
err = "Not properly terminated"
|
err = "Not properly terminated" # lost connection?
|
||||||
self.notify("pytest_testnodedown", node=self, error=err)
|
self.notify("pytest_testnodedown", node=self, error=err)
|
||||||
self._down = True
|
self._down = True
|
||||||
return
|
return
|
||||||
@@ -55,7 +55,8 @@ class TXNode(object):
|
|||||||
elif eventname == "slavefinished":
|
elif eventname == "slavefinished":
|
||||||
self._down = True
|
self._down = True
|
||||||
self.slaveoutput = kwargs['slaveoutput']
|
self.slaveoutput = kwargs['slaveoutput']
|
||||||
self.notify("pytest_testnodedown", error=None, node=self)
|
error = kwargs['error']
|
||||||
|
self.notify("pytest_testnodedown", error=error, node=self)
|
||||||
elif eventname in ("pytest_runtest_logreport",
|
elif eventname in ("pytest_runtest_logreport",
|
||||||
"pytest__teardown_final_logerror"):
|
"pytest__teardown_final_logerror"):
|
||||||
kwargs['report'].node = self
|
kwargs['report'].node = self
|
||||||
@@ -92,8 +93,16 @@ def install_slave(node):
|
|||||||
from xdist.txnode import SlaveSession
|
from xdist.txnode import SlaveSession
|
||||||
channel.send("basicimport")
|
channel.send("basicimport")
|
||||||
channel = PickleChannel(channel)
|
channel = PickleChannel(channel)
|
||||||
session = SlaveSession(channel)
|
import py
|
||||||
session.run()
|
config, slaveinput, basetemp, nodeid = channel.receive()
|
||||||
|
config.slaveinput = slaveinput
|
||||||
|
config.slaveoutput = {}
|
||||||
|
if basetemp:
|
||||||
|
config.basetemp = py.path.local(basetemp)
|
||||||
|
config.nodeid = nodeid
|
||||||
|
config.pluginmanager.do_configure(config)
|
||||||
|
session = SlaveSession(config, channel, nodeid)
|
||||||
|
session.dist_main()
|
||||||
""")
|
""")
|
||||||
channel.receive()
|
channel.receive()
|
||||||
channel = PickleChannel(channel)
|
channel = PickleChannel(channel)
|
||||||
@@ -108,9 +117,11 @@ def install_slave(node):
|
|||||||
channel.send((config, node.slaveinput, basetemp, node.gateway.id))
|
channel.send((config, node.slaveinput, basetemp, node.gateway.id))
|
||||||
return channel
|
return channel
|
||||||
|
|
||||||
class SlaveSession(object):
|
class SlaveSession(Session):
|
||||||
def __init__(self, channel):
|
def __init__(self, config, channel, nodeid):
|
||||||
self.channel = channel
|
self.channel = channel
|
||||||
|
self.nodeid = nodeid
|
||||||
|
super(SlaveSession, self).__init__(config=config)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<%s channel=%s>" %(self.__class__.__name__, self.channel)
|
return "<%s channel=%s>" %(self.__class__.__name__, self.channel)
|
||||||
@@ -124,21 +135,24 @@ class SlaveSession(object):
|
|||||||
def pytest__teardown_final_logerror(self, report):
|
def pytest__teardown_final_logerror(self, report):
|
||||||
self.sendevent("pytest__teardown_final_logerror", report=report)
|
self.sendevent("pytest__teardown_final_logerror", report=report)
|
||||||
|
|
||||||
def run(self):
|
def pytest_keyboard_interrupt(self, excinfo):
|
||||||
channel = self.channel
|
self._slaveerror = "SIGINT"
|
||||||
self.config, slaveinput, basetemp, self.nodeid = channel.receive()
|
|
||||||
if basetemp:
|
def pytest_internalerror(self, excrepr):
|
||||||
self.config.basetemp = py.path.local(basetemp)
|
self._slaveerror = "internal-error"
|
||||||
self.config.slaveinput = slaveinput
|
self.sendevent("pytest_internalerror", excrepr=excrepr)
|
||||||
self.config.slaveoutput = {}
|
|
||||||
self.config.pluginmanager.do_configure(self.config)
|
def dist_main(self):
|
||||||
self.config.pluginmanager.register(self)
|
|
||||||
self.runner = self.config.pluginmanager.getplugin("pytest_runner")
|
self.runner = self.config.pluginmanager.getplugin("pytest_runner")
|
||||||
self.sendevent("slaveready")
|
self.sendevent("slaveready")
|
||||||
try:
|
self.main(None)
|
||||||
self.config.hook.pytest_sessionstart(session=self)
|
error = getattr(self, '_slaveerror', None)
|
||||||
|
self.sendevent("slavefinished", error=error,
|
||||||
|
slaveoutput=self.config.slaveoutput)
|
||||||
|
|
||||||
|
def _mainloop(self, colitems):
|
||||||
while 1:
|
while 1:
|
||||||
task = channel.receive()
|
task = self.channel.receive()
|
||||||
if task is None:
|
if task is None:
|
||||||
break
|
break
|
||||||
if isinstance(task, list):
|
if isinstance(task, list):
|
||||||
@@ -146,17 +160,6 @@ class SlaveSession(object):
|
|||||||
self.run_single(item=item)
|
self.run_single(item=item)
|
||||||
else:
|
else:
|
||||||
self.run_single(item=task)
|
self.run_single(item=task)
|
||||||
self.config.hook.pytest_sessionfinish(
|
|
||||||
session=self,
|
|
||||||
exitstatus=outcome.EXIT_OK)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
raise
|
|
||||||
except:
|
|
||||||
er = py.code.ExceptionInfo().getrepr(funcargs=True, showlocals=True)
|
|
||||||
self.sendevent("pytest_internalerror", excrepr=er)
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
self.sendevent("slavefinished", slaveoutput=self.config.slaveoutput)
|
|
||||||
|
|
||||||
def run_single(self, item):
|
def run_single(self, item):
|
||||||
call = self.runner.CallInfo(item._reraiseunpicklingproblem, when='setup')
|
call = self.runner.CallInfo(item._reraiseunpicklingproblem, when='setup')
|
||||||
|
|||||||
Reference in New Issue
Block a user