Compare commits

...

19 Commits

Author SHA1 Message Date
Bruno Oliveira
ec6129639e Release 1.23.2 2018-09-28 06:38:50 -03:00
Bruno Oliveira
f6ac209cd5 Merge pull request #345 from BrandonHoffman/master
add exception handeling for warning serialization issues
2018-09-28 06:38:14 -03:00
Bruno Oliveira
b176b37606 Add a test which reproduces #344 and fix the handling code
Fix #344
2018-09-27 22:18:52 -03:00
Brandon Hoffman
22e36c9cb4 run black formatter 2018-09-27 19:32:10 -05:00
Brandon Hoffman
8825e7e234 use module name and class name for method instead of the reference to them 2018-09-27 19:27:49 -05:00
Brandon Hoffman
5d5f87b600 add moduel and class name to warning text 2018-09-27 19:24:58 -05:00
Brandon Hoffman
571d08fb8b remove unneeded lines 2018-09-26 21:05:30 -05:00
Brandon Hoffman
36c28ee154 add test case for invalid warning handeling 2018-09-26 20:44:01 -05:00
Brandon Hoffman
af3e975664 add changelog 2018-09-26 19:46:28 -05:00
Brandon Hoffman
bc2ba55f70 add exception handeling for warning serialization issues 2018-09-26 19:32:13 -05:00
Bruno Oliveira
8475876ce3 Merge pull request #343 from nicoddemus/release-1.23.1
Release 1.23.1
2018-09-25 17:05:40 -03:00
Bruno Oliveira
8ee6c0a4e6 Release 1.23.1 2018-09-25 12:11:08 -03:00
Bruno Oliveira
ec966d9800 Merge pull request #342 from nicoddemus/3.8-warnings
Fix warnings transfer between workers and master node with pytest >= 3.8
2018-09-25 12:10:10 -03:00
Bruno Oliveira
78030e3d32 Fix warnings transfer between workers and master node with pytest >= 3.8
Fix #341
2018-09-25 10:01:20 -03:00
Bruno Oliveira
ed2ab76b5c Merge pull request #334 from nicoddemus/release-1.23.0
Release 1.23.0
2018-08-25 01:49:04 -03:00
Bruno Oliveira
bea2467712 Release 1.23.0 2018-08-23 16:33:06 -03:00
Bruno Oliveira
c4a793de38 Merge pull request #332 from nicoddemus/optimize-report-data
Optimize collection performance
2018-08-23 16:31:09 -03:00
Bruno Oliveira
9bcd0e4352 Optimize collection performance
Only send failed collection reports, as master is only interested in failed
reports.

Fix #330
2018-08-18 23:09:00 -03:00
Bruno Oliveira
96af694bf3 Merge pull request #321 from nicoddemus/release-1.22.5
Revert dropped support to old pytest versions
2018-07-28 09:50:50 -03:00
9 changed files with 171 additions and 10 deletions

View File

@@ -22,6 +22,7 @@ env:
- TOXENV=py-pytest32 - TOXENV=py-pytest32
- TOXENV=py-pytest33 - TOXENV=py-pytest33
- TOXENV=py-pytest36 - TOXENV=py-pytest36
- TOXENV=py-pytest38
install: pip install tox setuptools_scm install: pip install tox setuptools_scm
script: tox script: tox

View File

@@ -1,3 +1,30 @@
pytest-xdist 1.23.2 (2018-09-28)
================================
Bug Fixes
---------
- `#344 <https://github.com/pytest-dev/pytest-xdist/issues/344>`_: Fix issue where Warnings could cause pytest to fail if they do not set the args attribute correctly.
pytest-xdist 1.23.1 (2018-09-25)
================================
Bug Fixes
---------
- `#341 <https://github.com/pytest-dev/pytest-xdist/issues/341>`_: Fix warnings transfer between workers and master node with pytest >= 3.8.
pytest-xdist 1.23.0 (2018-08-23)
================================
Features
--------
- `#330 <https://github.com/pytest-dev/pytest-xdist/issues/330>`_: Improve collection performance by reducing the number of events sent to ``master`` node.
pytest-xdist 1.22.5 (2018-07-27) pytest-xdist 1.22.5 (2018-07-27)
================================ ================================

View File

@@ -6,6 +6,7 @@ environment:
- TOXENV: "py35-pytest33" - TOXENV: "py35-pytest33"
- TOXENV: "py36-pytest33" - TOXENV: "py36-pytest33"
- TOXENV: "py36-pytest36" - TOXENV: "py36-pytest36"
- TOXENV: "py36-pytest38"
- TOXENV: "py27-pytest33-pexpect" - TOXENV: "py27-pytest33-pexpect"
- TOXENV: "py36-pytest33-pexpect" - TOXENV: "py36-pytest33-pexpect"

View File

@@ -402,7 +402,7 @@ class TestTerminalReporting:
@pytest.mark.parametrize("n", ["-n0", "-n1"]) @pytest.mark.parametrize("n", ["-n0", "-n1"])
@pytest.mark.parametrize("warn_type", ["pytest", "builtin"]) @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 from pkg_resources import parse_version
if parse_version(pytest.__version__) < parse_version("3.1"): if parse_version(pytest.__version__) < parse_version("3.1"):
@@ -417,7 +417,9 @@ class TestTerminalReporting:
assert False assert False
testdir.makepyfile( testdir.makepyfile(
""" """
import warnings, py import warnings, py, pytest
@pytest.mark.filterwarnings('ignore:config.warn has been deprecated')
def test_func(request): def test_func(request):
{warn_code} {warn_code}
""".format( """.format(
@@ -427,6 +429,35 @@ class TestTerminalReporting:
result = testdir.runpytest(n) result = testdir.runpytest(n)
result.stdout.fnmatch_lines(["*this is a warning*", "*1 passed, 1 warnings*"]) 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): def test_logfinish_hook(self, testdir):
"""Ensure the pytest_runtest_logfinish hook is being properly handled""" """Ensure the pytest_runtest_logfinish hook is being properly handled"""
from _pytest import hookspec from _pytest import hookspec

View File

@@ -321,8 +321,6 @@ class TestWorkerInteractor:
assert not ev.kwargs assert not ev.kwargs
ev = worker.popevent() ev = worker.popevent()
assert ev.name == "collectreport" assert ev.name == "collectreport"
ev = worker.popevent()
assert ev.name == "collectreport"
rep = unserialize_report(ev.name, ev.kwargs["data"]) rep = unserialize_report(ev.name, ev.kwargs["data"])
assert rep.failed assert rep.failed
ev = worker.popevent("collectionfinish") ev = worker.popevent("collectionfinish")

View File

@@ -2,7 +2,7 @@
# if you change the envlist, please update .travis.yml file as well # if you change the envlist, please update .travis.yml file as well
envlist= envlist=
linting 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}-pytest36-pexpect
py{27,36}-pytest{master,features} py{27,36}-pytest{master,features}
@@ -19,6 +19,7 @@ deps =
pytest32: pytest~=3.2.0 pytest32: pytest~=3.2.0
pytest33: pytest~=3.3.0 pytest33: pytest~=3.3.0
pytest36: pytest~=3.6.0 pytest36: pytest~=3.6.0
pytest38: pytest~=3.8.0
pytestmaster: git+https://github.com/pytest-dev/pytest.git@master pytestmaster: git+https://github.com/pytest-dev/pytest.git@master
pytestfeatures: git+https://github.com/pytest-dev/pytest.git@features pytestfeatures: git+https://github.com/pytest-dev/pytest.git@features
pexpect: pexpect pexpect: pexpect

View File

@@ -257,8 +257,12 @@ class DSession(object):
self.sched.mark_test_complete(node, item_index, duration) self.sched.mark_test_complete(node, item_index, duration)
def worker_collectreport(self, node, rep): def worker_collectreport(self, node, rep):
"""Emitted when a node calls the pytest_collectreport hook.""" """Emitted when a node calls the pytest_collectreport hook.
if rep.failed:
Because we only need the report when there's a failure, as optimization
we only expect to receive failed reports from workers (#330).
"""
assert rep.failed
self._failed_worker_collectreport(node, rep) self._failed_worker_collectreport(node, rep)
def worker_logwarning(self, message, code, nodeid, fslocation): def worker_logwarning(self, message, code, nodeid, fslocation):
@@ -266,6 +270,11 @@ class DSession(object):
kwargs = dict(message=message, code=code, nodeid=nodeid, fslocation=fslocation) kwargs = dict(message=message, code=code, nodeid=nodeid, fslocation=fslocation)
self.config.hook.pytest_logwarning.call_historic(kwargs=kwargs) 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): def _clone_node(self, node):
"""Return new node based on an existing one. """Return new node based on an existing one.

View File

@@ -109,6 +109,8 @@ class WorkerInteractor(object):
self.sendevent("testreport", data=data) self.sendevent("testreport", data=data)
def pytest_collectreport(self, report): def pytest_collectreport(self, report):
# master only needs reports that failed, as optimization send only them instead (#330)
if report.failed:
data = serialize_report(report) data = serialize_report(report)
self.sendevent("collectreport", data=data) self.sendevent("collectreport", data=data)
@@ -121,6 +123,18 @@ class WorkerInteractor(object):
fslocation=str(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,
# item cannot be serialized and will always be None when used with xdist
item=None,
)
def serialize_report(rep): def serialize_report(rep):
def disassembled_report(rep): def disassembled_report(rep):
@@ -163,6 +177,40 @@ def serialize_report(rep):
return d 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 = str(warning_message.message)
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(): def getinfodict():
import platform import platform

View File

@@ -327,6 +327,16 @@ class WorkerController(object):
nodeid=kwargs["nodeid"], nodeid=kwargs["nodeid"],
fslocation=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: else:
raise ValueError("unknown event: %s" % (eventname,)) raise ValueError("unknown event: %s" % (eventname,))
except KeyboardInterrupt: except KeyboardInterrupt:
@@ -409,6 +419,41 @@ def unserialize_report(name, reportdict):
return runner.CollectReport(**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"])
try:
message = cls(*data["message_args"])
except TypeError:
message_text = "{mod}.{cls}: {msg}".format(
mod=data["message_module"],
cls=data["message_class_name"],
msg=data["message_str"],
)
message = Warning(message_text)
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): def report_unserialization_failure(type_name, report_name, reportdict):
from pprint import pprint from pprint import pprint