Merge remote-tracking branch 'upstream/features' into gftea/features
This commit is contained in:
commit
5b295ec68e
|
@ -37,7 +37,7 @@ repos:
|
|||
- id: pyupgrade
|
||||
args: [--py3-plus]
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v0.750
|
||||
rev: v0.761
|
||||
hooks:
|
||||
- id: mypy
|
||||
files: ^(src/|testing/)
|
||||
|
@ -53,8 +53,8 @@ repos:
|
|||
- id: changelogs-rst
|
||||
name: changelog filenames
|
||||
language: fail
|
||||
entry: 'changelog files must be named ####.(feature|bugfix|doc|deprecation|removal|vendor|trivial).rst'
|
||||
exclude: changelog/(\d+\.(feature|improvement|bugfix|doc|deprecation|removal|vendor|trivial).rst|README.rst|_template.rst)
|
||||
entry: 'changelog files must be named ####.(breaking|bugfix|deprecation|doc|feature|improvement|trivial|vendor).rst'
|
||||
exclude: changelog/(\d+\.(breaking|bugfix|deprecation|doc|feature|improvement|trivial|vendor).rst|README.rst|_template.rst)
|
||||
files: ^changelog/
|
||||
- id: py-deprecated
|
||||
name: py library is deprecated
|
||||
|
|
7
AUTHORS
7
AUTHORS
|
@ -52,6 +52,7 @@ Carl Friedrich Bolz
|
|||
Carlos Jenkins
|
||||
Ceridwen
|
||||
Charles Cloud
|
||||
Charles Machalow
|
||||
Charnjit SiNGH (CCSJ)
|
||||
Chris Lamb
|
||||
Christian Boelsen
|
||||
|
@ -59,12 +60,13 @@ Christian Fetzer
|
|||
Christian Neumüller
|
||||
Christian Theunert
|
||||
Christian Tismer
|
||||
Christopher Gilling
|
||||
Christoph Buelter
|
||||
Christopher Dignam
|
||||
Christopher Gilling
|
||||
Claudio Madotto
|
||||
CrazyMerlyn
|
||||
Cyrus Maden
|
||||
Damian Skrzypczak
|
||||
Dhiren Serai
|
||||
Daniel Grana
|
||||
Daniel Hahler
|
||||
Daniel Nuri
|
||||
|
@ -79,6 +81,7 @@ David Szotten
|
|||
David Vierra
|
||||
Daw-Ran Liou
|
||||
Denis Kirisov
|
||||
Dhiren Serai
|
||||
Diego Russo
|
||||
Dmitry Dygalo
|
||||
Dmitry Pribysh
|
||||
|
|
|
@ -137,7 +137,7 @@ Save time, reduce risk, and improve code health, while paying the maintainers of
|
|||
Security
|
||||
^^^^^^^^
|
||||
|
||||
pytest has never been associated with a security vunerability, but in any case, to report a
|
||||
pytest has never been associated with a security vulnerability, but in any case, to report a
|
||||
security vulnerability please use the `Tidelift security contact <https://tidelift.com/security>`_.
|
||||
Tidelift will coordinate the fix and disclosure.
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
Option ``--no-print-logs`` is deprecated and meant to be removed in a future release. If you use ``--no-print-logs``, please try out ``--show-capture`` and
|
||||
provide feedback.
|
||||
|
||||
``--show-capture`` command-line option was added in ``pytest 3.5.0`` and allows to specify how to
|
||||
display captured output when tests fail: ``no``, ``stdout``, ``stderr``, ``log`` or ``all`` (the default).
|
|
@ -0,0 +1 @@
|
|||
New :ref:`--capture=tee-sys <capture-method>` option to allow both live printing and capturing of test output.
|
|
@ -1,3 +1,4 @@
|
|||
Revert "A warning is now issued when assertions are made for ``None``".
|
||||
|
||||
The warning proved to be less useful than initially expected and had quite a
|
||||
few false positive cases.
|
|
@ -1 +0,0 @@
|
|||
The supporting files in the ``.pytest_cache`` directory are kept with ``--cache-clear``, which only clears cached values now.
|
|
@ -0,0 +1 @@
|
|||
Matching of ``-k EXPRESSION`` to test names is now case-insensitive.
|
|
@ -0,0 +1,3 @@
|
|||
Fix summary entries appearing twice when ``f/F`` and ``s/S`` report chars were used at the same time in the ``-r`` command-line option (for example ``-rFf``).
|
||||
|
||||
The upper case variants were never documented and the preferred form should be the lower case.
|
|
@ -0,0 +1 @@
|
|||
Remove usage of ``parser`` module, deprecated in Python 3.9.
|
|
@ -18,7 +18,7 @@ Each file should be named like ``<ISSUE>.<TYPE>.rst``, where
|
|||
* ``bugfix``: fixes a reported bug.
|
||||
* ``doc``: documentation improvement, like rewording an entire session or adding missing docs.
|
||||
* ``deprecation``: feature deprecation.
|
||||
* ``removal``: feature removal.
|
||||
* ``breaking``: a change which may break existing suites, such as feature removal or behavior change.
|
||||
* ``vendor``: changes in packages vendored in pytest.
|
||||
* ``trivial``: fixing a small typo or internal change that might be noteworthy.
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ Release announcements
|
|||
:maxdepth: 2
|
||||
|
||||
|
||||
release-5.3.2
|
||||
release-5.3.1
|
||||
release-5.3.0
|
||||
release-5.2.4
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
pytest-5.3.2
|
||||
=======================================
|
||||
|
||||
pytest 5.3.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 https://docs.pytest.org/en/latest/changelog.html.
|
||||
|
||||
Thanks to all who contributed to this release, among them:
|
||||
|
||||
* Anthony Sottile
|
||||
* Bruno Oliveira
|
||||
* Claudio Madotto
|
||||
* Daniel Hahler
|
||||
* Jared Vasquez
|
||||
* Michael Rose
|
||||
* Ran Benita
|
||||
* Ronny Pfannschmidt
|
||||
* Zac Hatfield-Dodds
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
|
@ -3,6 +3,61 @@
|
|||
Backwards Compatibility Policy
|
||||
==============================
|
||||
|
||||
.. versionadded: 6.0
|
||||
|
||||
pytest is actively evolving and is a project that has been decades in the making,
|
||||
we keep learning about new and better structures to express different details about testing.
|
||||
|
||||
While we implement those modifications we try to ensure an easy transition and don't want to impose unnecessary churn on our users and community/plugin authors.
|
||||
|
||||
As of now, pytest considers multipe types of backward compatibility transitions:
|
||||
|
||||
a) trivial: APIs which trivially translate to the new mechanism,
|
||||
and do not cause problematic changes.
|
||||
|
||||
We try to support those indefinitely while encouraging users to switch to newer/better mechanisms through documentation.
|
||||
|
||||
b) transitional: the old and new API don't conflict
|
||||
and we can help users transition by using warnings, while supporting both for a prolonged time.
|
||||
|
||||
We will only start the removal of deprecated functionality in major releases (e.g. if we deprecate something in 3.0 we will start to remove it in 4.0), and keep it around for at least two minor releases (e.g. if we deprecate something in 3.9 and 4.0 is the next release, we start to remove it in 5.0, not in 4.0).
|
||||
|
||||
When the deprecation expires (e.g. 4.0 is released), we won't remove the deprecated functionality immediately, but will use the standard warning filters to turn them into **errors** by default. This approach makes it explicit that removal is imminent, and still gives you time to turn the deprecated feature into a warning instead of an error so it can be dealt with in your own time. In the next minor release (e.g. 4.1), the feature will be effectively removed.
|
||||
|
||||
|
||||
c) true breakage: should only to be considered when normal transition is unreasonably unsustainable and would offset important development/features by years.
|
||||
In addition, they should be limited to APIs where the number of actual users is very small (for example only impacting some plugins), and can be coordinated with the community in advance.
|
||||
|
||||
Examples for such upcoming changes:
|
||||
|
||||
* removal of ``pytest_runtest_protocol/nextitem`` - `#895`_
|
||||
* rearranging of the node tree to include ``FunctionDefinition``
|
||||
* rearranging of ``SetupState`` `#895`_
|
||||
|
||||
True breakages must be announced first in an issue containing:
|
||||
|
||||
* Detailed description of the change
|
||||
* Rationale
|
||||
* Expected impact on users and plugin authors (example in `#895`_)
|
||||
|
||||
After there's no hard *-1* on the issue it should be followed up by an initial proof-of-concept Pull Request.
|
||||
|
||||
This POC serves as both a coordination point to assess impact and potential inspriation to come up with a transitional solution after all.
|
||||
|
||||
After a reasonable amount of time the PR can be merged to base a new major release.
|
||||
|
||||
For the PR to mature from POC to acceptance, it must contain:
|
||||
* Setup of deprecation errors/warnings that help users fix and port their code. If it is possible to introduce a deprecation period under the current series, before the true breakage, it should be introduced in a separate PR and be part of the current release stream.
|
||||
* Detailed description of the rationale and examples on how to port code in ``doc/en/deprecations.rst``.
|
||||
|
||||
|
||||
History
|
||||
=========
|
||||
|
||||
|
||||
Focus primary on smooth transition - stance (pre 6.0)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Keeping backwards compatibility has a very high priority in the pytest project. Although we have deprecated functionality over the years, most of it is still supported. All deprecations in pytest were done because simpler or more efficient ways of accomplishing the same tasks have emerged, making the old way of doing things unnecessary.
|
||||
|
||||
With the pytest 3.0 release we introduced a clear communication scheme for when we will actually remove the old busted joint and politely ask you to use the new hotness instead, while giving you enough time to adjust your tests or raise concerns if there are valid reasons to keep deprecated functionality around.
|
||||
|
@ -20,3 +75,6 @@ Deprecation Roadmap
|
|||
Features currently deprecated and removed in previous releases can be found in :ref:`deprecations`.
|
||||
|
||||
We track future deprecation and removal of features using milestones and the `deprecation <https://github.com/pytest-dev/pytest/issues?q=label%3A%22type%3A+deprecation%22>`_ and `removal <https://github.com/pytest-dev/pytest/labels/type%3A%20removal>`_ labels on GitHub.
|
||||
|
||||
|
||||
.. _`#895`: https://github.com/pytest-dev/pytest/issues/895
|
||||
|
|
|
@ -21,18 +21,25 @@ file descriptors. This allows to capture output from simple
|
|||
print statements as well as output from a subprocess started by
|
||||
a test.
|
||||
|
||||
.. _capture-method:
|
||||
|
||||
Setting capturing methods or disabling capturing
|
||||
-------------------------------------------------
|
||||
|
||||
There are two ways in which ``pytest`` can perform capturing:
|
||||
There are three ways in which ``pytest`` can perform capturing:
|
||||
|
||||
* file descriptor (FD) level capturing (default): All writes going to the
|
||||
* ``fd`` (file descriptor) level capturing (default): All writes going to the
|
||||
operating system file descriptors 1 and 2 will be captured.
|
||||
|
||||
* ``sys`` level capturing: Only writes to Python files ``sys.stdout``
|
||||
and ``sys.stderr`` will be captured. No capturing of writes to
|
||||
filedescriptors is performed.
|
||||
|
||||
* ``tee-sys`` capturing: Python writes to ``sys.stdout`` and ``sys.stderr``
|
||||
will be captured, however the writes will also be passed-through to
|
||||
the actual ``sys.stdout`` and ``sys.stderr``. This allows output to be
|
||||
'live printed' and captured for plugin use, such as junitxml (new in pytest 5.4).
|
||||
|
||||
.. _`disable capturing`:
|
||||
|
||||
You can influence output capturing mechanisms from the command line:
|
||||
|
@ -42,6 +49,8 @@ You can influence output capturing mechanisms from the command line:
|
|||
pytest -s # disable all capturing
|
||||
pytest --capture=sys # replace sys.stdout/stderr with in-mem files
|
||||
pytest --capture=fd # also point filedescriptors 1 and 2 to temp file
|
||||
pytest --capture=tee-sys # combines 'sys' and '-s', capturing sys.stdout/stderr
|
||||
# and passing it along to the actual sys.stdout/stderr
|
||||
|
||||
.. _printdebugging:
|
||||
|
||||
|
|
|
@ -24,13 +24,32 @@ with advance notice in the **Deprecations** section of releases.
|
|||
|
||||
.. include:: _changelog_towncrier_draft.rst
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.. towncrier release notes start
|
||||
|
||||
pytest 5.3.2 (2019-12-13)
|
||||
=========================
|
||||
|
||||
Improvements
|
||||
------------
|
||||
|
||||
- `#4639 <https://github.com/pytest-dev/pytest/issues/4639>`_: Revert "A warning is now issued when assertions are made for ``None``".
|
||||
|
||||
The warning proved to be less useful than initially expected and had quite a
|
||||
few false positive cases.
|
||||
|
||||
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#5430 <https://github.com/pytest-dev/pytest/issues/5430>`_: junitxml: Logs for failed test are now passed to junit report in case the test fails during call phase.
|
||||
|
||||
|
||||
- `#6290 <https://github.com/pytest-dev/pytest/issues/6290>`_: The supporting files in the ``.pytest_cache`` directory are kept with ``--cache-clear``, which only clears cached values now.
|
||||
|
||||
|
||||
- `#6301 <https://github.com/pytest-dev/pytest/issues/6301>`_: Fix assertion rewriting for egg-based distributions and ``editable`` installs (``pip install --editable``).
|
||||
|
||||
|
||||
pytest 5.3.1 (2019-11-25)
|
||||
=========================
|
||||
|
@ -796,6 +815,20 @@ Improved Documentation
|
|||
- `#5416 <https://github.com/pytest-dev/pytest/issues/5416>`_: Fix PytestUnknownMarkWarning in run/skip example.
|
||||
|
||||
|
||||
pytest 4.6.7 (2019-12-05)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#5477 <https://github.com/pytest-dev/pytest/issues/5477>`_: The XML file produced by ``--junitxml`` now correctly contain a ``<testsuites>`` root element.
|
||||
|
||||
|
||||
- `#6044 <https://github.com/pytest-dev/pytest/issues/6044>`_: Properly ignore ``FileNotFoundError`` (``OSError.errno == NOENT`` in Python 2) exceptions when trying to remove old temporary directories,
|
||||
for instance when multiple processes try to remove the same directory (common with ``pytest-xdist``
|
||||
for example).
|
||||
|
||||
|
||||
pytest 4.6.6 (2019-10-11)
|
||||
=========================
|
||||
|
||||
|
|
|
@ -20,6 +20,20 @@ Below is a complete list of all pytest features which are considered deprecated.
|
|||
:ref:`standard warning filters <warnings>`.
|
||||
|
||||
|
||||
``--no-print-logs`` command-line option
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 5.4
|
||||
|
||||
|
||||
Option ``--no-print-logs`` is deprecated and meant to be removed in a future release. If you use ``--no-print-logs``, please try out ``--show-capture`` and
|
||||
provide feedback.
|
||||
|
||||
``--show-capture`` command-line option was added in ``pytest 3.5.0` and allows to specify how to
|
||||
display captured output when tests fail: ``no``, ``stdout``, ``stderr``, ``log`` or ``all`` (the default).
|
||||
|
||||
|
||||
|
||||
Node Construction changed to ``Node.from_parent``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -148,6 +148,10 @@ which implements a substring match on the test names instead of the
|
|||
exact match on markers that ``-m`` provides. This makes it easy to
|
||||
select tests based on their names:
|
||||
|
||||
.. versionadded: 5.4
|
||||
|
||||
The expression matching is now case-insensitive.
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -v -k http # running with the above defined example module
|
||||
|
|
|
@ -475,10 +475,10 @@ Running it results in some skips if we don't have all the python interpreters in
|
|||
.. code-block:: pytest
|
||||
|
||||
. $ pytest -rs -q multipython.py
|
||||
ssssssssssssssssssssssss... [100%]
|
||||
ssssssssssss...ssssssssssss [100%]
|
||||
========================= short test summary info ==========================
|
||||
SKIPPED [12] $REGENDOC_TMPDIR/CWD/multipython.py:29: 'python3.5' not found
|
||||
SKIPPED [12] $REGENDOC_TMPDIR/CWD/multipython.py:29: 'python3.6' not found
|
||||
SKIPPED [12] $REGENDOC_TMPDIR/CWD/multipython.py:29: 'python3.7' not found
|
||||
3 passed, 24 skipped in 0.12s
|
||||
|
||||
Indirect parametrization of optional implementations/imports
|
||||
|
@ -604,13 +604,13 @@ Then run ``pytest`` with verbose mode and with only the ``basic`` marker:
|
|||
platform linux -- Python 3.x.y, pytest-5.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collecting ... collected 18 items / 15 deselected / 3 selected
|
||||
collecting ... collected 17 items / 14 deselected / 3 selected
|
||||
|
||||
test_pytest_param_example.py::test_eval[1+7-8] PASSED [ 33%]
|
||||
test_pytest_param_example.py::test_eval[basic_2+4] PASSED [ 66%]
|
||||
test_pytest_param_example.py::test_eval[basic_6*9] XFAIL [100%]
|
||||
|
||||
=============== 2 passed, 15 deselected, 1 xfailed in 0.12s ================
|
||||
=============== 2 passed, 14 deselected, 1 xfailed in 0.12s ================
|
||||
|
||||
As the result:
|
||||
|
||||
|
|
|
@ -436,7 +436,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
items = [1, 2, 3]
|
||||
print("items is {!r}".format(items))
|
||||
> a, b = items.pop()
|
||||
E TypeError: cannot unpack non-iterable int object
|
||||
E TypeError: 'int' object is not iterable
|
||||
|
||||
failure_demo.py:181: TypeError
|
||||
--------------------------- Captured stdout call ---------------------------
|
||||
|
@ -516,7 +516,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
def test_z2_type_error(self):
|
||||
items = 3
|
||||
> a, b = items
|
||||
E TypeError: cannot unpack non-iterable int object
|
||||
E TypeError: 'int' object is not iterable
|
||||
|
||||
failure_demo.py:222: TypeError
|
||||
______________________ TestMoreErrors.test_startswith ______________________
|
||||
|
|
|
@ -442,8 +442,8 @@ Now we can profile which test functions execute the slowest:
|
|||
|
||||
========================= slowest 3 test durations =========================
|
||||
0.30s call test_some_are_slow.py::test_funcslow2
|
||||
0.21s call test_some_are_slow.py::test_funcslow1
|
||||
0.11s call test_some_are_slow.py::test_funcfast
|
||||
0.20s call test_some_are_slow.py::test_funcslow1
|
||||
0.10s call test_some_are_slow.py::test_funcfast
|
||||
============================ 3 passed in 0.12s =============================
|
||||
|
||||
incremental testing - test steps
|
||||
|
|
|
@ -28,7 +28,7 @@ Install ``pytest``
|
|||
.. code-block:: bash
|
||||
|
||||
$ pytest --version
|
||||
This is pytest version 5.x.y, imported from $PYTHON_PREFIX/lib/python3.7/site-packages/pytest.py
|
||||
This is pytest version 5.x.y, imported from $PYTHON_PREFIX/lib/python3.6/site-packages/pytest/__init__.py
|
||||
|
||||
.. _`simpletest`:
|
||||
|
||||
|
|
|
@ -112,7 +112,7 @@ Save time, reduce risk, and improve code health, while paying the maintainers of
|
|||
Security
|
||||
^^^^^^^^
|
||||
|
||||
pytest has never been associated with a security vunerability, but in any case, to report a
|
||||
pytest has never been associated with a security vulnerability, but in any case, to report a
|
||||
security vulnerability please use the `Tidelift security contact <https://tidelift.com/security>`_.
|
||||
Tidelift will coordinate the fix and disclosure.
|
||||
|
||||
|
|
|
@ -77,6 +77,8 @@ This is also discussed in details in :ref:`test discovery`.
|
|||
Invoking ``pytest`` versus ``python -m pytest``
|
||||
-----------------------------------------------
|
||||
|
||||
Running pytest with ``python -m pytest [...]`` instead of ``pytest [...]`` yields nearly
|
||||
equivalent behaviour, except that the former call will add the current directory to ``sys.path``.
|
||||
Running pytest with ``pytest [...]`` instead of ``python -m pytest [...]`` yields nearly
|
||||
equivalent behaviour, except that the latter will add the current directory to ``sys.path``, which
|
||||
is standard ``python`` behavior.
|
||||
|
||||
See also :ref:`cmdline`.
|
||||
|
|
|
@ -94,8 +94,8 @@ Pytest supports several ways to run and select tests from the command-line.
|
|||
|
||||
pytest -k "MyClass and not method"
|
||||
|
||||
This will run tests which contain names that match the given *string expression*, which can
|
||||
include Python operators that use filenames, class names and function names as variables.
|
||||
This will run tests which contain names that match the given *string expression* (case-insensitive),
|
||||
which can include Python operators that use filenames, class names and function names as variables.
|
||||
The example above will run ``TestMyClass.test_something`` but not ``TestMyClass.test_method_simple``.
|
||||
|
||||
.. _nodeids:
|
||||
|
|
|
@ -16,8 +16,8 @@ title_format = "pytest {version} ({project_date})"
|
|||
template = "changelog/_template.rst"
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "removal"
|
||||
name = "Removals"
|
||||
directory = "breaking"
|
||||
name = "Breaking Changes"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
|
|
|
@ -140,18 +140,13 @@ class Source:
|
|||
""" return True if source is parseable, heuristically
|
||||
deindenting it by default.
|
||||
"""
|
||||
from parser import suite as syntax_checker
|
||||
|
||||
if deindent:
|
||||
source = str(self.deindent())
|
||||
else:
|
||||
source = str(self)
|
||||
try:
|
||||
# compile(source+'\n', "x", "exec")
|
||||
syntax_checker(source + "\n")
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except Exception:
|
||||
ast.parse(source)
|
||||
except (SyntaxError, ValueError, TypeError):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
@ -339,9 +334,7 @@ def getstatementrange_ast(
|
|||
block_finder.started = source.lines[start][0].isspace()
|
||||
it = ((x + "\n") for x in source.lines[start:end])
|
||||
try:
|
||||
# Type ignored until next mypy release.
|
||||
# https://github.com/python/typeshed/commit/c0d46a20353b733befb85d8b9cc24e5b0bcd8f9a
|
||||
for tok in tokenize.generate_tokens(lambda: next(it)): # type: ignore
|
||||
for tok in tokenize.generate_tokens(lambda: next(it)):
|
||||
block_finder.tokeneater(*tok)
|
||||
except (inspect.EndOfBlock, IndentationError):
|
||||
end = block_finder.last + start
|
||||
|
|
|
@ -33,7 +33,7 @@ def pytest_addoption(parser):
|
|||
)
|
||||
|
||||
|
||||
def register_assert_rewrite(*names):
|
||||
def register_assert_rewrite(*names) -> None:
|
||||
"""Register one or more module names to be rewritten on import.
|
||||
|
||||
This function will make sure that this module or all modules inside
|
||||
|
|
|
@ -11,6 +11,7 @@ from io import UnsupportedOperation
|
|||
from tempfile import TemporaryFile
|
||||
|
||||
import pytest
|
||||
from _pytest.compat import CaptureAndPassthroughIO
|
||||
from _pytest.compat import CaptureIO
|
||||
from _pytest.fixtures import FixtureRequest
|
||||
|
||||
|
@ -24,8 +25,8 @@ def pytest_addoption(parser):
|
|||
action="store",
|
||||
default="fd" if hasattr(os, "dup") else "sys",
|
||||
metavar="method",
|
||||
choices=["fd", "sys", "no"],
|
||||
help="per-test capturing method: one of fd|sys|no.",
|
||||
choices=["fd", "sys", "no", "tee-sys"],
|
||||
help="per-test capturing method: one of fd|sys|no|tee-sys.",
|
||||
)
|
||||
group._addoption(
|
||||
"-s",
|
||||
|
@ -90,6 +91,8 @@ class CaptureManager:
|
|||
return MultiCapture(out=True, err=True, Capture=SysCapture)
|
||||
elif method == "no":
|
||||
return MultiCapture(out=False, err=False, in_=False)
|
||||
elif method == "tee-sys":
|
||||
return MultiCapture(out=True, err=True, in_=False, Capture=TeeSysCapture)
|
||||
raise ValueError("unknown capturing method: %r" % method) # pragma: no cover
|
||||
|
||||
def is_capturing(self):
|
||||
|
@ -681,6 +684,19 @@ class SysCapture:
|
|||
self._old.flush()
|
||||
|
||||
|
||||
class TeeSysCapture(SysCapture):
|
||||
def __init__(self, fd, tmpfile=None):
|
||||
name = patchsysdict[fd]
|
||||
self._old = getattr(sys, name)
|
||||
self.name = name
|
||||
if tmpfile is None:
|
||||
if name == "stdin":
|
||||
tmpfile = DontReadFromInput()
|
||||
else:
|
||||
tmpfile = CaptureAndPassthroughIO(self._old)
|
||||
self.tmpfile = tmpfile
|
||||
|
||||
|
||||
class SysCaptureBinary(SysCapture):
|
||||
# Ignore type because it doesn't match the type in the superclass (str).
|
||||
EMPTY_BUFFER = b"" # type: ignore
|
||||
|
|
|
@ -13,6 +13,7 @@ from inspect import signature
|
|||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Generic
|
||||
from typing import IO
|
||||
from typing import Optional
|
||||
from typing import overload
|
||||
from typing import Tuple
|
||||
|
@ -371,6 +372,16 @@ class CaptureIO(io.TextIOWrapper):
|
|||
return self.buffer.getvalue().decode("UTF-8")
|
||||
|
||||
|
||||
class CaptureAndPassthroughIO(CaptureIO):
|
||||
def __init__(self, other: IO) -> None:
|
||||
self._other = other
|
||||
super().__init__()
|
||||
|
||||
def write(self, s) -> int:
|
||||
super().write(s)
|
||||
return self._other.write(s)
|
||||
|
||||
|
||||
if sys.version_info < (3, 5, 2): # pragma: no cover
|
||||
|
||||
def overload(f): # noqa: F811
|
||||
|
|
|
@ -133,7 +133,13 @@ def directory_arg(path, optname):
|
|||
|
||||
|
||||
# Plugins that cannot be disabled via "-p no:X" currently.
|
||||
essential_plugins = ("mark", "main", "runner", "fixtures", "helpconfig") # Provides -p.
|
||||
essential_plugins = (
|
||||
"mark",
|
||||
"main",
|
||||
"runner",
|
||||
"fixtures",
|
||||
"helpconfig", # Provides -p.
|
||||
)
|
||||
|
||||
default_plugins = essential_plugins + (
|
||||
"python",
|
||||
|
@ -626,16 +632,67 @@ notset = Notset()
|
|||
|
||||
|
||||
def _iter_rewritable_modules(package_files):
|
||||
"""
|
||||
Given an iterable of file names in a source distribution, return the "names" that should
|
||||
be marked for assertion rewrite (for example the package "pytest_mock/__init__.py" should
|
||||
be added as "pytest_mock" in the assertion rewrite mechanism.
|
||||
|
||||
This function has to deal with dist-info based distributions and egg based distributions
|
||||
(which are still very much in use for "editable" installs).
|
||||
|
||||
Here are the file names as seen in a dist-info based distribution:
|
||||
|
||||
pytest_mock/__init__.py
|
||||
pytest_mock/_version.py
|
||||
pytest_mock/plugin.py
|
||||
pytest_mock.egg-info/PKG-INFO
|
||||
|
||||
Here are the file names as seen in an egg based distribution:
|
||||
|
||||
src/pytest_mock/__init__.py
|
||||
src/pytest_mock/_version.py
|
||||
src/pytest_mock/plugin.py
|
||||
src/pytest_mock.egg-info/PKG-INFO
|
||||
LICENSE
|
||||
setup.py
|
||||
|
||||
We have to take in account those two distribution flavors in order to determine which
|
||||
names should be considered for assertion rewriting.
|
||||
|
||||
More information:
|
||||
https://github.com/pytest-dev/pytest-mock/issues/167
|
||||
"""
|
||||
package_files = list(package_files)
|
||||
seen_some = False
|
||||
for fn in package_files:
|
||||
is_simple_module = "/" not in fn and fn.endswith(".py")
|
||||
is_package = fn.count("/") == 1 and fn.endswith("__init__.py")
|
||||
if is_simple_module:
|
||||
module_name, _ = os.path.splitext(fn)
|
||||
# we ignore "setup.py" at the root of the distribution
|
||||
if module_name != "setup":
|
||||
seen_some = True
|
||||
yield module_name
|
||||
elif is_package:
|
||||
package_name = os.path.dirname(fn)
|
||||
seen_some = True
|
||||
yield package_name
|
||||
|
||||
if not seen_some:
|
||||
# at this point we did not find any packages or modules suitable for assertion
|
||||
# rewriting, so we try again by stripping the first path component (to account for
|
||||
# "src" based source trees for example)
|
||||
# this approach lets us have the common case continue to be fast, as egg-distributions
|
||||
# are rarer
|
||||
new_package_files = []
|
||||
for fn in package_files:
|
||||
parts = fn.split("/")
|
||||
new_fn = "/".join(parts[1:])
|
||||
if new_fn:
|
||||
new_package_files.append(new_fn)
|
||||
if new_package_files:
|
||||
yield from _iter_rewritable_modules(new_package_files)
|
||||
|
||||
|
||||
class Config:
|
||||
"""
|
||||
|
|
|
@ -19,13 +19,11 @@ DEPRECATED_EXTERNAL_PLUGINS = {
|
|||
"pytest_faulthandler",
|
||||
}
|
||||
|
||||
|
||||
FUNCARGNAMES = PytestDeprecationWarning(
|
||||
"The `funcargnames` attribute was an alias for `fixturenames`, "
|
||||
"since pytest 2.3 - use the newer attribute instead."
|
||||
)
|
||||
|
||||
|
||||
RESULT_LOG = PytestDeprecationWarning(
|
||||
"--result-log is deprecated, please try the new pytest-reportlog plugin.\n"
|
||||
"See https://docs.pytest.org/en/latest/deprecations.html#result-log-result-log for more information."
|
||||
|
@ -43,5 +41,11 @@ NODE_USE_FROM_PARENT = UnformattedWarning(
|
|||
|
||||
JUNIT_XML_DEFAULT_FAMILY = PytestDeprecationWarning(
|
||||
"The 'junit_family' default value will change to 'xunit2' in pytest 6.0.\n"
|
||||
"Add 'junit_family=legacy' to your pytest.ini file to silence this warning and make your suite compatible."
|
||||
"Add 'junit_family=xunit1' to your pytest.ini file to keep the current format "
|
||||
"in future versions of pytest and silence this warning."
|
||||
)
|
||||
|
||||
NO_PRINT_LOGS = PytestDeprecationWarning(
|
||||
"--no-print-logs is deprecated and scheduled for removal in pytest 6.0.\n"
|
||||
"Please use --show-capture instead."
|
||||
)
|
||||
|
|
|
@ -451,7 +451,9 @@ class DoctestModule(pytest.Module):
|
|||
obj = getattr(obj, "fget", obj)
|
||||
return doctest.DocTestFinder._find_lineno(self, obj, source_lines)
|
||||
|
||||
def _find(self, tests, obj, name, module, source_lines, globs, seen):
|
||||
def _find(
|
||||
self, tests, obj, name, module, source_lines, globs, seen
|
||||
) -> None:
|
||||
if _is_mocked(obj):
|
||||
return
|
||||
with _patch_unwrap_mock_aware():
|
||||
|
|
|
@ -591,6 +591,8 @@ class LogXML:
|
|||
if report.when == "call":
|
||||
reporter.append_failure(report)
|
||||
self.open_reports.append(report)
|
||||
if not self.log_passing_tests:
|
||||
reporter.write_captured_output(report)
|
||||
else:
|
||||
reporter.append_error(report)
|
||||
elif report.skipped:
|
||||
|
|
|
@ -485,6 +485,12 @@ class LoggingPlugin:
|
|||
self._config = config
|
||||
|
||||
self.print_logs = get_option_ini(config, "log_print")
|
||||
if not self.print_logs:
|
||||
from _pytest.warnings import _issue_warning_captured
|
||||
from _pytest.deprecated import NO_PRINT_LOGS
|
||||
|
||||
_issue_warning_captured(NO_PRINT_LOGS, self._config.hook, stacklevel=2)
|
||||
|
||||
self.formatter = self._create_formatter(
|
||||
get_option_ini(config, "log_format"),
|
||||
get_option_ini(config, "log_date_format"),
|
||||
|
|
|
@ -52,7 +52,8 @@ def pytest_addoption(parser):
|
|||
"-k 'not test_method and not test_other' will eliminate the matches. "
|
||||
"Additionally keywords are matched to classes and functions "
|
||||
"containing extra names in their 'extra_keyword_matches' set, "
|
||||
"as well as functions which have names assigned directly to them.",
|
||||
"as well as functions which have names assigned directly to them. "
|
||||
"The matching is case-insensitive.",
|
||||
)
|
||||
|
||||
group._addoption(
|
||||
|
|
|
@ -57,7 +57,15 @@ class KeywordMapping:
|
|||
return cls(mapped_names)
|
||||
|
||||
def __getitem__(self, subname):
|
||||
for name in self._names:
|
||||
"""Return whether subname is included within stored names.
|
||||
|
||||
The string inclusion check is case-insensitive.
|
||||
|
||||
"""
|
||||
subname = subname.lower()
|
||||
names = (name.lower() for name in self._names)
|
||||
|
||||
for name in names:
|
||||
if subname in name:
|
||||
return True
|
||||
return False
|
||||
|
|
|
@ -829,7 +829,7 @@ class Testdir:
|
|||
items = [x.item for x in rec.getcalls("pytest_itemcollected")]
|
||||
return items, rec
|
||||
|
||||
def inline_run(self, *args, plugins=(), no_reraise_ctrlc=False):
|
||||
def inline_run(self, *args, plugins=(), no_reraise_ctrlc: bool = False):
|
||||
"""Run ``pytest.main()`` in-process, returning a HookRecorder.
|
||||
|
||||
Runs the :py:func:`pytest.main` function to run all of pytest inside
|
||||
|
|
|
@ -129,9 +129,7 @@ def warns( # noqa: F811
|
|||
return func(*args[1:], **kwargs)
|
||||
|
||||
|
||||
# Type ignored until next mypy release. Regression fixed by:
|
||||
# https://github.com/python/typeshed/commit/41bf6a19822d6694973449d795f8bfe1d50d5a03
|
||||
class WarningsRecorder(warnings.catch_warnings): # type: ignore
|
||||
class WarningsRecorder(warnings.catch_warnings):
|
||||
"""A context manager to record raised warnings.
|
||||
|
||||
Adapted from `warnings.catch_warnings`.
|
||||
|
|
|
@ -259,7 +259,7 @@ class TestReport(BaseReport):
|
|||
)
|
||||
|
||||
@classmethod
|
||||
def from_item_and_call(cls, item, call):
|
||||
def from_item_and_call(cls, item, call) -> "TestReport":
|
||||
"""
|
||||
Factory method to create and fill a TestReport with standard item and call info.
|
||||
"""
|
||||
|
|
|
@ -250,7 +250,7 @@ def pytest_runtest_makereport(item, call):
|
|||
return TestReport.from_item_and_call(item, call)
|
||||
|
||||
|
||||
def pytest_make_collect_report(collector):
|
||||
def pytest_make_collect_report(collector) -> CollectReport:
|
||||
call = CallInfo.from_call(lambda: list(collector.collect()), "collect")
|
||||
longrepr = None
|
||||
if not call.excinfo:
|
||||
|
|
|
@ -172,7 +172,11 @@ def getreportopt(config: Config) -> str:
|
|||
reportchars += "w"
|
||||
elif config.option.disable_warnings and "w" in reportchars:
|
||||
reportchars = reportchars.replace("w", "")
|
||||
aliases = {"F", "S"}
|
||||
for char in reportchars:
|
||||
# handle old aliases
|
||||
if char in aliases:
|
||||
char = char.lower()
|
||||
if char == "a":
|
||||
reportopts = "sxXwEf"
|
||||
elif char == "A":
|
||||
|
@ -185,19 +189,16 @@ def getreportopt(config: Config) -> str:
|
|||
|
||||
@pytest.hookimpl(trylast=True) # after _pytest.runner
|
||||
def pytest_report_teststatus(report: TestReport) -> Tuple[str, str, str]:
|
||||
letter = "F"
|
||||
if report.passed:
|
||||
letter = "."
|
||||
elif report.skipped:
|
||||
letter = "s"
|
||||
elif report.failed:
|
||||
letter = "F"
|
||||
if report.when != "call":
|
||||
letter = "f"
|
||||
|
||||
# Report failed CollectReports as "error" (in line with pytest_collectreport).
|
||||
outcome = report.outcome
|
||||
if report.when == "collect" and outcome == "failed":
|
||||
if report.when in ("collect", "setup", "teardown") and outcome == "failed":
|
||||
outcome = "error"
|
||||
letter = "E"
|
||||
|
||||
return outcome, letter, outcome.upper()
|
||||
|
||||
|
@ -988,9 +989,7 @@ class TerminalReporter:
|
|||
"x": show_xfailed,
|
||||
"X": show_xpassed,
|
||||
"f": partial(show_simple, "failed"),
|
||||
"F": partial(show_simple, "failed"),
|
||||
"s": show_skipped,
|
||||
"S": show_skipped,
|
||||
"p": partial(show_simple, "passed"),
|
||||
"E": partial(show_simple, "error"),
|
||||
} # type: Mapping[str, Callable[[List[str]], None]]
|
||||
|
|
|
@ -1285,3 +1285,28 @@ def test_pdb_can_be_rewritten(testdir):
|
|||
]
|
||||
)
|
||||
assert result.ret == 1
|
||||
|
||||
|
||||
def test_tee_stdio_captures_and_live_prints(testdir):
|
||||
testpath = testdir.makepyfile(
|
||||
"""
|
||||
import sys
|
||||
def test_simple():
|
||||
print ("@this is stdout@")
|
||||
print ("@this is stderr@", file=sys.stderr)
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest_subprocess(
|
||||
testpath, "--capture=tee-sys", "--junitxml=output.xml"
|
||||
)
|
||||
|
||||
# ensure stdout/stderr were 'live printed'
|
||||
result.stdout.fnmatch_lines(["*@this is stdout@*"])
|
||||
result.stderr.fnmatch_lines(["*@this is stderr@*"])
|
||||
|
||||
# now ensure the output is in the junitxml
|
||||
with open(os.path.join(testdir.tmpdir.strpath, "output.xml"), "r") as f:
|
||||
fullXml = f.read()
|
||||
|
||||
assert "<system-out>@this is stdout@\n</system-out>" in fullXml
|
||||
assert "<system-err>@this is stderr@\n</system-err>" in fullXml
|
||||
|
|
|
@ -318,16 +318,12 @@ class TestSourceParsingAndCompiling:
|
|||
|
||||
@pytest.mark.parametrize("name", ["", None, "my"])
|
||||
def test_compilefuncs_and_path_sanity(self, name: Optional[str]) -> None:
|
||||
def check(comp, name):
|
||||
def check(comp, name) -> None:
|
||||
co = comp(self.source, name)
|
||||
if not name:
|
||||
expected = "codegen %s:%d>" % (mypath, mylineno + 2 + 2) # type: ignore
|
||||
else:
|
||||
expected = "codegen %r %s:%d>" % (
|
||||
name,
|
||||
mypath, # type: ignore
|
||||
mylineno + 2 + 2, # type: ignore
|
||||
) # type: ignore
|
||||
expected = "codegen %r %s:%d>" % (name, mypath, mylineno + 2 + 2) # type: ignore
|
||||
fn = co.co_filename
|
||||
assert fn.endswith(expected)
|
||||
|
||||
|
|
|
@ -90,3 +90,44 @@ def test_node_direct_ctor_warning():
|
|||
nodes.Node(name="test", config=ms, session=ms, nodeid="None")
|
||||
assert w[0].lineno == inspect.currentframe().f_lineno - 1
|
||||
assert w[0].filename == __file__
|
||||
|
||||
|
||||
def assert_no_print_logs(testdir, args):
|
||||
result = testdir.runpytest(*args)
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"*--no-print-logs is deprecated and scheduled for removal in pytest 6.0*",
|
||||
"*Please use --show-capture instead.*",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings("default")
|
||||
def test_noprintlogs_is_deprecated_cmdline(testdir):
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
def test_foo():
|
||||
pass
|
||||
"""
|
||||
)
|
||||
|
||||
assert_no_print_logs(testdir, ("--no-print-logs",))
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings("default")
|
||||
def test_noprintlogs_is_deprecated_ini(testdir):
|
||||
testdir.makeini(
|
||||
"""
|
||||
[pytest]
|
||||
log_print=False
|
||||
"""
|
||||
)
|
||||
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
def test_foo():
|
||||
pass
|
||||
"""
|
||||
)
|
||||
|
||||
assert_no_print_logs(testdir, ())
|
||||
|
|
|
@ -32,6 +32,10 @@ def StdCapture(out=True, err=True, in_=True):
|
|||
return capture.MultiCapture(out, err, in_, Capture=capture.SysCapture)
|
||||
|
||||
|
||||
def TeeStdCapture(out=True, err=True, in_=True):
|
||||
return capture.MultiCapture(out, err, in_, Capture=capture.TeeSysCapture)
|
||||
|
||||
|
||||
class TestCaptureManager:
|
||||
def test_getmethod_default_no_fd(self, monkeypatch):
|
||||
from _pytest.capture import pytest_addoption
|
||||
|
@ -816,6 +820,25 @@ class TestCaptureIO:
|
|||
assert f.getvalue() == "foo\r\n"
|
||||
|
||||
|
||||
class TestCaptureAndPassthroughIO(TestCaptureIO):
|
||||
def test_text(self):
|
||||
sio = io.StringIO()
|
||||
f = capture.CaptureAndPassthroughIO(sio)
|
||||
f.write("hello")
|
||||
s1 = f.getvalue()
|
||||
assert s1 == "hello"
|
||||
s2 = sio.getvalue()
|
||||
assert s2 == s1
|
||||
f.close()
|
||||
sio.close()
|
||||
|
||||
def test_unicode_and_str_mixture(self):
|
||||
sio = io.StringIO()
|
||||
f = capture.CaptureAndPassthroughIO(sio)
|
||||
f.write("\u00f6")
|
||||
pytest.raises(TypeError, f.write, b"hello")
|
||||
|
||||
|
||||
def test_dontreadfrominput():
|
||||
from _pytest.capture import DontReadFromInput
|
||||
|
||||
|
@ -1112,6 +1135,23 @@ class TestStdCapture:
|
|||
pytest.raises(IOError, sys.stdin.read)
|
||||
|
||||
|
||||
class TestTeeStdCapture(TestStdCapture):
|
||||
captureclass = staticmethod(TeeStdCapture)
|
||||
|
||||
def test_capturing_error_recursive(self):
|
||||
""" for TeeStdCapture since we passthrough stderr/stdout, cap1
|
||||
should get all output, while cap2 should only get "cap2\n" """
|
||||
|
||||
with self.getcapture() as cap1:
|
||||
print("cap1")
|
||||
with self.getcapture() as cap2:
|
||||
print("cap2")
|
||||
out2, err2 = cap2.readouterr()
|
||||
out1, err1 = cap1.readouterr()
|
||||
assert out1 == "cap1\ncap2\n"
|
||||
assert out2 == "cap2\n"
|
||||
|
||||
|
||||
class TestStdCaptureFD(TestStdCapture):
|
||||
pytestmark = needsosdup
|
||||
captureclass = staticmethod(StdCaptureFD)
|
||||
|
@ -1252,7 +1292,7 @@ def test_close_and_capture_again(testdir):
|
|||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("method", ["SysCapture", "FDCapture"])
|
||||
@pytest.mark.parametrize("method", ["SysCapture", "FDCapture", "TeeSysCapture"])
|
||||
def test_capturing_and_logging_fundamentals(testdir, method):
|
||||
if method == "StdCaptureFD" and not hasattr(os, "dup"):
|
||||
pytest.skip("need os.dup")
|
||||
|
|
|
@ -809,6 +809,43 @@ class TestNodekeywords:
|
|||
reprec = testdir.inline_run("-k repr")
|
||||
reprec.assertoutcome(passed=1, failed=0)
|
||||
|
||||
def test_keyword_matching_is_case_insensitive_by_default(self, testdir):
|
||||
"""Check that selection via -k EXPRESSION is case-insensitive.
|
||||
|
||||
Since markers are also added to the node keywords, they too can
|
||||
be matched without having to think about case sensitivity.
|
||||
|
||||
"""
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
def test_sPeCiFiCToPiC_1():
|
||||
assert True
|
||||
|
||||
class TestSpecificTopic_2:
|
||||
def test(self):
|
||||
assert True
|
||||
|
||||
@pytest.mark.sPeCiFiCToPic_3
|
||||
def test():
|
||||
assert True
|
||||
|
||||
@pytest.mark.sPeCiFiCToPic_4
|
||||
class Test:
|
||||
def test(self):
|
||||
assert True
|
||||
|
||||
def test_failing_5():
|
||||
assert False, "This should not match"
|
||||
|
||||
"""
|
||||
)
|
||||
num_matching_tests = 4
|
||||
for expression in ("specifictopic", "SPECIFICTOPIC", "SpecificTopic"):
|
||||
reprec = testdir.inline_run("-k " + expression)
|
||||
reprec.assertoutcome(passed=num_matching_tests, failed=0)
|
||||
|
||||
|
||||
COLLECTION_ERROR_PY_FILES = dict(
|
||||
test_01_failure="""
|
||||
|
|
|
@ -422,15 +422,21 @@ class TestConfigAPI:
|
|||
@pytest.mark.parametrize(
|
||||
"names, expected",
|
||||
[
|
||||
# dist-info based distributions root are files as will be put in PYTHONPATH
|
||||
(["bar.py"], ["bar"]),
|
||||
(["foo", "bar.py"], []),
|
||||
(["foo", "bar.pyc"], []),
|
||||
(["foo", "__init__.py"], ["foo"]),
|
||||
(["foo", "bar", "__init__.py"], []),
|
||||
(["foo/bar.py"], ["bar"]),
|
||||
(["foo/bar.pyc"], []),
|
||||
(["foo/__init__.py"], ["foo"]),
|
||||
(["bar/__init__.py", "xz.py"], ["bar", "xz"]),
|
||||
(["setup.py"], []),
|
||||
# egg based distributions root contain the files from the dist root
|
||||
(["src/bar/__init__.py"], ["bar"]),
|
||||
(["src/bar/__init__.py", "setup.py"], ["bar"]),
|
||||
(["source/python/bar/__init__.py", "setup.py"], ["bar"]),
|
||||
],
|
||||
)
|
||||
def test_iter_rewritable_modules(self, names, expected):
|
||||
assert list(_iter_rewritable_modules(["/".join(names)])) == expected
|
||||
assert list(_iter_rewritable_modules(names)) == expected
|
||||
|
||||
|
||||
class TestConfigFromdictargs:
|
||||
|
|
|
@ -1477,3 +1477,45 @@ def test_logging_passing_tests_disabled_does_not_log_test_output(
|
|||
node = dom.find_first_by_tag("testcase")
|
||||
assert len(node.find_by_tag("system-err")) == 0
|
||||
assert len(node.find_by_tag("system-out")) == 0
|
||||
|
||||
|
||||
@parametrize_families
|
||||
@pytest.mark.parametrize("junit_logging", ["no", "system-out", "system-err"])
|
||||
def test_logging_passing_tests_disabled_logs_output_for_failing_test_issue5430(
|
||||
testdir, junit_logging, run_and_parse, xunit_family
|
||||
):
|
||||
testdir.makeini(
|
||||
"""
|
||||
[pytest]
|
||||
junit_log_passing_tests=False
|
||||
junit_family={family}
|
||||
""".format(
|
||||
family=xunit_family
|
||||
)
|
||||
)
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
import logging
|
||||
import sys
|
||||
|
||||
def test_func():
|
||||
logging.warning('hello')
|
||||
assert 0
|
||||
"""
|
||||
)
|
||||
result, dom = run_and_parse(
|
||||
"-o", "junit_logging=%s" % junit_logging, family=xunit_family
|
||||
)
|
||||
assert result.ret == 1
|
||||
node = dom.find_first_by_tag("testcase")
|
||||
if junit_logging == "system-out":
|
||||
assert len(node.find_by_tag("system-err")) == 0
|
||||
assert len(node.find_by_tag("system-out")) == 1
|
||||
elif junit_logging == "system-err":
|
||||
assert len(node.find_by_tag("system-err")) == 1
|
||||
assert len(node.find_by_tag("system-out")) == 0
|
||||
else:
|
||||
assert junit_logging == "no"
|
||||
assert len(node.find_by_tag("system-err")) == 0
|
||||
assert len(node.find_by_tag("system-out")) == 0
|
||||
|
|
|
@ -754,6 +754,35 @@ class TestTerminalFunctional:
|
|||
result = testdir.runpytest(*params)
|
||||
result.stdout.fnmatch_lines(["collected 3 items", "hello from hook: 3 items"])
|
||||
|
||||
def test_summary_f_alias(self, testdir):
|
||||
"""Test that 'f' and 'F' report chars are aliases and don't show up twice in the summary (#6334)"""
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
def test():
|
||||
assert False
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest("-rfF")
|
||||
expected = "FAILED test_summary_f_alias.py::test - assert False"
|
||||
result.stdout.fnmatch_lines([expected])
|
||||
assert result.stdout.lines.count(expected) == 1
|
||||
|
||||
def test_summary_s_alias(self, testdir):
|
||||
"""Test that 's' and 'S' report chars are aliases and don't show up twice in the summary"""
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
@pytest.mark.skip
|
||||
def test():
|
||||
pass
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest("-rsS")
|
||||
expected = "SKIPPED [1] test_summary_s_alias.py:3: unconditional skip"
|
||||
result.stdout.fnmatch_lines([expected])
|
||||
assert result.stdout.lines.count(expected) == 1
|
||||
|
||||
|
||||
def test_fail_extra_reporting(testdir, monkeypatch):
|
||||
monkeypatch.setenv("COLUMNS", "80")
|
||||
|
@ -1685,12 +1714,16 @@ class TestProgressWithTeardown:
|
|||
testdir.makepyfile(
|
||||
"""
|
||||
def test_foo(fail_teardown):
|
||||
assert False
|
||||
assert 0
|
||||
"""
|
||||
)
|
||||
output = testdir.runpytest()
|
||||
output = testdir.runpytest("-rfE")
|
||||
output.stdout.re_match_lines(
|
||||
[r"test_teardown_with_test_also_failing.py FE\s+\[100%\]"]
|
||||
[
|
||||
r"test_teardown_with_test_also_failing.py FE\s+\[100%\]",
|
||||
"FAILED test_teardown_with_test_also_failing.py::test_foo - assert 0",
|
||||
"ERROR test_teardown_with_test_also_failing.py::test_foo - assert False",
|
||||
]
|
||||
)
|
||||
|
||||
def test_teardown_many(self, testdir, many_files):
|
||||
|
|
Loading…
Reference in New Issue