From bc2ba55f70db35f0780a45c4149efbedc89e01c7 Mon Sep 17 00:00:00 2001 From: Brandon Hoffman Date: Wed, 26 Sep 2018 19:32:13 -0500 Subject: [PATCH 1/8] add exception handeling for warning serialization issues --- xdist/remote.py | 2 +- xdist/workermanage.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/xdist/remote.py b/xdist/remote.py index 091bde8..bee3471 100644 --- a/xdist/remote.py +++ b/xdist/remote.py @@ -182,7 +182,7 @@ def serialize_warning_message(warning_message): message_module = type(warning_message.message).__module__ message_class_name = type(warning_message.message).__name__ message_args = warning_message.message.args - message_str = None + message_str = str(warning_message.message) else: message_str = warning_message.message message_module = None diff --git a/xdist/workermanage.py b/xdist/workermanage.py index b607d4e..d6224a2 100644 --- a/xdist/workermanage.py +++ b/xdist/workermanage.py @@ -426,7 +426,10 @@ def unserialize_warning_message(data): if data["message_module"]: mod = importlib.import_module(data["message_module"]) cls = getattr(mod, data["message_class_name"]) - message = cls(*data["message_args"]) + try: + message = cls(*data["message_args"]) + except TypeError: + message = Warning(data["message_str"]) else: message = data["message_str"] From af3e9756645dcf27ee253ea9c7fbff8caa3aac4b Mon Sep 17 00:00:00 2001 From: Brandon Hoffman Date: Wed, 26 Sep 2018 19:46:28 -0500 Subject: [PATCH 2/8] add changelog --- changelog/344.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/344.bugfix diff --git a/changelog/344.bugfix b/changelog/344.bugfix new file mode 100644 index 0000000..a88fca2 --- /dev/null +++ b/changelog/344.bugfix @@ -0,0 +1 @@ +Fix issue where Warnings could cause pytest to fail if they do not set the args attribute correctly. From 36c28ee15405c98d25b0047e99495a1450a74634 Mon Sep 17 00:00:00 2001 From: Brandon Hoffman Date: Wed, 26 Sep 2018 20:44:01 -0500 Subject: [PATCH 3/8] add test case for invalid warning handeling --- testing/acceptance_test.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index dcf717d..ff4e1f7 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -401,10 +401,11 @@ class TestTerminalReporting: ) @pytest.mark.parametrize("n", ["-n0", "-n1"]) - @pytest.mark.parametrize("warn_type", ["pytest", "builtin"]) + @pytest.mark.parametrize("warn_type", ["pytest", "builtin", "invalid"]) def test_warnings(self, testdir, n, warn_type): from pkg_resources import parse_version + print(n, warn_type) if parse_version(pytest.__version__) < parse_version("3.1"): pytest.skip("pytest warnings requires >= 3.1") @@ -413,12 +414,21 @@ class TestTerminalReporting: elif warn_type == "pytest": warn_code = """request.config.warn('', 'this is a warning', fslocation=py.path.local())""" + elif warn_type == "invalid": + warn_code = "msg = UserWarning('this is a warning'); msg.args = (); warnings.warn(msg)" else: assert False testdir.makepyfile( """ import warnings, py, pytest + class BadWarning(Warning): + def __init__(self, param): + self.param = param + super(BadWarning, self).__init__() + def __str__(self): + return self.param + @pytest.mark.filterwarnings('ignore:config.warn has been deprecated') def test_func(request): {warn_code} From 571d08fb8b67836cb2975c4bc03c7f6f46e620e1 Mon Sep 17 00:00:00 2001 From: Brandon Hoffman Date: Wed, 26 Sep 2018 21:05:30 -0500 Subject: [PATCH 4/8] remove unneeded lines --- testing/acceptance_test.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index ff4e1f7..d57064d 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -405,7 +405,6 @@ class TestTerminalReporting: def test_warnings(self, testdir, n, warn_type): from pkg_resources import parse_version - print(n, warn_type) if parse_version(pytest.__version__) < parse_version("3.1"): pytest.skip("pytest warnings requires >= 3.1") @@ -422,13 +421,6 @@ class TestTerminalReporting: """ import warnings, py, pytest - class BadWarning(Warning): - def __init__(self, param): - self.param = param - super(BadWarning, self).__init__() - def __str__(self): - return self.param - @pytest.mark.filterwarnings('ignore:config.warn has been deprecated') def test_func(request): {warn_code} From 5d5f87b60082a0cfa2ec23e057626c8af57b52d9 Mon Sep 17 00:00:00 2001 From: Brandon Hoffman Date: Thu, 27 Sep 2018 19:24:58 -0500 Subject: [PATCH 5/8] add moduel and class name to warning text --- xdist/workermanage.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/xdist/workermanage.py b/xdist/workermanage.py index d6224a2..6a4b645 100644 --- a/xdist/workermanage.py +++ b/xdist/workermanage.py @@ -429,7 +429,10 @@ def unserialize_warning_message(data): try: message = cls(*data["message_args"]) except TypeError: - message = Warning(data["message_str"]) + message_text = "{mod}.{class}: {msg}".format(mod=mod, + cls=cls, + msg=data["message_str"]) + message = Warning(message_text) else: message = data["message_str"] From 8825e7e234095d60e7c5d5b89801078c34f4c18d Mon Sep 17 00:00:00 2001 From: Brandon Hoffman Date: Thu, 27 Sep 2018 19:27:49 -0500 Subject: [PATCH 6/8] use module name and class name for method instead of the reference to them --- xdist/workermanage.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xdist/workermanage.py b/xdist/workermanage.py index 6a4b645..a83ee48 100644 --- a/xdist/workermanage.py +++ b/xdist/workermanage.py @@ -429,8 +429,8 @@ def unserialize_warning_message(data): try: message = cls(*data["message_args"]) except TypeError: - message_text = "{mod}.{class}: {msg}".format(mod=mod, - cls=cls, + message_text = "{mod}.{class}: {msg}".format(mod=data["message_module"], + cls=data["message_class_name"], msg=data["message_str"]) message = Warning(message_text) else: From 22e36c9cb401c0c0a7b77d232c9f093525697f0a Mon Sep 17 00:00:00 2001 From: Brandon Hoffman Date: Thu, 27 Sep 2018 19:32:10 -0500 Subject: [PATCH 7/8] run black formatter --- xdist/workermanage.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/xdist/workermanage.py b/xdist/workermanage.py index a83ee48..5bc1f39 100644 --- a/xdist/workermanage.py +++ b/xdist/workermanage.py @@ -429,9 +429,11 @@ def unserialize_warning_message(data): try: message = cls(*data["message_args"]) except TypeError: - message_text = "{mod}.{class}: {msg}".format(mod=data["message_module"], - cls=data["message_class_name"], - msg=data["message_str"]) + message_text = "{mod}.{class}: {msg}".format( + mod=data["message_module"], + cls=data["message_class_name"], + msg=data["message_str"], + ) message = Warning(message_text) else: message = data["message_str"] From b176b376067de6eba121d173d70da1edd533cac4 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 27 Sep 2018 22:18:52 -0300 Subject: [PATCH 8/8] Add a test which reproduces #344 and fix the handling code Fix #344 --- testing/acceptance_test.py | 33 ++++++++++++++++++++++++++++++--- xdist/workermanage.py | 2 +- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index d57064d..987dbe6 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -401,7 +401,7 @@ class TestTerminalReporting: ) @pytest.mark.parametrize("n", ["-n0", "-n1"]) - @pytest.mark.parametrize("warn_type", ["pytest", "builtin", "invalid"]) + @pytest.mark.parametrize("warn_type", ["pytest", "builtin"]) def test_warnings(self, testdir, n, warn_type): from pkg_resources import parse_version @@ -413,8 +413,6 @@ class TestTerminalReporting: elif warn_type == "pytest": warn_code = """request.config.warn('', 'this is a warning', fslocation=py.path.local())""" - elif warn_type == "invalid": - warn_code = "msg = UserWarning('this is a warning'); msg.args = (); warnings.warn(msg)" else: assert False testdir.makepyfile( @@ -431,6 +429,35 @@ class TestTerminalReporting: result = testdir.runpytest(n) result.stdout.fnmatch_lines(["*this is a warning*", "*1 passed, 1 warnings*"]) + @pytest.mark.parametrize("n", ["-n0", "-n1"]) + def test_custom_subclass(self, testdir, n): + """Check that warning subclasses that don't honor the args attribute don't break + pytest-xdist (#344) + """ + from pkg_resources import parse_version + + if parse_version(pytest.__version__) < parse_version("3.1"): + pytest.skip("pytest warnings requires >= 3.1") + + testdir.makepyfile( + """ + import warnings, py, pytest + + class MyWarning(UserWarning): + + def __init__(self, p1, p2): + self.p1 = p1 + self.p2 = p2 + self.args = () + + def test_func(request): + warnings.warn(MyWarning("foo", 1)) + """ + ) + testdir.syspathinsert() + result = testdir.runpytest(n) + result.stdout.fnmatch_lines(["*MyWarning*", "*1 passed, 1 warnings*"]) + def test_logfinish_hook(self, testdir): """Ensure the pytest_runtest_logfinish hook is being properly handled""" from _pytest import hookspec diff --git a/xdist/workermanage.py b/xdist/workermanage.py index 5bc1f39..9eb6418 100644 --- a/xdist/workermanage.py +++ b/xdist/workermanage.py @@ -429,7 +429,7 @@ def unserialize_warning_message(data): try: message = cls(*data["message_args"]) except TypeError: - message_text = "{mod}.{class}: {msg}".format( + message_text = "{mod}.{cls}: {msg}".format( mod=data["message_module"], cls=data["message_class_name"], msg=data["message_str"],