From f8e138b98624ece570f1f13806bccfafa3753c2c Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 10 Aug 2017 10:43:40 -0300 Subject: [PATCH] Fix tests incorrectly identified if worker crashes during teardown stage Fix #124 --- changelog/124.bugfix | 1 + testing/acceptance_test.py | 30 +++++++++++++++++++++++------- xdist/dsession.py | 17 +++++++++-------- xdist/remote.py | 15 +++++++++++---- xdist/slavemanage.py | 2 ++ 5 files changed, 46 insertions(+), 19 deletions(-) create mode 100644 changelog/124.bugfix diff --git a/changelog/124.bugfix b/changelog/124.bugfix new file mode 100644 index 0000000..b73ee67 --- /dev/null +++ b/changelog/124.bugfix @@ -0,0 +1 @@ +Fix issue where tests were being incorrectly identified if a worker crashed during the ``teardown`` stage of the test. diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index b1d3428..0fe3576 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -471,18 +471,34 @@ def test_funcarg_teardown_failure(testdir): assert result.ret -def test_crashing_item(testdir): +@pytest.mark.parametrize('when', ['setup', 'call', 'teardown']) +def test_crashing_item(testdir, when): + """Ensure crashing item is correctly reported during all testing stages""" + code = dict(setup='', call='', teardown='') + code[when] = 'py.process.kill(os.getpid())' p = testdir.makepyfile(""" - import py import os - def test_crash(): - py.process.kill(os.getpid()) - def test_noncrash(): + import py + import pytest + + @pytest.fixture + def fix(): + {setup} + yield + {teardown} + + def test_crash(fix): + {call} pass - """) + + def test_ok(): + pass + """.format(**code)) + passes = 2 if when == 'teardown' else 1 result = testdir.runpytest("-n2", p) result.stdout.fnmatch_lines([ - "*crashed*test_crash*", "*1 failed*1 passed*" + "*crashed*test_crash*", + "*1 failed*%d passed*" % passes, ]) diff --git a/xdist/dsession.py b/xdist/dsession.py index 3700357..ed459ba 100644 --- a/xdist/dsession.py +++ b/xdist/dsession.py @@ -230,18 +230,19 @@ class DSession: nodeid=nodeid, location=location) def slave_testreport(self, node, rep): - """Emitted when a node calls the pytest_runtest_logreport hook. - - If the node indicates it is finished with a test item, remove - the item from the pending list in the scheduler. - """ - if rep.when == "call" or (rep.when == "setup" and not rep.passed): - self.sched.mark_test_complete(node, rep.item_index, rep.duration) - # self.report_line("testreport %s: %s" %(rep.id, rep.status)) + """Emitted when a node calls the pytest_runtest_logreport hook.""" rep.node = node self.config.hook.pytest_runtest_logreport(report=rep) self._handlefailures(rep) + def slave_runtest_protocol_complete(self, node, item_index, duration): + """ + Emitted when a node fires the 'runtest_protocol_complete' event, + signalling that a test has completed the runtestprotocol and should be + removed from the pending list in the scheduler. + """ + self.sched.mark_test_complete(node, item_index, duration) + def slave_collectreport(self, node, rep): """Emitted when a node calls the pytest_collectreport hook.""" if rep.failed: diff --git a/xdist/remote.py b/xdist/remote.py index 791da78..fb55a8e 100644 --- a/xdist/remote.py +++ b/xdist/remote.py @@ -8,6 +8,7 @@ import sys import os +import time import pytest @@ -59,23 +60,29 @@ class SlaveInteractor: self.log("items to run:", torun) # only run if we have an item and a next item while len(torun) >= 2: - self.run_tests(torun) + self.run_one_test(torun) if name == "shutdown": if torun: - self.run_tests(torun) + self.run_one_test(torun) break return True - def run_tests(self, torun): + def run_one_test(self, torun): items = self.session.items self.item_index = torun.pop(0) + item = items[self.item_index] if torun: nextitem = items[torun[0]] else: nextitem = None + + start = time.time() self.config.hook.pytest_runtest_protocol( - item=items[self.item_index], + item=item, nextitem=nextitem) + duration = time.time() - start + self.sendevent("runtest_protocol_complete", item_index=self.item_index, + duration=duration) def pytest_collection_finish(self, session): self.sendevent( diff --git a/xdist/slavemanage.py b/xdist/slavemanage.py index 3ec53a8..7fc0b2c 100644 --- a/xdist/slavemanage.py +++ b/xdist/slavemanage.py @@ -315,6 +315,8 @@ class SlaveController(object): self.notify_inproc(eventname, node=self, rep=rep) elif eventname == "collectionfinish": self.notify_inproc(eventname, node=self, ids=kwargs['ids']) + elif eventname == "runtest_protocol_complete": + self.notify_inproc(eventname, node=self, **kwargs) elif eventname == "logwarning": self.notify_inproc(eventname, message=kwargs['message'], code=kwargs['code'], nodeid=kwargs['nodeid'],