Skip to content

Commit

Permalink
Fix warnings transfer between workers and master node with pytest >= 3.8
Browse files Browse the repository at this point in the history
Fix #341
  • Loading branch information
nicoddemus committed Sep 25, 2018
1 parent ed2ab76 commit 81e3898
Show file tree
Hide file tree
Showing 8 changed files with 97 additions and 3 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ env:
- TOXENV=py-pytest32
- TOXENV=py-pytest33
- TOXENV=py-pytest36
- TOXENV=py-pytest38

install: pip install tox setuptools_scm
script: tox
Expand Down
1 change: 1 addition & 0 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ environment:
- TOXENV: "py35-pytest33"
- TOXENV: "py36-pytest33"
- TOXENV: "py36-pytest36"
- TOXENV: "py36-pytest38"
- TOXENV: "py27-pytest33-pexpect"
- TOXENV: "py36-pytest33-pexpect"

Expand Down
1 change: 1 addition & 0 deletions changelog/341.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix warnings transfer between workers and master node with pytest >= 3.8.
6 changes: 4 additions & 2 deletions testing/acceptance_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ def test_func():

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

if parse_version(pytest.__version__) < parse_version("3.1"):
Expand All @@ -417,7 +417,9 @@ def test_logwarning(self, testdir, n, warn_type):
assert False
testdir.makepyfile(
"""
import warnings, py
import warnings, py, pytest
@pytest.mark.filterwarnings('ignore:config.warn has been deprecated')
def test_func(request):
{warn_code}
""".format(
Expand Down
3 changes: 2 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# if you change the envlist, please update .travis.yml file as well
envlist=
linting
py{27,34,35,36}-pytest{30,31,32,33,36}
py{27,34,35,36}-pytest{30,31,32,33,36,38}
py{27,36}-pytest36-pexpect
py{27,36}-pytest{master,features}

Expand All @@ -19,6 +19,7 @@ deps =
pytest32: pytest~=3.2.0
pytest33: pytest~=3.3.0
pytest36: pytest~=3.6.0
pytest38: pytest~=3.8.0
pytestmaster: git+https://github.com/pytest-dev/pytest.git@master
pytestfeatures: git+https://github.com/pytest-dev/pytest.git@features
pexpect: pexpect
Expand Down
5 changes: 5 additions & 0 deletions xdist/dsession.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,11 @@ def worker_logwarning(self, message, code, nodeid, fslocation):
kwargs = dict(message=message, code=code, nodeid=nodeid, fslocation=fslocation)
self.config.hook.pytest_logwarning.call_historic(kwargs=kwargs)

def worker_warning_captured(self, warning_message, when, item):
"""Emitted when a node calls the pytest_logwarning hook."""
kwargs = dict(warning_message=warning_message, when=when, item=item)
self.config.hook.pytest_warning_captured.call_historic(kwargs=kwargs)

def _clone_node(self, node):
"""Return new node based on an existing one.
Expand Down
46 changes: 46 additions & 0 deletions xdist/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,18 @@ def pytest_logwarning(self, message, code, nodeid, fslocation):
fslocation=str(fslocation),
)

# the pytest_warning_captured hook was introduced in pytest 3.8
if hasattr(_pytest.hookspec, "pytest_warning_captured"):

def pytest_warning_captured(self, warning_message, when, item):
self.sendevent(
"warning_captured",
warning_message_data=serialize_warning_message(warning_message),
when=when,
# XXX what to do with "item" parameter? It can't be serialized
item=None,
)


def serialize_report(rep):
def disassembled_report(rep):
Expand Down Expand Up @@ -165,6 +177,40 @@ def disassembled_report(rep):
return d


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 = None
else:
message_str = warning_message.message
message_module = None
message_class_name = None
message_args = None
if warning_message.category:
category_module = warning_message.category.__module__
category_class_name = warning_message.category.__name__
else:
category_module = None
category_class_name = None

result = {
"message_str": message_str,
"message_module": message_module,
"message_class_name": message_class_name,
"message_args": message_args,
"category_module": category_module,
"category_class_name": category_class_name,
}
# access private _WARNING_DETAILS because the attributes vary between Python versions
for attr_name in warning_message._WARNING_DETAILS:
if attr_name in ("message", "category"):
continue
result[attr_name] = getattr(warning_message, attr_name)
return result


def getinfodict():
import platform

Expand Down
37 changes: 37 additions & 0 deletions xdist/workermanage.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,16 @@ def process_from_remote(self, eventcall): # noqa too complex
nodeid=kwargs["nodeid"],
fslocation=kwargs["nodeid"],
)
elif eventname == "warning_captured":
warning_message = unserialize_warning_message(
kwargs["warning_message_data"]
)
self.notify_inproc(
eventname,
warning_message=warning_message,
when=kwargs["when"],
item=kwargs["item"],
)
else:
raise ValueError("unknown event: %s" % (eventname,))
except KeyboardInterrupt:
Expand Down Expand Up @@ -409,6 +419,33 @@ def assembled_report(reportdict):
return runner.CollectReport(**assembled_report(reportdict))


def unserialize_warning_message(data):
import warnings
import importlib

if data["message_module"]:
mod = importlib.import_module(data["message_module"])
cls = getattr(mod, data["message_class_name"])
message = cls(*data["message_args"])
else:
message = data["message_str"]

if data["category_module"]:
mod = importlib.import_module(data["category_module"])
category = getattr(mod, data["category_class_name"])
else:
category = None

kwargs = {"message": message, "category": category}
# access private _WARNING_DETAILS because the attributes vary between Python versions
for attr_name in warnings.WarningMessage._WARNING_DETAILS:
if attr_name in ("message", "category"):
continue
kwargs[attr_name] = data[attr_name]

return warnings.WarningMessage(**kwargs)


def report_unserialization_failure(type_name, report_name, reportdict):
from pprint import pprint

Expand Down

0 comments on commit 81e3898

Please sign in to comment.