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 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.

View File

@@ -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

View File

@@ -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'):

View File

@@ -132,14 +132,15 @@ 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):
# once we are in shutdown mode we dont send # once we are in shutdown mode we dont send

View File

@@ -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,39 +135,31 @@ 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)
while 1: self.sendevent("slavefinished", error=error,
task = channel.receive() slaveoutput=self.config.slaveoutput)
if task is None:
break def _mainloop(self, colitems):
if isinstance(task, list): while 1:
for item in task: task = self.channel.receive()
self.run_single(item=item) if task is None:
else: break
self.run_single(item=task) if isinstance(task, list):
self.config.hook.pytest_sessionfinish( for item in task:
session=self, self.run_single(item=item)
exitstatus=outcome.EXIT_OK) else:
except KeyboardInterrupt: self.run_single(item=task)
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')