Merge branch 'master' of github.com:pytest-dev/pytest into fix-3854
This commit is contained in:
commit
e4f76f6350
|
@ -5,13 +5,13 @@ repos:
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
args: [--safe, --quiet]
|
args: [--safe, --quiet]
|
||||||
language_version: python3.6
|
language_version: python3
|
||||||
- repo: https://github.com/asottile/blacken-docs
|
- repo: https://github.com/asottile/blacken-docs
|
||||||
rev: v0.2.0
|
rev: v0.2.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: blacken-docs
|
- id: blacken-docs
|
||||||
additional_dependencies: [black==18.6b4]
|
additional_dependencies: [black==18.6b4]
|
||||||
language_version: python3.6
|
language_version: python3
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v1.3.0
|
rev: v1.3.0
|
||||||
hooks:
|
hooks:
|
||||||
|
@ -37,7 +37,6 @@ repos:
|
||||||
files: ^(CHANGELOG.rst|HOWTORELEASE.rst|README.rst|changelog/.*)$
|
files: ^(CHANGELOG.rst|HOWTORELEASE.rst|README.rst|changelog/.*)$
|
||||||
language: python
|
language: python
|
||||||
additional_dependencies: [pygments, restructuredtext_lint]
|
additional_dependencies: [pygments, restructuredtext_lint]
|
||||||
python_version: python3.6
|
|
||||||
- id: changelogs-rst
|
- id: changelogs-rst
|
||||||
name: changelog files must end in .rst
|
name: changelog files must end in .rst
|
||||||
entry: ./scripts/fail
|
entry: ./scripts/fail
|
||||||
|
|
3
AUTHORS
3
AUTHORS
|
@ -98,6 +98,7 @@ Javier Domingo Cansino
|
||||||
Javier Romero
|
Javier Romero
|
||||||
Jeff Rackauckas
|
Jeff Rackauckas
|
||||||
Jeff Widman
|
Jeff Widman
|
||||||
|
Jenni Rinker
|
||||||
John Eddie Ayson
|
John Eddie Ayson
|
||||||
John Towler
|
John Towler
|
||||||
Jon Sonesen
|
Jon Sonesen
|
||||||
|
@ -182,6 +183,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 +207,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
|
||||||
Vitaly Lashmanov
|
Vitaly Lashmanov
|
||||||
|
|
|
@ -18,6 +18,40 @@ with advance notice in the **Deprecations** section of releases.
|
||||||
|
|
||||||
.. 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 @@
|
||||||
|
Fixtures during teardown can again use ``capsys`` and ``cafd`` to inspect output captured during tests.
|
|
@ -1 +0,0 @@
|
||||||
Fix infinite recursion during collection if a ``pytest_ignore_collect`` returns ``False`` instead of ``None``.
|
|
|
@ -1 +0,0 @@
|
||||||
Fix bug where importing modules or other objects with prefix ``pytest_`` prefix would raise a ``PluginValidationError``.
|
|
|
@ -1 +0,0 @@
|
||||||
Fix ``AttributeError`` during teardown of ``TestCase`` subclasses which raise an exception during ``__init__``.
|
|
|
@ -0,0 +1 @@
|
||||||
|
Fix bug where ``--show-capture=no`` option would still show logs printed during fixture teardown.
|
|
@ -0,0 +1 @@
|
||||||
|
Fix ``stdout/stderr`` not getting captured when real-time cli logging is active.
|
|
@ -0,0 +1 @@
|
||||||
|
Added example for multiple glob pattern matches in ``python_files``.
|
|
@ -0,0 +1 @@
|
||||||
|
Replace broken type annotations with type comments.
|
|
@ -0,0 +1 @@
|
||||||
|
Added missing docs for ``pytester.Testdir``
|
|
@ -0,0 +1 @@
|
||||||
|
Fix collection error when specifying test functions directly in the command line using ``test.py::test`` syntax together with ``--doctest-module``.
|
|
@ -0,0 +1,2 @@
|
||||||
|
Remove a reference to issue `#568 <https://github.com/pytest-dev/pytest/issues/568>`_ from the documentation, which has since been
|
||||||
|
fixed.
|
|
@ -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
|
|
@ -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
|
||||||
--------------------------------------------------------------------
|
--------------------------------------------------------------------
|
||||||
|
|
|
@ -100,19 +100,21 @@ Changing naming conventions
|
||||||
|
|
||||||
You can configure different naming conventions by setting
|
You can configure different naming conventions by setting
|
||||||
the :confval:`python_files`, :confval:`python_classes` and
|
the :confval:`python_files`, :confval:`python_classes` and
|
||||||
:confval:`python_functions` configuration options. Example::
|
:confval:`python_functions` configuration options.
|
||||||
|
Here is an example::
|
||||||
|
|
||||||
|
# Example 1: have pytest look for "check" instead of "test"
|
||||||
# content of pytest.ini
|
# content of pytest.ini
|
||||||
# can also be defined in tox.ini or setup.cfg file, although the section
|
# can also be defined in tox.ini or setup.cfg file, although the section
|
||||||
# name in setup.cfg files should be "tool:pytest"
|
# name in setup.cfg files should be "tool:pytest"
|
||||||
[pytest]
|
[pytest]
|
||||||
python_files=check_*.py
|
python_files = check_*.py
|
||||||
python_classes=Check
|
python_classes = Check
|
||||||
python_functions=*_check
|
python_functions = *_check
|
||||||
|
|
||||||
This would make ``pytest`` look for tests in files that match the ``check_*
|
This would make ``pytest`` look for tests in files that match the ``check_*
|
||||||
.py`` glob-pattern, ``Check`` prefixes in classes, and functions and methods
|
.py`` glob-pattern, ``Check`` prefixes in classes, and functions and methods
|
||||||
that match ``*_check``. For example, if we have::
|
that match ``*_check``. For example, if we have::
|
||||||
|
|
||||||
# content of check_myapp.py
|
# content of check_myapp.py
|
||||||
class CheckMyApp(object):
|
class CheckMyApp(object):
|
||||||
|
@ -121,7 +123,7 @@ that match ``*_check``. For example, if we have::
|
||||||
def complex_check(self):
|
def complex_check(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
then the test collection looks like this::
|
The test collection would look like this::
|
||||||
|
|
||||||
$ pytest --collect-only
|
$ pytest --collect-only
|
||||||
=========================== test session starts ============================
|
=========================== test session starts ============================
|
||||||
|
@ -136,11 +138,19 @@ then the test collection looks like this::
|
||||||
|
|
||||||
======================= no tests ran in 0.12 seconds =======================
|
======================= no tests ran in 0.12 seconds =======================
|
||||||
|
|
||||||
|
You can check for multiple glob patterns by adding a space between the patterns::
|
||||||
|
|
||||||
|
# Example 2: have pytest look for files with "test" and "example"
|
||||||
|
# content of pytest.ini, tox.ini, or setup.cfg file (replace "pytest"
|
||||||
|
# with "tool:pytest" for setup.cfg)
|
||||||
|
[pytest]
|
||||||
|
python_files = test_*.py example_*.py
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
the ``python_functions`` and ``python_classes`` options has no effect
|
the ``python_functions`` and ``python_classes`` options has no effect
|
||||||
for ``unittest.TestCase`` test discovery because pytest delegates
|
for ``unittest.TestCase`` test discovery because pytest delegates
|
||||||
detection of test case methods to unittest code.
|
discovery of test case methods to unittest code.
|
||||||
|
|
||||||
Interpreting cmdline arguments as Python packages
|
Interpreting cmdline arguments as Python packages
|
||||||
-----------------------------------------------------
|
-----------------------------------------------------
|
||||||
|
|
|
@ -460,7 +460,7 @@ To use it, include in your top-most ``conftest.py`` file::
|
||||||
|
|
||||||
|
|
||||||
.. autoclass:: Testdir()
|
.. autoclass:: Testdir()
|
||||||
:members: runpytest,runpytest_subprocess,runpytest_inprocess,makeconftest,makepyfile
|
:members:
|
||||||
|
|
||||||
.. autoclass:: RunResult()
|
.. autoclass:: RunResult()
|
||||||
:members:
|
:members:
|
||||||
|
@ -1229,7 +1229,8 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||||
.. confval:: python_classes
|
.. confval:: python_classes
|
||||||
|
|
||||||
One or more name prefixes or glob-style patterns determining which classes
|
One or more name prefixes or glob-style patterns determining which classes
|
||||||
are considered for test collection. By default, pytest will consider any
|
are considered for test collection. Search for multiple glob patterns by
|
||||||
|
adding a space between patterns. By default, pytest will consider any
|
||||||
class prefixed with ``Test`` as a test collection. Here is an example of how
|
class prefixed with ``Test`` as a test collection. Here is an example of how
|
||||||
to collect tests from classes that end in ``Suite``:
|
to collect tests from classes that end in ``Suite``:
|
||||||
|
|
||||||
|
@ -1246,15 +1247,23 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||||
.. confval:: python_files
|
.. confval:: python_files
|
||||||
|
|
||||||
One or more Glob-style file patterns determining which python files
|
One or more Glob-style file patterns determining which python files
|
||||||
are considered as test modules. By default, pytest will consider
|
are considered as test modules. Search for multiple glob patterns by
|
||||||
any file matching with ``test_*.py`` and ``*_test.py`` globs as a test
|
adding a space between patterns::
|
||||||
module.
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[pytest]
|
||||||
|
python_files = test_*.py check_*.py example_*.py
|
||||||
|
|
||||||
|
By default, pytest will consider any file matching with ``test_*.py``
|
||||||
|
and ``*_test.py`` globs as a test module.
|
||||||
|
|
||||||
|
|
||||||
.. confval:: python_functions
|
.. confval:: python_functions
|
||||||
|
|
||||||
One or more name prefixes or glob-patterns determining which test functions
|
One or more name prefixes or glob-patterns determining which test functions
|
||||||
and methods are considered tests. By default, pytest will consider any
|
and methods are considered tests. Search for multiple glob patterns by
|
||||||
|
adding a space between patterns. By default, pytest will consider any
|
||||||
function prefixed with ``test`` as a test. Here is an example of how
|
function prefixed with ``test`` as a test. Here is an example of how
|
||||||
to collect test functions and methods that end in ``_test``:
|
to collect test functions and methods that end in ``_test``:
|
||||||
|
|
||||||
|
|
|
@ -136,12 +136,6 @@ You can use the ``skipif`` marker (as any other marker) on classes::
|
||||||
If the condition is ``True``, this marker will produce a skip result for
|
If the condition is ``True``, this marker will produce a skip result for
|
||||||
each of the test methods of that class.
|
each of the test methods of that class.
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
The use of ``skipif`` on classes that use inheritance is strongly
|
|
||||||
discouraged. `A Known bug <https://github.com/pytest-dev/pytest/issues/568>`_
|
|
||||||
in pytest's markers may cause unexpected behavior in super classes.
|
|
||||||
|
|
||||||
If you want to skip all test functions of a module, you may use
|
If you want to skip all test functions of a module, you may use
|
||||||
the ``pytestmark`` name on the global level:
|
the ``pytestmark`` name on the global level:
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -16,7 +16,6 @@ import six
|
||||||
import pytest
|
import pytest
|
||||||
from _pytest.compat import CaptureIO
|
from _pytest.compat import CaptureIO
|
||||||
|
|
||||||
|
|
||||||
patchsysdict = {0: "stdin", 1: "stdout", 2: "stderr"}
|
patchsysdict = {0: "stdin", 1: "stdout", 2: "stderr"}
|
||||||
|
|
||||||
|
|
||||||
|
@ -63,8 +62,9 @@ def pytest_load_initial_conftests(early_config, parser, args):
|
||||||
# finally trigger conftest loading but while capturing (issue93)
|
# finally trigger conftest loading but while capturing (issue93)
|
||||||
capman.start_global_capturing()
|
capman.start_global_capturing()
|
||||||
outcome = yield
|
outcome = yield
|
||||||
out, err = capman.suspend_global_capture()
|
capman.suspend_global_capture()
|
||||||
if outcome.excinfo is not None:
|
if outcome.excinfo is not None:
|
||||||
|
out, err = capman.read_global_capture()
|
||||||
sys.stdout.write(out)
|
sys.stdout.write(out)
|
||||||
sys.stderr.write(err)
|
sys.stderr.write(err)
|
||||||
|
|
||||||
|
@ -85,6 +85,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":
|
||||||
|
@ -96,6 +97,8 @@ class CaptureManager(object):
|
||||||
else:
|
else:
|
||||||
raise ValueError("unknown capturing method: %r" % method)
|
raise ValueError("unknown capturing method: %r" % method)
|
||||||
|
|
||||||
|
# Global capturing control
|
||||||
|
|
||||||
def start_global_capturing(self):
|
def start_global_capturing(self):
|
||||||
assert self._global_capturing is None
|
assert self._global_capturing is None
|
||||||
self._global_capturing = self._getcapture(self._method)
|
self._global_capturing = self._getcapture(self._method)
|
||||||
|
@ -110,16 +113,15 @@ class CaptureManager(object):
|
||||||
def resume_global_capture(self):
|
def resume_global_capture(self):
|
||||||
self._global_capturing.resume_capturing()
|
self._global_capturing.resume_capturing()
|
||||||
|
|
||||||
def suspend_global_capture(self, item=None, in_=False):
|
def suspend_global_capture(self, in_=False):
|
||||||
if item is not None:
|
|
||||||
self.deactivate_fixture(item)
|
|
||||||
cap = getattr(self, "_global_capturing", None)
|
cap = getattr(self, "_global_capturing", None)
|
||||||
if cap is not None:
|
if cap is not None:
|
||||||
try:
|
cap.suspend_capturing(in_=in_)
|
||||||
outerr = cap.readouterr()
|
|
||||||
finally:
|
def read_global_capture(self):
|
||||||
cap.suspend_capturing(in_=in_)
|
return self._global_capturing.readouterr()
|
||||||
return outerr
|
|
||||||
|
# Fixture Control (its just forwarding, think about removing this later)
|
||||||
|
|
||||||
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
|
||||||
|
@ -135,12 +137,53 @@ class CaptureManager(object):
|
||||||
if fixture is not None:
|
if fixture is not None:
|
||||||
fixture.close()
|
fixture.close()
|
||||||
|
|
||||||
|
def suspend_fixture(self, item):
|
||||||
|
fixture = getattr(item, "_capture_fixture", None)
|
||||||
|
if fixture is not None:
|
||||||
|
fixture._suspend()
|
||||||
|
|
||||||
|
def resume_fixture(self, item):
|
||||||
|
fixture = getattr(item, "_capture_fixture", None)
|
||||||
|
if fixture is not None:
|
||||||
|
fixture._resume()
|
||||||
|
|
||||||
|
# Helper context managers
|
||||||
|
|
||||||
|
@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
|
||||||
|
self.suspend_fixture(self._current_item)
|
||||||
|
self.suspend_global_capture(in_=False)
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
self.resume_global_capture()
|
||||||
|
self.resume_fixture(self._current_item)
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def item_capture(self, when, item):
|
||||||
|
self.resume_global_capture()
|
||||||
|
self.activate_fixture(item)
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
self.deactivate_fixture(item)
|
||||||
|
self.suspend_global_capture(in_=False)
|
||||||
|
|
||||||
|
out, err = self.read_global_capture()
|
||||||
|
item.add_report_section(when, "stdout", out)
|
||||||
|
item.add_report_section(when, "stderr", err)
|
||||||
|
|
||||||
|
# Hooks
|
||||||
|
|
||||||
@pytest.hookimpl(hookwrapper=True)
|
@pytest.hookimpl(hookwrapper=True)
|
||||||
def pytest_make_collect_report(self, collector):
|
def pytest_make_collect_report(self, collector):
|
||||||
if isinstance(collector, pytest.File):
|
if isinstance(collector, pytest.File):
|
||||||
self.resume_global_capture()
|
self.resume_global_capture()
|
||||||
outcome = yield
|
outcome = yield
|
||||||
out, err = self.suspend_global_capture()
|
self.suspend_global_capture()
|
||||||
|
out, err = self.read_global_capture()
|
||||||
rep = outcome.get_result()
|
rep = outcome.get_result()
|
||||||
if out:
|
if out:
|
||||||
rep.sections.append(("Captured stdout", out))
|
rep.sections.append(("Captured stdout", out))
|
||||||
|
@ -150,29 +193,25 @@ class CaptureManager(object):
|
||||||
yield
|
yield
|
||||||
|
|
||||||
@pytest.hookimpl(hookwrapper=True)
|
@pytest.hookimpl(hookwrapper=True)
|
||||||
def pytest_runtest_setup(self, item):
|
def pytest_runtest_protocol(self, item):
|
||||||
self.resume_global_capture()
|
self._current_item = item
|
||||||
# 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
|
|
||||||
# be activated during pytest_runtest_call
|
|
||||||
yield
|
yield
|
||||||
self.suspend_capture_item(item, "setup")
|
self._current_item = None
|
||||||
|
|
||||||
|
@pytest.hookimpl(hookwrapper=True)
|
||||||
|
def pytest_runtest_setup(self, item):
|
||||||
|
with self.item_capture("setup", item):
|
||||||
|
yield
|
||||||
|
|
||||||
@pytest.hookimpl(hookwrapper=True)
|
@pytest.hookimpl(hookwrapper=True)
|
||||||
def pytest_runtest_call(self, item):
|
def pytest_runtest_call(self, item):
|
||||||
self.resume_global_capture()
|
with self.item_capture("call", item):
|
||||||
# it is important to activate this fixture during the call phase so it overwrites the "global"
|
yield
|
||||||
# capture
|
|
||||||
self.activate_fixture(item)
|
|
||||||
yield
|
|
||||||
self.suspend_capture_item(item, "call")
|
|
||||||
|
|
||||||
@pytest.hookimpl(hookwrapper=True)
|
@pytest.hookimpl(hookwrapper=True)
|
||||||
def pytest_runtest_teardown(self, item):
|
def pytest_runtest_teardown(self, item):
|
||||||
self.resume_global_capture()
|
with self.item_capture("teardown", item):
|
||||||
self.activate_fixture(item)
|
yield
|
||||||
yield
|
|
||||||
self.suspend_capture_item(item, "teardown")
|
|
||||||
|
|
||||||
@pytest.hookimpl(tryfirst=True)
|
@pytest.hookimpl(tryfirst=True)
|
||||||
def pytest_keyboard_interrupt(self, excinfo):
|
def pytest_keyboard_interrupt(self, excinfo):
|
||||||
|
@ -182,11 +221,6 @@ class CaptureManager(object):
|
||||||
def pytest_internalerror(self, excinfo):
|
def pytest_internalerror(self, excinfo):
|
||||||
self.stop_global_capturing()
|
self.stop_global_capturing()
|
||||||
|
|
||||||
def suspend_capture_item(self, item, when, in_=False):
|
|
||||||
out, err = self.suspend_global_capture(item, in_=in_)
|
|
||||||
item.add_report_section(when, "stdout", out)
|
|
||||||
item.add_report_section(when, "stderr", err)
|
|
||||||
|
|
||||||
|
|
||||||
capture_fixtures = {"capfd", "capfdbinary", "capsys", "capsysbinary"}
|
capture_fixtures = {"capfd", "capfdbinary", "capsys", "capsysbinary"}
|
||||||
|
|
||||||
|
@ -290,40 +324,54 @@ class CaptureFixture(object):
|
||||||
def __init__(self, captureclass, request):
|
def __init__(self, captureclass, request):
|
||||||
self.captureclass = captureclass
|
self.captureclass = captureclass
|
||||||
self.request = request
|
self.request = request
|
||||||
|
self._capture = None
|
||||||
|
self._captured_out = self.captureclass.EMPTY_BUFFER
|
||||||
|
self._captured_err = self.captureclass.EMPTY_BUFFER
|
||||||
|
|
||||||
def _start(self):
|
def _start(self):
|
||||||
self._capture = MultiCapture(
|
# Start if not started yet
|
||||||
out=True, err=True, in_=False, Capture=self.captureclass
|
if getattr(self, "_capture", None) is None:
|
||||||
)
|
self._capture = MultiCapture(
|
||||||
self._capture.start_capturing()
|
out=True, err=True, in_=False, Capture=self.captureclass
|
||||||
|
)
|
||||||
|
self._capture.start_capturing()
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
cap = self.__dict__.pop("_capture", None)
|
if self._capture is not None:
|
||||||
if cap is not None:
|
out, err = self._capture.pop_outerr_to_orig()
|
||||||
self._outerr = cap.pop_outerr_to_orig()
|
self._captured_out += out
|
||||||
cap.stop_capturing()
|
self._captured_err += err
|
||||||
|
self._capture.stop_capturing()
|
||||||
|
self._capture = None
|
||||||
|
|
||||||
def readouterr(self):
|
def readouterr(self):
|
||||||
"""Read and return the captured output so far, resetting the internal buffer.
|
"""Read and return the captured output so far, resetting the internal buffer.
|
||||||
|
|
||||||
:return: captured content as a namedtuple with ``out`` and ``err`` string attributes
|
:return: captured content as a namedtuple with ``out`` and ``err`` string attributes
|
||||||
"""
|
"""
|
||||||
try:
|
captured_out, captured_err = self._captured_out, self._captured_err
|
||||||
return self._capture.readouterr()
|
if self._capture is not None:
|
||||||
except AttributeError:
|
out, err = self._capture.readouterr()
|
||||||
return self._outerr
|
captured_out += out
|
||||||
|
captured_err += err
|
||||||
|
self._captured_out = self.captureclass.EMPTY_BUFFER
|
||||||
|
self._captured_err = self.captureclass.EMPTY_BUFFER
|
||||||
|
return CaptureResult(captured_out, captured_err)
|
||||||
|
|
||||||
|
def _suspend(self):
|
||||||
|
"""Suspends this fixture's own capturing temporarily."""
|
||||||
|
self._capture.suspend_capturing()
|
||||||
|
|
||||||
|
def _resume(self):
|
||||||
|
"""Resumes this fixture's own capturing temporarily."""
|
||||||
|
self._capture.resume_capturing()
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def disabled(self):
|
def disabled(self):
|
||||||
"""Temporarily disables capture while inside the 'with' block."""
|
"""Temporarily disables capture while inside the 'with' block."""
|
||||||
self._capture.suspend_capturing()
|
|
||||||
capmanager = self.request.config.pluginmanager.getplugin("capturemanager")
|
capmanager = self.request.config.pluginmanager.getplugin("capturemanager")
|
||||||
capmanager.suspend_global_capture(item=None, in_=False)
|
with capmanager.global_and_fixture_disabled():
|
||||||
try:
|
|
||||||
yield
|
yield
|
||||||
finally:
|
|
||||||
capmanager.resume_global_capture()
|
|
||||||
self._capture.resume_capturing()
|
|
||||||
|
|
||||||
|
|
||||||
def safe_text_dupfile(f, mode, default_encoding="UTF8"):
|
def safe_text_dupfile(f, mode, default_encoding="UTF8"):
|
||||||
|
@ -440,6 +488,7 @@ class MultiCapture(object):
|
||||||
|
|
||||||
|
|
||||||
class NoCapture(object):
|
class NoCapture(object):
|
||||||
|
EMPTY_BUFFER = None
|
||||||
__init__ = start = done = suspend = resume = lambda *args: None
|
__init__ = start = done = suspend = resume = lambda *args: None
|
||||||
|
|
||||||
|
|
||||||
|
@ -449,6 +498,8 @@ class FDCaptureBinary(object):
|
||||||
snap() produces `bytes`
|
snap() produces `bytes`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
EMPTY_BUFFER = bytes()
|
||||||
|
|
||||||
def __init__(self, targetfd, tmpfile=None):
|
def __init__(self, targetfd, tmpfile=None):
|
||||||
self.targetfd = targetfd
|
self.targetfd = targetfd
|
||||||
try:
|
try:
|
||||||
|
@ -522,6 +573,8 @@ class FDCapture(FDCaptureBinary):
|
||||||
snap() produces text
|
snap() produces text
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
EMPTY_BUFFER = str()
|
||||||
|
|
||||||
def snap(self):
|
def snap(self):
|
||||||
res = FDCaptureBinary.snap(self)
|
res = FDCaptureBinary.snap(self)
|
||||||
enc = getattr(self.tmpfile, "encoding", None)
|
enc = getattr(self.tmpfile, "encoding", None)
|
||||||
|
@ -531,6 +584,9 @@ class FDCapture(FDCaptureBinary):
|
||||||
|
|
||||||
|
|
||||||
class SysCapture(object):
|
class SysCapture(object):
|
||||||
|
|
||||||
|
EMPTY_BUFFER = str()
|
||||||
|
|
||||||
def __init__(self, fd, tmpfile=None):
|
def __init__(self, fd, tmpfile=None):
|
||||||
name = patchsysdict[fd]
|
name = patchsysdict[fd]
|
||||||
self._old = getattr(sys, name)
|
self._old = getattr(sys, name)
|
||||||
|
@ -568,6 +624,8 @@ class SysCapture(object):
|
||||||
|
|
||||||
|
|
||||||
class SysCaptureBinary(SysCapture):
|
class SysCaptureBinary(SysCapture):
|
||||||
|
EMPTY_BUFFER = bytes()
|
||||||
|
|
||||||
def snap(self):
|
def snap(self):
|
||||||
res = self.tmpfile.buffer.getvalue()
|
res = self.tmpfile.buffer.getvalue()
|
||||||
self.tmpfile.seek(0)
|
self.tmpfile.seek(0)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -102,7 +102,8 @@ class PdbInvoke(object):
|
||||||
def pytest_exception_interact(self, node, call, report):
|
def pytest_exception_interact(self, node, call, report):
|
||||||
capman = node.config.pluginmanager.getplugin("capturemanager")
|
capman = node.config.pluginmanager.getplugin("capturemanager")
|
||||||
if capman:
|
if capman:
|
||||||
out, err = capman.suspend_global_capture(in_=True)
|
capman.suspend_global_capture(in_=True)
|
||||||
|
out, err = capman.read_global_capture()
|
||||||
sys.stdout.write(out)
|
sys.stdout.write(out)
|
||||||
sys.stdout.write(err)
|
sys.stdout.write(err)
|
||||||
_enter_pdb(node, call.excinfo, report)
|
_enter_pdb(node, call.excinfo, report)
|
||||||
|
|
|
@ -307,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
|
||||||
|
|
|
@ -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.
|
||||||
|
@ -624,11 +625,12 @@ class Session(nodes.FSCollector):
|
||||||
resultnodes.append(node)
|
resultnodes.append(node)
|
||||||
continue
|
continue
|
||||||
assert isinstance(node, nodes.Collector)
|
assert isinstance(node, nodes.Collector)
|
||||||
if node.nodeid in self._node_cache:
|
key = (type(node), node.nodeid)
|
||||||
rep = self._node_cache[node.nodeid]
|
if key in self._node_cache:
|
||||||
|
rep = self._node_cache[key]
|
||||||
else:
|
else:
|
||||||
rep = collect_one_node(node)
|
rep = collect_one_node(node)
|
||||||
self._node_cache[node.nodeid] = rep
|
self._node_cache[key] = rep
|
||||||
if rep.passed:
|
if rep.passed:
|
||||||
has_matched = False
|
has_matched = False
|
||||||
for x in rep.result:
|
for x in rep.result:
|
||||||
|
|
|
@ -550,18 +550,22 @@ class Testdir(object):
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def makefile(self, ext, *args, **kwargs):
|
def makefile(self, ext, *args, **kwargs):
|
||||||
"""Create a new file in the testdir.
|
r"""Create new file(s) in the testdir.
|
||||||
|
|
||||||
ext: The extension the file should use, including the dot, e.g. `.py`.
|
:param str ext: The extension the file(s) should use, including the dot, e.g. `.py`.
|
||||||
|
:param list[str] args: All args will be treated as strings and joined using newlines.
|
||||||
args: All args will be treated as strings and joined using newlines.
|
|
||||||
The result will be written as contents to the file. The name of the
|
The result will be written as contents to the file. The name of the
|
||||||
file will be based on the test function requesting this fixture.
|
file will be based on the test function requesting this fixture.
|
||||||
E.g. "testdir.makefile('.txt', 'line1', 'line2')"
|
:param kwargs: Each keyword is the name of a file, while the value of it will
|
||||||
|
|
||||||
kwargs: Each keyword is the name of a file, while the value of it will
|
|
||||||
be written as contents of the file.
|
be written as contents of the file.
|
||||||
E.g. "testdir.makefile('.ini', pytest='[pytest]\naddopts=-rs\n')"
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
testdir.makefile(".txt", "line1", "line2")
|
||||||
|
|
||||||
|
testdir.makefile(".ini", pytest="[pytest]\naddopts=-rs\n")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self._makefile(ext, args, kwargs)
|
return self._makefile(ext, args, kwargs)
|
||||||
|
|
|
@ -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,9 +542,7 @@ 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())
|
||||||
|
@ -594,6 +580,15 @@ class Package(Module):
|
||||||
return path in self.session._initialpaths
|
return path in self.session._initialpaths
|
||||||
|
|
||||||
def collect(self):
|
def collect(self):
|
||||||
|
# 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()
|
this_path = self.fspath.dirpath()
|
||||||
pkg_prefix = None
|
pkg_prefix = None
|
||||||
yield Module(this_path.join("__init__.py"), self)
|
yield Module(this_path.join("__init__.py"), self)
|
||||||
|
@ -884,12 +879,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.
|
||||||
|
|
|
@ -51,7 +51,8 @@ def _show_fixture_action(fixturedef, msg):
|
||||||
config = fixturedef._fixturemanager.config
|
config = fixturedef._fixturemanager.config
|
||||||
capman = config.pluginmanager.getplugin("capturemanager")
|
capman = config.pluginmanager.getplugin("capturemanager")
|
||||||
if capman:
|
if capman:
|
||||||
out, err = capman.suspend_global_capture()
|
capman.suspend_global_capture()
|
||||||
|
out, err = capman.read_global_capture()
|
||||||
|
|
||||||
tw = config.get_terminal_writer()
|
tw = config.get_terminal_writer()
|
||||||
tw.line()
|
tw.line()
|
||||||
|
|
|
@ -706,7 +706,12 @@ class TerminalReporter(object):
|
||||||
self._outrep_summary(rep)
|
self._outrep_summary(rep)
|
||||||
|
|
||||||
def print_teardown_sections(self, rep):
|
def print_teardown_sections(self, rep):
|
||||||
|
showcapture = self.config.option.showcapture
|
||||||
|
if showcapture == "no":
|
||||||
|
return
|
||||||
for secname, content in rep.sections:
|
for secname, content in rep.sections:
|
||||||
|
if showcapture != "all" and showcapture not in secname:
|
||||||
|
continue
|
||||||
if "teardown" in secname:
|
if "teardown" in secname:
|
||||||
self._tw.sep("-", secname)
|
self._tw.sep("-", secname)
|
||||||
if content[-1:] == "\n":
|
if content[-1:] == "\n":
|
||||||
|
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -660,6 +660,16 @@ class TestInvocationVariants(object):
|
||||||
["*test_world.py::test_other*PASSED*", "*1 passed*"]
|
["*test_world.py::test_other*PASSED*", "*1 passed*"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_invoke_test_and_doctestmodules(self, testdir):
|
||||||
|
p = testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
def test():
|
||||||
|
pass
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = testdir.runpytest(str(p) + "::test", "--doctest-modules")
|
||||||
|
result.stdout.fnmatch_lines(["*1 passed*"])
|
||||||
|
|
||||||
@pytest.mark.skipif(not hasattr(os, "symlink"), reason="requires symlinks")
|
@pytest.mark.skipif(not hasattr(os, "symlink"), reason="requires symlinks")
|
||||||
def test_cmdline_python_package_symlink(self, testdir, monkeypatch):
|
def test_cmdline_python_package_symlink(self, testdir, monkeypatch):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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"])
|
||||||
|
|
|
@ -876,22 +876,18 @@ 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.logging import _LiveLoggingStreamHandler
|
from _pytest.logging import _LiveLoggingStreamHandler
|
||||||
|
|
||||||
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
|
|
||||||
assert CaptureManager.suspend_capture_item
|
|
||||||
assert CaptureManager.resume_global_capture
|
|
||||||
|
|
||||||
class DummyTerminal(six.StringIO):
|
class DummyTerminal(six.StringIO):
|
||||||
def section(self, *args, **kwargs):
|
def section(self, *args, **kwargs):
|
||||||
|
@ -908,10 +904,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"
|
||||||
|
|
|
@ -1583,3 +1583,43 @@ def test_package_collection_infinite_recursion(testdir):
|
||||||
testdir.copy_example("collect/package_infinite_recursion")
|
testdir.copy_example("collect/package_infinite_recursion")
|
||||||
result = testdir.runpytest()
|
result = testdir.runpytest()
|
||||||
result.stdout.fnmatch_lines("*1 passed*")
|
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)
|
||||||
|
|
|
@ -70,19 +70,23 @@ class TestCaptureManager(object):
|
||||||
try:
|
try:
|
||||||
capman = CaptureManager(method)
|
capman = CaptureManager(method)
|
||||||
capman.start_global_capturing()
|
capman.start_global_capturing()
|
||||||
outerr = capman.suspend_global_capture()
|
capman.suspend_global_capture()
|
||||||
|
outerr = capman.read_global_capture()
|
||||||
assert outerr == ("", "")
|
assert outerr == ("", "")
|
||||||
outerr = capman.suspend_global_capture()
|
capman.suspend_global_capture()
|
||||||
|
outerr = capman.read_global_capture()
|
||||||
assert outerr == ("", "")
|
assert outerr == ("", "")
|
||||||
print("hello")
|
print("hello")
|
||||||
out, err = capman.suspend_global_capture()
|
capman.suspend_global_capture()
|
||||||
|
out, err = capman.read_global_capture()
|
||||||
if method == "no":
|
if method == "no":
|
||||||
assert old == (sys.stdout, sys.stderr, sys.stdin)
|
assert old == (sys.stdout, sys.stderr, sys.stdin)
|
||||||
else:
|
else:
|
||||||
assert not out
|
assert not out
|
||||||
capman.resume_global_capture()
|
capman.resume_global_capture()
|
||||||
print("hello")
|
print("hello")
|
||||||
out, err = capman.suspend_global_capture()
|
capman.suspend_global_capture()
|
||||||
|
out, err = capman.read_global_capture()
|
||||||
if method != "no":
|
if method != "no":
|
||||||
assert out == "hello\n"
|
assert out == "hello\n"
|
||||||
capman.stop_global_capturing()
|
capman.stop_global_capturing()
|
||||||
|
@ -647,6 +651,34 @@ class TestCaptureFixture(object):
|
||||||
assert "stdout contents begin" not in result.stdout.str()
|
assert "stdout contents begin" not in result.stdout.str()
|
||||||
assert "stderr contents begin" not in result.stdout.str()
|
assert "stderr contents begin" not in result.stdout.str()
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("cap", ["capsys", "capfd"])
|
||||||
|
def test_fixture_use_by_other_fixtures_teardown(self, testdir, cap):
|
||||||
|
"""Ensure we can access setup and teardown buffers from teardown when using capsys/capfd (##3033)"""
|
||||||
|
testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import pytest
|
||||||
|
import os
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def fix({cap}):
|
||||||
|
print("setup out")
|
||||||
|
sys.stderr.write("setup err\\n")
|
||||||
|
yield
|
||||||
|
out, err = {cap}.readouterr()
|
||||||
|
assert out == 'setup out\\ncall out\\n'
|
||||||
|
assert err == 'setup err\\ncall err\\n'
|
||||||
|
|
||||||
|
def test_a(fix):
|
||||||
|
print("call out")
|
||||||
|
sys.stderr.write("call err\\n")
|
||||||
|
""".format(
|
||||||
|
cap=cap
|
||||||
|
)
|
||||||
|
)
|
||||||
|
reprec = testdir.inline_run()
|
||||||
|
reprec.assertoutcome(passed=1)
|
||||||
|
|
||||||
|
|
||||||
def test_setup_failure_does_not_kill_capturing(testdir):
|
def test_setup_failure_does_not_kill_capturing(testdir):
|
||||||
sub1 = testdir.mkpydir("sub1")
|
sub1 = testdir.mkpydir("sub1")
|
||||||
|
@ -1385,3 +1417,95 @@ 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_global_capture_with_live_logging(testdir):
|
||||||
|
# Issue 3819
|
||||||
|
# capture should work with live cli logging
|
||||||
|
|
||||||
|
# Teardown report seems to have the capture for the whole process (setup, capture, teardown)
|
||||||
|
testdir.makeconftest(
|
||||||
|
"""
|
||||||
|
def pytest_runtest_logreport(report):
|
||||||
|
if "test_global" in report.nodeid:
|
||||||
|
if report.when == "teardown":
|
||||||
|
with open("caplog", "w") as f:
|
||||||
|
f.write(report.caplog)
|
||||||
|
with open("capstdout", "w") as f:
|
||||||
|
f.write(report.capstdout)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def fix1():
|
||||||
|
print("fix setup")
|
||||||
|
logging.info("fix setup")
|
||||||
|
yield
|
||||||
|
logging.info("fix teardown")
|
||||||
|
print("fix teardown")
|
||||||
|
|
||||||
|
def test_global(fix1):
|
||||||
|
print("begin test")
|
||||||
|
logging.info("something in test")
|
||||||
|
print("end test")
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = testdir.runpytest_subprocess("--log-cli-level=INFO")
|
||||||
|
assert result.ret == 0
|
||||||
|
|
||||||
|
with open("caplog", "r") as f:
|
||||||
|
caplog = f.read()
|
||||||
|
|
||||||
|
assert "fix setup" in caplog
|
||||||
|
assert "something in test" in caplog
|
||||||
|
assert "fix teardown" in caplog
|
||||||
|
|
||||||
|
with open("capstdout", "r") as f:
|
||||||
|
capstdout = f.read()
|
||||||
|
|
||||||
|
assert "fix setup" in capstdout
|
||||||
|
assert "begin test" in capstdout
|
||||||
|
assert "end test" in capstdout
|
||||||
|
assert "fix teardown" in capstdout
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("capture_fixture", ["capsys", "capfd"])
|
||||||
|
def test_capture_with_live_logging(testdir, capture_fixture):
|
||||||
|
# Issue 3819
|
||||||
|
# capture should work with live cli logging
|
||||||
|
|
||||||
|
testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def test_capture({0}):
|
||||||
|
print("hello")
|
||||||
|
sys.stderr.write("world\\n")
|
||||||
|
captured = {0}.readouterr()
|
||||||
|
assert captured.out == "hello\\n"
|
||||||
|
assert captured.err == "world\\n"
|
||||||
|
|
||||||
|
logging.info("something")
|
||||||
|
print("next")
|
||||||
|
logging.info("something")
|
||||||
|
|
||||||
|
captured = {0}.readouterr()
|
||||||
|
assert captured.out == "next\\n"
|
||||||
|
""".format(
|
||||||
|
capture_fixture
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
|
@ -948,6 +948,46 @@ def pytest_report_header(config, startdir):
|
||||||
assert "!This is stderr!" not in stdout
|
assert "!This is stderr!" not in stdout
|
||||||
assert "!This is a warning log msg!" not in stdout
|
assert "!This is a warning log msg!" not in stdout
|
||||||
|
|
||||||
|
def test_show_capture_with_teardown_logs(self, testdir):
|
||||||
|
"""Ensure that the capturing of teardown logs honor --show-capture setting"""
|
||||||
|
testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
@pytest.fixture(scope="function", autouse="True")
|
||||||
|
def hook_each_test(request):
|
||||||
|
yield
|
||||||
|
sys.stdout.write("!stdout!")
|
||||||
|
sys.stderr.write("!stderr!")
|
||||||
|
logging.warning("!log!")
|
||||||
|
|
||||||
|
def test_func():
|
||||||
|
assert False
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
result = testdir.runpytest("--show-capture=stdout", "--tb=short").stdout.str()
|
||||||
|
assert "!stdout!" in result
|
||||||
|
assert "!stderr!" not in result
|
||||||
|
assert "!log!" not in result
|
||||||
|
|
||||||
|
result = testdir.runpytest("--show-capture=stderr", "--tb=short").stdout.str()
|
||||||
|
assert "!stdout!" not in result
|
||||||
|
assert "!stderr!" in result
|
||||||
|
assert "!log!" not in result
|
||||||
|
|
||||||
|
result = testdir.runpytest("--show-capture=log", "--tb=short").stdout.str()
|
||||||
|
assert "!stdout!" not in result
|
||||||
|
assert "!stderr!" not in result
|
||||||
|
assert "!log!" in result
|
||||||
|
|
||||||
|
result = testdir.runpytest("--show-capture=no", "--tb=short").stdout.str()
|
||||||
|
assert "!stdout!" not in result
|
||||||
|
assert "!stderr!" not in result
|
||||||
|
assert "!log!" not in result
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail("not hasattr(os, 'dup')")
|
@pytest.mark.xfail("not hasattr(os, 'dup')")
|
||||||
def test_fdopen_kept_alive_issue124(testdir):
|
def test_fdopen_kept_alive_issue124(testdir):
|
||||||
|
|
|
@ -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