Merge remote-tracking branch 'upstream/master' into merge-master-into-features
This commit is contained in:
commit
c64a8c9c7f
2
AUTHORS
2
AUTHORS
|
@ -182,6 +182,7 @@ Russel Winder
|
||||||
Ryan Wooden
|
Ryan Wooden
|
||||||
Samuel Dion-Girardeau
|
Samuel Dion-Girardeau
|
||||||
Samuele Pedroni
|
Samuele Pedroni
|
||||||
|
Sankt Petersbug
|
||||||
Segev Finer
|
Segev Finer
|
||||||
Serhii Mozghovyi
|
Serhii Mozghovyi
|
||||||
Simon Gomizelj
|
Simon Gomizelj
|
||||||
|
@ -205,6 +206,7 @@ Trevor Bekolay
|
||||||
Tyler Goodlet
|
Tyler Goodlet
|
||||||
Tzu-ping Chung
|
Tzu-ping Chung
|
||||||
Vasily Kuznetsov
|
Vasily Kuznetsov
|
||||||
|
Victor Maryama
|
||||||
Victor Uriarte
|
Victor Uriarte
|
||||||
Vidar T. Fauske
|
Vidar T. Fauske
|
||||||
Virgil Dupras
|
Virgil Dupras
|
||||||
|
|
|
@ -1,3 +1,13 @@
|
||||||
|
=================
|
||||||
|
Changelog history
|
||||||
|
=================
|
||||||
|
|
||||||
|
Versions follow `Semantic Versioning <https://semver.org/>`_ (``<major>.<minor>.<patch>``).
|
||||||
|
|
||||||
|
Backward incompatible (breaking) changes will only be introduced in major versions
|
||||||
|
with advance notice in the **Deprecations** section of releases.
|
||||||
|
|
||||||
|
|
||||||
..
|
..
|
||||||
You should *NOT* be adding new change log entries to this file, this
|
You should *NOT* be adding new change log entries to this file, this
|
||||||
file is managed by towncrier. You *may* edit previous change logs to
|
file is managed by towncrier. You *may* edit previous change logs to
|
||||||
|
@ -8,6 +18,40 @@
|
||||||
|
|
||||||
.. towncrier release notes start
|
.. towncrier release notes start
|
||||||
|
|
||||||
|
pytest 3.7.2 (2018-08-16)
|
||||||
|
=========================
|
||||||
|
|
||||||
|
Bug Fixes
|
||||||
|
---------
|
||||||
|
|
||||||
|
- `#3671 <https://github.com/pytest-dev/pytest/issues/3671>`_: Fix ``filterwarnings`` not being registered as a builtin mark.
|
||||||
|
|
||||||
|
|
||||||
|
- `#3768 <https://github.com/pytest-dev/pytest/issues/3768>`_, `#3789 <https://github.com/pytest-dev/pytest/issues/3789>`_: Fix test collection from packages mixed with normal directories.
|
||||||
|
|
||||||
|
|
||||||
|
- `#3771 <https://github.com/pytest-dev/pytest/issues/3771>`_: Fix infinite recursion during collection if a ``pytest_ignore_collect`` hook returns ``False`` instead of ``None``.
|
||||||
|
|
||||||
|
|
||||||
|
- `#3774 <https://github.com/pytest-dev/pytest/issues/3774>`_: Fix bug where decorated fixtures would lose functionality (for example ``@mock.patch``).
|
||||||
|
|
||||||
|
|
||||||
|
- `#3775 <https://github.com/pytest-dev/pytest/issues/3775>`_: Fix bug where importing modules or other objects with prefix ``pytest_`` prefix would raise a ``PluginValidationError``.
|
||||||
|
|
||||||
|
|
||||||
|
- `#3788 <https://github.com/pytest-dev/pytest/issues/3788>`_: Fix ``AttributeError`` during teardown of ``TestCase`` subclasses which raise an exception during ``__init__``.
|
||||||
|
|
||||||
|
|
||||||
|
- `#3804 <https://github.com/pytest-dev/pytest/issues/3804>`_: Fix traceback reporting for exceptions with ``__cause__`` cycles.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Improved Documentation
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
- `#3746 <https://github.com/pytest-dev/pytest/issues/3746>`_: Add documentation for ``metafunc.config`` that had been mistakenly hidden.
|
||||||
|
|
||||||
|
|
||||||
pytest 3.7.1 (2018-08-02)
|
pytest 3.7.1 (2018-08-02)
|
||||||
=========================
|
=========================
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Fix ``stdout/stderr`` not getting captured when real-time cli logging is active.
|
|
@ -0,0 +1 @@
|
||||||
|
Replace broken type annotations with type comments.
|
|
@ -6,6 +6,7 @@ Release announcements
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
|
|
||||||
|
release-3.7.2
|
||||||
release-3.7.1
|
release-3.7.1
|
||||||
release-3.7.0
|
release-3.7.0
|
||||||
release-3.6.4
|
release-3.6.4
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
pytest-3.7.2
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
pytest 3.7.2 has just been released to PyPI.
|
||||||
|
|
||||||
|
This is a bug-fix release, being a drop-in replacement. To upgrade::
|
||||||
|
|
||||||
|
pip install --upgrade pytest
|
||||||
|
|
||||||
|
The full changelog is available at http://doc.pytest.org/en/latest/changelog.html.
|
||||||
|
|
||||||
|
Thanks to all who contributed to this release, among them:
|
||||||
|
|
||||||
|
* Anthony Sottile
|
||||||
|
* Bruno Oliveira
|
||||||
|
* Daniel Hahler
|
||||||
|
* Josh Holland
|
||||||
|
* Ronny Pfannschmidt
|
||||||
|
* Sankt Petersbug
|
||||||
|
* Wes Thomas
|
||||||
|
* turturica
|
||||||
|
|
||||||
|
|
||||||
|
Happy testing,
|
||||||
|
The pytest Development Team
|
|
@ -1,7 +1,4 @@
|
||||||
|
|
||||||
.. _changelog:
|
.. _changelog:
|
||||||
|
|
||||||
Changelog history
|
|
||||||
=================================
|
|
||||||
|
|
||||||
.. include:: ../../CHANGELOG.rst
|
.. include:: ../../CHANGELOG.rst
|
||||||
|
|
|
@ -200,6 +200,8 @@ You can ask which markers exist for your test suite - the list includes our just
|
||||||
$ pytest --markers
|
$ pytest --markers
|
||||||
@pytest.mark.webtest: mark a test as a webtest.
|
@pytest.mark.webtest: mark a test as a webtest.
|
||||||
|
|
||||||
|
@pytest.mark.filterwarnings(warning): add a warning filter to the given test. see http://pytest.org/latest/warnings.html#pytest-mark-filterwarnings
|
||||||
|
|
||||||
@pytest.mark.skip(reason=None): skip the given test function with an optional reason. Example: skip(reason="no way of currently testing this") skips the test.
|
@pytest.mark.skip(reason=None): skip the given test function with an optional reason. Example: skip(reason="no way of currently testing this") skips the test.
|
||||||
|
|
||||||
@pytest.mark.skipif(condition): skip the given test function if eval(condition) results in a True value. Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform. see http://pytest.org/latest/skipping.html
|
@pytest.mark.skipif(condition): skip the given test function if eval(condition) results in a True value. Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform. see http://pytest.org/latest/skipping.html
|
||||||
|
@ -374,6 +376,8 @@ The ``--markers`` option always gives you a list of available markers::
|
||||||
$ pytest --markers
|
$ pytest --markers
|
||||||
@pytest.mark.env(name): mark test to run only on named environment
|
@pytest.mark.env(name): mark test to run only on named environment
|
||||||
|
|
||||||
|
@pytest.mark.filterwarnings(warning): add a warning filter to the given test. see http://pytest.org/latest/warnings.html#pytest-mark-filterwarnings
|
||||||
|
|
||||||
@pytest.mark.skip(reason=None): skip the given test function with an optional reason. Example: skip(reason="no way of currently testing this") skips the test.
|
@pytest.mark.skip(reason=None): skip the given test function with an optional reason. Example: skip(reason="no way of currently testing this") skips the test.
|
||||||
|
|
||||||
@pytest.mark.skipif(condition): skip the given test function if eval(condition) results in a True value. Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform. see http://pytest.org/latest/skipping.html
|
@pytest.mark.skipif(condition): skip the given test function if eval(condition) results in a True value. Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform. see http://pytest.org/latest/skipping.html
|
||||||
|
|
|
@ -84,8 +84,9 @@ interesting to just look at the collection tree::
|
||||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||||
rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
|
rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
|
||||||
collected 2 items
|
collected 2 items
|
||||||
<YamlFile 'test_simple.yml'>
|
<Package '$REGENDOC_TMPDIR/nonpython'>
|
||||||
<YamlItem 'hello'>
|
<YamlFile 'test_simple.yml'>
|
||||||
<YamlItem 'ok'>
|
<YamlItem 'hello'>
|
||||||
|
<YamlItem 'ok'>
|
||||||
|
|
||||||
======================= no tests ran in 0.12 seconds =======================
|
======================= no tests ran in 0.12 seconds =======================
|
||||||
|
|
|
@ -411,11 +411,10 @@ is to be run with different sets of arguments for its three arguments:
|
||||||
Running it results in some skips if we don't have all the python interpreters installed and otherwise runs all combinations (5 interpreters times 5 interpreters times 3 objects to serialize/deserialize)::
|
Running it results in some skips if we don't have all the python interpreters installed and otherwise runs all combinations (5 interpreters times 5 interpreters times 3 objects to serialize/deserialize)::
|
||||||
|
|
||||||
. $ pytest -rs -q multipython.py
|
. $ pytest -rs -q multipython.py
|
||||||
...ssssssssssssssssssssssss [100%]
|
...sss...sssssssss...sss... [100%]
|
||||||
========================= short test summary info ==========================
|
========================= short test summary info ==========================
|
||||||
SKIP [12] $REGENDOC_TMPDIR/CWD/multipython.py:28: 'python3.4' not found
|
SKIP [15] $REGENDOC_TMPDIR/CWD/multipython.py:28: 'python3.4' not found
|
||||||
SKIP [12] $REGENDOC_TMPDIR/CWD/multipython.py:28: 'python3.5' not found
|
12 passed, 15 skipped in 0.12 seconds
|
||||||
3 passed, 24 skipped in 0.12 seconds
|
|
||||||
|
|
||||||
Indirect parametrization of optional implementations/imports
|
Indirect parametrization of optional implementations/imports
|
||||||
--------------------------------------------------------------------
|
--------------------------------------------------------------------
|
||||||
|
|
|
@ -719,7 +719,9 @@ class FormattedExcinfo(object):
|
||||||
repr_chain = []
|
repr_chain = []
|
||||||
e = excinfo.value
|
e = excinfo.value
|
||||||
descr = None
|
descr = None
|
||||||
while e is not None:
|
seen = set()
|
||||||
|
while e is not None and id(e) not in seen:
|
||||||
|
seen.add(id(e))
|
||||||
if excinfo:
|
if excinfo:
|
||||||
reprtraceback = self.repr_traceback(excinfo)
|
reprtraceback = self.repr_traceback(excinfo)
|
||||||
reprcrash = excinfo._getreprcrash()
|
reprcrash = excinfo._getreprcrash()
|
||||||
|
|
|
@ -14,8 +14,7 @@ from tempfile import TemporaryFile
|
||||||
|
|
||||||
import six
|
import six
|
||||||
import pytest
|
import pytest
|
||||||
from _pytest.compat import CaptureIO
|
from _pytest.compat import CaptureIO, dummy_context_manager
|
||||||
|
|
||||||
|
|
||||||
patchsysdict = {0: "stdin", 1: "stdout", 2: "stderr"}
|
patchsysdict = {0: "stdin", 1: "stdout", 2: "stderr"}
|
||||||
|
|
||||||
|
@ -85,6 +84,7 @@ class CaptureManager(object):
|
||||||
def __init__(self, method):
|
def __init__(self, method):
|
||||||
self._method = method
|
self._method = method
|
||||||
self._global_capturing = None
|
self._global_capturing = None
|
||||||
|
self._current_item = None
|
||||||
|
|
||||||
def _getcapture(self, method):
|
def _getcapture(self, method):
|
||||||
if method == "fd":
|
if method == "fd":
|
||||||
|
@ -121,6 +121,19 @@ class CaptureManager(object):
|
||||||
cap.suspend_capturing(in_=in_)
|
cap.suspend_capturing(in_=in_)
|
||||||
return outerr
|
return outerr
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def global_and_fixture_disabled(self):
|
||||||
|
"""Context manager to temporarily disables global and current fixture capturing."""
|
||||||
|
# Need to undo local capsys-et-al if exists before disabling global capture
|
||||||
|
fixture = getattr(self._current_item, "_capture_fixture", None)
|
||||||
|
ctx_manager = fixture._suspend() if fixture else dummy_context_manager()
|
||||||
|
with ctx_manager:
|
||||||
|
self.suspend_global_capture(item=None, in_=False)
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
self.resume_global_capture()
|
||||||
|
|
||||||
def activate_fixture(self, item):
|
def activate_fixture(self, item):
|
||||||
"""If the current item is using ``capsys`` or ``capfd``, activate them so they take precedence over
|
"""If the current item is using ``capsys`` or ``capfd``, activate them so they take precedence over
|
||||||
the global capture.
|
the global capture.
|
||||||
|
@ -151,28 +164,34 @@ class CaptureManager(object):
|
||||||
|
|
||||||
@pytest.hookimpl(hookwrapper=True)
|
@pytest.hookimpl(hookwrapper=True)
|
||||||
def pytest_runtest_setup(self, item):
|
def pytest_runtest_setup(self, item):
|
||||||
|
self._current_item = item
|
||||||
self.resume_global_capture()
|
self.resume_global_capture()
|
||||||
# no need to activate a capture fixture because they activate themselves during creation; this
|
# no need to activate a capture fixture because they activate themselves during creation; this
|
||||||
# only makes sense when a fixture uses a capture fixture, otherwise the capture fixture will
|
# only makes sense when a fixture uses a capture fixture, otherwise the capture fixture will
|
||||||
# be activated during pytest_runtest_call
|
# be activated during pytest_runtest_call
|
||||||
yield
|
yield
|
||||||
self.suspend_capture_item(item, "setup")
|
self.suspend_capture_item(item, "setup")
|
||||||
|
self._current_item = None
|
||||||
|
|
||||||
@pytest.hookimpl(hookwrapper=True)
|
@pytest.hookimpl(hookwrapper=True)
|
||||||
def pytest_runtest_call(self, item):
|
def pytest_runtest_call(self, item):
|
||||||
|
self._current_item = item
|
||||||
self.resume_global_capture()
|
self.resume_global_capture()
|
||||||
# it is important to activate this fixture during the call phase so it overwrites the "global"
|
# it is important to activate this fixture during the call phase so it overwrites the "global"
|
||||||
# capture
|
# capture
|
||||||
self.activate_fixture(item)
|
self.activate_fixture(item)
|
||||||
yield
|
yield
|
||||||
self.suspend_capture_item(item, "call")
|
self.suspend_capture_item(item, "call")
|
||||||
|
self._current_item = None
|
||||||
|
|
||||||
@pytest.hookimpl(hookwrapper=True)
|
@pytest.hookimpl(hookwrapper=True)
|
||||||
def pytest_runtest_teardown(self, item):
|
def pytest_runtest_teardown(self, item):
|
||||||
|
self._current_item = item
|
||||||
self.resume_global_capture()
|
self.resume_global_capture()
|
||||||
self.activate_fixture(item)
|
self.activate_fixture(item)
|
||||||
yield
|
yield
|
||||||
self.suspend_capture_item(item, "teardown")
|
self.suspend_capture_item(item, "teardown")
|
||||||
|
self._current_item = None
|
||||||
|
|
||||||
@pytest.hookimpl(tryfirst=True)
|
@pytest.hookimpl(tryfirst=True)
|
||||||
def pytest_keyboard_interrupt(self, excinfo):
|
def pytest_keyboard_interrupt(self, excinfo):
|
||||||
|
@ -314,17 +333,21 @@ class CaptureFixture(object):
|
||||||
return self._outerr
|
return self._outerr
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def disabled(self):
|
def _suspend(self):
|
||||||
"""Temporarily disables capture while inside the 'with' block."""
|
"""Suspends this fixture's own capturing temporarily."""
|
||||||
self._capture.suspend_capturing()
|
self._capture.suspend_capturing()
|
||||||
capmanager = self.request.config.pluginmanager.getplugin("capturemanager")
|
|
||||||
capmanager.suspend_global_capture(item=None, in_=False)
|
|
||||||
try:
|
try:
|
||||||
yield
|
yield
|
||||||
finally:
|
finally:
|
||||||
capmanager.resume_global_capture()
|
|
||||||
self._capture.resume_capturing()
|
self._capture.resume_capturing()
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def disabled(self):
|
||||||
|
"""Temporarily disables capture while inside the 'with' block."""
|
||||||
|
capmanager = self.request.config.pluginmanager.getplugin("capturemanager")
|
||||||
|
with capmanager.global_and_fixture_disabled():
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
def safe_text_dupfile(f, mode, default_encoding="UTF8"):
|
def safe_text_dupfile(f, mode, default_encoding="UTF8"):
|
||||||
""" return an open text file object that's a duplicate of f on the
|
""" return an open text file object that's a duplicate of f on the
|
||||||
|
|
|
@ -8,6 +8,7 @@ import functools
|
||||||
import inspect
|
import inspect
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
import py
|
import py
|
||||||
|
|
||||||
|
@ -151,6 +152,13 @@ def getfuncargnames(function, is_method=False, cls=None):
|
||||||
return arg_names
|
return arg_names
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def dummy_context_manager():
|
||||||
|
"""Context manager that does nothing, useful in situations where you might need an actual context manager or not
|
||||||
|
depending on some condition. Using this allow to keep the same code"""
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
def get_default_arg_names(function):
|
def get_default_arg_names(function):
|
||||||
# Note: this code intentionally mirrors the code at the beginning of getfuncargnames,
|
# Note: this code intentionally mirrors the code at the beginning of getfuncargnames,
|
||||||
# to get the arguments which were excluded from its result because they had default values
|
# to get the arguments which were excluded from its result because they had default values
|
||||||
|
@ -228,12 +236,31 @@ else:
|
||||||
return val.encode("unicode-escape")
|
return val.encode("unicode-escape")
|
||||||
|
|
||||||
|
|
||||||
|
class _PytestWrapper(object):
|
||||||
|
"""Dummy wrapper around a function object for internal use only.
|
||||||
|
|
||||||
|
Used to correctly unwrap the underlying function object
|
||||||
|
when we are creating fixtures, because we wrap the function object ourselves with a decorator
|
||||||
|
to issue warnings when the fixture function is called directly.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, obj):
|
||||||
|
self.obj = obj
|
||||||
|
|
||||||
|
|
||||||
def get_real_func(obj):
|
def get_real_func(obj):
|
||||||
""" gets the real function object of the (possibly) wrapped object by
|
""" gets the real function object of the (possibly) wrapped object by
|
||||||
functools.wraps or functools.partial.
|
functools.wraps or functools.partial.
|
||||||
"""
|
"""
|
||||||
start_obj = obj
|
start_obj = obj
|
||||||
for i in range(100):
|
for i in range(100):
|
||||||
|
# __pytest_wrapped__ is set by @pytest.fixture when wrapping the fixture function
|
||||||
|
# to trigger a warning if it gets called directly instead of by pytest: we don't
|
||||||
|
# want to unwrap further than this otherwise we lose useful wrappings like @mock.patch (#3774)
|
||||||
|
new_obj = getattr(obj, "__pytest_wrapped__", None)
|
||||||
|
if isinstance(new_obj, _PytestWrapper):
|
||||||
|
obj = new_obj.obj
|
||||||
|
break
|
||||||
new_obj = getattr(obj, "__wrapped__", None)
|
new_obj = getattr(obj, "__wrapped__", None)
|
||||||
if new_obj is None:
|
if new_obj is None:
|
||||||
break
|
break
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
""" command line options, ini-file and conftest.py processing. """
|
""" command line options, ini-file and conftest.py processing. """
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import absolute_import, division, print_function
|
||||||
import argparse
|
import argparse
|
||||||
|
import inspect
|
||||||
import shlex
|
import shlex
|
||||||
import traceback
|
import traceback
|
||||||
import types
|
import types
|
||||||
|
@ -252,6 +253,10 @@ class PytestPluginManager(PluginManager):
|
||||||
method = getattr(plugin, name)
|
method = getattr(plugin, name)
|
||||||
opts = super(PytestPluginManager, self).parse_hookimpl_opts(plugin, name)
|
opts = super(PytestPluginManager, self).parse_hookimpl_opts(plugin, name)
|
||||||
|
|
||||||
|
# consider only actual functions for hooks (#3775)
|
||||||
|
if not inspect.isroutine(method):
|
||||||
|
return
|
||||||
|
|
||||||
# collect unmarked hooks as long as they have the `pytest_' prefix
|
# collect unmarked hooks as long as they have the `pytest_' prefix
|
||||||
if opts is None and name.startswith("pytest_"):
|
if opts is None and name.startswith("pytest_"):
|
||||||
opts = {}
|
opts = {}
|
||||||
|
|
|
@ -174,23 +174,23 @@ class Argument(object):
|
||||||
if isinstance(typ, six.string_types):
|
if isinstance(typ, six.string_types):
|
||||||
if typ == "choice":
|
if typ == "choice":
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"type argument to addoption() is a string %r."
|
"`type` argument to addoption() is the string %r."
|
||||||
" For parsearg this is optional and when supplied"
|
" For choices this is optional and can be omitted, "
|
||||||
" should be a type."
|
" but when supplied should be a type (for example `str` or `int`)."
|
||||||
" (options: %s)" % (typ, names),
|
" (options: %s)" % (typ, names),
|
||||||
DeprecationWarning,
|
DeprecationWarning,
|
||||||
stacklevel=3,
|
stacklevel=4,
|
||||||
)
|
)
|
||||||
# argparse expects a type here take it from
|
# argparse expects a type here take it from
|
||||||
# the type of the first element
|
# the type of the first element
|
||||||
attrs["type"] = type(attrs["choices"][0])
|
attrs["type"] = type(attrs["choices"][0])
|
||||||
else:
|
else:
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"type argument to addoption() is a string %r."
|
"`type` argument to addoption() is the string %r, "
|
||||||
" For parsearg this should be a type."
|
" but when supplied should be a type (for example `str` or `int`)."
|
||||||
" (options: %s)" % (typ, names),
|
" (options: %s)" % (typ, names),
|
||||||
DeprecationWarning,
|
DeprecationWarning,
|
||||||
stacklevel=3,
|
stacklevel=4,
|
||||||
)
|
)
|
||||||
attrs["type"] = Argument._typ_map[typ]
|
attrs["type"] = Argument._typ_map[typ]
|
||||||
# used in test_parseopt -> test_parse_defaultgetter
|
# used in test_parseopt -> test_parse_defaultgetter
|
||||||
|
|
|
@ -31,6 +31,7 @@ from _pytest.compat import (
|
||||||
safe_getattr,
|
safe_getattr,
|
||||||
FuncargnamesCompatAttr,
|
FuncargnamesCompatAttr,
|
||||||
get_real_method,
|
get_real_method,
|
||||||
|
_PytestWrapper,
|
||||||
)
|
)
|
||||||
from _pytest.deprecated import FIXTURE_FUNCTION_CALL, RemovedInPytest4Warning
|
from _pytest.deprecated import FIXTURE_FUNCTION_CALL, RemovedInPytest4Warning
|
||||||
from _pytest.outcomes import fail, TEST_OUTCOME
|
from _pytest.outcomes import fail, TEST_OUTCOME
|
||||||
|
@ -306,8 +307,8 @@ class FuncFixtureInfo(object):
|
||||||
# fixture names specified via usefixtures and via autouse=True in fixture
|
# fixture names specified via usefixtures and via autouse=True in fixture
|
||||||
# definitions.
|
# definitions.
|
||||||
initialnames = attr.ib(type=tuple)
|
initialnames = attr.ib(type=tuple)
|
||||||
names_closure = attr.ib(type="List[str]")
|
names_closure = attr.ib() # type: List[str]
|
||||||
name2fixturedefs = attr.ib(type="List[str, List[FixtureDef]]")
|
name2fixturedefs = attr.ib() # type: List[str, List[FixtureDef]]
|
||||||
|
|
||||||
def prune_dependency_tree(self):
|
def prune_dependency_tree(self):
|
||||||
"""Recompute names_closure from initialnames and name2fixturedefs
|
"""Recompute names_closure from initialnames and name2fixturedefs
|
||||||
|
@ -954,9 +955,6 @@ def _ensure_immutable_ids(ids):
|
||||||
def wrap_function_to_warning_if_called_directly(function, fixture_marker):
|
def wrap_function_to_warning_if_called_directly(function, fixture_marker):
|
||||||
"""Wrap the given fixture function so we can issue warnings about it being called directly, instead of
|
"""Wrap the given fixture function so we can issue warnings about it being called directly, instead of
|
||||||
used as an argument in a test function.
|
used as an argument in a test function.
|
||||||
|
|
||||||
The warning is emitted only in Python 3, because I didn't find a reliable way to make the wrapper function
|
|
||||||
keep the original signature, and we probably will drop Python 2 in Pytest 4 anyway.
|
|
||||||
"""
|
"""
|
||||||
is_yield_function = is_generator(function)
|
is_yield_function = is_generator(function)
|
||||||
msg = FIXTURE_FUNCTION_CALL.format(name=fixture_marker.name or function.__name__)
|
msg = FIXTURE_FUNCTION_CALL.format(name=fixture_marker.name or function.__name__)
|
||||||
|
@ -982,6 +980,10 @@ def wrap_function_to_warning_if_called_directly(function, fixture_marker):
|
||||||
if six.PY2:
|
if six.PY2:
|
||||||
result.__wrapped__ = function
|
result.__wrapped__ = function
|
||||||
|
|
||||||
|
# keep reference to the original function in our own custom attribute so we don't unwrap
|
||||||
|
# further than this point and lose useful wrappings like @mock.patch (#3774)
|
||||||
|
result.__pytest_wrapped__ = _PytestWrapper(function)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ from contextlib import closing, contextmanager
|
||||||
import re
|
import re
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
from _pytest.compat import dummy_context_manager
|
||||||
from _pytest.config import create_terminal_writer
|
from _pytest.config import create_terminal_writer
|
||||||
import pytest
|
import pytest
|
||||||
import py
|
import py
|
||||||
|
@ -369,11 +370,6 @@ def pytest_configure(config):
|
||||||
config.pluginmanager.register(LoggingPlugin(config), "logging-plugin")
|
config.pluginmanager.register(LoggingPlugin(config), "logging-plugin")
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def _dummy_context_manager():
|
|
||||||
yield
|
|
||||||
|
|
||||||
|
|
||||||
class LoggingPlugin(object):
|
class LoggingPlugin(object):
|
||||||
"""Attaches to the logging module and captures log messages for each test.
|
"""Attaches to the logging module and captures log messages for each test.
|
||||||
"""
|
"""
|
||||||
|
@ -537,7 +533,7 @@ class LoggingPlugin(object):
|
||||||
log_cli_handler, formatter=log_cli_formatter, level=log_cli_level
|
log_cli_handler, formatter=log_cli_formatter, level=log_cli_level
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.live_logs_context = _dummy_context_manager()
|
self.live_logs_context = dummy_context_manager()
|
||||||
|
|
||||||
|
|
||||||
class _LiveLoggingStreamHandler(logging.StreamHandler):
|
class _LiveLoggingStreamHandler(logging.StreamHandler):
|
||||||
|
@ -572,9 +568,12 @@ class _LiveLoggingStreamHandler(logging.StreamHandler):
|
||||||
self._test_outcome_written = False
|
self._test_outcome_written = False
|
||||||
|
|
||||||
def emit(self, record):
|
def emit(self, record):
|
||||||
if self.capture_manager is not None:
|
ctx_manager = (
|
||||||
self.capture_manager.suspend_global_capture()
|
self.capture_manager.global_and_fixture_disabled()
|
||||||
try:
|
if self.capture_manager
|
||||||
|
else dummy_context_manager()
|
||||||
|
)
|
||||||
|
with ctx_manager:
|
||||||
if not self._first_record_emitted:
|
if not self._first_record_emitted:
|
||||||
self.stream.write("\n")
|
self.stream.write("\n")
|
||||||
self._first_record_emitted = True
|
self._first_record_emitted = True
|
||||||
|
@ -586,6 +585,3 @@ class _LiveLoggingStreamHandler(logging.StreamHandler):
|
||||||
self.stream.section("live log " + self._when, sep="-", bold=True)
|
self.stream.section("live log " + self._when, sep="-", bold=True)
|
||||||
self._section_name_shown = True
|
self._section_name_shown = True
|
||||||
logging.StreamHandler.emit(self, record)
|
logging.StreamHandler.emit(self, record)
|
||||||
finally:
|
|
||||||
if self.capture_manager is not None:
|
|
||||||
self.capture_manager.resume_global_capture()
|
|
||||||
|
|
|
@ -505,8 +505,9 @@ class Session(nodes.FSCollector):
|
||||||
root = self._node_cache[pkginit]
|
root = self._node_cache[pkginit]
|
||||||
else:
|
else:
|
||||||
col = root._collectfile(pkginit)
|
col = root._collectfile(pkginit)
|
||||||
if col and isinstance(col, Package):
|
if col:
|
||||||
root = col[0]
|
if isinstance(col[0], Package):
|
||||||
|
root = col[0]
|
||||||
self._node_cache[root.fspath] = root
|
self._node_cache[root.fspath] = root
|
||||||
|
|
||||||
# If it's a directory argument, recurse and look for any Subpackages.
|
# If it's a directory argument, recurse and look for any Subpackages.
|
||||||
|
|
|
@ -216,18 +216,6 @@ def pytest_pycollect_makemodule(path, parent):
|
||||||
return Module(path, parent)
|
return Module(path, parent)
|
||||||
|
|
||||||
|
|
||||||
def pytest_ignore_collect(path, config):
|
|
||||||
# Skip duplicate packages.
|
|
||||||
keepduplicates = config.getoption("keepduplicates")
|
|
||||||
if keepduplicates:
|
|
||||||
duplicate_paths = config.pluginmanager._duplicatepaths
|
|
||||||
if path.basename == "__init__.py":
|
|
||||||
if path in duplicate_paths:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
duplicate_paths.add(path)
|
|
||||||
|
|
||||||
|
|
||||||
@hookimpl(hookwrapper=True)
|
@hookimpl(hookwrapper=True)
|
||||||
def pytest_pycollect_makeitem(collector, name, obj):
|
def pytest_pycollect_makeitem(collector, name, obj):
|
||||||
outcome = yield
|
outcome = yield
|
||||||
|
@ -554,14 +542,12 @@ class Package(Module):
|
||||||
self.name = fspath.dirname
|
self.name = fspath.dirname
|
||||||
self.trace = session.trace
|
self.trace = session.trace
|
||||||
self._norecursepatterns = session._norecursepatterns
|
self._norecursepatterns = session._norecursepatterns
|
||||||
for path in list(session.config.pluginmanager._duplicatepaths):
|
self.fspath = fspath
|
||||||
if path.dirname == fspath.dirname and path != fspath:
|
|
||||||
session.config.pluginmanager._duplicatepaths.remove(path)
|
|
||||||
|
|
||||||
def _recurse(self, path):
|
def _recurse(self, path):
|
||||||
ihook = self.gethookproxy(path.dirpath())
|
ihook = self.gethookproxy(path.dirpath())
|
||||||
if ihook.pytest_ignore_collect(path=path, config=self.config):
|
if ihook.pytest_ignore_collect(path=path, config=self.config):
|
||||||
return
|
return False
|
||||||
for pat in self._norecursepatterns:
|
for pat in self._norecursepatterns:
|
||||||
if path.check(fnmatch=pat):
|
if path.check(fnmatch=pat):
|
||||||
return False
|
return False
|
||||||
|
@ -594,9 +580,21 @@ class Package(Module):
|
||||||
return path in self.session._initialpaths
|
return path in self.session._initialpaths
|
||||||
|
|
||||||
def collect(self):
|
def collect(self):
|
||||||
path = self.fspath.dirpath()
|
# XXX: HACK!
|
||||||
|
# Before starting to collect any files from this package we need
|
||||||
|
# to cleanup the duplicate paths added by the session's collect().
|
||||||
|
# Proper fix is to not track these as duplicates in the first place.
|
||||||
|
for path in list(self.session.config.pluginmanager._duplicatepaths):
|
||||||
|
# if path.parts()[:len(self.fspath.dirpath().parts())] == self.fspath.dirpath().parts():
|
||||||
|
if path.dirname.startswith(self.name):
|
||||||
|
self.session.config.pluginmanager._duplicatepaths.remove(path)
|
||||||
|
|
||||||
|
this_path = self.fspath.dirpath()
|
||||||
pkg_prefix = None
|
pkg_prefix = None
|
||||||
for path in path.visit(fil=lambda x: 1, rec=self._recurse, bf=True, sort=True):
|
for path in this_path.visit(rec=self._recurse, bf=True, sort=True):
|
||||||
|
# we will visit our own __init__.py file, in which case we skip it
|
||||||
|
if path.basename == "__init__.py" and path.dirpath() == this_path:
|
||||||
|
continue
|
||||||
if pkg_prefix and pkg_prefix in path.parts():
|
if pkg_prefix and pkg_prefix in path.parts():
|
||||||
continue
|
continue
|
||||||
for x in self._collectfile(path):
|
for x in self._collectfile(path):
|
||||||
|
@ -880,12 +878,13 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, definition, fixtureinfo, config, cls=None, module=None):
|
def __init__(self, definition, fixtureinfo, config, cls=None, module=None):
|
||||||
#: access to the :class:`_pytest.config.Config` object for the test session
|
|
||||||
assert (
|
assert (
|
||||||
isinstance(definition, FunctionDefinition)
|
isinstance(definition, FunctionDefinition)
|
||||||
or type(definition).__name__ == "DefinitionMock"
|
or type(definition).__name__ == "DefinitionMock"
|
||||||
)
|
)
|
||||||
self.definition = definition
|
self.definition = definition
|
||||||
|
|
||||||
|
#: access to the :class:`_pytest.config.Config` object for the test session
|
||||||
self.config = config
|
self.config = config
|
||||||
|
|
||||||
#: the module object where the test function is defined in.
|
#: the module object where the test function is defined in.
|
||||||
|
|
|
@ -69,6 +69,7 @@ class UnitTestCase(Class):
|
||||||
class TestCaseFunction(Function):
|
class TestCaseFunction(Function):
|
||||||
nofuncargs = True
|
nofuncargs = True
|
||||||
_excinfo = None
|
_excinfo = None
|
||||||
|
_testcase = None
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
self._testcase = self.parent.obj(self.name)
|
self._testcase = self.parent.obj(self.name)
|
||||||
|
|
|
@ -49,6 +49,14 @@ def pytest_addoption(parser):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_configure(config):
|
||||||
|
config.addinivalue_line(
|
||||||
|
"markers",
|
||||||
|
"filterwarnings(warning): add a warning filter to the given test. "
|
||||||
|
"see http://pytest.org/latest/warnings.html#pytest-mark-filterwarnings ",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def catch_warnings_for_item(item):
|
def catch_warnings_for_item(item):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1044,3 +1044,10 @@ def test_frame_leak_on_failing_test(testdir):
|
||||||
)
|
)
|
||||||
result = testdir.runpytest_subprocess()
|
result = testdir.runpytest_subprocess()
|
||||||
result.stdout.fnmatch_lines(["*1 failed, 1 passed in*"])
|
result.stdout.fnmatch_lines(["*1 failed, 1 passed in*"])
|
||||||
|
|
||||||
|
|
||||||
|
def test_fixture_mock_integration(testdir):
|
||||||
|
"""Test that decorators applied to fixture are left working (#3774)"""
|
||||||
|
p = testdir.copy_example("acceptance/fixture_mock_integration.py")
|
||||||
|
result = testdir.runpytest(p)
|
||||||
|
result.stdout.fnmatch_lines("*1 passed*")
|
||||||
|
|
|
@ -4,6 +4,7 @@ from __future__ import absolute_import, division, print_function
|
||||||
import operator
|
import operator
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import textwrap
|
||||||
import _pytest
|
import _pytest
|
||||||
import py
|
import py
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -1265,6 +1266,50 @@ raise ValueError()
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@pytest.mark.skipif("sys.version_info[0] < 3")
|
||||||
|
def test_exc_chain_repr_cycle(self, importasmod):
|
||||||
|
mod = importasmod(
|
||||||
|
"""
|
||||||
|
class Err(Exception):
|
||||||
|
pass
|
||||||
|
def fail():
|
||||||
|
return 0 / 0
|
||||||
|
def reraise():
|
||||||
|
try:
|
||||||
|
fail()
|
||||||
|
except ZeroDivisionError as e:
|
||||||
|
raise Err() from e
|
||||||
|
def unreraise():
|
||||||
|
try:
|
||||||
|
reraise()
|
||||||
|
except Err as e:
|
||||||
|
raise e.__cause__
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
excinfo = pytest.raises(ZeroDivisionError, mod.unreraise)
|
||||||
|
r = excinfo.getrepr(style="short")
|
||||||
|
tw = TWMock()
|
||||||
|
r.toterminal(tw)
|
||||||
|
out = "\n".join(line for line in tw.lines if isinstance(line, str))
|
||||||
|
expected_out = textwrap.dedent(
|
||||||
|
"""\
|
||||||
|
:13: in unreraise
|
||||||
|
reraise()
|
||||||
|
:10: in reraise
|
||||||
|
raise Err() from e
|
||||||
|
E test_exc_chain_repr_cycle0.mod.Err
|
||||||
|
|
||||||
|
During handling of the above exception, another exception occurred:
|
||||||
|
:15: in unreraise
|
||||||
|
raise e.__cause__
|
||||||
|
:8: in reraise
|
||||||
|
fail()
|
||||||
|
:5: in fail
|
||||||
|
return 0 / 0
|
||||||
|
E ZeroDivisionError: division by zero"""
|
||||||
|
)
|
||||||
|
assert out == expected_out
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("style", ["short", "long"])
|
@pytest.mark.parametrize("style", ["short", "long"])
|
||||||
@pytest.mark.parametrize("encoding", [None, "utf8", "utf16"])
|
@pytest.mark.parametrize("encoding", [None, "utf8", "utf16"])
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
"""Reproduces issue #3774"""
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
config = {"mykey": "ORIGINAL"}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="function")
|
||||||
|
@mock.patch.dict(config, {"mykey": "MOCKED"})
|
||||||
|
def my_fixture():
|
||||||
|
return config["mykey"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_foobar(my_fixture):
|
||||||
|
assert my_fixture == "MOCKED"
|
|
@ -0,0 +1,2 @@
|
||||||
|
def pytest_ignore_collect(path):
|
||||||
|
return False
|
|
@ -0,0 +1,2 @@
|
||||||
|
def test():
|
||||||
|
pass
|
|
@ -0,0 +1,2 @@
|
||||||
|
class pytest_something(object):
|
||||||
|
pass
|
|
@ -0,0 +1,2 @@
|
||||||
|
def test_foo():
|
||||||
|
pass
|
|
@ -876,6 +876,7 @@ def test_live_logging_suspends_capture(has_capture_manager, request):
|
||||||
is installed.
|
is installed.
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
import contextlib
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from _pytest.capture import CaptureManager
|
from _pytest.capture import CaptureManager
|
||||||
from _pytest.logging import _LiveLoggingStreamHandler
|
from _pytest.logging import _LiveLoggingStreamHandler
|
||||||
|
@ -883,11 +884,11 @@ def test_live_logging_suspends_capture(has_capture_manager, request):
|
||||||
class MockCaptureManager:
|
class MockCaptureManager:
|
||||||
calls = []
|
calls = []
|
||||||
|
|
||||||
def suspend_global_capture(self):
|
@contextlib.contextmanager
|
||||||
self.calls.append("suspend_global_capture")
|
def global_and_fixture_disabled(self):
|
||||||
|
self.calls.append("enter disabled")
|
||||||
def resume_global_capture(self):
|
yield
|
||||||
self.calls.append("resume_global_capture")
|
self.calls.append("exit disabled")
|
||||||
|
|
||||||
# sanity check
|
# sanity check
|
||||||
assert CaptureManager.suspend_capture_item
|
assert CaptureManager.suspend_capture_item
|
||||||
|
@ -908,10 +909,7 @@ def test_live_logging_suspends_capture(has_capture_manager, request):
|
||||||
|
|
||||||
logger.critical("some message")
|
logger.critical("some message")
|
||||||
if has_capture_manager:
|
if has_capture_manager:
|
||||||
assert MockCaptureManager.calls == [
|
assert MockCaptureManager.calls == ["enter disabled", "exit disabled"]
|
||||||
"suspend_global_capture",
|
|
||||||
"resume_global_capture",
|
|
||||||
]
|
|
||||||
else:
|
else:
|
||||||
assert MockCaptureManager.calls == []
|
assert MockCaptureManager.calls == []
|
||||||
assert out_file.getvalue() == "\nsome message\n"
|
assert out_file.getvalue() == "\nsome message\n"
|
||||||
|
|
|
@ -1577,3 +1577,49 @@ def test_keep_duplicates(testdir):
|
||||||
)
|
)
|
||||||
result = testdir.runpytest("--keep-duplicates", a.strpath, a.strpath)
|
result = testdir.runpytest("--keep-duplicates", a.strpath, a.strpath)
|
||||||
result.stdout.fnmatch_lines(["*collected 2 item*"])
|
result.stdout.fnmatch_lines(["*collected 2 item*"])
|
||||||
|
|
||||||
|
|
||||||
|
def test_package_collection_infinite_recursion(testdir):
|
||||||
|
testdir.copy_example("collect/package_infinite_recursion")
|
||||||
|
result = testdir.runpytest()
|
||||||
|
result.stdout.fnmatch_lines("*1 passed*")
|
||||||
|
|
||||||
|
|
||||||
|
def test_package_with_modules(testdir):
|
||||||
|
"""
|
||||||
|
.
|
||||||
|
└── root
|
||||||
|
├── __init__.py
|
||||||
|
├── sub1
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ └── sub1_1
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ └── test_in_sub1.py
|
||||||
|
└── sub2
|
||||||
|
└── test
|
||||||
|
└── test_in_sub2.py
|
||||||
|
|
||||||
|
"""
|
||||||
|
root = testdir.mkpydir("root")
|
||||||
|
sub1 = root.mkdir("sub1")
|
||||||
|
sub1.ensure("__init__.py")
|
||||||
|
sub1_test = sub1.mkdir("sub1_1")
|
||||||
|
sub1_test.ensure("__init__.py")
|
||||||
|
sub2 = root.mkdir("sub2")
|
||||||
|
sub2_test = sub2.mkdir("sub2")
|
||||||
|
|
||||||
|
sub1_test.join("test_in_sub1.py").write("def test_1(): pass")
|
||||||
|
sub2_test.join("test_in_sub2.py").write("def test_2(): pass")
|
||||||
|
|
||||||
|
# Execute from .
|
||||||
|
result = testdir.runpytest("-v", "-s")
|
||||||
|
result.assert_outcomes(passed=2)
|
||||||
|
|
||||||
|
# Execute from . with one argument "root"
|
||||||
|
result = testdir.runpytest("-v", "-s", "root")
|
||||||
|
result.assert_outcomes(passed=2)
|
||||||
|
|
||||||
|
# Chdir into package's root and execute with no args
|
||||||
|
root.chdir()
|
||||||
|
result = testdir.runpytest("-v", "-s")
|
||||||
|
result.assert_outcomes(passed=2)
|
||||||
|
|
|
@ -1385,3 +1385,34 @@ def test_pickling_and_unpickling_encoded_file():
|
||||||
ef = capture.EncodedFile(None, None)
|
ef = capture.EncodedFile(None, None)
|
||||||
ef_as_str = pickle.dumps(ef)
|
ef_as_str = pickle.dumps(ef)
|
||||||
pickle.loads(ef_as_str)
|
pickle.loads(ef_as_str)
|
||||||
|
|
||||||
|
|
||||||
|
def test_capsys_with_cli_logging(testdir):
|
||||||
|
# Issue 3819
|
||||||
|
# capsys should work with real-time cli logging
|
||||||
|
testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def test_myoutput(capsys): # or use "capfd" for fd-level
|
||||||
|
print("hello")
|
||||||
|
sys.stderr.write("world\\n")
|
||||||
|
captured = capsys.readouterr()
|
||||||
|
assert captured.out == "hello\\n"
|
||||||
|
assert captured.err == "world\\n"
|
||||||
|
|
||||||
|
logging.info("something")
|
||||||
|
|
||||||
|
print("next")
|
||||||
|
|
||||||
|
logging.info("something")
|
||||||
|
|
||||||
|
captured = capsys.readouterr()
|
||||||
|
assert captured.out == "next\\n"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = testdir.runpytest_subprocess("--log-cli-level=INFO")
|
||||||
|
assert result.ret == 0
|
||||||
|
|
|
@ -638,6 +638,10 @@ class Test_getinitialnodes(object):
|
||||||
assert col.config is config
|
assert col.config is config
|
||||||
|
|
||||||
def test_pkgfile(self, testdir):
|
def test_pkgfile(self, testdir):
|
||||||
|
"""Verify nesting when a module is within a package.
|
||||||
|
The parent chain should match: Module<x.py> -> Package<subdir> -> Session.
|
||||||
|
Session's parent should always be None.
|
||||||
|
"""
|
||||||
tmpdir = testdir.tmpdir
|
tmpdir = testdir.tmpdir
|
||||||
subdir = tmpdir.join("subdir")
|
subdir = tmpdir.join("subdir")
|
||||||
x = subdir.ensure("x.py")
|
x = subdir.ensure("x.py")
|
||||||
|
@ -645,9 +649,12 @@ class Test_getinitialnodes(object):
|
||||||
with subdir.as_cwd():
|
with subdir.as_cwd():
|
||||||
config = testdir.parseconfigure(x)
|
config = testdir.parseconfigure(x)
|
||||||
col = testdir.getnode(config, x)
|
col = testdir.getnode(config, x)
|
||||||
assert isinstance(col, pytest.Module)
|
|
||||||
assert col.name == "x.py"
|
assert col.name == "x.py"
|
||||||
assert col.parent.parent is None
|
assert isinstance(col, pytest.Module)
|
||||||
|
assert isinstance(col.parent, pytest.Package)
|
||||||
|
assert isinstance(col.parent.parent, pytest.Session)
|
||||||
|
# session is batman (has no parents)
|
||||||
|
assert col.parent.parent.parent is None
|
||||||
for col in col.listchain():
|
for col in col.listchain():
|
||||||
assert col.config is config
|
assert col.config is config
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import absolute_import, division, print_function
|
||||||
import sys
|
import sys
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from _pytest.compat import is_generator, get_real_func, safe_getattr
|
from _pytest.compat import is_generator, get_real_func, safe_getattr, _PytestWrapper
|
||||||
from _pytest.outcomes import OutcomeException
|
from _pytest.outcomes import OutcomeException
|
||||||
|
|
||||||
|
|
||||||
|
@ -38,6 +41,33 @@ def test_real_func_loop_limit():
|
||||||
print(res)
|
print(res)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_real_func():
|
||||||
|
"""Check that get_real_func correctly unwraps decorators until reaching the real function"""
|
||||||
|
|
||||||
|
def decorator(f):
|
||||||
|
@wraps(f)
|
||||||
|
def inner():
|
||||||
|
pass
|
||||||
|
|
||||||
|
if six.PY2:
|
||||||
|
inner.__wrapped__ = f
|
||||||
|
return inner
|
||||||
|
|
||||||
|
def func():
|
||||||
|
pass
|
||||||
|
|
||||||
|
wrapped_func = decorator(decorator(func))
|
||||||
|
assert get_real_func(wrapped_func) is func
|
||||||
|
|
||||||
|
wrapped_func2 = decorator(decorator(wrapped_func))
|
||||||
|
assert get_real_func(wrapped_func2) is func
|
||||||
|
|
||||||
|
# special case for __pytest_wrapped__ attribute: used to obtain the function up until the point
|
||||||
|
# a function was wrapped by pytest itself
|
||||||
|
wrapped_func2.__pytest_wrapped__ = _PytestWrapper(wrapped_func)
|
||||||
|
assert get_real_func(wrapped_func2) is wrapped_func
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(
|
||||||
sys.version_info < (3, 4), reason="asyncio available in Python 3.4+"
|
sys.version_info < (3, 4), reason="asyncio available in Python 3.4+"
|
||||||
)
|
)
|
||||||
|
|
|
@ -765,6 +765,24 @@ def test_get_plugin_specs_as_list():
|
||||||
assert _get_plugin_specs_as_list(("foo", "bar")) == ["foo", "bar"]
|
assert _get_plugin_specs_as_list(("foo", "bar")) == ["foo", "bar"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_collect_pytest_prefix_bug_integration(testdir):
|
||||||
|
"""Integration test for issue #3775"""
|
||||||
|
p = testdir.copy_example("config/collect_pytest_prefix")
|
||||||
|
result = testdir.runpytest(p)
|
||||||
|
result.stdout.fnmatch_lines("* 1 passed *")
|
||||||
|
|
||||||
|
|
||||||
|
def test_collect_pytest_prefix_bug(pytestconfig):
|
||||||
|
"""Ensure we collect only actual functions from conftest files (#3775)"""
|
||||||
|
|
||||||
|
class Dummy(object):
|
||||||
|
class pytest_something(object):
|
||||||
|
pass
|
||||||
|
|
||||||
|
pm = pytestconfig.pluginmanager
|
||||||
|
assert pm.parse_hookimpl_opts(Dummy(), "pytest_something") is None
|
||||||
|
|
||||||
|
|
||||||
class TestWarning(object):
|
class TestWarning(object):
|
||||||
def test_warn_config(self, testdir):
|
def test_warn_config(self, testdir):
|
||||||
testdir.makeconftest(
|
testdir.makeconftest(
|
||||||
|
|
|
@ -989,3 +989,24 @@ def test_usefixtures_marker_on_unittest(base, testdir):
|
||||||
|
|
||||||
result = testdir.runpytest("-s")
|
result = testdir.runpytest("-s")
|
||||||
result.assert_outcomes(passed=2)
|
result.assert_outcomes(passed=2)
|
||||||
|
|
||||||
|
|
||||||
|
def test_testcase_handles_init_exceptions(testdir):
|
||||||
|
"""
|
||||||
|
Regression test to make sure exceptions in the __init__ method are bubbled up correctly.
|
||||||
|
See https://github.com/pytest-dev/pytest/issues/3788
|
||||||
|
"""
|
||||||
|
testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
from unittest import TestCase
|
||||||
|
import pytest
|
||||||
|
class MyTestCase(TestCase):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
raise Exception("should raise this exception")
|
||||||
|
def test_hello(self):
|
||||||
|
pass
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = testdir.runpytest()
|
||||||
|
assert "should raise this exception" in result.stdout.str()
|
||||||
|
assert "ERROR at teardown of MyTestCase.test_hello" not in result.stdout.str()
|
||||||
|
|
|
@ -287,3 +287,18 @@ def test_non_string_warning_argument(testdir):
|
||||||
)
|
)
|
||||||
result = testdir.runpytest("-W", "always")
|
result = testdir.runpytest("-W", "always")
|
||||||
result.stdout.fnmatch_lines(["*= 1 passed, 1 warnings in *"])
|
result.stdout.fnmatch_lines(["*= 1 passed, 1 warnings in *"])
|
||||||
|
|
||||||
|
|
||||||
|
def test_filterwarnings_mark_registration(testdir):
|
||||||
|
"""Ensure filterwarnings mark is registered"""
|
||||||
|
testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
@pytest.mark.filterwarnings('error')
|
||||||
|
def test_func():
|
||||||
|
pass
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = testdir.runpytest("--strict")
|
||||||
|
assert result.ret == 0
|
||||||
|
|
Loading…
Reference in New Issue