Skip to content

Commit 81e3898

Browse files
committed
Fix warnings transfer between workers and master node with pytest >= 3.8
Fix #341
1 parent ed2ab76 commit 81e3898

File tree

8 files changed

+97
-3
lines changed

8 files changed

+97
-3
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ env:
2222
- TOXENV=py-pytest32
2323
- TOXENV=py-pytest33
2424
- TOXENV=py-pytest36
25+
- TOXENV=py-pytest38
2526

2627
install: pip install tox setuptools_scm
2728
script: tox

appveyor.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ environment:
66
- TOXENV: "py35-pytest33"
77
- TOXENV: "py36-pytest33"
88
- TOXENV: "py36-pytest36"
9+
- TOXENV: "py36-pytest38"
910
- TOXENV: "py27-pytest33-pexpect"
1011
- TOXENV: "py36-pytest33-pexpect"
1112

changelog/341.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix warnings transfer between workers and master node with pytest >= 3.8.

testing/acceptance_test.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ def test_func():
402402

403403
@pytest.mark.parametrize("n", ["-n0", "-n1"])
404404
@pytest.mark.parametrize("warn_type", ["pytest", "builtin"])
405-
def test_logwarning(self, testdir, n, warn_type):
405+
def test_warnings(self, testdir, n, warn_type):
406406
from pkg_resources import parse_version
407407

408408
if parse_version(pytest.__version__) < parse_version("3.1"):
@@ -417,7 +417,9 @@ def test_logwarning(self, testdir, n, warn_type):
417417
assert False
418418
testdir.makepyfile(
419419
"""
420-
import warnings, py
420+
import warnings, py, pytest
421+
422+
@pytest.mark.filterwarnings('ignore:config.warn has been deprecated')
421423
def test_func(request):
422424
{warn_code}
423425
""".format(

tox.ini

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# if you change the envlist, please update .travis.yml file as well
33
envlist=
44
linting
5-
py{27,34,35,36}-pytest{30,31,32,33,36}
5+
py{27,34,35,36}-pytest{30,31,32,33,36,38}
66
py{27,36}-pytest36-pexpect
77
py{27,36}-pytest{master,features}
88

@@ -19,6 +19,7 @@ deps =
1919
pytest32: pytest~=3.2.0
2020
pytest33: pytest~=3.3.0
2121
pytest36: pytest~=3.6.0
22+
pytest38: pytest~=3.8.0
2223
pytestmaster: git+https://github.com/pytest-dev/pytest.git@master
2324
pytestfeatures: git+https://github.com/pytest-dev/pytest.git@features
2425
pexpect: pexpect

xdist/dsession.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,11 @@ def worker_logwarning(self, message, code, nodeid, fslocation):
270270
kwargs = dict(message=message, code=code, nodeid=nodeid, fslocation=fslocation)
271271
self.config.hook.pytest_logwarning.call_historic(kwargs=kwargs)
272272

273+
def worker_warning_captured(self, warning_message, when, item):
274+
"""Emitted when a node calls the pytest_logwarning hook."""
275+
kwargs = dict(warning_message=warning_message, when=when, item=item)
276+
self.config.hook.pytest_warning_captured.call_historic(kwargs=kwargs)
277+
273278
def _clone_node(self, node):
274279
"""Return new node based on an existing one.
275280

xdist/remote.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,18 @@ def pytest_logwarning(self, message, code, nodeid, fslocation):
123123
fslocation=str(fslocation),
124124
)
125125

126+
# the pytest_warning_captured hook was introduced in pytest 3.8
127+
if hasattr(_pytest.hookspec, "pytest_warning_captured"):
128+
129+
def pytest_warning_captured(self, warning_message, when, item):
130+
self.sendevent(
131+
"warning_captured",
132+
warning_message_data=serialize_warning_message(warning_message),
133+
when=when,
134+
# XXX what to do with "item" parameter? It can't be serialized
135+
item=None,
136+
)
137+
126138

127139
def serialize_report(rep):
128140
def disassembled_report(rep):
@@ -165,6 +177,40 @@ def disassembled_report(rep):
165177
return d
166178

167179

180+
def serialize_warning_message(warning_message):
181+
if isinstance(warning_message.message, Warning):
182+
message_module = type(warning_message.message).__module__
183+
message_class_name = type(warning_message.message).__name__
184+
message_args = warning_message.message.args
185+
message_str = None
186+
else:
187+
message_str = warning_message.message
188+
message_module = None
189+
message_class_name = None
190+
message_args = None
191+
if warning_message.category:
192+
category_module = warning_message.category.__module__
193+
category_class_name = warning_message.category.__name__
194+
else:
195+
category_module = None
196+
category_class_name = None
197+
198+
result = {
199+
"message_str": message_str,
200+
"message_module": message_module,
201+
"message_class_name": message_class_name,
202+
"message_args": message_args,
203+
"category_module": category_module,
204+
"category_class_name": category_class_name,
205+
}
206+
# access private _WARNING_DETAILS because the attributes vary between Python versions
207+
for attr_name in warning_message._WARNING_DETAILS:
208+
if attr_name in ("message", "category"):
209+
continue
210+
result[attr_name] = getattr(warning_message, attr_name)
211+
return result
212+
213+
168214
def getinfodict():
169215
import platform
170216

xdist/workermanage.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,16 @@ def process_from_remote(self, eventcall): # noqa too complex
327327
nodeid=kwargs["nodeid"],
328328
fslocation=kwargs["nodeid"],
329329
)
330+
elif eventname == "warning_captured":
331+
warning_message = unserialize_warning_message(
332+
kwargs["warning_message_data"]
333+
)
334+
self.notify_inproc(
335+
eventname,
336+
warning_message=warning_message,
337+
when=kwargs["when"],
338+
item=kwargs["item"],
339+
)
330340
else:
331341
raise ValueError("unknown event: %s" % (eventname,))
332342
except KeyboardInterrupt:
@@ -409,6 +419,33 @@ def assembled_report(reportdict):
409419
return runner.CollectReport(**assembled_report(reportdict))
410420

411421

422+
def unserialize_warning_message(data):
423+
import warnings
424+
import importlib
425+
426+
if data["message_module"]:
427+
mod = importlib.import_module(data["message_module"])
428+
cls = getattr(mod, data["message_class_name"])
429+
message = cls(*data["message_args"])
430+
else:
431+
message = data["message_str"]
432+
433+
if data["category_module"]:
434+
mod = importlib.import_module(data["category_module"])
435+
category = getattr(mod, data["category_class_name"])
436+
else:
437+
category = None
438+
439+
kwargs = {"message": message, "category": category}
440+
# access private _WARNING_DETAILS because the attributes vary between Python versions
441+
for attr_name in warnings.WarningMessage._WARNING_DETAILS:
442+
if attr_name in ("message", "category"):
443+
continue
444+
kwargs[attr_name] = data[attr_name]
445+
446+
return warnings.WarningMessage(**kwargs)
447+
448+
412449
def report_unserialization_failure(type_name, report_name, reportdict):
413450
from pprint import pprint
414451

0 commit comments

Comments
 (0)