Skip to content

Commit

Permalink
Merge pull request #608 from nicoddemus/better-errors
Browse files Browse the repository at this point in the history
Propagate internal errors to the master node
  • Loading branch information
nicoddemus committed Dec 14, 2020
2 parents 948f137 + 16d6363 commit c5fadcd
Show file tree
Hide file tree
Showing 5 changed files with 36 additions and 1 deletion.
1 change: 1 addition & 0 deletions changelog/608.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Internal errors in workers are now propagated to the master node.
18 changes: 18 additions & 0 deletions src/xdist/dsession.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,24 @@ def worker_workerfinished(self, node):
assert not crashitem, (crashitem, node)
self._active_nodes.remove(node)

def worker_internal_error(self, node, formatted_error):
"""
pytest_internalerror() was called on the worker.
pytest_internalerror() arguments are an excinfo and an excrepr, which can't
be serialized, so we go with a poor man's solution of raising an exception
here ourselves using the formatted message.
"""
self._active_nodes.remove(node)
try:
assert False, formatted_error
except AssertionError:
from _pytest._code import ExceptionInfo

excinfo = ExceptionInfo.from_current()
excrepr = excinfo.getrepr()
self.config.hook.pytest_internalerror(excrepr=excrepr, excinfo=excinfo)

def worker_errordown(self, node, error):
"""Emitted by the WorkerController when a node dies."""
self.config.hook.pytest_testnodedown(node=node, error=error)
Expand Down
4 changes: 3 additions & 1 deletion src/xdist/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ def sendevent(self, name, **kwargs):
self.channel.send((name, kwargs))

def pytest_internalerror(self, excrepr):
for line in str(excrepr).split("\n"):
formatted_error = str(excrepr)
for line in formatted_error.split("\n"):
self.log("IERROR>", line)
interactor.sendevent("internal_error", formatted_error=formatted_error)

def pytest_sessionstart(self, session):
self.session = session
Expand Down
2 changes: 2 additions & 0 deletions src/xdist/workermanage.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,8 @@ def process_from_remote(self, eventcall): # noqa too complex
self.log("ignoring {}({})".format(eventname, kwargs))
elif eventname == "workerready":
self.notify_inproc(eventname, node=self, **kwargs)
elif eventname == "internal_error":
self.notify_inproc(eventname, node=self, **kwargs)
elif eventname == "workerfinished":
self._down = True
self.workeroutput = kwargs["workeroutput"]
Expand Down
12 changes: 12 additions & 0 deletions testing/acceptance_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1138,6 +1138,18 @@ def test_aaa1(crasher):
assert "INTERNALERROR" not in result.stderr.str()


def test_internal_errors_propagate_to_master(testdir):
testdir.makeconftest(
"""
def pytest_collection_modifyitems():
raise RuntimeError("Some runtime error")
"""
)
testdir.makepyfile("def test(): pass")
result = testdir.runpytest("-n1")
result.stdout.fnmatch_lines(["*RuntimeError: Some runtime error*"])


class TestLoadScope:
def test_by_module(self, testdir):
test_file = """
Expand Down

0 comments on commit c5fadcd

Please sign in to comment.