Merge pull request #2072 from nicoddemus/integrate-pytest-warnings

Integrate pytest warnings
This commit is contained in:
Ronny Pfannschmidt 2017-03-22 17:10:04 +01:00 committed by GitHub
commit 2a130daae6
28 changed files with 588 additions and 226 deletions

View File

@ -19,6 +19,10 @@ New Features
* ``pytest.param`` can be used to declare test parameter sets with marks and test ids.
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
-------

View File

@ -99,7 +99,7 @@ default_plugins = (
"mark main terminal runner python fixtures debugging unittest capture skipping "
"tmpdir monkeypatch recwarn pastebin helpconfig nose assertion "
"junitxml resultlog doctest cacheprovider freeze_support "
"setuponly setupplan").split()
"setuponly setupplan warnings").split()
builtin_plugins = set(default_plugins)
builtin_plugins.add("pytester")
@ -911,11 +911,11 @@ class Config(object):
fin = self._cleanup.pop()
fin()
def warn(self, code, message, fslocation=None):
def warn(self, code, message, fslocation=None, nodeid=None):
""" generate a warning for this test session. """
self.hook.pytest_logwarning.call_historic(kwargs=dict(
code=code, message=message,
fslocation=fslocation, nodeid=None))
fslocation=fslocation, nodeid=nodeid))
def get_terminal_writer(self):
return self.pluginmanager.get_plugin("terminalreporter")._tw

View File

@ -1081,7 +1081,7 @@ class FixtureManager(object):
continue
marker = defaultfuncargprefixmarker
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):]
elif not isinstance(marker, FixtureFunctionMarker):
# magic globals with __getattr__ might have got us a wrong

View File

@ -1008,7 +1008,7 @@ class Testdir(object):
The pexpect child is returned.
"""
basetemp = self.tmpdir.mkdir("pexpect")
basetemp = self.tmpdir.mkdir("temp-pexpect")
invoke = " ".join(map(str, self._getpytestargs()))
cmd = "%s --basetemp=%s %s" % (invoke, basetemp, string)
return self.spawn(cmd, expect_timeout=expect_timeout)

View File

@ -936,7 +936,7 @@ def _idval(val, argname, idx, idfn, config=None):
import warnings
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.'
warnings.warn(msg)
warnings.warn(msg, DeprecationWarning)
if s:
return _escape_strings(s)

View File

@ -56,14 +56,12 @@ def deprecated_call(func=None, *args, **kwargs):
def warn_explicit(message, category, *args, **kwargs):
categories.append(category)
old_warn_explicit(message, category, *args, **kwargs)
def warn(message, category=None, *args, **kwargs):
if isinstance(message, Warning):
categories.append(message.__class__)
else:
categories.append(category)
old_warn(message, category, *args, **kwargs)
old_warn = warnings.warn
old_warn_explicit = warnings.warn_explicit

View File

@ -554,14 +554,21 @@ def importorskip(modname, minversion=None):
__version__ attribute. If no minversion is specified the a skip
is only triggered if the module can not be imported.
"""
import warnings
__tracebackhide__ = True
compile(modname, '', 'eval') # to catch syntaxerrors
should_skip = False
try:
__import__(modname)
except ImportError:
# Do not raise chained exception here(#1485)
should_skip = True
with warnings.catch_warnings():
# make sure to ignore ImportWarnings that might happen because
# of existing directories with the same name we're trying to
# 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:
raise Skipped("could not import %r" %(modname,), allow_module_level=True)
mod = sys.modules[modname]

View File

@ -4,6 +4,7 @@ This is a good source for looking at the various reporting hooks.
"""
from __future__ import absolute_import, division, print_function
import itertools
from _pytest.main import EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, \
EXIT_USAGEERROR, EXIT_NOTESTSCOLLECTED
import pytest
@ -26,11 +27,11 @@ def pytest_addoption(parser):
help="show extra test summary info as specified by chars (f)ailed, "
"(E)error, (s)skipped, (x)failed, (X)passed, "
"(p)passed, (P)passed with output, (a)all except pP. "
"The pytest warnings are displayed at all times except when "
"--disable-pytest-warnings is set")
group._addoption('--disable-pytest-warnings', default=False,
dest='disablepytestwarnings', action='store_true',
help='disable warnings summary, overrides -r w flag')
"Warnings are displayed at all times except when "
"--disable-warnings is set")
group._addoption('--disable-warnings', '--disable-pytest-warnings', default=False,
dest='disable_warnings', action='store_true',
help='disable warnings summary')
group._addoption('-l', '--showlocals',
action="store_true", dest="showlocals", default=False,
help="show locals in tracebacks (disabled by default).")
@ -59,9 +60,9 @@ def pytest_configure(config):
def getreportopt(config):
reportopts = ""
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'
elif config.option.disablepytestwarnings and 'w' in reportchars:
elif config.option.disable_warnings and 'w' in reportchars:
reportchars = reportchars.replace('w', '')
if reportchars:
for char in reportchars:
@ -82,13 +83,40 @@ def pytest_report_teststatus(report):
letter = "f"
return report.outcome, letter, report.outcome.upper()
class WarningReport(object):
"""
Simple structure to hold warnings information captured by ``pytest_logwarning``.
"""
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.message = message
self.nodeid = nodeid
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):
def __init__(self, config, file=None):
@ -168,8 +196,6 @@ class TerminalReporter(object):
def pytest_logwarning(self, code, fslocation, message, nodeid):
warnings = self.stats.setdefault("warnings", [])
if isinstance(fslocation, tuple):
fslocation = "%s:%d" % fslocation
warning = WarningReport(code=code, fslocation=fslocation,
message=message, nodeid=nodeid)
warnings.append(warning)
@ -440,13 +466,21 @@ class TerminalReporter(object):
def summary_warnings(self):
if self.hasopt("w"):
warnings = self.stats.get("warnings")
if not warnings:
all_warnings = self.stats.get("warnings")
if not all_warnings:
return
self.write_sep("=", "pytest-warning summary")
for w in warnings:
self._tw.line("W%s %s %s" % (w.code,
w.fslocation, w.message))
grouped = itertools.groupby(all_warnings, key=lambda wr: wr.get_location(self.config))
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):
if self.config.option.tbstyle != "no":
@ -548,8 +582,7 @@ def flatten(l):
def build_summary_stats_line(stats):
keys = ("failed passed skipped deselected "
"xfailed xpassed warnings error").split()
key_translation = {'warnings': 'pytest-warnings'}
"xfailed xpassed warnings error").split()
unknown_key_seen = False
for key in stats.keys():
if key not in keys:
@ -560,8 +593,7 @@ def build_summary_stats_line(stats):
for key in keys:
val = stats.get(key, None)
if val:
key_name = key_translation.get(key, key)
parts.append("%d %s" % (len(val), key_name))
parts.append("%d %s" % (len(val), key))
if parts:
line = ", ".join(parts)

73
_pytest/warnings.py Normal file
View File

@ -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

View File

@ -18,7 +18,7 @@ Full pytest documentation
monkeypatch
tmpdir
capture
recwarn
warnings
doctest
mark
skipping

View File

@ -240,3 +240,23 @@ Builtin configuration file options
By default, pytest will stop searching for ``conftest.py`` files upwards
from ``pytest.ini``/``tox.ini``/``setup.cfg`` of the project if any,
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`.

View File

@ -1,142 +1,3 @@
.. _`asserting warnings`:
:orphan:
.. _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()
This page has been moved, please see :ref:`assertwarnings`.

218
doc/en/warnings.rst Normal file
View File

@ -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()

View File

@ -339,10 +339,16 @@ class TestGeneralUsage(object):
"*ERROR*test_b.py::b*",
])
@pytest.mark.usefixtures('recwarn')
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
# gracefully handled, would make our test crash.
"""
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
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')
p = testdir.makepyfile("""
try:
@ -524,6 +530,7 @@ class TestInvocationVariants(object):
])
def test_cmdline_python_package(self, testdir, monkeypatch):
import warnings
monkeypatch.delenv('PYTHONDONTWRITEBYTECODE', False)
path = testdir.mkpydir("tpkg")
path.join("test_hello.py").write("def test_hello(): pass")
@ -546,7 +553,11 @@ class TestInvocationVariants(object):
return what
empty_package = testdir.mkpydir("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
result.stdout.fnmatch_lines([
"*2 passed*"

View File

@ -27,7 +27,7 @@ def test_funcarg_prefix_deprecation(testdir):
""")
result = testdir.runpytest('-ra')
result.stdout.fnmatch_lines([
('WC1 None pytest_funcarg__value: '
('*pytest_funcarg__value: '
'declaring fixtures using "pytest_funcarg__" prefix is deprecated '
'and scheduled to be removed in pytest 4.0. '
'Please remove the prefix and use the @pytest.fixture decorator instead.'),

View File

@ -113,9 +113,9 @@ class TestClass(object):
pass
""")
result = testdir.runpytest("-rw")
result.stdout.fnmatch_lines_random("""
WC1*test_class_with_init_warning.py*__init__*
""")
result.stdout.fnmatch_lines([
"*cannot collect test class 'TestClass1' because it has a __init__ constructor",
])
def test_class_subclassobject(self, testdir):
testdir.getmodulecol("""
@ -1243,8 +1243,8 @@ def test_dont_collect_non_function_callable(testdir):
result = testdir.runpytest('-rw')
result.stdout.fnmatch_lines([
'*collected 1 item*',
'WC2 *',
'*1 passed, 1 pytest-warnings in *',
"*cannot collect 'test_a' because it is not a function*",
'*1 passed, 1 warnings in *',
])

View File

@ -545,22 +545,33 @@ class TestRequestBasic(object):
return l.pop()
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
fixture_fetcher = getattr(req, getfixmethod)
pytest.raises(FixtureLookupError, fixture_fetcher, "notexists")
val = fixture_fetcher("something")
assert val == 1
val = fixture_fetcher("something")
assert val == 1
val2 = fixture_fetcher("other")
assert val2 == 2
val2 = fixture_fetcher("other") # see about caching
assert val2 == 2
pytest._fillfuncargs(item)
assert item.funcargs["something"] == 1
assert len(get_public_names(item.funcargs)) == 2
assert "request" in item.funcargs
#assert item.funcargs == {'something': 1, "other": 2}
with warning_expectation:
fixture_fetcher = getattr(req, getfixmethod)
with pytest.raises(FixtureLookupError):
fixture_fetcher("notexists")
val = fixture_fetcher("something")
assert val == 1
val = fixture_fetcher("something")
assert val == 1
val2 = fixture_fetcher("other")
assert val2 == 2
val2 = fixture_fetcher("other") # see about caching
assert val2 == 2
pytest._fillfuncargs(item)
assert item.funcargs["something"] == 1
assert len(get_public_names(item.funcargs)) == 2
assert "request" in item.funcargs
def test_request_addfinalizer(self, testdir):
item = testdir.getitem("""

View File

@ -347,7 +347,8 @@ class TestMetafunc(object):
def test_foo(arg):
pass
""")
result = testdir.runpytest("--collect-only")
with pytest.warns(DeprecationWarning):
result = testdir.runpytest("--collect-only")
result.stdout.fnmatch_lines([
"<Module 'test_parametrize_ids_exception.py'>",
" <Function 'test_foo[a]'>",

View File

@ -976,7 +976,10 @@ def test_assert_tuple_warning(testdir):
assert(False, 'you shall not pass')
""")
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):
testdir.makepyfile("""

View File

@ -55,7 +55,7 @@ class TestNewAPI(object):
assert result.ret == 1
result.stdout.fnmatch_lines([
"*could not create cache path*",
"*1 pytest-warnings*",
"*1 warnings*",
])
def test_config_cache(self, testdir):

View File

@ -141,7 +141,7 @@ class TestConfigAPI(object):
from __future__ import unicode_literals
def pytest_addoption(parser):
parser.addoption('--hello', type='string')
parser.addoption('--hello', type=str)
""")
config = testdir.parseconfig('--hello=this')
assert config.getoption('hello') == 'this'
@ -633,13 +633,14 @@ class TestWarning(object):
pass
""")
result = testdir.runpytest("--disable-pytest-warnings")
assert result.parseoutcomes()["pytest-warnings"] > 0
assert result.parseoutcomes()["warnings"] > 0
assert "hello" not in result.stdout.str()
result = testdir.runpytest()
result.stdout.fnmatch_lines("""
===*pytest-warning summary*===
*WT1*test_warn_on_test_item*:7 hello*
===*warnings summary*===
*test_warn_on_test_item_from_request.py::test_hello*
*hello*
""")
class TestRootdir(object):

View File

@ -852,7 +852,10 @@ def test_record_property(testdir):
pnodes = psnode.find_by_tag('property')
pnodes[0].assert_attr(name="bar", 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):

View File

@ -34,15 +34,16 @@ class TestParser(object):
)
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
argument = parseopt.Argument('-t', dest='abc', type='string')
argument = parseopt.Argument('-t', dest='abc', type=str)
assert argument.type is str
argument = parseopt.Argument('-t', dest='abc', type=float)
assert argument.type is float
with pytest.raises(KeyError):
argument = parseopt.Argument('-t', dest='abc', type='choice')
argument = parseopt.Argument('-t', dest='abc', type='choice',
with pytest.warns(DeprecationWarning):
with pytest.raises(KeyError):
argument = parseopt.Argument('-t', dest='abc', type='choice')
argument = parseopt.Argument('-t', dest='abc', type=str,
choices=['red', 'blue'])
assert argument.type is str
@ -176,8 +177,8 @@ class TestParser(object):
elif option.type is str:
option.default = "world"
parser = parseopt.Parser(processopt=defaultget)
parser.addoption("--this", dest="this", type="int", action="store")
parser.addoption("--hello", dest="hello", type="string", action="store")
parser.addoption("--this", dest="this", type=int, action="store")
parser.addoption("--hello", dest="hello", type=str, action="store")
parser.addoption("--no", dest="no", action="store_true")
option = parser.parse([])
assert option.hello == "world"

View File

@ -285,8 +285,8 @@ class TestPytestPluginManager(object):
result = testdir.runpytest("-rw", "-p", "skipping1", syspathinsert=True)
assert result.ret == EXIT_NOTESTSCOLLECTED
result.stdout.fnmatch_lines([
"WI1*skipped plugin*skipping1*hello*",
"WI1*skipped plugin*skipping2*hello*",
"*skipped plugin*skipping1*hello*",
"*skipped plugin*skipping2*hello*",
])
def test_consider_env_plugin_instantiation(self, testdir, monkeypatch, pytestpm):

View File

@ -146,7 +146,9 @@ class TestDeprecatedCall(object):
pytest.deprecated_call(deprecated_function)
""")
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):

View File

@ -616,7 +616,7 @@ def test_getreportopt():
class config(object):
class option(object):
reportchars = ""
disablepytestwarnings = True
disable_warnings = True
config.option.reportchars = "sf"
assert getreportopt(config) == "sf"
@ -625,11 +625,11 @@ def test_getreportopt():
assert getreportopt(config) == "sfx"
config.option.reportchars = "sfx"
config.option.disablepytestwarnings = False
config.option.disable_warnings = False
assert getreportopt(config) == "sfxw"
config.option.reportchars = "sfxw"
config.option.disablepytestwarnings = False
config.option.disable_warnings = False
assert getreportopt(config) == "sfxw"
@ -838,8 +838,8 @@ def test_terminal_summary_warnings_are_displayed(testdir):
""")
result = testdir.runpytest('-rw')
result.stdout.fnmatch_lines([
'*C1*internal warning',
'*== 1 pytest-warnings in *',
'*internal warning',
'*== 1 warnings in *',
])
@ -859,9 +859,9 @@ def test_terminal_summary_warnings_are_displayed(testdir):
("yellow", "1 weird", {"weird": (1,)}),
("yellow", "1 passed, 1 weird", {"weird": (1,), "passed": (1,)}),
("yellow", "1 pytest-warnings", {"warnings": (1,)}),
("yellow", "1 passed, 1 pytest-warnings", {"warnings": (1,),
"passed": (1,)}),
("yellow", "1 warnings", {"warnings": (1,)}),
("yellow", "1 passed, 1 warnings", {"warnings": (1,),
"passed": (1,)}),
("green", "5 passed", {"passed": (1,2,3,4,5)}),

108
testing/test_warnings.py Normal file
View File

@ -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
View File

@ -176,7 +176,15 @@ python_files=test_*.py *_test.py testing/*/*.py
python_classes=Test Acceptance
python_functions=test
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]
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