Skip to content

Commit c5fadcd

Browse files
authored
Merge pull request #608 from nicoddemus/better-errors
Propagate internal errors to the master node
2 parents 948f137 + 16d6363 commit c5fadcd

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)