Skip to content

Commit 16d6363

Browse files
committed
Propagate internal errors to the master node
This should help users diagnose internal errors in workers like exceptions from hooks or in pytest itself.
1 parent 948f137 commit 16d6363

File tree

5 files changed

+36
-1
lines changed

5 files changed

+36
-1
lines changed

changelog/608.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Internal errors in workers are now propagated to the master node.

src/xdist/dsession.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,24 @@ def worker_workerfinished(self, node):
174174
assert not crashitem, (crashitem, node)
175175
self._active_nodes.remove(node)
176176

177+
def worker_internal_error(self, node, formatted_error):
178+
"""
179+
pytest_internalerror() was called on the worker.
180+
181+
pytest_internalerror() arguments are an excinfo and an excrepr, which can't
182+
be serialized, so we go with a poor man's solution of raising an exception
183+
here ourselves using the formatted message.
184+
"""
185+
self._active_nodes.remove(node)
186+
try:
187+
assert False, formatted_error
188+
except AssertionError:
189+
from _pytest._code import ExceptionInfo
190+
191+
excinfo = ExceptionInfo.from_current()
192+
excrepr = excinfo.getrepr()
193+
self.config.hook.pytest_internalerror(excrepr=excrepr, excinfo=excinfo)
194+
177195
def worker_errordown(self, node, error):
178196
"""Emitted by the WorkerController when a node dies."""
179197
self.config.hook.pytest_testnodedown(node=node, error=error)

src/xdist/remote.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,10 @@ def sendevent(self, name, **kwargs):
3333
self.channel.send((name, kwargs))
3434

3535
def pytest_internalerror(self, excrepr):
36-
for line in str(excrepr).split("\n"):
36+
formatted_error = str(excrepr)
37+
for line in formatted_error.split("\n"):
3738
self.log("IERROR>", line)
39+
interactor.sendevent("internal_error", formatted_error=formatted_error)
3840

3941
def pytest_sessionstart(self, session):
4042
self.session = session

src/xdist/workermanage.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,8 @@ def process_from_remote(self, eventcall): # noqa too complex
324324
self.log("ignoring {}({})".format(eventname, kwargs))
325325
elif eventname == "workerready":
326326
self.notify_inproc(eventname, node=self, **kwargs)
327+
elif eventname == "internal_error":
328+
self.notify_inproc(eventname, node=self, **kwargs)
327329
elif eventname == "workerfinished":
328330
self._down = True
329331
self.workeroutput = kwargs["workeroutput"]

testing/acceptance_test.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1138,6 +1138,18 @@ def test_aaa1(crasher):
11381138
assert "INTERNALERROR" not in result.stderr.str()
11391139

11401140

1141+
def test_internal_errors_propagate_to_master(testdir):
1142+
testdir.makeconftest(
1143+
"""
1144+
def pytest_collection_modifyitems():
1145+
raise RuntimeError("Some runtime error")
1146+
"""
1147+
)
1148+
testdir.makepyfile("def test(): pass")
1149+
result = testdir.runpytest("-n1")
1150+
result.stdout.fnmatch_lines(["*RuntimeError: Some runtime error*"])
1151+
1152+
11411153
class TestLoadScope:
11421154
def test_by_module(self, testdir):
11431155
test_file = """

0 commit comments

Comments
 (0)