Merge pull request #2072 from nicoddemus/integrate-pytest-warnings
Integrate pytest warnings
This commit is contained in:
commit
2a130daae6
|
@ -19,6 +19,10 @@ New Features
|
||||||
* ``pytest.param`` can be used to declare test parameter sets with marks and test ids.
|
* ``pytest.param`` can be used to declare test parameter sets with marks and test ids.
|
||||||
Thanks `@RonnyPfannschmidt`_ for the PR.
|
Thanks `@RonnyPfannschmidt`_ for the PR.
|
||||||
|
|
||||||
|
* The ``pytest-warnings`` plugin has been integrated into the core, so now ``pytest`` automatically
|
||||||
|
captures and displays warnings at the end of the test session.
|
||||||
|
Thanks `@nicoddemus`_ for the PR.
|
||||||
|
|
||||||
|
|
||||||
Changes
|
Changes
|
||||||
-------
|
-------
|
||||||
|
|
|
@ -99,7 +99,7 @@ default_plugins = (
|
||||||
"mark main terminal runner python fixtures debugging unittest capture skipping "
|
"mark main terminal runner python fixtures debugging unittest capture skipping "
|
||||||
"tmpdir monkeypatch recwarn pastebin helpconfig nose assertion "
|
"tmpdir monkeypatch recwarn pastebin helpconfig nose assertion "
|
||||||
"junitxml resultlog doctest cacheprovider freeze_support "
|
"junitxml resultlog doctest cacheprovider freeze_support "
|
||||||
"setuponly setupplan").split()
|
"setuponly setupplan warnings").split()
|
||||||
|
|
||||||
builtin_plugins = set(default_plugins)
|
builtin_plugins = set(default_plugins)
|
||||||
builtin_plugins.add("pytester")
|
builtin_plugins.add("pytester")
|
||||||
|
@ -911,11 +911,11 @@ class Config(object):
|
||||||
fin = self._cleanup.pop()
|
fin = self._cleanup.pop()
|
||||||
fin()
|
fin()
|
||||||
|
|
||||||
def warn(self, code, message, fslocation=None):
|
def warn(self, code, message, fslocation=None, nodeid=None):
|
||||||
""" generate a warning for this test session. """
|
""" generate a warning for this test session. """
|
||||||
self.hook.pytest_logwarning.call_historic(kwargs=dict(
|
self.hook.pytest_logwarning.call_historic(kwargs=dict(
|
||||||
code=code, message=message,
|
code=code, message=message,
|
||||||
fslocation=fslocation, nodeid=None))
|
fslocation=fslocation, nodeid=nodeid))
|
||||||
|
|
||||||
def get_terminal_writer(self):
|
def get_terminal_writer(self):
|
||||||
return self.pluginmanager.get_plugin("terminalreporter")._tw
|
return self.pluginmanager.get_plugin("terminalreporter")._tw
|
||||||
|
|
|
@ -1081,7 +1081,7 @@ class FixtureManager(object):
|
||||||
continue
|
continue
|
||||||
marker = defaultfuncargprefixmarker
|
marker = defaultfuncargprefixmarker
|
||||||
from _pytest import deprecated
|
from _pytest import deprecated
|
||||||
self.config.warn('C1', deprecated.FUNCARG_PREFIX.format(name=name))
|
self.config.warn('C1', deprecated.FUNCARG_PREFIX.format(name=name), nodeid=nodeid)
|
||||||
name = name[len(self._argprefix):]
|
name = name[len(self._argprefix):]
|
||||||
elif not isinstance(marker, FixtureFunctionMarker):
|
elif not isinstance(marker, FixtureFunctionMarker):
|
||||||
# magic globals with __getattr__ might have got us a wrong
|
# magic globals with __getattr__ might have got us a wrong
|
||||||
|
|
|
@ -1008,7 +1008,7 @@ class Testdir(object):
|
||||||
The pexpect child is returned.
|
The pexpect child is returned.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
basetemp = self.tmpdir.mkdir("pexpect")
|
basetemp = self.tmpdir.mkdir("temp-pexpect")
|
||||||
invoke = " ".join(map(str, self._getpytestargs()))
|
invoke = " ".join(map(str, self._getpytestargs()))
|
||||||
cmd = "%s --basetemp=%s %s" % (invoke, basetemp, string)
|
cmd = "%s --basetemp=%s %s" % (invoke, basetemp, string)
|
||||||
return self.spawn(cmd, expect_timeout=expect_timeout)
|
return self.spawn(cmd, expect_timeout=expect_timeout)
|
||||||
|
|
|
@ -936,7 +936,7 @@ def _idval(val, argname, idx, idfn, config=None):
|
||||||
import warnings
|
import warnings
|
||||||
msg = "Raised while trying to determine id of parameter %s at position %d." % (argname, idx)
|
msg = "Raised while trying to determine id of parameter %s at position %d." % (argname, idx)
|
||||||
msg += '\nUpdate your code as this will raise an error in pytest-4.0.'
|
msg += '\nUpdate your code as this will raise an error in pytest-4.0.'
|
||||||
warnings.warn(msg)
|
warnings.warn(msg, DeprecationWarning)
|
||||||
if s:
|
if s:
|
||||||
return _escape_strings(s)
|
return _escape_strings(s)
|
||||||
|
|
||||||
|
|
|
@ -56,14 +56,12 @@ def deprecated_call(func=None, *args, **kwargs):
|
||||||
|
|
||||||
def warn_explicit(message, category, *args, **kwargs):
|
def warn_explicit(message, category, *args, **kwargs):
|
||||||
categories.append(category)
|
categories.append(category)
|
||||||
old_warn_explicit(message, category, *args, **kwargs)
|
|
||||||
|
|
||||||
def warn(message, category=None, *args, **kwargs):
|
def warn(message, category=None, *args, **kwargs):
|
||||||
if isinstance(message, Warning):
|
if isinstance(message, Warning):
|
||||||
categories.append(message.__class__)
|
categories.append(message.__class__)
|
||||||
else:
|
else:
|
||||||
categories.append(category)
|
categories.append(category)
|
||||||
old_warn(message, category, *args, **kwargs)
|
|
||||||
|
|
||||||
old_warn = warnings.warn
|
old_warn = warnings.warn
|
||||||
old_warn_explicit = warnings.warn_explicit
|
old_warn_explicit = warnings.warn_explicit
|
||||||
|
|
|
@ -554,14 +554,21 @@ def importorskip(modname, minversion=None):
|
||||||
__version__ attribute. If no minversion is specified the a skip
|
__version__ attribute. If no minversion is specified the a skip
|
||||||
is only triggered if the module can not be imported.
|
is only triggered if the module can not be imported.
|
||||||
"""
|
"""
|
||||||
|
import warnings
|
||||||
__tracebackhide__ = True
|
__tracebackhide__ = True
|
||||||
compile(modname, '', 'eval') # to catch syntaxerrors
|
compile(modname, '', 'eval') # to catch syntaxerrors
|
||||||
should_skip = False
|
should_skip = False
|
||||||
try:
|
|
||||||
__import__(modname)
|
with warnings.catch_warnings():
|
||||||
except ImportError:
|
# make sure to ignore ImportWarnings that might happen because
|
||||||
# Do not raise chained exception here(#1485)
|
# of existing directories with the same name we're trying to
|
||||||
should_skip = True
|
# import but without a __init__.py file
|
||||||
|
warnings.simplefilter('ignore')
|
||||||
|
try:
|
||||||
|
__import__(modname)
|
||||||
|
except ImportError:
|
||||||
|
# Do not raise chained exception here(#1485)
|
||||||
|
should_skip = True
|
||||||
if should_skip:
|
if should_skip:
|
||||||
raise Skipped("could not import %r" %(modname,), allow_module_level=True)
|
raise Skipped("could not import %r" %(modname,), allow_module_level=True)
|
||||||
mod = sys.modules[modname]
|
mod = sys.modules[modname]
|
||||||
|
|
|
@ -4,6 +4,7 @@ This is a good source for looking at the various reporting hooks.
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
|
import itertools
|
||||||
from _pytest.main import EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, \
|
from _pytest.main import EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, \
|
||||||
EXIT_USAGEERROR, EXIT_NOTESTSCOLLECTED
|
EXIT_USAGEERROR, EXIT_NOTESTSCOLLECTED
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -26,11 +27,11 @@ def pytest_addoption(parser):
|
||||||
help="show extra test summary info as specified by chars (f)ailed, "
|
help="show extra test summary info as specified by chars (f)ailed, "
|
||||||
"(E)error, (s)skipped, (x)failed, (X)passed, "
|
"(E)error, (s)skipped, (x)failed, (X)passed, "
|
||||||
"(p)passed, (P)passed with output, (a)all except pP. "
|
"(p)passed, (P)passed with output, (a)all except pP. "
|
||||||
"The pytest warnings are displayed at all times except when "
|
"Warnings are displayed at all times except when "
|
||||||
"--disable-pytest-warnings is set")
|
"--disable-warnings is set")
|
||||||
group._addoption('--disable-pytest-warnings', default=False,
|
group._addoption('--disable-warnings', '--disable-pytest-warnings', default=False,
|
||||||
dest='disablepytestwarnings', action='store_true',
|
dest='disable_warnings', action='store_true',
|
||||||
help='disable warnings summary, overrides -r w flag')
|
help='disable warnings summary')
|
||||||
group._addoption('-l', '--showlocals',
|
group._addoption('-l', '--showlocals',
|
||||||
action="store_true", dest="showlocals", default=False,
|
action="store_true", dest="showlocals", default=False,
|
||||||
help="show locals in tracebacks (disabled by default).")
|
help="show locals in tracebacks (disabled by default).")
|
||||||
|
@ -59,9 +60,9 @@ def pytest_configure(config):
|
||||||
def getreportopt(config):
|
def getreportopt(config):
|
||||||
reportopts = ""
|
reportopts = ""
|
||||||
reportchars = config.option.reportchars
|
reportchars = config.option.reportchars
|
||||||
if not config.option.disablepytestwarnings and 'w' not in reportchars:
|
if not config.option.disable_warnings and 'w' not in reportchars:
|
||||||
reportchars += 'w'
|
reportchars += 'w'
|
||||||
elif config.option.disablepytestwarnings and 'w' in reportchars:
|
elif config.option.disable_warnings and 'w' in reportchars:
|
||||||
reportchars = reportchars.replace('w', '')
|
reportchars = reportchars.replace('w', '')
|
||||||
if reportchars:
|
if reportchars:
|
||||||
for char in reportchars:
|
for char in reportchars:
|
||||||
|
@ -82,13 +83,40 @@ def pytest_report_teststatus(report):
|
||||||
letter = "f"
|
letter = "f"
|
||||||
return report.outcome, letter, report.outcome.upper()
|
return report.outcome, letter, report.outcome.upper()
|
||||||
|
|
||||||
|
|
||||||
class WarningReport(object):
|
class WarningReport(object):
|
||||||
|
"""
|
||||||
|
Simple structure to hold warnings information captured by ``pytest_logwarning``.
|
||||||
|
"""
|
||||||
def __init__(self, code, message, nodeid=None, fslocation=None):
|
def __init__(self, code, message, nodeid=None, fslocation=None):
|
||||||
|
"""
|
||||||
|
:param code: unused
|
||||||
|
:param str message: user friendly message about the warning
|
||||||
|
:param str|None nodeid: node id that generated the warning (see ``get_location``).
|
||||||
|
:param tuple|py.path.local fslocation:
|
||||||
|
file system location of the source of the warning (see ``get_location``).
|
||||||
|
"""
|
||||||
self.code = code
|
self.code = code
|
||||||
self.message = message
|
self.message = message
|
||||||
self.nodeid = nodeid
|
self.nodeid = nodeid
|
||||||
self.fslocation = fslocation
|
self.fslocation = fslocation
|
||||||
|
|
||||||
|
def get_location(self, config):
|
||||||
|
"""
|
||||||
|
Returns the more user-friendly information about the location
|
||||||
|
of a warning, or None.
|
||||||
|
"""
|
||||||
|
if self.nodeid:
|
||||||
|
return self.nodeid
|
||||||
|
if self.fslocation:
|
||||||
|
if isinstance(self.fslocation, tuple) and len(self.fslocation) == 2:
|
||||||
|
filename, linenum = self.fslocation
|
||||||
|
relpath = py.path.local(filename).relto(config.invocation_dir)
|
||||||
|
return '%s:%d' % (relpath, linenum)
|
||||||
|
else:
|
||||||
|
return str(self.fslocation)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class TerminalReporter(object):
|
class TerminalReporter(object):
|
||||||
def __init__(self, config, file=None):
|
def __init__(self, config, file=None):
|
||||||
|
@ -168,8 +196,6 @@ class TerminalReporter(object):
|
||||||
|
|
||||||
def pytest_logwarning(self, code, fslocation, message, nodeid):
|
def pytest_logwarning(self, code, fslocation, message, nodeid):
|
||||||
warnings = self.stats.setdefault("warnings", [])
|
warnings = self.stats.setdefault("warnings", [])
|
||||||
if isinstance(fslocation, tuple):
|
|
||||||
fslocation = "%s:%d" % fslocation
|
|
||||||
warning = WarningReport(code=code, fslocation=fslocation,
|
warning = WarningReport(code=code, fslocation=fslocation,
|
||||||
message=message, nodeid=nodeid)
|
message=message, nodeid=nodeid)
|
||||||
warnings.append(warning)
|
warnings.append(warning)
|
||||||
|
@ -440,13 +466,21 @@ class TerminalReporter(object):
|
||||||
|
|
||||||
def summary_warnings(self):
|
def summary_warnings(self):
|
||||||
if self.hasopt("w"):
|
if self.hasopt("w"):
|
||||||
warnings = self.stats.get("warnings")
|
all_warnings = self.stats.get("warnings")
|
||||||
if not warnings:
|
if not all_warnings:
|
||||||
return
|
return
|
||||||
self.write_sep("=", "pytest-warning summary")
|
|
||||||
for w in warnings:
|
grouped = itertools.groupby(all_warnings, key=lambda wr: wr.get_location(self.config))
|
||||||
self._tw.line("W%s %s %s" % (w.code,
|
|
||||||
w.fslocation, w.message))
|
self.write_sep("=", "warnings summary", yellow=True, bold=False)
|
||||||
|
for location, warnings in grouped:
|
||||||
|
self._tw.line(str(location) or '<undetermined location>')
|
||||||
|
for w in warnings:
|
||||||
|
lines = w.message.splitlines()
|
||||||
|
indented = '\n'.join(' ' + x for x in lines)
|
||||||
|
self._tw.line(indented)
|
||||||
|
self._tw.line()
|
||||||
|
self._tw.line('-- Docs: http://doc.pytest.org/en/latest/warnings.html')
|
||||||
|
|
||||||
def summary_passes(self):
|
def summary_passes(self):
|
||||||
if self.config.option.tbstyle != "no":
|
if self.config.option.tbstyle != "no":
|
||||||
|
@ -548,8 +582,7 @@ def flatten(l):
|
||||||
|
|
||||||
def build_summary_stats_line(stats):
|
def build_summary_stats_line(stats):
|
||||||
keys = ("failed passed skipped deselected "
|
keys = ("failed passed skipped deselected "
|
||||||
"xfailed xpassed warnings error").split()
|
"xfailed xpassed warnings error").split()
|
||||||
key_translation = {'warnings': 'pytest-warnings'}
|
|
||||||
unknown_key_seen = False
|
unknown_key_seen = False
|
||||||
for key in stats.keys():
|
for key in stats.keys():
|
||||||
if key not in keys:
|
if key not in keys:
|
||||||
|
@ -560,8 +593,7 @@ def build_summary_stats_line(stats):
|
||||||
for key in keys:
|
for key in keys:
|
||||||
val = stats.get(key, None)
|
val = stats.get(key, None)
|
||||||
if val:
|
if val:
|
||||||
key_name = key_translation.get(key, key)
|
parts.append("%d %s" % (len(val), key))
|
||||||
parts.append("%d %s" % (len(val), key_name))
|
|
||||||
|
|
||||||
if parts:
|
if parts:
|
||||||
line = ", ".join(parts)
|
line = ", ".join(parts)
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
|
import warnings
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
def _setoption(wmod, arg):
|
||||||
|
"""
|
||||||
|
Copy of the warning._setoption function but does not escape arguments.
|
||||||
|
"""
|
||||||
|
parts = arg.split(':')
|
||||||
|
if len(parts) > 5:
|
||||||
|
raise wmod._OptionError("too many fields (max 5): %r" % (arg,))
|
||||||
|
while len(parts) < 5:
|
||||||
|
parts.append('')
|
||||||
|
action, message, category, module, lineno = [s.strip()
|
||||||
|
for s in parts]
|
||||||
|
action = wmod._getaction(action)
|
||||||
|
category = wmod._getcategory(category)
|
||||||
|
if lineno:
|
||||||
|
try:
|
||||||
|
lineno = int(lineno)
|
||||||
|
if lineno < 0:
|
||||||
|
raise ValueError
|
||||||
|
except (ValueError, OverflowError):
|
||||||
|
raise wmod._OptionError("invalid lineno %r" % (lineno,))
|
||||||
|
else:
|
||||||
|
lineno = 0
|
||||||
|
wmod.filterwarnings(action, message, category, module, lineno)
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_addoption(parser):
|
||||||
|
group = parser.getgroup("pytest-warnings")
|
||||||
|
group.addoption(
|
||||||
|
'-W', '--pythonwarnings', action='append',
|
||||||
|
help="set which warnings to report, see -W option of python itself.")
|
||||||
|
parser.addini("filterwarnings", type="linelist",
|
||||||
|
help="Each line specifies warning filter pattern which would be passed"
|
||||||
|
"to warnings.filterwarnings. Process after -W and --pythonwarnings.")
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def catch_warnings_for_item(item):
|
||||||
|
"""
|
||||||
|
catches the warnings generated during setup/call/teardown execution
|
||||||
|
of the given item and after it is done posts them as warnings to this
|
||||||
|
item.
|
||||||
|
"""
|
||||||
|
args = item.config.getoption('pythonwarnings') or []
|
||||||
|
inifilters = item.config.getini("filterwarnings")
|
||||||
|
with warnings.catch_warnings(record=True) as log:
|
||||||
|
warnings.simplefilter('once')
|
||||||
|
for arg in args:
|
||||||
|
warnings._setoption(arg)
|
||||||
|
|
||||||
|
for arg in inifilters:
|
||||||
|
_setoption(warnings, arg)
|
||||||
|
|
||||||
|
yield
|
||||||
|
|
||||||
|
for warning in log:
|
||||||
|
msg = warnings.formatwarning(
|
||||||
|
warning.message, warning.category,
|
||||||
|
warning.filename, warning.lineno, warning.line)
|
||||||
|
item.warn("unused", msg)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.hookimpl(hookwrapper=True)
|
||||||
|
def pytest_runtest_protocol(item):
|
||||||
|
with catch_warnings_for_item(item):
|
||||||
|
yield
|
|
@ -18,7 +18,7 @@ Full pytest documentation
|
||||||
monkeypatch
|
monkeypatch
|
||||||
tmpdir
|
tmpdir
|
||||||
capture
|
capture
|
||||||
recwarn
|
warnings
|
||||||
doctest
|
doctest
|
||||||
mark
|
mark
|
||||||
skipping
|
skipping
|
||||||
|
|
|
@ -240,3 +240,23 @@ Builtin configuration file options
|
||||||
By default, pytest will stop searching for ``conftest.py`` files upwards
|
By default, pytest will stop searching for ``conftest.py`` files upwards
|
||||||
from ``pytest.ini``/``tox.ini``/``setup.cfg`` of the project if any,
|
from ``pytest.ini``/``tox.ini``/``setup.cfg`` of the project if any,
|
||||||
or up to the file-system root.
|
or up to the file-system root.
|
||||||
|
|
||||||
|
|
||||||
|
.. confval:: filterwarnings
|
||||||
|
|
||||||
|
.. versionadded:: 3.1
|
||||||
|
|
||||||
|
Sets a list of filters and actions that should be taken for matched
|
||||||
|
warnings. By default all warnings emitted during the test session
|
||||||
|
will be displayed in a summary at the end of the test session.
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
# content of pytest.ini
|
||||||
|
[pytest]
|
||||||
|
filterwarnings =
|
||||||
|
error
|
||||||
|
ignore::DeprecationWarning
|
||||||
|
|
||||||
|
This tells pytest to ignore deprecation warnings and turn all other warnings
|
||||||
|
into errors. For more information please refer to :ref:`warnings`.
|
||||||
|
|
|
@ -1,142 +1,3 @@
|
||||||
.. _`asserting warnings`:
|
:orphan:
|
||||||
|
|
||||||
.. _assertwarnings:
|
This page has been moved, please see :ref:`assertwarnings`.
|
||||||
|
|
||||||
Asserting Warnings
|
|
||||||
=====================================================
|
|
||||||
|
|
||||||
.. _`asserting warnings with the warns function`:
|
|
||||||
|
|
||||||
.. _warns:
|
|
||||||
|
|
||||||
Asserting warnings with the warns function
|
|
||||||
-----------------------------------------------
|
|
||||||
|
|
||||||
.. versionadded:: 2.8
|
|
||||||
|
|
||||||
You can check that code raises a particular warning using ``pytest.warns``,
|
|
||||||
which works in a similar manner to :ref:`raises <assertraises>`::
|
|
||||||
|
|
||||||
import warnings
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
def test_warning():
|
|
||||||
with pytest.warns(UserWarning):
|
|
||||||
warnings.warn("my warning", UserWarning)
|
|
||||||
|
|
||||||
The test will fail if the warning in question is not raised.
|
|
||||||
|
|
||||||
You can also call ``pytest.warns`` on a function or code string::
|
|
||||||
|
|
||||||
pytest.warns(expected_warning, func, *args, **kwargs)
|
|
||||||
pytest.warns(expected_warning, "func(*args, **kwargs)")
|
|
||||||
|
|
||||||
The function also returns a list of all raised warnings (as
|
|
||||||
``warnings.WarningMessage`` objects), which you can query for
|
|
||||||
additional information::
|
|
||||||
|
|
||||||
with pytest.warns(RuntimeWarning) as record:
|
|
||||||
warnings.warn("another warning", RuntimeWarning)
|
|
||||||
|
|
||||||
# check that only one warning was raised
|
|
||||||
assert len(record) == 1
|
|
||||||
# check that the message matches
|
|
||||||
assert record[0].message.args[0] == "another warning"
|
|
||||||
|
|
||||||
Alternatively, you can examine raised warnings in detail using the
|
|
||||||
:ref:`recwarn <recwarn>` fixture (see below).
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated
|
|
||||||
differently; see :ref:`ensuring_function_triggers`.
|
|
||||||
|
|
||||||
.. _`recording warnings`:
|
|
||||||
|
|
||||||
.. _recwarn:
|
|
||||||
|
|
||||||
Recording warnings
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
You can record raised warnings either using ``pytest.warns`` or with
|
|
||||||
the ``recwarn`` fixture.
|
|
||||||
|
|
||||||
To record with ``pytest.warns`` without asserting anything about the warnings,
|
|
||||||
pass ``None`` as the expected warning type::
|
|
||||||
|
|
||||||
with pytest.warns(None) as record:
|
|
||||||
warnings.warn("user", UserWarning)
|
|
||||||
warnings.warn("runtime", RuntimeWarning)
|
|
||||||
|
|
||||||
assert len(record) == 2
|
|
||||||
assert str(record[0].message) == "user"
|
|
||||||
assert str(record[1].message) == "runtime"
|
|
||||||
|
|
||||||
The ``recwarn`` fixture will record warnings for the whole function::
|
|
||||||
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
def test_hello(recwarn):
|
|
||||||
warnings.warn("hello", UserWarning)
|
|
||||||
assert len(recwarn) == 1
|
|
||||||
w = recwarn.pop(UserWarning)
|
|
||||||
assert issubclass(w.category, UserWarning)
|
|
||||||
assert str(w.message) == "hello"
|
|
||||||
assert w.filename
|
|
||||||
assert w.lineno
|
|
||||||
|
|
||||||
Both ``recwarn`` and ``pytest.warns`` return the same interface for recorded
|
|
||||||
warnings: a WarningsRecorder instance. To view the recorded warnings, you can
|
|
||||||
iterate over this instance, call ``len`` on it to get the number of recorded
|
|
||||||
warnings, or index into it to get a particular recorded warning. It also
|
|
||||||
provides these methods:
|
|
||||||
|
|
||||||
.. autoclass:: _pytest.recwarn.WarningsRecorder()
|
|
||||||
:members:
|
|
||||||
|
|
||||||
Each recorded warning has the attributes ``message``, ``category``,
|
|
||||||
``filename``, ``lineno``, ``file``, and ``line``. The ``category`` is the
|
|
||||||
class of the warning. The ``message`` is the warning itself; calling
|
|
||||||
``str(message)`` will return the actual message of the warning.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
:class:`RecordedWarning` was changed from a plain class to a namedtuple in pytest 3.1
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated
|
|
||||||
differently; see :ref:`ensuring_function_triggers`.
|
|
||||||
|
|
||||||
.. _`ensuring a function triggers a deprecation warning`:
|
|
||||||
|
|
||||||
.. _ensuring_function_triggers:
|
|
||||||
|
|
||||||
Ensuring a function triggers a deprecation warning
|
|
||||||
-------------------------------------------------------
|
|
||||||
|
|
||||||
You can also call a global helper for checking
|
|
||||||
that a certain function call triggers a ``DeprecationWarning`` or
|
|
||||||
``PendingDeprecationWarning``::
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
def test_global():
|
|
||||||
pytest.deprecated_call(myfunction, 17)
|
|
||||||
|
|
||||||
By default, ``DeprecationWarning`` and ``PendingDeprecationWarning`` will not be
|
|
||||||
caught when using ``pytest.warns`` or ``recwarn`` because default Python warnings filters hide
|
|
||||||
them. If you wish to record them in your own code, use the
|
|
||||||
command ``warnings.simplefilter('always')``::
|
|
||||||
|
|
||||||
import warnings
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
def test_deprecation(recwarn):
|
|
||||||
warnings.simplefilter('always')
|
|
||||||
warnings.warn("deprecated", DeprecationWarning)
|
|
||||||
assert len(recwarn) == 1
|
|
||||||
assert recwarn.pop(DeprecationWarning)
|
|
||||||
|
|
||||||
You can also use it as a contextmanager::
|
|
||||||
|
|
||||||
def test_global():
|
|
||||||
with pytest.deprecated_call():
|
|
||||||
myobject.deprecated_method()
|
|
||||||
|
|
|
@ -0,0 +1,218 @@
|
||||||
|
.. _`warnings`:
|
||||||
|
|
||||||
|
Warnings Capture
|
||||||
|
================
|
||||||
|
|
||||||
|
.. versionadded:: 3.1
|
||||||
|
|
||||||
|
Starting from version ``3.1``, pytest now automatically catches all warnings during test execution
|
||||||
|
and displays them at the end of the session::
|
||||||
|
|
||||||
|
# content of test_show_warnings.py
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
def deprecated_function():
|
||||||
|
warnings.warn("this function is deprecated, use another_function()", DeprecationWarning)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def test_one():
|
||||||
|
assert deprecated_function() == 1
|
||||||
|
|
||||||
|
Running pytest now produces this output::
|
||||||
|
|
||||||
|
$ pytest test_show_warnings.py
|
||||||
|
.
|
||||||
|
============================== warnings summary ===============================
|
||||||
|
test_show_warning.py::test_one
|
||||||
|
C:\pytest\.tmp\test_show_warning.py:4: DeprecationWarning: this function is deprecated, use another_function()
|
||||||
|
warnings.warn("this function is deprecated, use another_function()", DeprecationWarning)
|
||||||
|
|
||||||
|
-- Docs: http://doc.pytest.org/en/latest/warnings.html
|
||||||
|
1 passed, 1 warnings in 0.01 seconds
|
||||||
|
|
||||||
|
The ``-W`` flag can be passed to control which warnings will be displayed or even turn
|
||||||
|
them into errors::
|
||||||
|
|
||||||
|
$ pytest -q test_show_warning.py -W error::DeprecationWarning
|
||||||
|
F
|
||||||
|
================================== FAILURES ===================================
|
||||||
|
__________________________________ test_one ___________________________________
|
||||||
|
|
||||||
|
def test_one():
|
||||||
|
> assert deprecated_function() == 1
|
||||||
|
|
||||||
|
test_show_warning.py:8:
|
||||||
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||||
|
|
||||||
|
def deprecated_function():
|
||||||
|
> warnings.warn("this function is deprecated, use another_function()", DeprecationWarning)
|
||||||
|
E DeprecationWarning: this function is deprecated, use another_function()
|
||||||
|
|
||||||
|
test_show_warning.py:4: DeprecationWarning
|
||||||
|
1 failed in 0.02 seconds
|
||||||
|
|
||||||
|
The same option can be set in the ``pytest.ini`` file using the ``filterwarnings`` ini option.
|
||||||
|
For example, the configuration below will ignore all deprecation warnings, but will transform
|
||||||
|
all other warnings into errors.
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[pytest]
|
||||||
|
filterwarnings =
|
||||||
|
error
|
||||||
|
ignore::DeprecationWarning
|
||||||
|
|
||||||
|
|
||||||
|
When a warning matches more than one option in the list, the action for the last matching option
|
||||||
|
is performed.
|
||||||
|
|
||||||
|
Both ``-W`` command-line option and ``filterwarnings`` ini option are based on Python's own
|
||||||
|
`-W option`_ and `warnings.simplefilter`_, so please refer to those sections in the Python
|
||||||
|
documentation for other examples and advanced usage.
|
||||||
|
|
||||||
|
*Credits go to Florian Schulze for the reference implementation in the* `pytest-warnings`_
|
||||||
|
*plugin.*
|
||||||
|
|
||||||
|
.. _`-W option`: https://docs.python.org/3/using/cmdline.html?highlight=#cmdoption-W
|
||||||
|
.. _warnings.simplefilter: https://docs.python.org/3/library/warnings.html#warnings.simplefilter
|
||||||
|
.. _`pytest-warnings`: https://github.com/fschulze/pytest-warnings
|
||||||
|
|
||||||
|
.. _`asserting warnings`:
|
||||||
|
|
||||||
|
.. _assertwarnings:
|
||||||
|
|
||||||
|
.. _`asserting warnings with the warns function`:
|
||||||
|
|
||||||
|
.. _warns:
|
||||||
|
|
||||||
|
Asserting warnings with the warns function
|
||||||
|
-----------------------------------------------
|
||||||
|
|
||||||
|
.. versionadded:: 2.8
|
||||||
|
|
||||||
|
You can check that code raises a particular warning using ``pytest.warns``,
|
||||||
|
which works in a similar manner to :ref:`raises <assertraises>`::
|
||||||
|
|
||||||
|
import warnings
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
def test_warning():
|
||||||
|
with pytest.warns(UserWarning):
|
||||||
|
warnings.warn("my warning", UserWarning)
|
||||||
|
|
||||||
|
The test will fail if the warning in question is not raised.
|
||||||
|
|
||||||
|
You can also call ``pytest.warns`` on a function or code string::
|
||||||
|
|
||||||
|
pytest.warns(expected_warning, func, *args, **kwargs)
|
||||||
|
pytest.warns(expected_warning, "func(*args, **kwargs)")
|
||||||
|
|
||||||
|
The function also returns a list of all raised warnings (as
|
||||||
|
``warnings.WarningMessage`` objects), which you can query for
|
||||||
|
additional information::
|
||||||
|
|
||||||
|
with pytest.warns(RuntimeWarning) as record:
|
||||||
|
warnings.warn("another warning", RuntimeWarning)
|
||||||
|
|
||||||
|
# check that only one warning was raised
|
||||||
|
assert len(record) == 1
|
||||||
|
# check that the message matches
|
||||||
|
assert record[0].message.args[0] == "another warning"
|
||||||
|
|
||||||
|
Alternatively, you can examine raised warnings in detail using the
|
||||||
|
:ref:`recwarn <recwarn>` fixture (see below).
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated
|
||||||
|
differently; see :ref:`ensuring_function_triggers`.
|
||||||
|
|
||||||
|
.. _`recording warnings`:
|
||||||
|
|
||||||
|
.. _recwarn:
|
||||||
|
|
||||||
|
Recording warnings
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
You can record raised warnings either using ``pytest.warns`` or with
|
||||||
|
the ``recwarn`` fixture.
|
||||||
|
|
||||||
|
To record with ``pytest.warns`` without asserting anything about the warnings,
|
||||||
|
pass ``None`` as the expected warning type::
|
||||||
|
|
||||||
|
with pytest.warns(None) as record:
|
||||||
|
warnings.warn("user", UserWarning)
|
||||||
|
warnings.warn("runtime", RuntimeWarning)
|
||||||
|
|
||||||
|
assert len(record) == 2
|
||||||
|
assert str(record[0].message) == "user"
|
||||||
|
assert str(record[1].message) == "runtime"
|
||||||
|
|
||||||
|
The ``recwarn`` fixture will record warnings for the whole function::
|
||||||
|
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
def test_hello(recwarn):
|
||||||
|
warnings.warn("hello", UserWarning)
|
||||||
|
assert len(recwarn) == 1
|
||||||
|
w = recwarn.pop(UserWarning)
|
||||||
|
assert issubclass(w.category, UserWarning)
|
||||||
|
assert str(w.message) == "hello"
|
||||||
|
assert w.filename
|
||||||
|
assert w.lineno
|
||||||
|
|
||||||
|
Both ``recwarn`` and ``pytest.warns`` return the same interface for recorded
|
||||||
|
warnings: a WarningsRecorder instance. To view the recorded warnings, you can
|
||||||
|
iterate over this instance, call ``len`` on it to get the number of recorded
|
||||||
|
warnings, or index into it to get a particular recorded warning. It also
|
||||||
|
provides these methods:
|
||||||
|
|
||||||
|
.. autoclass:: _pytest.recwarn.WarningsRecorder()
|
||||||
|
:members:
|
||||||
|
|
||||||
|
Each recorded warning has the attributes ``message``, ``category``,
|
||||||
|
``filename``, ``lineno``, ``file``, and ``line``. The ``category`` is the
|
||||||
|
class of the warning. The ``message`` is the warning itself; calling
|
||||||
|
``str(message)`` will return the actual message of the warning.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
:class:`RecordedWarning` was changed from a plain class to a namedtuple in pytest 3.1
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated
|
||||||
|
differently; see :ref:`ensuring_function_triggers`.
|
||||||
|
|
||||||
|
.. _`ensuring a function triggers a deprecation warning`:
|
||||||
|
|
||||||
|
.. _ensuring_function_triggers:
|
||||||
|
|
||||||
|
Ensuring a function triggers a deprecation warning
|
||||||
|
-------------------------------------------------------
|
||||||
|
|
||||||
|
You can also call a global helper for checking
|
||||||
|
that a certain function call triggers a ``DeprecationWarning`` or
|
||||||
|
``PendingDeprecationWarning``::
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
def test_global():
|
||||||
|
pytest.deprecated_call(myfunction, 17)
|
||||||
|
|
||||||
|
By default, ``DeprecationWarning`` and ``PendingDeprecationWarning`` will not be
|
||||||
|
caught when using ``pytest.warns`` or ``recwarn`` because default Python warnings filters hide
|
||||||
|
them. If you wish to record them in your own code, use the
|
||||||
|
command ``warnings.simplefilter('always')``::
|
||||||
|
|
||||||
|
import warnings
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
def test_deprecation(recwarn):
|
||||||
|
warnings.simplefilter('always')
|
||||||
|
warnings.warn("deprecated", DeprecationWarning)
|
||||||
|
assert len(recwarn) == 1
|
||||||
|
assert recwarn.pop(DeprecationWarning)
|
||||||
|
|
||||||
|
You can also use it as a contextmanager::
|
||||||
|
|
||||||
|
def test_global():
|
||||||
|
with pytest.deprecated_call():
|
||||||
|
myobject.deprecated_method()
|
|
@ -339,10 +339,16 @@ class TestGeneralUsage(object):
|
||||||
"*ERROR*test_b.py::b*",
|
"*ERROR*test_b.py::b*",
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('recwarn')
|
||||||
def test_namespace_import_doesnt_confuse_import_hook(self, testdir):
|
def test_namespace_import_doesnt_confuse_import_hook(self, testdir):
|
||||||
# Ref #383. Python 3.3's namespace package messed with our import hooks
|
"""
|
||||||
# Importing a module that didn't exist, even if the ImportError was
|
Ref #383. Python 3.3's namespace package messed with our import hooks
|
||||||
# gracefully handled, would make our test crash.
|
Importing a module that didn't exist, even if the ImportError was
|
||||||
|
gracefully handled, would make our test crash.
|
||||||
|
|
||||||
|
Use recwarn here to silence this warning in Python 2.6 and 2.7:
|
||||||
|
ImportWarning: Not importing directory '...\not_a_package': missing __init__.py
|
||||||
|
"""
|
||||||
testdir.mkdir('not_a_package')
|
testdir.mkdir('not_a_package')
|
||||||
p = testdir.makepyfile("""
|
p = testdir.makepyfile("""
|
||||||
try:
|
try:
|
||||||
|
@ -524,6 +530,7 @@ class TestInvocationVariants(object):
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_cmdline_python_package(self, testdir, monkeypatch):
|
def test_cmdline_python_package(self, testdir, monkeypatch):
|
||||||
|
import warnings
|
||||||
monkeypatch.delenv('PYTHONDONTWRITEBYTECODE', False)
|
monkeypatch.delenv('PYTHONDONTWRITEBYTECODE', False)
|
||||||
path = testdir.mkpydir("tpkg")
|
path = testdir.mkpydir("tpkg")
|
||||||
path.join("test_hello.py").write("def test_hello(): pass")
|
path.join("test_hello.py").write("def test_hello(): pass")
|
||||||
|
@ -546,7 +553,11 @@ class TestInvocationVariants(object):
|
||||||
return what
|
return what
|
||||||
empty_package = testdir.mkpydir("empty_package")
|
empty_package = testdir.mkpydir("empty_package")
|
||||||
monkeypatch.setenv('PYTHONPATH', join_pythonpath(empty_package))
|
monkeypatch.setenv('PYTHONPATH', join_pythonpath(empty_package))
|
||||||
result = testdir.runpytest("--pyargs", ".")
|
# the path which is not a package raises a warning on pypy;
|
||||||
|
# no idea why only pypy and not normal python warn about it here
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter('ignore', ImportWarning)
|
||||||
|
result = testdir.runpytest("--pyargs", ".")
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
"*2 passed*"
|
"*2 passed*"
|
||||||
|
|
|
@ -27,7 +27,7 @@ def test_funcarg_prefix_deprecation(testdir):
|
||||||
""")
|
""")
|
||||||
result = testdir.runpytest('-ra')
|
result = testdir.runpytest('-ra')
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
('WC1 None pytest_funcarg__value: '
|
('*pytest_funcarg__value: '
|
||||||
'declaring fixtures using "pytest_funcarg__" prefix is deprecated '
|
'declaring fixtures using "pytest_funcarg__" prefix is deprecated '
|
||||||
'and scheduled to be removed in pytest 4.0. '
|
'and scheduled to be removed in pytest 4.0. '
|
||||||
'Please remove the prefix and use the @pytest.fixture decorator instead.'),
|
'Please remove the prefix and use the @pytest.fixture decorator instead.'),
|
||||||
|
|
|
@ -113,9 +113,9 @@ class TestClass(object):
|
||||||
pass
|
pass
|
||||||
""")
|
""")
|
||||||
result = testdir.runpytest("-rw")
|
result = testdir.runpytest("-rw")
|
||||||
result.stdout.fnmatch_lines_random("""
|
result.stdout.fnmatch_lines([
|
||||||
WC1*test_class_with_init_warning.py*__init__*
|
"*cannot collect test class 'TestClass1' because it has a __init__ constructor",
|
||||||
""")
|
])
|
||||||
|
|
||||||
def test_class_subclassobject(self, testdir):
|
def test_class_subclassobject(self, testdir):
|
||||||
testdir.getmodulecol("""
|
testdir.getmodulecol("""
|
||||||
|
@ -1243,8 +1243,8 @@ def test_dont_collect_non_function_callable(testdir):
|
||||||
result = testdir.runpytest('-rw')
|
result = testdir.runpytest('-rw')
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
'*collected 1 item*',
|
'*collected 1 item*',
|
||||||
'WC2 *',
|
"*cannot collect 'test_a' because it is not a function*",
|
||||||
'*1 passed, 1 pytest-warnings in *',
|
'*1 passed, 1 warnings in *',
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -545,22 +545,33 @@ class TestRequestBasic(object):
|
||||||
return l.pop()
|
return l.pop()
|
||||||
def test_func(something): pass
|
def test_func(something): pass
|
||||||
""")
|
""")
|
||||||
|
import contextlib
|
||||||
|
if getfixmethod == 'getfuncargvalue':
|
||||||
|
warning_expectation = pytest.warns(DeprecationWarning)
|
||||||
|
else:
|
||||||
|
# see #1830 for a cleaner way to accomplish this
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def expecting_no_warning(): yield
|
||||||
|
|
||||||
|
warning_expectation = expecting_no_warning()
|
||||||
|
|
||||||
req = item._request
|
req = item._request
|
||||||
fixture_fetcher = getattr(req, getfixmethod)
|
with warning_expectation:
|
||||||
pytest.raises(FixtureLookupError, fixture_fetcher, "notexists")
|
fixture_fetcher = getattr(req, getfixmethod)
|
||||||
val = fixture_fetcher("something")
|
with pytest.raises(FixtureLookupError):
|
||||||
assert val == 1
|
fixture_fetcher("notexists")
|
||||||
val = fixture_fetcher("something")
|
val = fixture_fetcher("something")
|
||||||
assert val == 1
|
assert val == 1
|
||||||
val2 = fixture_fetcher("other")
|
val = fixture_fetcher("something")
|
||||||
assert val2 == 2
|
assert val == 1
|
||||||
val2 = fixture_fetcher("other") # see about caching
|
val2 = fixture_fetcher("other")
|
||||||
assert val2 == 2
|
assert val2 == 2
|
||||||
pytest._fillfuncargs(item)
|
val2 = fixture_fetcher("other") # see about caching
|
||||||
assert item.funcargs["something"] == 1
|
assert val2 == 2
|
||||||
assert len(get_public_names(item.funcargs)) == 2
|
pytest._fillfuncargs(item)
|
||||||
assert "request" in item.funcargs
|
assert item.funcargs["something"] == 1
|
||||||
#assert item.funcargs == {'something': 1, "other": 2}
|
assert len(get_public_names(item.funcargs)) == 2
|
||||||
|
assert "request" in item.funcargs
|
||||||
|
|
||||||
def test_request_addfinalizer(self, testdir):
|
def test_request_addfinalizer(self, testdir):
|
||||||
item = testdir.getitem("""
|
item = testdir.getitem("""
|
||||||
|
|
|
@ -347,7 +347,8 @@ class TestMetafunc(object):
|
||||||
def test_foo(arg):
|
def test_foo(arg):
|
||||||
pass
|
pass
|
||||||
""")
|
""")
|
||||||
result = testdir.runpytest("--collect-only")
|
with pytest.warns(DeprecationWarning):
|
||||||
|
result = testdir.runpytest("--collect-only")
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
"<Module 'test_parametrize_ids_exception.py'>",
|
"<Module 'test_parametrize_ids_exception.py'>",
|
||||||
" <Function 'test_foo[a]'>",
|
" <Function 'test_foo[a]'>",
|
||||||
|
|
|
@ -976,7 +976,10 @@ def test_assert_tuple_warning(testdir):
|
||||||
assert(False, 'you shall not pass')
|
assert(False, 'you shall not pass')
|
||||||
""")
|
""")
|
||||||
result = testdir.runpytest('-rw')
|
result = testdir.runpytest('-rw')
|
||||||
result.stdout.fnmatch_lines('WR1*:2 assertion is always true*')
|
result.stdout.fnmatch_lines([
|
||||||
|
'*test_assert_tuple_warning.py:2',
|
||||||
|
'*assertion is always true*',
|
||||||
|
])
|
||||||
|
|
||||||
def test_assert_indirect_tuple_no_warning(testdir):
|
def test_assert_indirect_tuple_no_warning(testdir):
|
||||||
testdir.makepyfile("""
|
testdir.makepyfile("""
|
||||||
|
|
|
@ -55,7 +55,7 @@ class TestNewAPI(object):
|
||||||
assert result.ret == 1
|
assert result.ret == 1
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
"*could not create cache path*",
|
"*could not create cache path*",
|
||||||
"*1 pytest-warnings*",
|
"*1 warnings*",
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_config_cache(self, testdir):
|
def test_config_cache(self, testdir):
|
||||||
|
|
|
@ -141,7 +141,7 @@ class TestConfigAPI(object):
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
def pytest_addoption(parser):
|
def pytest_addoption(parser):
|
||||||
parser.addoption('--hello', type='string')
|
parser.addoption('--hello', type=str)
|
||||||
""")
|
""")
|
||||||
config = testdir.parseconfig('--hello=this')
|
config = testdir.parseconfig('--hello=this')
|
||||||
assert config.getoption('hello') == 'this'
|
assert config.getoption('hello') == 'this'
|
||||||
|
@ -633,13 +633,14 @@ class TestWarning(object):
|
||||||
pass
|
pass
|
||||||
""")
|
""")
|
||||||
result = testdir.runpytest("--disable-pytest-warnings")
|
result = testdir.runpytest("--disable-pytest-warnings")
|
||||||
assert result.parseoutcomes()["pytest-warnings"] > 0
|
assert result.parseoutcomes()["warnings"] > 0
|
||||||
assert "hello" not in result.stdout.str()
|
assert "hello" not in result.stdout.str()
|
||||||
|
|
||||||
result = testdir.runpytest()
|
result = testdir.runpytest()
|
||||||
result.stdout.fnmatch_lines("""
|
result.stdout.fnmatch_lines("""
|
||||||
===*pytest-warning summary*===
|
===*warnings summary*===
|
||||||
*WT1*test_warn_on_test_item*:7 hello*
|
*test_warn_on_test_item_from_request.py::test_hello*
|
||||||
|
*hello*
|
||||||
""")
|
""")
|
||||||
|
|
||||||
class TestRootdir(object):
|
class TestRootdir(object):
|
||||||
|
|
|
@ -852,7 +852,10 @@ def test_record_property(testdir):
|
||||||
pnodes = psnode.find_by_tag('property')
|
pnodes = psnode.find_by_tag('property')
|
||||||
pnodes[0].assert_attr(name="bar", value="1")
|
pnodes[0].assert_attr(name="bar", value="1")
|
||||||
pnodes[1].assert_attr(name="foo", value="<1")
|
pnodes[1].assert_attr(name="foo", value="<1")
|
||||||
result.stdout.fnmatch_lines('*C3*test_record_property.py*experimental*')
|
result.stdout.fnmatch_lines([
|
||||||
|
'test_record_property.py::test_record',
|
||||||
|
'*record_xml_property*experimental*',
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
def test_record_property_same_name(testdir):
|
def test_record_property_same_name(testdir):
|
||||||
|
|
|
@ -34,15 +34,16 @@ class TestParser(object):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_argument_type(self):
|
def test_argument_type(self):
|
||||||
argument = parseopt.Argument('-t', dest='abc', type='int')
|
argument = parseopt.Argument('-t', dest='abc', type=int)
|
||||||
assert argument.type is int
|
assert argument.type is int
|
||||||
argument = parseopt.Argument('-t', dest='abc', type='string')
|
argument = parseopt.Argument('-t', dest='abc', type=str)
|
||||||
assert argument.type is str
|
assert argument.type is str
|
||||||
argument = parseopt.Argument('-t', dest='abc', type=float)
|
argument = parseopt.Argument('-t', dest='abc', type=float)
|
||||||
assert argument.type is float
|
assert argument.type is float
|
||||||
with pytest.raises(KeyError):
|
with pytest.warns(DeprecationWarning):
|
||||||
argument = parseopt.Argument('-t', dest='abc', type='choice')
|
with pytest.raises(KeyError):
|
||||||
argument = parseopt.Argument('-t', dest='abc', type='choice',
|
argument = parseopt.Argument('-t', dest='abc', type='choice')
|
||||||
|
argument = parseopt.Argument('-t', dest='abc', type=str,
|
||||||
choices=['red', 'blue'])
|
choices=['red', 'blue'])
|
||||||
assert argument.type is str
|
assert argument.type is str
|
||||||
|
|
||||||
|
@ -176,8 +177,8 @@ class TestParser(object):
|
||||||
elif option.type is str:
|
elif option.type is str:
|
||||||
option.default = "world"
|
option.default = "world"
|
||||||
parser = parseopt.Parser(processopt=defaultget)
|
parser = parseopt.Parser(processopt=defaultget)
|
||||||
parser.addoption("--this", dest="this", type="int", action="store")
|
parser.addoption("--this", dest="this", type=int, action="store")
|
||||||
parser.addoption("--hello", dest="hello", type="string", action="store")
|
parser.addoption("--hello", dest="hello", type=str, action="store")
|
||||||
parser.addoption("--no", dest="no", action="store_true")
|
parser.addoption("--no", dest="no", action="store_true")
|
||||||
option = parser.parse([])
|
option = parser.parse([])
|
||||||
assert option.hello == "world"
|
assert option.hello == "world"
|
||||||
|
|
|
@ -285,8 +285,8 @@ class TestPytestPluginManager(object):
|
||||||
result = testdir.runpytest("-rw", "-p", "skipping1", syspathinsert=True)
|
result = testdir.runpytest("-rw", "-p", "skipping1", syspathinsert=True)
|
||||||
assert result.ret == EXIT_NOTESTSCOLLECTED
|
assert result.ret == EXIT_NOTESTSCOLLECTED
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
"WI1*skipped plugin*skipping1*hello*",
|
"*skipped plugin*skipping1*hello*",
|
||||||
"WI1*skipped plugin*skipping2*hello*",
|
"*skipped plugin*skipping2*hello*",
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_consider_env_plugin_instantiation(self, testdir, monkeypatch, pytestpm):
|
def test_consider_env_plugin_instantiation(self, testdir, monkeypatch, pytestpm):
|
||||||
|
|
|
@ -146,7 +146,9 @@ class TestDeprecatedCall(object):
|
||||||
pytest.deprecated_call(deprecated_function)
|
pytest.deprecated_call(deprecated_function)
|
||||||
""")
|
""")
|
||||||
result = testdir.runpytest()
|
result = testdir.runpytest()
|
||||||
result.stdout.fnmatch_lines('*=== 2 passed in *===')
|
# the 2 tests must pass, but the call to test_one() will generate a warning
|
||||||
|
# in pytest's summary
|
||||||
|
result.stdout.fnmatch_lines('*=== 2 passed, 1 warnings in *===')
|
||||||
|
|
||||||
|
|
||||||
class TestWarns(object):
|
class TestWarns(object):
|
||||||
|
|
|
@ -616,7 +616,7 @@ def test_getreportopt():
|
||||||
class config(object):
|
class config(object):
|
||||||
class option(object):
|
class option(object):
|
||||||
reportchars = ""
|
reportchars = ""
|
||||||
disablepytestwarnings = True
|
disable_warnings = True
|
||||||
|
|
||||||
config.option.reportchars = "sf"
|
config.option.reportchars = "sf"
|
||||||
assert getreportopt(config) == "sf"
|
assert getreportopt(config) == "sf"
|
||||||
|
@ -625,11 +625,11 @@ def test_getreportopt():
|
||||||
assert getreportopt(config) == "sfx"
|
assert getreportopt(config) == "sfx"
|
||||||
|
|
||||||
config.option.reportchars = "sfx"
|
config.option.reportchars = "sfx"
|
||||||
config.option.disablepytestwarnings = False
|
config.option.disable_warnings = False
|
||||||
assert getreportopt(config) == "sfxw"
|
assert getreportopt(config) == "sfxw"
|
||||||
|
|
||||||
config.option.reportchars = "sfxw"
|
config.option.reportchars = "sfxw"
|
||||||
config.option.disablepytestwarnings = False
|
config.option.disable_warnings = False
|
||||||
assert getreportopt(config) == "sfxw"
|
assert getreportopt(config) == "sfxw"
|
||||||
|
|
||||||
|
|
||||||
|
@ -838,8 +838,8 @@ def test_terminal_summary_warnings_are_displayed(testdir):
|
||||||
""")
|
""")
|
||||||
result = testdir.runpytest('-rw')
|
result = testdir.runpytest('-rw')
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
'*C1*internal warning',
|
'*internal warning',
|
||||||
'*== 1 pytest-warnings in *',
|
'*== 1 warnings in *',
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
|
@ -859,9 +859,9 @@ def test_terminal_summary_warnings_are_displayed(testdir):
|
||||||
("yellow", "1 weird", {"weird": (1,)}),
|
("yellow", "1 weird", {"weird": (1,)}),
|
||||||
("yellow", "1 passed, 1 weird", {"weird": (1,), "passed": (1,)}),
|
("yellow", "1 passed, 1 weird", {"weird": (1,), "passed": (1,)}),
|
||||||
|
|
||||||
("yellow", "1 pytest-warnings", {"warnings": (1,)}),
|
("yellow", "1 warnings", {"warnings": (1,)}),
|
||||||
("yellow", "1 passed, 1 pytest-warnings", {"warnings": (1,),
|
("yellow", "1 passed, 1 warnings", {"warnings": (1,),
|
||||||
"passed": (1,)}),
|
"passed": (1,)}),
|
||||||
|
|
||||||
("green", "5 passed", {"passed": (1,2,3,4,5)}),
|
("green", "5 passed", {"passed": (1,2,3,4,5)}),
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
WARNINGS_SUMMARY_HEADER = 'warnings summary'
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def pyfile_with_warnings(testdir, request):
|
||||||
|
"""
|
||||||
|
Create a test file which calls a function in a module which generates warnings.
|
||||||
|
"""
|
||||||
|
testdir.syspathinsert()
|
||||||
|
test_name = request.function.__name__
|
||||||
|
module_name = test_name.lstrip('test_') + '_module'
|
||||||
|
testdir.makepyfile(**{
|
||||||
|
module_name: '''
|
||||||
|
import warnings
|
||||||
|
def foo():
|
||||||
|
warnings.warn(PendingDeprecationWarning("functionality is pending deprecation"))
|
||||||
|
warnings.warn(DeprecationWarning("functionality is deprecated"))
|
||||||
|
return 1
|
||||||
|
''',
|
||||||
|
test_name: '''
|
||||||
|
import {module_name}
|
||||||
|
def test_func():
|
||||||
|
assert {module_name}.foo() == 1
|
||||||
|
'''.format(module_name=module_name)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def test_normal_flow(testdir, pyfile_with_warnings):
|
||||||
|
"""
|
||||||
|
Check that the warnings section is displayed, containing test node ids followed by
|
||||||
|
all warnings generated by that test node.
|
||||||
|
"""
|
||||||
|
result = testdir.runpytest()
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
'*== %s ==*' % WARNINGS_SUMMARY_HEADER,
|
||||||
|
|
||||||
|
'*test_normal_flow.py::test_func',
|
||||||
|
|
||||||
|
'*normal_flow_module.py:3: PendingDeprecationWarning: functionality is pending deprecation',
|
||||||
|
'* warnings.warn(PendingDeprecationWarning("functionality is pending deprecation"))',
|
||||||
|
|
||||||
|
'*normal_flow_module.py:4: DeprecationWarning: functionality is deprecated',
|
||||||
|
'* warnings.warn(DeprecationWarning("functionality is deprecated"))',
|
||||||
|
'* 1 passed, 2 warnings*',
|
||||||
|
])
|
||||||
|
assert result.stdout.str().count('test_normal_flow.py::test_func') == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_setup_teardown_warnings(testdir, pyfile_with_warnings):
|
||||||
|
testdir.makepyfile('''
|
||||||
|
import warnings
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def fix():
|
||||||
|
warnings.warn(UserWarning("warning during setup"))
|
||||||
|
yield
|
||||||
|
warnings.warn(UserWarning("warning during teardown"))
|
||||||
|
|
||||||
|
def test_func(fix):
|
||||||
|
pass
|
||||||
|
''')
|
||||||
|
result = testdir.runpytest()
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
'*== %s ==*' % WARNINGS_SUMMARY_HEADER,
|
||||||
|
|
||||||
|
'*test_setup_teardown_warnings.py:6: UserWarning: warning during setup',
|
||||||
|
'*warnings.warn(UserWarning("warning during setup"))',
|
||||||
|
|
||||||
|
'*test_setup_teardown_warnings.py:8: UserWarning: warning during teardown',
|
||||||
|
'*warnings.warn(UserWarning("warning during teardown"))',
|
||||||
|
'* 1 passed, 2 warnings*',
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('method', ['cmdline', 'ini'])
|
||||||
|
def test_as_errors(testdir, pyfile_with_warnings, method):
|
||||||
|
args = ('-W', 'error') if method == 'cmdline' else ()
|
||||||
|
if method == 'ini':
|
||||||
|
testdir.makeini('''
|
||||||
|
[pytest]
|
||||||
|
filterwarnings= error
|
||||||
|
''')
|
||||||
|
result = testdir.runpytest(*args)
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
'E PendingDeprecationWarning: functionality is pending deprecation',
|
||||||
|
'as_errors_module.py:3: PendingDeprecationWarning',
|
||||||
|
'* 1 failed in *',
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('method', ['cmdline', 'ini'])
|
||||||
|
def test_ignore(testdir, pyfile_with_warnings, method):
|
||||||
|
args = ('-W', 'ignore') if method == 'cmdline' else ()
|
||||||
|
if method == 'ini':
|
||||||
|
testdir.makeini('''
|
||||||
|
[pytest]
|
||||||
|
filterwarnings= ignore
|
||||||
|
''')
|
||||||
|
|
||||||
|
result = testdir.runpytest(*args)
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
'* 1 passed in *',
|
||||||
|
])
|
||||||
|
assert WARNINGS_SUMMARY_HEADER not in result.stdout.str()
|
||||||
|
|
10
tox.ini
10
tox.ini
|
@ -176,7 +176,15 @@ python_files=test_*.py *_test.py testing/*/*.py
|
||||||
python_classes=Test Acceptance
|
python_classes=Test Acceptance
|
||||||
python_functions=test
|
python_functions=test
|
||||||
norecursedirs = .tox ja .hg cx_freeze_source
|
norecursedirs = .tox ja .hg cx_freeze_source
|
||||||
|
filterwarnings= error
|
||||||
|
# produced by path.local
|
||||||
|
ignore:bad escape.*:DeprecationWarning:re
|
||||||
|
# produced by path.readlines
|
||||||
|
ignore:.*U.*mode is deprecated:DeprecationWarning
|
||||||
|
# produced by pytest-xdist
|
||||||
|
ignore:.*type argument to addoption.*:DeprecationWarning
|
||||||
|
# produced by python >=3.5 on execnet (pytest-xdist)
|
||||||
|
ignore:.*inspect.getargspec.*deprecated, use inspect.signature.*:DeprecationWarning
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
ignore =E401,E225,E261,E128,E124,E301,E302,E121,E303,W391,E501,E231,E126,E701,E265,E241,E251,E226,E101,W191,E131,E203,E122,E123,E271,E712,E222,E127,E125,E221,W292,E111,E113,E293,E262,W293,E129,E702,E201,E272,E202,E704,E731,E402
|
ignore =E401,E225,E261,E128,E124,E301,E302,E121,E303,W391,E501,E231,E126,E701,E265,E241,E251,E226,E101,W191,E131,E203,E122,E123,E271,E712,E222,E127,E125,E221,W292,E111,E113,E293,E262,W293,E129,E702,E201,E272,E202,E704,E731,E402
|
||||||
|
|
Loading…
Reference in New Issue