Skip to content

Commit 7b9546b

Browse files
authored
Merge pull request #218 from nicoddemus/teardown-crash
Identify correct test crashed during teardown and support multiple test logs from plugins
2 parents 542111d + 2adf810 commit 7b9546b

File tree

6 files changed

+74
-19
lines changed

6 files changed

+74
-19
lines changed

changelog/124.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix issue where tests were being incorrectly identified if a worker crashed during the ``teardown`` stage of the test.

changelog/206.feature

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
``xdist`` now supports tests to log results multiple times, improving integration with plugins which require
2+
it like `pytest-rerunfailures <https://github.com/gocept/pytest-rerunfailures>_`.

testing/acceptance_test.py

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -471,18 +471,60 @@ def test_hello(myarg):
471471
assert result.ret
472472

473473

474-
def test_crashing_item(testdir):
474+
@pytest.mark.parametrize('when', ['setup', 'call', 'teardown'])
475+
def test_crashing_item(testdir, when):
476+
"""Ensure crashing item is correctly reported during all testing stages"""
477+
code = dict(setup='', call='', teardown='')
478+
code[when] = 'py.process.kill(os.getpid())'
475479
p = testdir.makepyfile("""
476-
import py
477480
import os
478-
def test_crash():
479-
py.process.kill(os.getpid())
480-
def test_noncrash():
481+
import py
482+
import pytest
483+
484+
@pytest.fixture
485+
def fix():
486+
{setup}
487+
yield
488+
{teardown}
489+
490+
def test_crash(fix):
491+
{call}
481492
pass
482-
""")
493+
494+
def test_ok():
495+
pass
496+
""".format(**code))
497+
passes = 2 if when == 'teardown' else 1
483498
result = testdir.runpytest("-n2", p)
484499
result.stdout.fnmatch_lines([
485-
"*crashed*test_crash*", "*1 failed*1 passed*"
500+
"*crashed*test_crash*",
501+
"*1 failed*%d passed*" % passes,
502+
])
503+
504+
505+
def test_multiple_log_reports(testdir):
506+
"""
507+
Ensure that pytest-xdist supports plugins that emit multiple logreports
508+
(#206).
509+
Inspired by pytest-rerunfailures.
510+
"""
511+
testdir.makeconftest("""
512+
from _pytest.runner import runtestprotocol
513+
def pytest_runtest_protocol(item, nextitem):
514+
item.ihook.pytest_runtest_logstart(nodeid=item.nodeid,
515+
location=item.location)
516+
reports = runtestprotocol(item, nextitem=nextitem)
517+
for report in reports:
518+
item.ihook.pytest_runtest_logreport(report=report)
519+
return True
520+
""")
521+
testdir.makepyfile("""
522+
def test():
523+
pass
524+
""")
525+
result = testdir.runpytest("-n1")
526+
result.stdout.fnmatch_lines([
527+
"*2 passed*",
486528
])
487529

488530

xdist/dsession.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -230,18 +230,19 @@ def slave_logstart(self, node, nodeid, location):
230230
nodeid=nodeid, location=location)
231231

232232
def slave_testreport(self, node, rep):
233-
"""Emitted when a node calls the pytest_runtest_logreport hook.
234-
235-
If the node indicates it is finished with a test item, remove
236-
the item from the pending list in the scheduler.
237-
"""
238-
if rep.when == "call" or (rep.when == "setup" and not rep.passed):
239-
self.sched.mark_test_complete(node, rep.item_index, rep.duration)
240-
# self.report_line("testreport %s: %s" %(rep.id, rep.status))
233+
"""Emitted when a node calls the pytest_runtest_logreport hook."""
241234
rep.node = node
242235
self.config.hook.pytest_runtest_logreport(report=rep)
243236
self._handlefailures(rep)
244237

238+
def slave_runtest_protocol_complete(self, node, item_index, duration):
239+
"""
240+
Emitted when a node fires the 'runtest_protocol_complete' event,
241+
signalling that a test has completed the runtestprotocol and should be
242+
removed from the pending list in the scheduler.
243+
"""
244+
self.sched.mark_test_complete(node, item_index, duration)
245+
245246
def slave_collectreport(self, node, rep):
246247
"""Emitted when a node calls the pytest_collectreport hook."""
247248
if rep.failed:

xdist/remote.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import sys
1010
import os
11+
import time
1112
import pytest
1213

1314

@@ -59,23 +60,29 @@ def pytest_runtestloop(self, session):
5960
self.log("items to run:", torun)
6061
# only run if we have an item and a next item
6162
while len(torun) >= 2:
62-
self.run_tests(torun)
63+
self.run_one_test(torun)
6364
if name == "shutdown":
6465
if torun:
65-
self.run_tests(torun)
66+
self.run_one_test(torun)
6667
break
6768
return True
6869

69-
def run_tests(self, torun):
70+
def run_one_test(self, torun):
7071
items = self.session.items
7172
self.item_index = torun.pop(0)
73+
item = items[self.item_index]
7274
if torun:
7375
nextitem = items[torun[0]]
7476
else:
7577
nextitem = None
78+
79+
start = time.time()
7680
self.config.hook.pytest_runtest_protocol(
77-
item=items[self.item_index],
81+
item=item,
7882
nextitem=nextitem)
83+
duration = time.time() - start
84+
self.sendevent("runtest_protocol_complete", item_index=self.item_index,
85+
duration=duration)
7986

8087
def pytest_collection_finish(self, session):
8188
self.sendevent(

xdist/slavemanage.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,8 @@ def process_from_remote(self, eventcall): # noqa too complex
315315
self.notify_inproc(eventname, node=self, rep=rep)
316316
elif eventname == "collectionfinish":
317317
self.notify_inproc(eventname, node=self, ids=kwargs['ids'])
318+
elif eventname == "runtest_protocol_complete":
319+
self.notify_inproc(eventname, node=self, **kwargs)
318320
elif eventname == "logwarning":
319321
self.notify_inproc(eventname, message=kwargs['message'],
320322
code=kwargs['code'], nodeid=kwargs['nodeid'],

0 commit comments

Comments
 (0)