Merge remote-tracking branch 'upstream/features' into gftea/features

This commit is contained in:
Bruno Oliveira 2020-01-09 18:41:47 -03:00
commit 5b295ec68e
50 changed files with 562 additions and 84 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1 @@
New :ref:`--capture=tee-sys <capture-method>` option to allow both live printing and capturing of test output.

View File

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

View File

@ -1 +0,0 @@
The supporting files in the ``.pytest_cache`` directory are kept with ``--cache-clear``, which only clears cached values now.

View File

@ -0,0 +1 @@
Matching of ``-k EXPRESSION`` to test names is now case-insensitive.

View File

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

View File

@ -0,0 +1 @@
Remove usage of ``parser`` module, deprecated in Python 3.9.

View File

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

View File

@ -6,6 +6,7 @@ Release announcements
:maxdepth: 2
release-5.3.2
release-5.3.1
release-5.3.0
release-5.2.4

View File

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

View File

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

View File

@ -21,27 +21,36 @@ 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:
.. code-block:: bash
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 -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:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)
yield module_name
# 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:
"""

View File

@ -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."
)

View File

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

View File

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

View File

@ -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"),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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