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:
holger krekel
2010-04-26 18:35:53 +02:00
parent 25cb514331
commit 9c74f59e55
5 changed files with 70 additions and 43 deletions

View File

@@ -1,6 +1,9 @@
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
to send and receive data from a slave.

View File

@@ -152,3 +152,23 @@ class TestDistribution:
"*1 passed*"
])
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

View File

@@ -81,7 +81,7 @@ class TestMasterSlaveConnection:
node.send(123) # invalid item
kwargs = mysetup.geteventargs("pytest_testnodedown")
assert kwargs['node'] is node
assert isinstance(kwargs['error'], execnet.RemoteError)
#assert isinstance(kwargs['error'], execnet.RemoteError)
def test_crash_killed(self, testdir, mysetup):
if not hasattr(py.std.os, 'kill'):

View File

@@ -132,14 +132,15 @@ class DSession(Session):
call(**kwargs)
# 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())):
if self.config.option.exitfirst:
raise ExitFirstInterrupt()
self.triggershutdown()
loopstate.shuttingdown = True
elif not self.node2pending:
loopstate.exitstatus = outcome.EXIT_NOHOSTS
if not self.node2pending:
loopstate.exitstatus = outcome.EXIT_NOHOSTS
def loop_once_shutdown(self, loopstate):
# once we are in shutdown mode we dont send

View File

@@ -3,7 +3,7 @@
"""
import py
from xdist.mypickle import PickleChannel
from py._test import outcome
from py._test.session import Session
class TXNode(object):
""" Represents a Test Execution environment in the controlling process.
@@ -45,7 +45,7 @@ class TXNode(object):
err = self.channel._getremoteerror()
if not self._down:
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._down = True
return
@@ -55,7 +55,8 @@ class TXNode(object):
elif eventname == "slavefinished":
self._down = True
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",
"pytest__teardown_final_logerror"):
kwargs['report'].node = self
@@ -92,8 +93,16 @@ def install_slave(node):
from xdist.txnode import SlaveSession
channel.send("basicimport")
channel = PickleChannel(channel)
session = SlaveSession(channel)
session.run()
import py
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 = PickleChannel(channel)
@@ -108,9 +117,11 @@ def install_slave(node):
channel.send((config, node.slaveinput, basetemp, node.gateway.id))
return channel
class SlaveSession(object):
def __init__(self, channel):
class SlaveSession(Session):
def __init__(self, config, channel, nodeid):
self.channel = channel
self.nodeid = nodeid
super(SlaveSession, self).__init__(config=config)
def __repr__(self):
return "<%s channel=%s>" %(self.__class__.__name__, self.channel)
@@ -124,39 +135,31 @@ class SlaveSession(object):
def pytest__teardown_final_logerror(self, report):
self.sendevent("pytest__teardown_final_logerror", report=report)
def run(self):
channel = self.channel
self.config, slaveinput, basetemp, self.nodeid = channel.receive()
if basetemp:
self.config.basetemp = py.path.local(basetemp)
self.config.slaveinput = slaveinput
self.config.slaveoutput = {}
self.config.pluginmanager.do_configure(self.config)
self.config.pluginmanager.register(self)
def pytest_keyboard_interrupt(self, excinfo):
self._slaveerror = "SIGINT"
def pytest_internalerror(self, excrepr):
self._slaveerror = "internal-error"
self.sendevent("pytest_internalerror", excrepr=excrepr)
def dist_main(self):
self.runner = self.config.pluginmanager.getplugin("pytest_runner")
self.sendevent("slaveready")
try:
self.config.hook.pytest_sessionstart(session=self)
while 1:
task = channel.receive()
if task is None:
break
if isinstance(task, list):
for item in task:
self.run_single(item=item)
else:
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)
self.main(None)
error = getattr(self, '_slaveerror', None)
self.sendevent("slavefinished", error=error,
slaveoutput=self.config.slaveoutput)
def _mainloop(self, colitems):
while 1:
task = self.channel.receive()
if task is None:
break
if isinstance(task, list):
for item in task:
self.run_single(item=item)
else:
self.run_single(item=task)
def run_single(self, item):
call = self.runner.CallInfo(item._reraiseunpicklingproblem, when='setup')