diff --git a/changelog/349.bugfix.rst b/changelog/349.bugfix.rst new file mode 100644 index 0000000..af0fa43 --- /dev/null +++ b/changelog/349.bugfix.rst @@ -0,0 +1 @@ +Correctly handle warnings created with arguments that can't be serialized during the transfer from workers to master node. diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 095543a..deb3c1d 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -766,6 +766,23 @@ class TestWarnings: result = testdir.runpytest(n) result.stdout.fnmatch_lines(["*MyWarning*", "*1 passed, 1 warnings*"]) + @pytest.mark.parametrize("n", ["-n0", "-n1"]) + def test_unserializable_arguments(self, testdir, n): + """Check that warnings with unserializable arguments are handled correctly (#349).""" + testdir.makepyfile( + """ + import warnings, pytest + + def test_func(tmpdir): + fn = (tmpdir / 'foo.txt').ensure(file=1) + with fn.open('r') as f: + warnings.warn(UserWarning("foo", f)) + """ + ) + testdir.syspathinsert() + result = testdir.runpytest(n) + result.stdout.fnmatch_lines(["*UserWarning*foo.txt*", "*1 passed, 1 warnings*"]) + class TestNodeFailure: def test_load_single(self, testdir): diff --git a/xdist/remote.py b/xdist/remote.py index 0c5aa30..66b270c 100644 --- a/xdist/remote.py +++ b/xdist/remote.py @@ -12,6 +12,7 @@ import time import _pytest.hookspec import pytest +from execnet.gateway_base import dumps, DumpError class WorkerInteractor(object): @@ -181,8 +182,15 @@ def serialize_warning_message(warning_message): if isinstance(warning_message.message, Warning): message_module = type(warning_message.message).__module__ message_class_name = type(warning_message.message).__name__ - message_args = warning_message.message.args message_str = str(warning_message.message) + # check now if we can serialize the warning arguments (#349) + # if not, we will just use the exception message on the master node + try: + dumps(warning_message.message.args) + except DumpError: + message_args = None + else: + message_args = warning_message.message.args else: message_str = warning_message.message message_module = None diff --git a/xdist/workermanage.py b/xdist/workermanage.py index 9eb6418..3b35c14 100644 --- a/xdist/workermanage.py +++ b/xdist/workermanage.py @@ -426,9 +426,16 @@ def unserialize_warning_message(data): if data["message_module"]: mod = importlib.import_module(data["message_module"]) cls = getattr(mod, data["message_class_name"]) - try: - message = cls(*data["message_args"]) - except TypeError: + message = None + if data["message_args"] is not None: + try: + message = cls(*data["message_args"]) + except TypeError: + pass + if message is None: + # could not recreate the original warning instance; + # create a generic Warning instance with the original + # message at least message_text = "{mod}.{cls}: {msg}".format( mod=data["message_module"], cls=data["message_class_name"],