Merge branch 'features' into unittest-debug

This commit is contained in:
Bruno Oliveira 2019-12-03 10:52:53 -03:00 committed by GitHub
commit 41b7b109e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
140 changed files with 2151 additions and 1287 deletions

View File

@ -2,15 +2,22 @@
Thanks for submitting a PR, your contribution is really appreciated!
Here is a quick checklist that should be present in PRs.
(please delete this text from the final description, this is just a guideline)
-->
- [ ] Target the `master` branch for bug fixes, documentation updates and trivial changes.
- [ ] Target the `features` branch for new features, improvements, and removals/deprecations.
- [ ] Include documentation when adding new features.
- [ ] Include new tests or update existing tests when applicable.
Unless your change is trivial or a small documentation fix (e.g., a typo or reword of a small section) please:
Unless your change is trivial or a small documentation fix (e.g., a typo or reword of a small section) please:
- [ ] Create a new changelog file in the `changelog` folder, with a name like `<ISSUE NUMBER>.<TYPE>.rst`. See [changelog/README.rst](https://github.com/pytest-dev/pytest/blob/master/changelog/README.rst) for details.
- [ ] Add yourself to `AUTHORS` in alphabetical order;
Write sentences in the **past or present tense**, examples:
* *Improved verbose diff output with sequences.*
* *Terminal summary statistics now use multiple colors.*
Also make sure to end the sentence with a `.`.
- [ ] Add yourself to `AUTHORS` in alphabetical order.
-->

1
.gitignore vendored
View File

@ -31,6 +31,7 @@ dist/
issue/
env/
.env/
.venv/
3rdparty/
.tox
.cache

View File

@ -1,7 +1,7 @@
exclude: doc/en/example/py2py3/test_py2.py
repos:
- repo: https://github.com/psf/black
rev: 19.3b0
rev: 19.10b0
hooks:
- id: black
args: [--safe, --quiet]
@ -37,12 +37,8 @@ repos:
hooks:
- id: pyupgrade
args: [--py3-plus]
- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.4.0
hooks:
- id: rst-backticks
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.740
rev: v0.750
hooks:
- id: mypy
files: ^(src/|testing/)

View File

@ -108,7 +108,7 @@ before_script:
export _PYTEST_TOX_EXTRA_DEP=coverage-enable-subprocess
fi
script: tox -vv
script: tox
after_success:
- |

View File

@ -70,6 +70,7 @@ Daniel Hahler
Daniel Nuri
Daniel Wandschneider
Danielle Jenkins
Daniil Galiev
Dave Hunt
David Díaz-Barquero
David Mohr
@ -134,6 +135,7 @@ Jordan Guymon
Jordan Moldow
Jordan Speicher
Joseph Hunkeler
Josh Karpel
Joshua Bronson
Jurko Gospodnetić
Justyna Janczyszyn
@ -163,6 +165,7 @@ Marcelo Duarte Trevisani
Marcin Bachry
Marco Gorelli
Mark Abramowitz
Mark Dickinson
Markus Unterwaditzer
Martijn Faassen
Martin Altmayer
@ -204,6 +207,7 @@ Oscar Benjamin
Patrick Hayes
Paweł Adamczak
Pedro Algarvio
Philipp Loose
Pieter Mulder
Piotr Banaszkiewicz
Pulkit Goyal
@ -263,6 +267,7 @@ Virgil Dupras
Vitaly Lashmanov
Vlad Dragos
Volodymyr Piskun
Wei Lin
Wil Cooley
William Lee
Wim Glenn

View File

@ -18,6 +18,238 @@ with advance notice in the **Deprecations** section of releases.
.. towncrier release notes start
pytest 5.3.0 (2019-11-19)
=========================
Deprecations
------------
- `#6179 <https://github.com/pytest-dev/pytest/issues/6179>`_: The default value of ``junit_family`` option will change to ``xunit2`` in pytest 6.0, given
that this is the version supported by default in modern tools that manipulate this type of file.
In order to smooth the transition, pytest will issue a warning in case the ``--junitxml`` option
is given in the command line but ``junit_family`` is not explicitly configured in ``pytest.ini``.
For more information, `see the docs <https://docs.pytest.org/en/latest/deprecations.html#junit-family-default-value-change-to-xunit2>`__.
Features
--------
- `#4488 <https://github.com/pytest-dev/pytest/issues/4488>`_: The pytest team has created the `pytest-reportlog <https://github.com/pytest-dev/pytest-reportlog>`__
plugin, which provides a new ``--report-log=FILE`` option that writes *report logs* into a file as the test session executes.
Each line of the report log contains a self contained JSON object corresponding to a testing event,
such as a collection or a test result report. The file is guaranteed to be flushed after writing
each line, so systems can read and process events in real-time.
The plugin is meant to replace the ``--resultlog`` option, which is deprecated and meant to be removed
in a future release. If you use ``--resultlog``, please try out ``pytest-reportlog`` and
provide feedback.
- `#4730 <https://github.com/pytest-dev/pytest/issues/4730>`_: When ``sys.pycache_prefix`` (Python 3.8+) is set, it will be used by pytest to cache test files changed by the assertion rewriting mechanism.
This makes it easier to benefit of cached ``.pyc`` files even on file systems without permissions.
- `#5515 <https://github.com/pytest-dev/pytest/issues/5515>`_: Allow selective auto-indentation of multiline log messages.
Adds command line option ``--log-auto-indent``, config option
``log_auto_indent`` and support for per-entry configuration of
indentation behavior on calls to ``logging.log()``.
Alters the default for auto-indention from ``on`` to ``off``. This
restores the older behavior that existed prior to v4.6.0. This
reversion to earlier behavior was done because it is better to
activate new features that may lead to broken tests explicitly
rather than implicitly.
- `#5914 <https://github.com/pytest-dev/pytest/issues/5914>`_: ``pytester`` learned two new functions, `no_fnmatch_line <https://docs.pytest.org/en/latest/reference.html#_pytest.pytester.LineMatcher.no_fnmatch_line>`_ and
`no_re_match_line <https://docs.pytest.org/en/latest/reference.html#_pytest.pytester.LineMatcher.no_re_match_line>`_.
The functions are used to ensure the captured text *does not* match the given
pattern.
The previous idiom was to use ``re.match``:
.. code-block:: python
assert re.match(pat, result.stdout.str()) is None
Or the ``in`` operator:
.. code-block:: python
assert text in result.stdout.str()
But the new functions produce best output on failure.
- `#6057 <https://github.com/pytest-dev/pytest/issues/6057>`_: Added tolerances to complex values when printing ``pytest.approx``.
For example, ``repr(pytest.approx(3+4j))`` returns ``(3+4j) ± 5e-06 ∠ ±180°``. This is polar notation indicating a circle around the expected value, with a radius of 5e-06. For ``approx`` comparisons to return ``True``, the actual value should fall within this circle.
- `#6061 <https://github.com/pytest-dev/pytest/issues/6061>`_: Added the pluginmanager as an argument to ``pytest_addoption``
so that hooks can be invoked when setting up command line options. This is
useful for having one plugin communicate things to another plugin,
such as default values or which set of command line options to add.
Improvements
------------
- `#5061 <https://github.com/pytest-dev/pytest/issues/5061>`_: Use multiple colors with terminal summary statistics.
- `#5630 <https://github.com/pytest-dev/pytest/issues/5630>`_: Quitting from debuggers is now properly handled in ``doctest`` items.
- `#5924 <https://github.com/pytest-dev/pytest/issues/5924>`_: Improved verbose diff output with sequences.
Before:
::
E AssertionError: assert ['version', '...version_info'] == ['version', '...version', ...]
E Right contains 3 more items, first extra item: ' '
E Full diff:
E - ['version', 'version_info', 'sys.version', 'sys.version_info']
E + ['version',
E + 'version_info',
E + 'sys.version',
E + 'sys.version_info',
E + ' ',
E + 'sys.version',
E + 'sys.version_info']
After:
::
E AssertionError: assert ['version', '...version_info'] == ['version', '...version', ...]
E Right contains 3 more items, first extra item: ' '
E Full diff:
E [
E 'version',
E 'version_info',
E 'sys.version',
E 'sys.version_info',
E + ' ',
E + 'sys.version',
E + 'sys.version_info',
E ]
- `#5934 <https://github.com/pytest-dev/pytest/issues/5934>`_: ``repr`` of ``ExceptionInfo`` objects has been improved to honor the ``__repr__`` method of the underlying exception.
- `#5936 <https://github.com/pytest-dev/pytest/issues/5936>`_: Display untruncated assertion message with ``-vv``.
- `#5990 <https://github.com/pytest-dev/pytest/issues/5990>`_: Fixed plurality mismatch in test summary (e.g. display "1 error" instead of "1 errors").
- `#6008 <https://github.com/pytest-dev/pytest/issues/6008>`_: ``Config.InvocationParams.args`` is now always a ``tuple`` to better convey that it should be
immutable and avoid accidental modifications.
- `#6023 <https://github.com/pytest-dev/pytest/issues/6023>`_: ``pytest.main`` returns a ``pytest.ExitCode`` instance now, except for when custom exit codes are used (where it returns ``int`` then still).
- `#6026 <https://github.com/pytest-dev/pytest/issues/6026>`_: Align prefixes in output of pytester's ``LineMatcher``.
- `#6059 <https://github.com/pytest-dev/pytest/issues/6059>`_: Collection errors are reported as errors (and not failures like before) in the terminal's short test summary.
- `#6069 <https://github.com/pytest-dev/pytest/issues/6069>`_: ``pytester.spawn`` does not skip/xfail tests on FreeBSD anymore unconditionally.
- `#6097 <https://github.com/pytest-dev/pytest/issues/6097>`_: The "[...%]" indicator in the test summary is now colored according to the final (new) multi-colored line's main color.
- `#6116 <https://github.com/pytest-dev/pytest/issues/6116>`_: Added ``--co`` as a synonym to ``--collect-only``.
- `#6148 <https://github.com/pytest-dev/pytest/issues/6148>`_: ``atomicwrites`` is now only used on Windows, fixing a performance regression with assertion rewriting on Unix.
- `#6152 <https://github.com/pytest-dev/pytest/issues/6152>`_: Now parametrization will use the ``__name__`` attribute of any object for the id, if present. Previously it would only use ``__name__`` for functions and classes.
- `#6176 <https://github.com/pytest-dev/pytest/issues/6176>`_: Improved failure reporting with pytester's ``Hookrecorder.assertoutcome``.
- `#6181 <https://github.com/pytest-dev/pytest/issues/6181>`_: The reason for a stopped session, e.g. with ``--maxfail`` / ``-x``, now gets reported in the test summary.
- `#6206 <https://github.com/pytest-dev/pytest/issues/6206>`_: Improved ``cache.set`` robustness and performance.
Bug Fixes
---------
- `#2049 <https://github.com/pytest-dev/pytest/issues/2049>`_: Fixed ``--setup-plan`` showing inaccurate information about fixture lifetimes.
- `#2548 <https://github.com/pytest-dev/pytest/issues/2548>`_: Fixed line offset mismatch of skipped tests in terminal summary.
- `#6039 <https://github.com/pytest-dev/pytest/issues/6039>`_: The ``PytestDoctestRunner`` is now properly invalidated when unconfiguring the doctest plugin.
This is important when used with ``pytester``'s ``runpytest_inprocess``.
- `#6047 <https://github.com/pytest-dev/pytest/issues/6047>`_: BaseExceptions are now handled in ``saferepr``, which includes ``pytest.fail.Exception`` etc.
- `#6074 <https://github.com/pytest-dev/pytest/issues/6074>`_: pytester: fixed order of arguments in ``rm_rf`` warning when cleaning up temporary directories, and do not emit warnings for errors with ``os.open``.
- `#6189 <https://github.com/pytest-dev/pytest/issues/6189>`_: Fixed result of ``getmodpath`` method.
Trivial/Internal Changes
------------------------
- `#4901 <https://github.com/pytest-dev/pytest/issues/4901>`_: ``RunResult`` from ``pytester`` now displays the mnemonic of the ``ret`` attribute when it is a
valid ``pytest.ExitCode`` value.
pytest 5.2.4 (2019-11-15)
=========================
Bug Fixes
---------
- `#6194 <https://github.com/pytest-dev/pytest/issues/6194>`_: Fix incorrect discovery of non-test ``__init__.py`` files.
- `#6197 <https://github.com/pytest-dev/pytest/issues/6197>`_: Revert "The first test in a package (``__init__.py``) marked with ``@pytest.mark.skip`` is now correctly skipped.".
pytest 5.2.3 (2019-11-14)
=========================
Bug Fixes
---------
- `#5830 <https://github.com/pytest-dev/pytest/issues/5830>`_: The first test in a package (``__init__.py``) marked with ``@pytest.mark.skip`` is now correctly skipped.
- `#6099 <https://github.com/pytest-dev/pytest/issues/6099>`_: Fix ``--trace`` when used with parametrized functions.
- `#6183 <https://github.com/pytest-dev/pytest/issues/6183>`_: Using ``request`` as a parameter name in ``@pytest.mark.parametrize`` now produces a more
user-friendly error.
pytest 5.2.2 (2019-10-24)
=========================
@ -1873,7 +2105,8 @@ Features
live-logging is enabled and/or when they are logged to a file.
- `#3985 <https://github.com/pytest-dev/pytest/issues/3985>`_: Introduce ``tmp_path`` as a fixture providing a Path object.
- `#3985 <https://github.com/pytest-dev/pytest/issues/3985>`_: Introduce ``tmp_path`` as a fixture providing a Path object. Also introduce ``tmp_path_factory`` as
a session-scoped fixture for creating arbitrary temporary directories from any other fixture or test.
- `#4013 <https://github.com/pytest-dev/pytest/issues/4013>`_: Deprecation warnings are now shown even if you customize the warnings filters yourself. In the previous version
@ -3462,7 +3695,7 @@ Deprecations and Removals
- ``pytest.approx`` no longer supports ``>``, ``>=``, ``<`` and ``<=``
operators to avoid surprising/inconsistent behavior. See `the approx docs
<https://docs.pytest.org/en/latest/builtin.html#pytest.approx>`_ for more
<https://docs.pytest.org/en/latest/reference.html#pytest-approx>`_ for more
information. (`#2003 <https://github.com/pytest-dev/pytest/issues/2003>`_)
- All old-style specific behavior in current classes in the pytest's API is
@ -4819,7 +5052,7 @@ time or change existing behaviors in order to make them less surprising/more use
* Fix (`#1422`_): junit record_xml_property doesn't allow multiple records
with same name.
.. _`traceback style docs`: https://pytest.org/latest/usage.html#modifying-python-traceback-printing
.. _`traceback style docs`: https://pytest.org/en/latest/usage.html#modifying-python-traceback-printing
.. _#1609: https://github.com/pytest-dev/pytest/issues/1609
.. _#1422: https://github.com/pytest-dev/pytest/issues/1422
@ -5337,7 +5570,7 @@ time or change existing behaviors in order to make them less surprising/more use
- add ability to set command line options by environment variable PYTEST_ADDOPTS.
- added documentation on the new pytest-dev teams on bitbucket and
github. See https://pytest.org/latest/contributing.html .
github. See https://pytest.org/en/latest/contributing.html .
Thanks to Anatoly for pushing and initial work on this.
- fix issue650: new option ``--docttest-ignore-import-errors`` which
@ -6078,7 +6311,7 @@ Bug fixes:
- yielded test functions will now have autouse-fixtures active but
cannot accept fixtures as funcargs - it's anyway recommended to
rather use the post-2.0 parametrize features instead of yield, see:
http://pytest.org/latest/example/parametrize.html
http://pytest.org/en/latest/example/parametrize.html
- fix autouse-issue where autouse-fixtures would not be discovered
if defined in an a/conftest.py file and tests in a/tests/test_some.py
- fix issue226 - LIFO ordering for fixture teardowns
@ -6211,7 +6444,7 @@ Bug fixes:
- pluginmanager.register(...) now raises ValueError if the
plugin has been already registered or the name is taken
- fix issue159: improve http://pytest.org/latest/faq.html
- fix issue159: improve http://pytest.org/en/latest/faq.html
especially with respect to the "magic" history, also mention
pytest-django, trial and unittest integration.
@ -6324,7 +6557,7 @@ Bug fixes:
or through plugin hooks. Also introduce a "--strict" option which
will treat unregistered markers as errors
allowing to avoid typos and maintain a well described set of markers
for your test suite. See exaples at http://pytest.org/latest/mark.html
for your test suite. See exaples at http://pytest.org/en/latest/mark.html
and its links.
- issue50: introduce "-m marker" option to select tests based on markers
(this is a stricter and more predictable version of '-k' in that "-m"
@ -6507,7 +6740,7 @@ Bug fixes:
- refinements to "collecting" output on non-ttys
- refine internal plugin registration and --traceconfig output
- introduce a mechanism to prevent/unregister plugins from the
command line, see http://pytest.org/plugins.html#cmdunregister
command line, see http://pytest.org/en/latest/plugins.html#cmdunregister
- activate resultlog plugin by default
- fix regression wrt yielded tests which due to the
collection-before-running semantics were not

View File

@ -262,6 +262,19 @@ Here is a simple overview, with pytest-specific bits:
When committing, ``pre-commit`` will re-format the files if necessary.
#. If instead of using ``tox`` you prefer to run the tests directly, then we suggest to create a virtual environment and use
an editable install with the ``testing`` extra::
$ python3 -m venv .venv
$ source .venv/bin/activate # Linux
$ .venv/Scripts/activate.bat # Windows
$ pip install -e ".[testing]"
Afterwards, you can edit the files and run pytest normally::
$ pytest testing/test_config.py
#. Commit and push once your tests pass and you are happy with your change(s)::
$ git commit -a -m "<commit message>"

View File

@ -57,7 +57,7 @@ jobs:
export COVERAGE_FILE="$PWD/.coverage"
export COVERAGE_PROCESS_START="$PWD/.coveragerc"
fi
python -m tox -e $(tox.env) -vv
python -m tox -e $(tox.env)
displayName: 'Run tests'
- task: PublishTestResults@2

View File

@ -0,0 +1 @@
``pytest.mark.parametrize`` accepts integers for ``ids`` again, converting it to strings.

View File

@ -1 +0,0 @@
Fix line offset mismatch with skipped tests in terminal summary.

View File

@ -0,0 +1 @@
Fixed some warning reports produced by pytest to point to the correct location of the warning in the user's code.

View File

@ -1,9 +0,0 @@
New ``--report-log=FILE`` option that writes *report logs* into a file as the test session executes.
Each line of the report log contains a self contained JSON object corresponding to a testing event,
such as a collection or a test result report. The file is guaranteed to be flushed after writing
each line, so systems can read and process events in real-time.
This option is meant to replace ``--resultlog``, which is deprecated and meant to be removed
in a future release. If you use ``--resultlog``, please try out ``--report-log`` and
provide feedback.

View File

@ -1,3 +0,0 @@
When ``sys.pycache_prefix`` (Python 3.8+) is set, it will be used by pytest to cache test files changed by the assertion rewriting mechanism.
This makes it easier to benefit of cached ``.pyc`` files even on file systems without permissions.

View File

@ -1,2 +0,0 @@
``RunResult`` from ``pytester`` now displays the mnemonic of the ``ret`` attribute when it is a
valid ``pytest.ExitCode`` value.

View File

@ -1 +0,0 @@
Use multiple colors with terminal summary statistics.

View File

@ -1,11 +0,0 @@
Allow selective auto-indentation of multiline log messages.
Adds command line option ``--log-auto-indent``, config option
``log_auto_indent`` and support for per-entry configuration of
indentation behavior on calls to ``logging.log()``.
Alters the default for auto-indention from ``on`` to ``off``. This
restores the older behavior that existed prior to v4.6.0. This
reversion to earlier behavior was done because it is better to
activate new features that may lead to broken tests explicitly
rather than implicitly.

View File

@ -1 +0,0 @@
Quitting from debuggers is now properly handled in ``doctest`` items.

View File

@ -0,0 +1 @@
pytester: fix ``no_fnmatch_line`` when used after positive matching.

View File

@ -1,19 +0,0 @@
``pytester`` learned two new functions, `no_fnmatch_line <https://docs.pytest.org/en/latest/reference.html#_pytest.pytester.LineMatcher.no_fnmatch_line>`_ and
`no_re_match_line <https://docs.pytest.org/en/latest/reference.html#_pytest.pytester.LineMatcher.no_re_match_line>`_.
The functions are used to ensure the captured text *does not* match the given
pattern.
The previous idiom was to use ``re.match``:
.. code-block:: python
assert re.match(pat, result.stdout.str()) is None
Or the ``in`` operator:
.. code-block:: python
assert text in result.stdout.str()
But the new functions produce best output on failure.

View File

@ -1,34 +0,0 @@
Improve verbose diff output with sequences.
Before:
.. code-block::
E AssertionError: assert ['version', '...version_info'] == ['version', '...version', ...]
E Right contains 3 more items, first extra item: ' '
E Full diff:
E - ['version', 'version_info', 'sys.version', 'sys.version_info']
E + ['version',
E + 'version_info',
E + 'sys.version',
E + 'sys.version_info',
E + ' ',
E + 'sys.version',
E + 'sys.version_info']
After:
.. code-block::
E AssertionError: assert ['version', '...version_info'] == ['version', '...version', ...]
E Right contains 3 more items, first extra item: ' '
E Full diff:
E [
E 'version',
E 'version_info',
E 'sys.version',
E 'sys.version_info',
E + ' ',
E + 'sys.version',
E + 'sys.version_info',
E ]

View File

@ -0,0 +1 @@
Report ``PytestUnknownMarkWarning`` at the level of the user's code, not ``pytest``'s.

View File

@ -1 +0,0 @@
Display untruncated assertion message with ``-vv``.

View File

@ -0,0 +1,6 @@
Deprecate using direct constructors for ``Nodes``.
Instead they are new constructed via ``Node.from_parent``.
This transitional mechanism enables us to detangle the very intensely
entangled ``Node`` relationships by enforcing more controlled creation/configruation patterns.

View File

@ -0,0 +1 @@
The ``pytest_warning_captured`` hook now receives a ``location`` parameter with the code location that generated the warning.

View File

@ -1 +0,0 @@
Fix plurality mismatch in test summary (e.g. display "1 error" instead of "1 errors").

View File

@ -1,2 +0,0 @@
``Config.InvocationParams.args`` is now always a ``tuple`` to better convey that it should be
immutable and avoid accidental modifications.

View File

@ -1 +0,0 @@
``pytest.main`` returns a ``pytest.ExitCode`` instance now, except for when custom exit codes are used (where it returns ``int`` then still).

View File

@ -1 +0,0 @@
Align prefixes in output of pytester's ``LineMatcher``.

View File

@ -1,3 +0,0 @@
The ``PytestDoctestRunner`` is properly invalidated when unconfiguring the doctest plugin.
This is important when used with ``pytester``'s ``runpytest_inprocess``.

View File

@ -1 +0,0 @@
BaseExceptions are handled in ``saferepr``, which includes ``pytest.fail.Exception`` etc.

View File

@ -1,3 +0,0 @@
Add tolerances to complex values when printing ``pytest.approx``.
For example, ``repr(pytest.approx(3+4j))`` returns ``(3+4j) ± 5e-06 ∠ ±180°``. This is polar notation indicating a circle around the expected value, with a radius of 5e-06. For ``approx`` comparisons to return ``True``, the actual value should fall within this circle.

View File

@ -1 +0,0 @@
Collection errors are reported as errors (and not failures like before) in the terminal's short test summary.

View File

@ -1,4 +0,0 @@
Adding the pluginmanager as an option ``pytest_addoption``
so that hooks can be invoked when setting up command line options. This is
useful for having one plugin communicate things to another plugin,
such as default values or which set of command line options to add.

View File

@ -1 +0,0 @@
``pytester.spawn`` does not skip/xfail tests on FreeBSD anymore unconditionally.

View File

@ -1 +0,0 @@
pytester: fix order of arguments in ``rm_rf`` warning when cleaning up temporary directories, and do not emit warnings for errors with ``os.open``.

View File

@ -1 +0,0 @@
The "[XXX%]" indicator in the test summary is colored according to the final (new) multi-colored line's main color.

View File

@ -1 +0,0 @@
Fix ``--trace`` when used with parametrized functions.

View File

@ -1 +0,0 @@
Add ``--co`` as a synonym to ``--collect-only``.

View File

@ -1 +0,0 @@
``atomicwrites`` is now only used on Windows, fixing a performance regression with assertion rewriting on Unix.

View File

@ -1 +0,0 @@
Now parametrization will use the ``__name__`` attribute of any object for the id, if present. Previously it would only use ``__name__`` for functions and classes.

View File

@ -0,0 +1 @@
pytester: the ``testdir`` fixture respects environment settings from the ``monkeypatch`` fixture for inner runs.

View File

@ -0,0 +1 @@
Improve check for misspelling of ``pytest.mark.parametrize``.

View File

@ -0,0 +1 @@
``--fulltrace`` is honored with collection errors.

View File

@ -0,0 +1,3 @@
Clear the ``sys.last_traceback``, ``sys.last_type`` and ``sys.last_value``
attributes by deleting them instead of setting them to ``None``. This better
matches the behaviour of the Python standard library.

View File

@ -0,0 +1 @@
``pytest.mark.parametrize`` supports iterators and generators for ``ids``.

View File

@ -1,12 +1,14 @@
This directory contains "newsfragments" which are short files that contain a small **ReST**-formatted
text that will be added to the next ``CHANGELOG``.
The ``CHANGELOG`` will be read by users, so this description should be aimed to pytest users
The ``CHANGELOG`` will be read by **users**, so this description should be aimed to pytest users
instead of describing internal changes which are only relevant to the developers.
Make sure to use full sentences with correct case and punctuation, for example::
Make sure to use full sentences in the **past or present tense** and use punctuation, examples::
Fix issue with non-ascii messages from the ``warnings`` module.
Improved verbose diff output with sequences.
Terminal summary statistics now use multiple colors.
Each file should be named like ``<ISSUE>.<TYPE>.rst``, where
``<ISSUE>`` is an issue number, and ``<TYPE>`` is one of:

View File

@ -1 +0,0 @@
``repr`` of ``ExceptionInfo`` objects has been improved to honor the ``__repr__`` method of the underlying exception.

View File

@ -6,6 +6,9 @@ Release announcements
:maxdepth: 2
release-5.3.0
release-5.2.4
release-5.2.3
release-5.2.2
release-5.2.1
release-5.2.0

View File

@ -7,7 +7,7 @@ see below for summary and detailed lists. A lot of long-deprecated code
has been removed, resulting in a much smaller and cleaner
implementation. See the new docs with examples here:
http://pytest.org/2.0.0/index.html
http://pytest.org/en/latest/index.html
A note on packaging: pytest used to part of the "py" distribution up
until version py-1.3.4 but this has changed now: pytest-2.0.0 only
@ -36,12 +36,12 @@ New Features
import pytest ; pytest.main(arglist, pluginlist)
see http://pytest.org/2.0.0/usage.html for details.
see http://pytest.org/en/latest/usage.html for details.
- new and better reporting information in assert expressions
if comparing lists, sequences or strings.
see http://pytest.org/2.0.0/assert.html#newreport
see http://pytest.org/en/latest/assert.html#newreport
- new configuration through ini-files (setup.cfg or tox.ini recognized),
for example::
@ -50,7 +50,7 @@ New Features
norecursedirs = .hg data* # don't ever recurse in such dirs
addopts = -x --pyargs # add these command line options by default
see http://pytest.org/2.0.0/customize.html
see http://pytest.org/en/latest/customize.html
- improved standard unittest support. In general py.test should now
better be able to run custom unittest.TestCases like twisted trial

View File

@ -57,7 +57,7 @@ Changes between 2.0.0 and 2.0.1
- refinements to "collecting" output on non-ttys
- refine internal plugin registration and --traceconfig output
- introduce a mechanism to prevent/unregister plugins from the
command line, see http://pytest.org/latest/plugins.html#cmdunregister
command line, see http://pytest.org/en/latest/plugins.html#cmdunregister
- activate resultlog plugin by default
- fix regression wrt yielded tests which due to the
collection-before-running semantics were not

View File

@ -9,7 +9,7 @@ with these improvements:
- new @pytest.mark.parametrize decorator to run tests with different arguments
- new metafunc.parametrize() API for parametrizing arguments independently
- see examples at http://pytest.org/latest/example/parametrize.html
- see examples at http://pytest.org/en/latest/example/parametrize.html
- NOTE that parametrize() related APIs are still a bit experimental
and might change in future releases.
@ -18,7 +18,7 @@ with these improvements:
- "-m markexpr" option for selecting tests according to their mark
- a new "markers" ini-variable for registering test markers for your project
- the new "--strict" bails out with an error if using unregistered markers.
- see examples at http://pytest.org/latest/example/markers.html
- see examples at http://pytest.org/en/latest/example/markers.html
* duration profiling: new "--duration=N" option showing the N slowest test
execution or setup/teardown calls. This is most useful if you want to
@ -78,7 +78,7 @@ Changes between 2.1.3 and 2.2.0
or through plugin hooks. Also introduce a "--strict" option which
will treat unregistered markers as errors
allowing to avoid typos and maintain a well described set of markers
for your test suite. See examples at http://pytest.org/latest/mark.html
for your test suite. See examples at http://pytest.org/en/latest/mark.html
and its links.
- issue50: introduce "-m marker" option to select tests based on markers
(this is a stricter and more predictable version of "-k" in that "-m"

View File

@ -13,12 +13,12 @@ re-useable fixture design.
For detailed info and tutorial-style examples, see:
http://pytest.org/latest/fixture.html
http://pytest.org/en/latest/fixture.html
Moreover, there is now support for using pytest fixtures/funcargs with
unittest-style suites, see here for examples:
http://pytest.org/latest/unittest.html
http://pytest.org/en/latest/unittest.html
Besides, more unittest-test suites are now expected to "simply work"
with pytest.
@ -29,11 +29,11 @@ pytest-2.2.4.
If you are interested in the precise reasoning (including examples) of the
pytest-2.3 fixture evolution, please consult
http://pytest.org/latest/funcarg_compare.html
http://pytest.org/en/latest/funcarg_compare.html
For general info on installation and getting started:
http://pytest.org/latest/getting-started.html
http://pytest.org/en/latest/getting-started.html
Docs and PDF access as usual at:
@ -94,7 +94,7 @@ Changes between 2.2.4 and 2.3.0
- pluginmanager.register(...) now raises ValueError if the
plugin has been already registered or the name is taken
- fix issue159: improve http://pytest.org/latest/faq.html
- fix issue159: improve http://pytest.org/en/latest/faq.html
especially with respect to the "magic" history, also mention
pytest-django, trial and unittest integration.

View File

@ -16,7 +16,7 @@ comes with the following fixes and features:
- yielded test functions will now have autouse-fixtures active but
cannot accept fixtures as funcargs - it's anyway recommended to
rather use the post-2.0 parametrize features instead of yield, see:
http://pytest.org/latest/example/parametrize.html
http://pytest.org/en/latest/example/parametrize.html
- fix autouse-issue where autouse-fixtures would not be discovered
if defined in an a/conftest.py file and tests in a/tests/test_some.py
- fix issue226 - LIFO ordering for fixture teardowns

View File

@ -7,7 +7,7 @@ from a few supposedly very minor incompatibilities. See below for
a full list of details. A few feature highlights:
- new yield-style fixtures `pytest.yield_fixture
<http://pytest.org/latest/yieldfixture.html>`_, allowing to use
<http://pytest.org/en/latest/yieldfixture.html>`_, allowing to use
existing with-style context managers in fixture functions.
- improved pdb support: ``import pdb ; pdb.set_trace()`` now works

View File

@ -52,7 +52,7 @@ holger krekel
- add ability to set command line options by environment variable PYTEST_ADDOPTS.
- added documentation on the new pytest-dev teams on bitbucket and
github. See https://pytest.org/latest/contributing.html .
github. See https://pytest.org/en/latest/contributing.html .
Thanks to Anatoly for pushing and initial work on this.
- fix issue650: new option ``--docttest-ignore-import-errors`` which

View File

@ -131,7 +131,7 @@ The py.test Development Team
with same name.
.. _`traceback style docs`: https://pytest.org/latest/usage.html#modifying-python-traceback-printing
.. _`traceback style docs`: https://pytest.org/en/latest/usage.html#modifying-python-traceback-printing
.. _#1422: https://github.com/pytest-dev/pytest/issues/1422
.. _#1379: https://github.com/pytest-dev/pytest/issues/1379

View File

@ -0,0 +1,28 @@
pytest-5.2.3
=======================================
pytest 5.2.3 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
* Brett Cannon
* Bruno Oliveira
* Daniel Hahler
* Daniil Galiev
* David Szotten
* Florian Bruhin
* Patrick Harmon
* Ran Benita
* Zac Hatfield-Dodds
* Zak Hassan
Happy testing,
The pytest Development Team

View File

@ -0,0 +1,22 @@
pytest-5.2.4
=======================================
pytest 5.2.4 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
* Daniel Hahler
* Hugo
* Michael Shields
Happy testing,
The pytest Development Team

View File

@ -0,0 +1,45 @@
pytest-5.3.0
=======================================
The pytest team is proud to announce the 5.3.0 release!
pytest is a mature Python testing tool with more than a 2000 tests
against itself, passing on many different interpreters and platforms.
This release contains a number of bugs fixes and improvements, so users are encouraged
to take a look at the CHANGELOG:
https://docs.pytest.org/en/latest/changelog.html
For complete documentation, please visit:
https://docs.pytest.org/en/latest/
As usual, you can upgrade from pypi via:
pip install -U pytest
Thanks to all who contributed to this release, among them:
* AnjoMan
* Anthony Sottile
* Anton Lodder
* Bruno Oliveira
* Daniel Hahler
* Gregory Lee
* Josh Karpel
* JoshKarpel
* Joshua Storck
* Kale Kundert
* MarcoGorelli
* Michael Krebs
* NNRepos
* Ran Benita
* TH3CHARLie
* Tibor Arpas
* Zac Hatfield-Dodds
* 林玮
Happy testing,
The Pytest Development Team

View File

@ -92,7 +92,7 @@ exclude_patterns = [
# The reST default role (used for this markup: `text`) to use for all documents.
# default_role = None
default_role = "literal"
# If true, '()' will be appended to :func: etc. cross-reference text.
# add_function_parentheses = True
@ -112,6 +112,19 @@ pygments_style = "sphinx"
# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
# A list of regular expressions that match URIs that should not be checked when
# doing a linkcheck.
linkcheck_ignore = [
"https://github.com/numpy/numpy/blob/master/doc/release/1.16.0-notes.rst#new-deprecations",
"https://blogs.msdn.microsoft.com/bharry/2017/06/28/testing-in-a-cloud-delivery-cadence/",
"http://pythontesting.net/framework/pytest-introduction/",
r"https://github.com/pytest-dev/pytest/issues/\d+",
r"https://github.com/pytest-dev/pytest/pull/\d+",
]
# The number of worker threads to use when checking links (default=5).
linkcheck_workers = 5
# -- Options for HTML output ---------------------------------------------------

View File

@ -27,7 +27,6 @@ Full pytest documentation
unittest
nose
xunit_setup
report_log
plugins
writing_plugins
logging

View File

@ -20,6 +20,37 @@ Below is a complete list of all pytest features which are considered deprecated.
:ref:`standard warning filters <warnings>`.
Node Construction changed to ``Node.from_parent``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 5.3
The construction of nodes new should use the named constructor ``from_parent``.
This limitation in api surface intends to enable better/simpler refactoring of the collection tree.
``junit_family`` default value change to "xunit2"
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 5.2
The default value of ``junit_family`` option will change to ``xunit2`` in pytest 6.0, given
that this is the version supported by default in modern tools that manipulate this type of file.
In order to smooth the transition, pytest will issue a warning in case the ``--junitxml`` option
is given in the command line but ``junit_family`` is not explicitly configured in ``pytest.ini``::
PytestDeprecationWarning: The 'junit_family' default value will change to 'xunit2' in pytest 6.0.
Add 'junit_family=legacy' to your pytest.ini file to silence this warning and make your suite compatible.
In order to silence this warning, users just need to configure the ``junit_family`` option explicitly:
.. code-block:: ini
[pytest]
junit_family=legacy
``funcargnames`` alias for ``fixturenames``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -43,11 +74,12 @@ The ``--result-log`` option produces a stream of test reports which can be
analysed at runtime, but it uses a custom format which requires users to implement their own
parser.
The :ref:`--report-log <report_log>` option provides a more standard and extensible alternative, producing
The `pytest-reportlog <https://github.com/pytest-dev/pytest-reportlog>`__ plugin provides a ``--report-log`` option, a more standard and extensible alternative, producing
one JSON object per-line, and should cover the same use cases. Please try it out and provide feedback.
The plan is remove the ``--result-log`` option in pytest 6.0 after ``--result-log`` proves satisfactory
to all users and is deemed stable.
The plan is remove the ``--result-log`` option in pytest 6.0 if ``pytest-reportlog`` proves satisfactory
to all users and is deemed stable. The ``pytest-reportlog`` plugin might even be merged into the core
at some point, depending on the plans for the plugins and number of users using it.
Removed Features

View File

@ -622,7 +622,7 @@ then you will see two tests skipped and two executed tests as expected:
test_plat.py s.s. [100%]
========================= short test summary info ==========================
SKIPPED [2] $REGENDOC_TMPDIR/conftest.py:13: cannot run on platform linux
SKIPPED [2] $REGENDOC_TMPDIR/conftest.py:12: cannot run on platform linux
======================= 2 passed, 2 skipped in 0.12s =======================
Note that if you specify a platform via the marker-command line option like this:

View File

@ -4,7 +4,7 @@ import pytest
def pytest_collect_file(parent, path):
if path.ext == ".yaml" and path.basename.startswith("test"):
return YamlFile(path, parent)
return YamlFile.from_parent(parent, fspath=path)
class YamlFile(pytest.File):
@ -13,7 +13,7 @@ class YamlFile(pytest.File):
raw = yaml.safe_load(self.fspath.open())
for name, spec in sorted(raw.items()):
yield YamlItem(name, self, spec)
yield YamlItem.from_parent(self, name=name, spec=spec)
class YamlItem(pytest.Item):

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:30: 'python3.5' not found
SKIPPED [12] $REGENDOC_TMPDIR/CWD/multipython.py:30: 'python3.6' not found
SKIPPED [12] $REGENDOC_TMPDIR/CWD/multipython.py:29: 'python3.5' 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
@ -547,7 +547,7 @@ If you run this with reporting for skips enabled:
test_module.py .s [100%]
========================= short test summary info ==========================
SKIPPED [1] $REGENDOC_TMPDIR/conftest.py:13: could not import 'opt2': No module named 'opt2'
SKIPPED [1] $REGENDOC_TMPDIR/conftest.py:12: could not import 'opt2': No module named 'opt2'
======================= 1 passed, 1 skipped in 0.12s =======================
You'll see that we don't have an ``opt2`` module and thus the second test run

View File

@ -13,4 +13,4 @@ class DummyCollector(pytest.collect.File):
def pytest_pycollect_makemodule(path, parent):
bn = path.basename
if "py3" in bn and not py3 or ("py2" in bn and py3):
return DummyCollector(path, parent=parent)
return DummyCollector.from_parent(parent, fspath=path)

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

@ -300,36 +300,33 @@ behave differently if called from a test. But if you
absolutely must find out if your application code is
running from a test you can do something like this:
.. code-block:: python
# content of your_module.py
_called_from_test = False
.. code-block:: python
# content of conftest.py
def pytest_configure(config):
import sys
your_module._called_from_test = True
sys._called_from_test = True
def pytest_unconfigure(config):
import sys
del sys._called_from_test
and then check for the ``sys._called_from_test`` flag:
and then check for the ``your_module._called_from_test`` flag:
.. code-block:: python
if hasattr(sys, "_called_from_test"):
if your_module._called_from_test:
# called from within a test run
...
else:
# called "normally"
...
accordingly in your application. It's also a good idea
to use your own application module rather than ``sys``
for handling flag.
accordingly in your application.
Adding info to test report header
--------------------------------------------------------------
@ -446,7 +443,7 @@ 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.20s call test_some_are_slow.py::test_funcslow1
0.10s call test_some_are_slow.py::test_funcfast
0.11s call test_some_are_slow.py::test_funcfast
============================ 3 passed in 0.12s =============================
incremental testing - test steps

View File

@ -9,9 +9,9 @@ pytest fixtures: explicit, modular, scalable
.. _`xUnit`: http://en.wikipedia.org/wiki/XUnit
.. _`purpose of test fixtures`: http://en.wikipedia.org/wiki/Test_fixture#Software
.. _`Dependency injection`: http://en.wikipedia.org/wiki/Dependency_injection
.. _`xUnit`: https://en.wikipedia.org/wiki/XUnit
.. _`purpose of test fixtures`: https://en.wikipedia.org/wiki/Test_fixture#Software
.. _`Dependency injection`: https://en.wikipedia.org/wiki/Dependency_injection
The `purpose of test fixtures`_ is to provide a fixed baseline
upon which tests can reliably and repeatedly execute. pytest fixtures

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.py
.. _`simpletest`:

View File

@ -73,7 +73,6 @@ Some organisations using pytest
* `Square Kilometre Array, Cape Town <http://ska.ac.za/>`_
* `Some Mozilla QA people <http://www.theautomatedtester.co.uk/blog/2011/pytest_and_xdist_plugin.html>`_ use pytest to distribute their Selenium tests
* `Tandberg <http://www.tandberg.com/>`_
* `Shootq <http://web.shootq.com/>`_
* `Stups department of Heinrich Heine University Duesseldorf <http://www.stups.uni-duesseldorf.de/projects.php>`_
* cellzome

View File

@ -59,7 +59,7 @@ pytest.raises
**Tutorial**: :ref:`assertraises`.
.. autofunction:: pytest.raises(expected_exception: Exception, [match])
.. autofunction:: pytest.raises(expected_exception: Exception [, *, match])
:with: excinfo
pytest.deprecated_call

View File

@ -1,70 +0,0 @@
.. _report_log:
Report files
============
.. versionadded:: 5.3
The ``--report-log=FILE`` option writes *report logs* into a file as the test session executes.
Each line of the report log contains a self contained JSON object corresponding to a testing event,
such as a collection or a test result report. The file is guaranteed to be flushed after writing
each line, so systems can read and process events in real-time.
Each JSON object contains a special key ``$report_type``, which contains a unique identifier for
that kind of report object. For future compatibility, consumers of the file should ignore reports
they don't recognize, as well as ignore unknown properties/keys in JSON objects that they do know,
as future pytest versions might enrich the objects with more properties/keys.
.. note::
This option is meant to the replace ``--resultlog``, which is deprecated and meant to be removed
in a future release. If you use ``--resultlog``, please try out ``--report-log`` and
provide feedback.
Example
-------
Consider this file:
.. code-block:: python
# content of test_report_example.py
def test_ok():
assert 5 + 5 == 10
def test_fail():
assert 4 + 4 == 1
.. code-block:: pytest
$ pytest test_report_example.py -q --report-log=log.json
.F [100%]
================================= FAILURES =================================
________________________________ test_fail _________________________________
def test_fail():
> assert 4 + 4 == 1
E assert (4 + 4) == 1
test_report_example.py:8: AssertionError
------------------- generated report log file: log.json --------------------
1 failed, 1 passed in 0.12s
The generated ``log.json`` will contain a JSON object per line:
::
$ cat log.json
{"pytest_version": "5.2.3.dev90+gd1129cf96.d20191026", "$report_type": "Header"}
{"nodeid": "", "outcome": "passed", "longrepr": null, "result": null, "sections": [], "$report_type": "CollectReport"}
{"nodeid": "test_report_example.py", "outcome": "passed", "longrepr": null, "result": null, "sections": [], "$report_type": "CollectReport"}
{"nodeid": "test_report_example.py::test_ok", "location": ["test_report_example.py", 2, "test_ok"], "keywords": {"report_log.rst-39": 1, "test_report_example.py": 1, "test_ok": 1}, "outcome": "passed", "longrepr": null, "when": "setup", "user_properties": [], "sections": [], "duration": 0.00021314620971679688, "$report_type": "TestReport"}
{"nodeid": "test_report_example.py::test_ok", "location": ["test_report_example.py", 2, "test_ok"], "keywords": {"report_log.rst-39": 1, "test_report_example.py": 1, "test_ok": 1}, "outcome": "passed", "longrepr": null, "when": "call", "user_properties": [], "sections": [], "duration": 0.00014543533325195312, "$report_type": "TestReport"}
{"nodeid": "test_report_example.py::test_ok", "location": ["test_report_example.py", 2, "test_ok"], "keywords": {"report_log.rst-39": 1, "test_report_example.py": 1, "test_ok": 1}, "outcome": "passed", "longrepr": null, "when": "teardown", "user_properties": [], "sections": [], "duration": 0.00016427040100097656, "$report_type": "TestReport"}
{"nodeid": "test_report_example.py::test_fail", "location": ["test_report_example.py", 6, "test_fail"], "keywords": {"test_fail": 1, "test_report_example.py": 1, "report_log.rst-39": 1}, "outcome": "passed", "longrepr": null, "when": "setup", "user_properties": [], "sections": [], "duration": 0.00013589859008789062, "$report_type": "TestReport"}
{"nodeid": "test_report_example.py::test_fail", "location": ["test_report_example.py", 6, "test_fail"], "keywords": {"test_fail": 1, "test_report_example.py": 1, "report_log.rst-39": 1}, "outcome": "failed", "longrepr": {"reprcrash": {"path": "$REGENDOC_TMPDIR/test_report_example.py", "lineno": 8, "message": "assert (4 + 4) == 1"}, "reprtraceback": {"reprentries": [{"type": "ReprEntry", "data": {"lines": [" def test_fail():", "> assert 4 + 4 == 1", "E assert (4 + 4) == 1"], "reprfuncargs": {"args": []}, "reprlocals": null, "reprfileloc": {"path": "test_report_example.py", "lineno": 8, "message": "AssertionError"}, "style": "long"}}], "extraline": null, "style": "long"}, "sections": [], "chain": [[{"reprentries": [{"type": "ReprEntry", "data": {"lines": [" def test_fail():", "> assert 4 + 4 == 1", "E assert (4 + 4) == 1"], "reprfuncargs": {"args": []}, "reprlocals": null, "reprfileloc": {"path": "test_report_example.py", "lineno": 8, "message": "AssertionError"}, "style": "long"}}], "extraline": null, "style": "long"}, {"path": "$REGENDOC_TMPDIR/test_report_example.py", "lineno": 8, "message": "assert (4 + 4) == 1"}, null]]}, "when": "call", "user_properties": [], "sections": [], "duration": 0.00027489662170410156, "$report_type": "TestReport"}
{"nodeid": "test_report_example.py::test_fail", "location": ["test_report_example.py", 6, "test_fail"], "keywords": {"test_fail": 1, "test_report_example.py": 1, "report_log.rst-39": 1}, "outcome": "passed", "longrepr": null, "when": "teardown", "user_properties": [], "sections": [], "duration": 0.00016689300537109375, "$report_type": "TestReport"}

View File

@ -64,7 +64,7 @@ Talks and blog postings
- `pytest introduction from Brian Okken (January 2013)
<http://pythontesting.net/framework/pytest-introduction/>`_
- pycon australia 2012 pytest talk from Brianna Laugher (`video <http://www.youtube.com/watch?v=DTNejE9EraI>`_, `slides <http://www.slideshare.net/pfctdayelise/funcargs-other-fun-with-pytest>`_, `code <https://gist.github.com/3386951>`_)
- pycon australia 2012 pytest talk from Brianna Laugher (`video <http://www.youtube.com/watch?v=DTNejE9EraI>`_, `slides <https://www.slideshare.net/pfctdayelise/funcargs-other-fun-with-pytest>`_, `code <https://gist.github.com/3386951>`_)
- `pycon 2012 US talk video from Holger Krekel <http://www.youtube.com/watch?v=9LVqBQcFmyw>`_
- `monkey patching done right`_ (blog post, consult `monkeypatch plugin`_ for up-to-date API)

View File

@ -66,8 +66,8 @@ To stop the testing process after the first (N) failures:
.. code-block:: bash
pytest -x # stop after first failure
pytest --maxfail=2 # stop after two failures
pytest -x # stop after first failure
pytest --maxfail=2 # stop after two failures
.. _select-tests:
@ -241,7 +241,7 @@ Example:
test_example.py:14: AssertionError
========================= short test summary info ==========================
SKIPPED [1] $REGENDOC_TMPDIR/test_example.py:23: skipping this test
SKIPPED [1] $REGENDOC_TMPDIR/test_example.py:22: skipping this test
XFAIL test_example.py::test_xfail
reason: xfailing this test
XPASS test_example.py::test_xpass always xfail
@ -296,7 +296,7 @@ More than one character can be used, so for example to only see failed and skipp
test_example.py:14: AssertionError
========================= short test summary info ==========================
FAILED test_example.py::test_fail - assert 0
SKIPPED [1] $REGENDOC_TMPDIR/test_example.py:23: skipping this test
SKIPPED [1] $REGENDOC_TMPDIR/test_example.py:22: skipping this test
== 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12s ===
Using ``p`` lists the passing tests, whilst ``P`` adds an extra section "PASSES" with those tests that passed but had
@ -692,7 +692,7 @@ by the `PyPy-test`_ web page to show test results over several revisions.
This option is rarely used and is scheduled for removal in pytest 6.0.
If you use this option, consider using the new :ref:`--result-log <report_log>`.
If you use this option, consider using the new `pytest-reportlog <https://github.com/pytest-dev/pytest-reportlog>`__ plugin instead.
See `the deprecation docs <https://docs.pytest.org/en/latest/deprecations.html#result-log-result-log>`__
for more information.

View File

@ -41,7 +41,7 @@ Running pytest now produces this output:
warnings.warn(UserWarning("api v1, should use functions from v2"))
-- Docs: https://docs.pytest.org/en/latest/warnings.html
====================== 1 passed, 1 warnings in 0.12s =======================
======================= 1 passed, 1 warning in 0.12s =======================
The ``-W`` flag can be passed to control which warnings will be displayed or even turn
them into errors:
@ -407,7 +407,7 @@ defines an ``__init__`` constructor, as this prevents the class from being insta
class Test:
-- Docs: https://docs.pytest.org/en/latest/warnings.html
1 warnings in 0.12s
1 warning in 0.12s
These warnings might be filtered using the same builtin mechanisms used to filter other types of warnings.

View File

@ -442,7 +442,7 @@ additionally it is possible to copy examples for an example folder before runnin
testdir.copy_example("test_example.py")
-- Docs: https://docs.pytest.org/en/latest/warnings.html
====================== 2 passed, 1 warnings in 0.12s =======================
======================= 2 passed, 1 warning in 0.12s =======================
For more information about the result object that ``runpytest()`` returns, and
the methods that it provides please check out the :py:class:`RunResult

View File

@ -79,12 +79,19 @@ def fix_formatting():
call(["pre-commit", "run", "--all-files"])
def check_links():
"""Runs sphinx-build to check links"""
print(f"{Fore.CYAN}[generate.check_links] {Fore.RESET}Checking links")
check_call(["tox", "-e", "docs-checklinks"])
def pre_release(version):
"""Generates new docs, release announcements and creates a local tag."""
announce(version)
regen()
changelog(version, write_out=True)
fix_formatting()
check_links()
msg = "Preparing release version {}".format(version)
check_call(["git", "commit", "-a", "-m", msg])

View File

@ -57,12 +57,13 @@ upload-dir = doc/en/build/html
[check-manifest]
ignore =
_pytest/_version.py
src/_pytest/_version.py
[devpi:upload]
formats = sdist.tgz,bdist_wheel
[mypy]
mypy_path = src
ignore_missing_imports = True
no_implicit_optional = True
strict_equality = True

View File

@ -53,19 +53,22 @@ If things do not work right away:
which should throw a KeyError: 'COMPLINE' (which is properly set by the
global argcomplete script).
"""
import argparse
import os
import sys
from glob import glob
from typing import Any
from typing import List
from typing import Optional
class FastFilesCompleter:
"Fast file completer class"
def __init__(self, directories=True):
def __init__(self, directories: bool = True) -> None:
self.directories = directories
def __call__(self, prefix, **kwargs):
def __call__(self, prefix: str, **kwargs: Any) -> List[str]:
"""only called on non option completions"""
if os.path.sep in prefix[1:]:
prefix_dir = len(os.path.dirname(prefix) + os.path.sep)
@ -94,13 +97,13 @@ if os.environ.get("_ARGCOMPLETE"):
sys.exit(-1)
filescompleter = FastFilesCompleter() # type: Optional[FastFilesCompleter]
def try_argcomplete(parser):
def try_argcomplete(parser: argparse.ArgumentParser) -> None:
argcomplete.autocomplete(parser, always_complete_options=False)
else:
def try_argcomplete(parser):
def try_argcomplete(parser: argparse.ArgumentParser) -> None:
pass
filescompleter = None

View File

@ -7,13 +7,17 @@ from inspect import CO_VARKEYWORDS
from io import StringIO
from traceback import format_exception_only
from types import CodeType
from types import FrameType
from types import TracebackType
from typing import Any
from typing import Callable
from typing import Dict
from typing import Generic
from typing import Iterable
from typing import List
from typing import Optional
from typing import Pattern
from typing import Sequence
from typing import Set
from typing import Tuple
from typing import TypeVar
@ -27,9 +31,16 @@ import py
import _pytest
from _pytest._io.saferepr import safeformat
from _pytest._io.saferepr import saferepr
from _pytest.compat import overload
if False: # TYPE_CHECKING
from typing import Type
from typing_extensions import Literal
from weakref import ReferenceType # noqa: F401
from _pytest._code import Source
_TracebackStyle = Literal["long", "short", "no", "native"]
class Code:
@ -38,13 +49,12 @@ class Code:
def __init__(self, rawcode) -> None:
if not hasattr(rawcode, "co_filename"):
rawcode = getrawcode(rawcode)
try:
self.filename = rawcode.co_filename
self.firstlineno = rawcode.co_firstlineno - 1
self.name = rawcode.co_name
except AttributeError:
if not isinstance(rawcode, CodeType):
raise TypeError("not a code object: {!r}".format(rawcode))
self.raw = rawcode # type: CodeType
self.filename = rawcode.co_filename
self.firstlineno = rawcode.co_firstlineno - 1
self.name = rawcode.co_name
self.raw = rawcode
def __eq__(self, other):
return self.raw == other.raw
@ -72,7 +82,7 @@ class Code:
return p
@property
def fullsource(self):
def fullsource(self) -> Optional["Source"]:
""" return a _pytest._code.Source object for the full source file of the code
"""
from _pytest._code import source
@ -80,7 +90,7 @@ class Code:
full, _ = source.findsource(self.raw)
return full
def source(self):
def source(self) -> "Source":
""" return a _pytest._code.Source object for the code object's source only
"""
# return source only for that part of code
@ -88,7 +98,7 @@ class Code:
return _pytest._code.Source(self.raw)
def getargs(self, var=False):
def getargs(self, var: bool = False) -> Tuple[str, ...]:
""" return a tuple with the argument names for the code object
if 'var' is set True also return the names of the variable and
@ -107,7 +117,7 @@ class Frame:
"""Wrapper around a Python frame holding f_locals and f_globals
in which expressions can be evaluated."""
def __init__(self, frame):
def __init__(self, frame: FrameType) -> None:
self.lineno = frame.f_lineno - 1
self.f_globals = frame.f_globals
self.f_locals = frame.f_locals
@ -115,7 +125,7 @@ class Frame:
self.code = Code(frame.f_code)
@property
def statement(self):
def statement(self) -> "Source":
""" statement this frame is at """
import _pytest._code
@ -134,7 +144,7 @@ class Frame:
f_locals.update(vars)
return eval(code, self.f_globals, f_locals)
def exec_(self, code, **vars):
def exec_(self, code, **vars) -> None:
""" exec 'code' in the frame
'vars' are optional; additional local variables
@ -143,7 +153,7 @@ class Frame:
f_locals.update(vars)
exec(code, self.f_globals, f_locals)
def repr(self, object):
def repr(self, object: object) -> str:
""" return a 'safe' (non-recursive, one-line) string repr for 'object'
"""
return saferepr(object)
@ -151,7 +161,7 @@ class Frame:
def is_true(self, object):
return object
def getargs(self, var=False):
def getargs(self, var: bool = False):
""" return a list of tuples (name, value) for all arguments
if 'var' is set True also include the variable and keyword
@ -169,35 +179,34 @@ class Frame:
class TracebackEntry:
""" a single entry in a traceback """
_repr_style = None
_repr_style = None # type: Optional[Literal["short", "long"]]
exprinfo = None
def __init__(self, rawentry, excinfo=None):
def __init__(self, rawentry: TracebackType, excinfo=None) -> None:
self._excinfo = excinfo
self._rawentry = rawentry
self.lineno = rawentry.tb_lineno - 1
def set_repr_style(self, mode):
def set_repr_style(self, mode: "Literal['short', 'long']") -> None:
assert mode in ("short", "long")
self._repr_style = mode
@property
def frame(self):
import _pytest._code
return _pytest._code.Frame(self._rawentry.tb_frame)
def frame(self) -> Frame:
return Frame(self._rawentry.tb_frame)
@property
def relline(self):
def relline(self) -> int:
return self.lineno - self.frame.code.firstlineno
def __repr__(self):
def __repr__(self) -> str:
return "<TracebackEntry %s:%d>" % (self.frame.code.path, self.lineno + 1)
@property
def statement(self):
def statement(self) -> "Source":
""" _pytest._code.Source object for the current statement """
source = self.frame.code.fullsource
assert source is not None
return source.getstatement(self.lineno)
@property
@ -206,14 +215,14 @@ class TracebackEntry:
return self.frame.code.path
@property
def locals(self):
def locals(self) -> Dict[str, Any]:
""" locals of underlying frame """
return self.frame.f_locals
def getfirstlinesource(self):
def getfirstlinesource(self) -> int:
return self.frame.code.firstlineno
def getsource(self, astcache=None):
def getsource(self, astcache=None) -> Optional["Source"]:
""" return failing source code. """
# we use the passed in astcache to not reparse asttrees
# within exception info printing
@ -258,7 +267,7 @@ class TracebackEntry:
return tbh(None if self._excinfo is None else self._excinfo())
return tbh
def __str__(self):
def __str__(self) -> str:
try:
fn = str(self.path)
except py.error.Error:
@ -273,33 +282,42 @@ class TracebackEntry:
return " File %r:%d in %s\n %s\n" % (fn, self.lineno + 1, name, line)
@property
def name(self):
def name(self) -> str:
""" co_name of underlying code """
return self.frame.code.raw.co_name
class Traceback(list):
class Traceback(List[TracebackEntry]):
""" Traceback objects encapsulate and offer higher level
access to Traceback entries.
"""
Entry = TracebackEntry
def __init__(self, tb, excinfo=None):
def __init__(
self,
tb: Union[TracebackType, Iterable[TracebackEntry]],
excinfo: Optional["ReferenceType[ExceptionInfo]"] = None,
) -> None:
""" initialize from given python traceback object and ExceptionInfo """
self._excinfo = excinfo
if hasattr(tb, "tb_next"):
if isinstance(tb, TracebackType):
def f(cur):
while cur is not None:
yield self.Entry(cur, excinfo=excinfo)
cur = cur.tb_next
def f(cur: TracebackType) -> Iterable[TracebackEntry]:
cur_ = cur # type: Optional[TracebackType]
while cur_ is not None:
yield TracebackEntry(cur_, excinfo=excinfo)
cur_ = cur_.tb_next
list.__init__(self, f(tb))
super().__init__(f(tb))
else:
list.__init__(self, tb)
super().__init__(tb)
def cut(self, path=None, lineno=None, firstlineno=None, excludepath=None):
def cut(
self,
path=None,
lineno: Optional[int] = None,
firstlineno: Optional[int] = None,
excludepath=None,
) -> "Traceback":
""" return a Traceback instance wrapping part of this Traceback
by providing any combination of path, lineno and firstlineno, the
@ -325,13 +343,25 @@ class Traceback(list):
return Traceback(x._rawentry, self._excinfo)
return self
def __getitem__(self, key):
val = super().__getitem__(key)
if isinstance(key, type(slice(0))):
val = self.__class__(val)
return val
@overload
def __getitem__(self, key: int) -> TracebackEntry:
raise NotImplementedError()
def filter(self, fn=lambda x: not x.ishidden()):
@overload # noqa: F811
def __getitem__(self, key: slice) -> "Traceback": # noqa: F811
raise NotImplementedError()
def __getitem__( # noqa: F811
self, key: Union[int, slice]
) -> Union[TracebackEntry, "Traceback"]:
if isinstance(key, slice):
return self.__class__(super().__getitem__(key))
else:
return super().__getitem__(key)
def filter(
self, fn: Callable[[TracebackEntry], bool] = lambda x: not x.ishidden()
) -> "Traceback":
""" return a Traceback instance with certain items removed
fn is a function that gets a single argument, a TracebackEntry
@ -343,7 +373,7 @@ class Traceback(list):
"""
return Traceback(filter(fn, self), self._excinfo)
def getcrashentry(self):
def getcrashentry(self) -> TracebackEntry:
""" return last non-hidden traceback entry that lead
to the exception of a traceback.
"""
@ -353,7 +383,7 @@ class Traceback(list):
return entry
return self[-1]
def recursionindex(self):
def recursionindex(self) -> Optional[int]:
""" return the index of the frame/TracebackEntry where recursion
originates if appropriate, None if no recursion occurred
"""
@ -449,7 +479,7 @@ class ExceptionInfo(Generic[_E]):
assert tup[1] is not None, "no current exception"
assert tup[2] is not None, "no current exception"
exc_info = (tup[0], tup[1], tup[2])
return cls.from_exc_info(exc_info, exprinfo)
return ExceptionInfo.from_exc_info(exc_info, exprinfo)
@classmethod
def for_later(cls) -> "ExceptionInfo[_E]":
@ -543,7 +573,7 @@ class ExceptionInfo(Generic[_E]):
def getrepr(
self,
showlocals: bool = False,
style: str = "long",
style: "_TracebackStyle" = "long",
abspath: bool = False,
tbfilter: bool = True,
funcargs: bool = False,
@ -621,16 +651,16 @@ class FormattedExcinfo:
flow_marker = ">"
fail_marker = "E"
showlocals = attr.ib(default=False)
style = attr.ib(default="long")
abspath = attr.ib(default=True)
tbfilter = attr.ib(default=True)
funcargs = attr.ib(default=False)
truncate_locals = attr.ib(default=True)
chain = attr.ib(default=True)
showlocals = attr.ib(type=bool, default=False)
style = attr.ib(type="_TracebackStyle", default="long")
abspath = attr.ib(type=bool, default=True)
tbfilter = attr.ib(type=bool, default=True)
funcargs = attr.ib(type=bool, default=False)
truncate_locals = attr.ib(type=bool, default=True)
chain = attr.ib(type=bool, default=True)
astcache = attr.ib(default=attr.Factory(dict), init=False, repr=False)
def _getindent(self, source):
def _getindent(self, source: "Source") -> int:
# figure out indent for given source
try:
s = str(source.getstatement(len(source) - 1))
@ -645,20 +675,27 @@ class FormattedExcinfo:
return 0
return 4 + (len(s) - len(s.lstrip()))
def _getentrysource(self, entry):
def _getentrysource(self, entry: TracebackEntry) -> Optional["Source"]:
source = entry.getsource(self.astcache)
if source is not None:
source = source.deindent()
return source
def repr_args(self, entry):
def repr_args(self, entry: TracebackEntry) -> Optional["ReprFuncArgs"]:
if self.funcargs:
args = []
for argname, argvalue in entry.frame.getargs(var=True):
args.append((argname, saferepr(argvalue)))
return ReprFuncArgs(args)
return None
def get_source(self, source, line_index=-1, excinfo=None, short=False) -> List[str]:
def get_source(
self,
source: "Source",
line_index: int = -1,
excinfo: Optional[ExceptionInfo] = None,
short: bool = False,
) -> List[str]:
""" return formatted and marked up source lines. """
import _pytest._code
@ -682,19 +719,21 @@ class FormattedExcinfo:
lines.extend(self.get_exconly(excinfo, indent=indent, markall=True))
return lines
def get_exconly(self, excinfo, indent=4, markall=False):
def get_exconly(
self, excinfo: ExceptionInfo, indent: int = 4, markall: bool = False
) -> List[str]:
lines = []
indent = " " * indent
indentstr = " " * indent
# get the real exception information out
exlines = excinfo.exconly(tryshort=True).split("\n")
failindent = self.fail_marker + indent[1:]
failindent = self.fail_marker + indentstr[1:]
for line in exlines:
lines.append(failindent + line)
if not markall:
failindent = indent
failindent = indentstr
return lines
def repr_locals(self, locals):
def repr_locals(self, locals: Dict[str, object]) -> Optional["ReprLocals"]:
if self.showlocals:
lines = []
keys = [loc for loc in locals if loc[0] != "@"]
@ -719,8 +758,11 @@ class FormattedExcinfo:
# # XXX
# pprint.pprint(value, stream=self.excinfowriter)
return ReprLocals(lines)
return None
def repr_traceback_entry(self, entry, excinfo=None):
def repr_traceback_entry(
self, entry: TracebackEntry, excinfo: Optional[ExceptionInfo] = None
) -> "ReprEntry":
import _pytest._code
source = self._getentrysource(entry)
@ -731,9 +773,7 @@ class FormattedExcinfo:
line_index = entry.lineno - entry.getfirstlinesource()
lines = [] # type: List[str]
style = entry._repr_style
if style is None:
style = self.style
style = entry._repr_style if entry._repr_style is not None else self.style
if style in ("short", "long"):
short = style == "short"
reprargs = self.repr_args(entry) if not short else None
@ -763,7 +803,7 @@ class FormattedExcinfo:
path = np
return path
def repr_traceback(self, excinfo):
def repr_traceback(self, excinfo: ExceptionInfo) -> "ReprTraceback":
traceback = excinfo.traceback
if self.tbfilter:
traceback = traceback.filter()
@ -781,7 +821,9 @@ class FormattedExcinfo:
entries.append(reprentry)
return ReprTraceback(entries, extraline, style=self.style)
def _truncate_recursive_traceback(self, traceback):
def _truncate_recursive_traceback(
self, traceback: Traceback
) -> Tuple[Traceback, Optional[str]]:
"""
Truncate the given recursive traceback trying to find the starting point
of the recursion.
@ -808,7 +850,9 @@ class FormattedExcinfo:
max_frames=max_frames,
total=len(traceback),
) # type: Optional[str]
traceback = traceback[:max_frames] + traceback[-max_frames:]
# Type ignored because adding two instaces of a List subtype
# currently incorrectly has type List instead of the subtype.
traceback = traceback[:max_frames] + traceback[-max_frames:] # type: ignore
else:
if recursionindex is not None:
extraline = "!!! Recursion detected (same locals & position)"
@ -865,7 +909,7 @@ class FormattedExcinfo:
class TerminalRepr:
def __str__(self):
def __str__(self) -> str:
# FYI this is called from pytest-xdist's serialization of exception
# information.
io = StringIO()
@ -873,7 +917,7 @@ class TerminalRepr:
self.toterminal(tw)
return io.getvalue().strip()
def __repr__(self):
def __repr__(self) -> str:
return "<{} instance at {:0x}>".format(self.__class__, id(self))
def toterminal(self, tw) -> None:
@ -884,7 +928,7 @@ class ExceptionRepr(TerminalRepr):
def __init__(self) -> None:
self.sections = [] # type: List[Tuple[str, str, str]]
def addsection(self, name, content, sep="-"):
def addsection(self, name: str, content: str, sep: str = "-") -> None:
self.sections.append((name, content, sep))
def toterminal(self, tw) -> None:
@ -894,7 +938,12 @@ class ExceptionRepr(TerminalRepr):
class ExceptionChainRepr(ExceptionRepr):
def __init__(self, chain):
def __init__(
self,
chain: Sequence[
Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]]
],
) -> None:
super().__init__()
self.chain = chain
# reprcrash and reprtraceback of the outermost (the newest) exception
@ -912,7 +961,9 @@ class ExceptionChainRepr(ExceptionRepr):
class ReprExceptionInfo(ExceptionRepr):
def __init__(self, reprtraceback, reprcrash):
def __init__(
self, reprtraceback: "ReprTraceback", reprcrash: "ReprFileLocation"
) -> None:
super().__init__()
self.reprtraceback = reprtraceback
self.reprcrash = reprcrash
@ -925,7 +976,12 @@ class ReprExceptionInfo(ExceptionRepr):
class ReprTraceback(TerminalRepr):
entrysep = "_ "
def __init__(self, reprentries, extraline, style):
def __init__(
self,
reprentries: Sequence[Union["ReprEntry", "ReprEntryNative"]],
extraline: Optional[str],
style: "_TracebackStyle",
) -> None:
self.reprentries = reprentries
self.extraline = extraline
self.style = style
@ -950,16 +1006,16 @@ class ReprTraceback(TerminalRepr):
class ReprTracebackNative(ReprTraceback):
def __init__(self, tblines):
def __init__(self, tblines: Sequence[str]) -> None:
self.style = "native"
self.reprentries = [ReprEntryNative(tblines)]
self.extraline = None
class ReprEntryNative(TerminalRepr):
style = "native"
style = "native" # type: _TracebackStyle
def __init__(self, tblines):
def __init__(self, tblines: Sequence[str]) -> None:
self.lines = tblines
def toterminal(self, tw) -> None:
@ -967,7 +1023,14 @@ class ReprEntryNative(TerminalRepr):
class ReprEntry(TerminalRepr):
def __init__(self, lines, reprfuncargs, reprlocals, filelocrepr, style):
def __init__(
self,
lines: Sequence[str],
reprfuncargs: Optional["ReprFuncArgs"],
reprlocals: Optional["ReprLocals"],
filelocrepr: Optional["ReprFileLocation"],
style: "_TracebackStyle",
) -> None:
self.lines = lines
self.reprfuncargs = reprfuncargs
self.reprlocals = reprlocals
@ -976,6 +1039,7 @@ class ReprEntry(TerminalRepr):
def toterminal(self, tw) -> None:
if self.style == "short":
assert self.reprfileloc is not None
self.reprfileloc.toterminal(tw)
for line in self.lines:
red = line.startswith("E ")
@ -994,14 +1058,14 @@ class ReprEntry(TerminalRepr):
tw.line("")
self.reprfileloc.toterminal(tw)
def __str__(self):
def __str__(self) -> str:
return "{}\n{}\n{}".format(
"\n".join(self.lines), self.reprlocals, self.reprfileloc
)
class ReprFileLocation(TerminalRepr):
def __init__(self, path, lineno, message):
def __init__(self, path, lineno: int, message: str) -> None:
self.path = str(path)
self.lineno = lineno
self.message = message
@ -1018,7 +1082,7 @@ class ReprFileLocation(TerminalRepr):
class ReprLocals(TerminalRepr):
def __init__(self, lines):
def __init__(self, lines: Sequence[str]) -> None:
self.lines = lines
def toterminal(self, tw) -> None:
@ -1027,7 +1091,7 @@ class ReprLocals(TerminalRepr):
class ReprFuncArgs(TerminalRepr):
def __init__(self, args):
def __init__(self, args: Sequence[Tuple[str, object]]) -> None:
self.args = args
def toterminal(self, tw) -> None:
@ -1049,13 +1113,11 @@ class ReprFuncArgs(TerminalRepr):
tw.line("")
def getrawcode(obj, trycall=True):
def getrawcode(obj, trycall: bool = True):
""" return code object for given function. """
try:
return obj.__code__
except AttributeError:
obj = getattr(obj, "im_func", obj)
obj = getattr(obj, "func_code", obj)
obj = getattr(obj, "f_code", obj)
obj = getattr(obj, "__code__", obj)
if trycall and not hasattr(obj, "co_firstlineno"):
@ -1079,7 +1141,7 @@ _PYTEST_DIR = py.path.local(_pytest.__file__).dirpath()
_PY_DIR = py.path.local(py.__file__).dirpath()
def filter_traceback(entry):
def filter_traceback(entry: TracebackEntry) -> bool:
"""Return True if a TracebackEntry instance should be removed from tracebacks:
* dynamically generated code (no code to show up for it);
* internal traceback from pytest or its internal libraries, py and pluggy.

View File

@ -8,6 +8,7 @@ import warnings
from ast import PyCF_ONLY_AST as _AST_FLAG
from bisect import bisect_right
from types import FrameType
from typing import Iterator
from typing import List
from typing import Optional
from typing import Sequence
@ -60,7 +61,7 @@ class Source:
raise NotImplementedError()
@overload # noqa: F811
def __getitem__(self, key: slice) -> "Source":
def __getitem__(self, key: slice) -> "Source": # noqa: F811
raise NotImplementedError()
def __getitem__(self, key: Union[int, slice]) -> Union[str, "Source"]: # noqa: F811
@ -73,6 +74,9 @@ class Source:
newsource.lines = self.lines[key.start : key.stop]
return newsource
def __iter__(self) -> Iterator[str]:
return iter(self.lines)
def __len__(self) -> int:
return len(self.lines)
@ -335,7 +339,9 @@ def getstatementrange_ast(
block_finder.started = source.lines[start][0].isspace()
it = ((x + "\n") for x in source.lines[start:end])
try:
for tok in tokenize.generate_tokens(lambda: next(it)):
# Type ignored until next mypy release.
# https://github.com/python/typeshed/commit/c0d46a20353b733befb85d8b9cc24e5b0bcd8f9a
for tok in tokenize.generate_tokens(lambda: next(it)): # type: ignore
block_finder.tokeneater(*tok)
except (inspect.EndOfBlock, IndentationError):
end = block_finder.last + start

View File

@ -80,3 +80,24 @@ def saferepr(obj: Any, maxsize: int = 240) -> str:
around the Repr/reprlib functionality of the standard 2.6 lib.
"""
return SafeRepr(maxsize).repr(obj)
class AlwaysDispatchingPrettyPrinter(pprint.PrettyPrinter):
"""PrettyPrinter that always dispatches (regardless of width)."""
def _format(self, object, stream, indent, allowance, context, level):
p = self._dispatch.get(type(object).__repr__, None)
objid = id(object)
if objid in context or p is None:
return super()._format(object, stream, indent, allowance, context, level)
context[objid] = 1
p(self, object, stream, indent, allowance, context, level + 1)
del context[objid]
def _pformat_dispatch(object, indent=1, width=80, depth=None, *, compact=False):
return AlwaysDispatchingPrettyPrinter(
indent=1, width=80, depth=None, compact=False
).pformat(object)

View File

@ -13,7 +13,6 @@ import struct
import sys
import tokenize
import types
from pathlib import Path
from typing import Dict
from typing import List
from typing import Optional
@ -28,6 +27,7 @@ from _pytest.assertion.util import ( # noqa: F401
)
from _pytest.compat import fspath
from _pytest.pathlib import fnmatch_ex
from _pytest.pathlib import Path
from _pytest.pathlib import PurePath
# pytest caches rewritten pycs in pycache dirs
@ -807,8 +807,9 @@ class AssertionRewriter(ast.NodeVisitor):
)
)
negation = ast.UnaryOp(ast.Not(), top_condition)
if self.enable_assertion_pass_hook: # Experimental pytest_assertion_pass hook
negation = ast.UnaryOp(ast.Not(), top_condition)
msg = self.pop_format_context(ast.Str(explanation))
# Failed
@ -860,7 +861,6 @@ class AssertionRewriter(ast.NodeVisitor):
else: # Original assertion rewriting
# Create failure message.
body = self.expl_stmts
negation = ast.UnaryOp(ast.Not(), top_condition)
self.statements.append(ast.If(negation, body, []))
if assert_.msg:
assertmsg = self.helper("_format_assertmsg", assert_.msg)

View File

@ -13,6 +13,7 @@ from typing import Tuple
import _pytest._code
from _pytest import outcomes
from _pytest._io.saferepr import _pformat_dispatch
from _pytest._io.saferepr import safeformat
from _pytest._io.saferepr import saferepr
from _pytest.compat import ATTRS_EQ_FIELD
@ -270,15 +271,8 @@ def _compare_eq_iterable(
lines_left = len(left_formatting)
lines_right = len(right_formatting)
if lines_left != lines_right:
if lines_left > lines_right:
max_width = min(len(x) for x in left_formatting)
else:
max_width = min(len(x) for x in right_formatting)
right_formatting = pprint.pformat(right, width=max_width).splitlines()
lines_right = len(right_formatting)
left_formatting = pprint.pformat(left, width=max_width).splitlines()
lines_left = len(left_formatting)
left_formatting = _pformat_dispatch(left).splitlines()
right_formatting = _pformat_dispatch(right).splitlines()
if lines_left > 1 or lines_right > 1:
_surrounding_parens_on_own_lines(left_formatting)

View File

@ -125,13 +125,14 @@ class Cache:
return
if not cache_dir_exists_already:
self._ensure_supporting_files()
data = json.dumps(value, indent=2, sort_keys=True)
try:
f = path.open("w")
except (IOError, OSError):
self.warn("cache could not write path {path}", path=path)
else:
with f:
json.dump(value, f, indent=2, sort_keys=True)
f.write(data)
def _ensure_supporting_files(self):
"""Create supporting files in the cache dir that are not really part of the cache."""

View File

@ -10,7 +10,14 @@ import sys
from contextlib import contextmanager
from inspect import Parameter
from inspect import signature
from typing import Any
from typing import Callable
from typing import Generic
from typing import Optional
from typing import overload
from typing import Tuple
from typing import TypeVar
from typing import Union
import attr
import py
@ -20,6 +27,13 @@ from _pytest._io.saferepr import saferepr
from _pytest.outcomes import fail
from _pytest.outcomes import TEST_OUTCOME
if False: # TYPE_CHECKING
from typing import Type # noqa: F401 (used in type string)
_T = TypeVar("_T")
_S = TypeVar("_S")
NOTSET = object()
@ -29,12 +43,12 @@ MODULE_NOT_FOUND_ERROR = (
if sys.version_info >= (3, 8):
from importlib import metadata as importlib_metadata # noqa: F401
from importlib import metadata as importlib_metadata
else:
import importlib_metadata # noqa: F401
def _format_args(func):
def _format_args(func: Callable[..., Any]) -> str:
return str(signature(func))
@ -55,12 +69,12 @@ else:
fspath = os.fspath
def is_generator(func):
def is_generator(func: object) -> bool:
genfunc = inspect.isgeneratorfunction(func)
return genfunc and not iscoroutinefunction(func)
def iscoroutinefunction(func):
def iscoroutinefunction(func: object) -> bool:
"""
Return True if func is a coroutine function (a function defined with async
def syntax, and doesn't contain yield), or a function decorated with
@ -73,7 +87,7 @@ def iscoroutinefunction(func):
return inspect.iscoroutinefunction(func) or getattr(func, "_is_coroutine", False)
def getlocation(function, curdir=None):
def getlocation(function, curdir=None) -> str:
function = get_real_func(function)
fn = py.path.local(inspect.getfile(function))
lineno = function.__code__.co_firstlineno
@ -82,7 +96,7 @@ def getlocation(function, curdir=None):
return "%s:%d" % (fn, lineno + 1)
def num_mock_patch_args(function):
def num_mock_patch_args(function) -> int:
""" return number of arguments used up by mock arguments (if any) """
patchings = getattr(function, "patchings", None)
if not patchings:
@ -101,7 +115,13 @@ def num_mock_patch_args(function):
)
def getfuncargnames(function, *, name: str = "", is_method=False, cls=None):
def getfuncargnames(
function: Callable[..., Any],
*,
name: str = "",
is_method: bool = False,
cls: Optional[type] = None
) -> Tuple[str, ...]:
"""Returns the names of a function's mandatory arguments.
This should return the names of all function arguments that:
@ -169,7 +189,7 @@ else:
from contextlib import nullcontext # noqa
def get_default_arg_names(function):
def get_default_arg_names(function: Callable[..., Any]) -> Tuple[str, ...]:
# 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
return tuple(
@ -188,18 +208,18 @@ _non_printable_ascii_translate_table.update(
)
def _translate_non_printable(s):
def _translate_non_printable(s: str) -> str:
return s.translate(_non_printable_ascii_translate_table)
STRING_TYPES = bytes, str
def _bytes_to_ascii(val):
def _bytes_to_ascii(val: bytes) -> str:
return val.decode("ascii", "backslashreplace")
def ascii_escaped(val):
def ascii_escaped(val: Union[bytes, str]):
"""If val is pure ascii, returns it as a str(). Otherwise, escapes
bytes objects into a sequence of escaped bytes:
@ -296,7 +316,7 @@ def getimfunc(func):
return func
def safe_getattr(object, name, default):
def safe_getattr(object: Any, name: str, default: Any) -> Any:
""" Like getattr but return default upon any Exception or any OutcomeException.
Attribute access can potentially fail for 'evil' Python objects.
@ -310,7 +330,7 @@ def safe_getattr(object, name, default):
return default
def safe_isclass(obj):
def safe_isclass(obj: object) -> bool:
"""Ignore any exception via isinstance on Python 3."""
try:
return inspect.isclass(obj)
@ -331,39 +351,26 @@ COLLECT_FAKEMODULE_ATTRIBUTES = (
)
def _setup_collect_fakemodule():
def _setup_collect_fakemodule() -> None:
from types import ModuleType
import pytest
pytest.collect = ModuleType("pytest.collect")
pytest.collect.__all__ = [] # used for setns
# Types ignored because the module is created dynamically.
pytest.collect = ModuleType("pytest.collect") # type: ignore
pytest.collect.__all__ = [] # type: ignore # used for setns
for attr_name in COLLECT_FAKEMODULE_ATTRIBUTES:
setattr(pytest.collect, attr_name, getattr(pytest, attr_name))
setattr(pytest.collect, attr_name, getattr(pytest, attr_name)) # type: ignore
class CaptureIO(io.TextIOWrapper):
def __init__(self):
def __init__(self) -> None:
super().__init__(io.BytesIO(), encoding="UTF-8", newline="", write_through=True)
def getvalue(self):
def getvalue(self) -> str:
assert isinstance(self.buffer, io.BytesIO)
return self.buffer.getvalue().decode("UTF-8")
class FuncargnamesCompatAttr:
""" helper class so that Metafunc, Function and FixtureRequest
don't need to each define the "funcargnames" compatibility attribute.
"""
@property
def funcargnames(self):
""" alias attribute for ``fixturenames`` for pre-2.3 compatibility"""
import warnings
from _pytest.deprecated import FUNCARGNAMES
warnings.warn(FUNCARGNAMES, stacklevel=2)
return self.fixturenames
if sys.version_info < (3, 5, 2): # pragma: no cover
def overload(f): # noqa: F811
@ -374,3 +381,33 @@ if getattr(attr, "__version_info__", ()) >= (19, 2):
ATTRS_EQ_FIELD = "eq"
else:
ATTRS_EQ_FIELD = "cmp"
if sys.version_info >= (3, 8):
from functools import cached_property
else:
class cached_property(Generic[_S, _T]):
__slots__ = ("func", "__doc__")
def __init__(self, func: Callable[[_S], _T]) -> None:
self.func = func
self.__doc__ = func.__doc__
@overload
def __get__(
self, instance: None, owner: Optional["Type[_S]"] = ...
) -> "cached_property[_S, _T]":
raise NotImplementedError()
@overload # noqa: F811
def __get__( # noqa: F811
self, instance: _S, owner: Optional["Type[_S]"] = ...
) -> _T:
raise NotImplementedError()
def __get__(self, instance, owner=None): # noqa: F811
if instance is None:
return self
value = instance.__dict__[self.func.__name__] = self.func(instance)
return value

View File

@ -8,7 +8,6 @@ import sys
import types
import warnings
from functools import lru_cache
from pathlib import Path
from types import TracebackType
from typing import Any
from typing import Callable
@ -40,11 +39,14 @@ from _pytest._code import filter_traceback
from _pytest.compat import importlib_metadata
from _pytest.outcomes import fail
from _pytest.outcomes import Skipped
from _pytest.pathlib import Path
from _pytest.warning_types import PytestConfigWarning
if False: # TYPE_CHECKING
from typing import Type
from .argparsing import Argument
hookimpl = HookimplMarker("pytest")
hookspec = HookspecMarker("pytest")
@ -131,13 +133,7 @@ def directory_arg(path, optname):
# Plugins that cannot be disabled via "-p no:X" currently.
essential_plugins = ( # fmt: off
"mark",
"main",
"runner",
"fixtures",
"helpconfig", # Provides -p.
) # fmt: on
essential_plugins = ("mark", "main", "runner", "fixtures", "helpconfig") # Provides -p.
default_plugins = essential_plugins + (
"python",
@ -154,7 +150,6 @@ default_plugins = essential_plugins + (
"assertion",
"junitxml",
"resultlog",
"report_log",
"doctest",
"cacheprovider",
"freeze_support",
@ -588,7 +583,7 @@ class PytestPluginManager(PluginManager):
_issue_warning_captured(
PytestConfigWarning("skipped plugin {!r}: {}".format(modname, e.msg)),
self.hook,
stacklevel=1,
stacklevel=2,
)
else:
mod = sys.modules[importspec]
@ -680,7 +675,7 @@ class Config:
plugins = attr.ib()
dir = attr.ib(type=Path)
def __init__(self, pluginmanager, *, invocation_params=None):
def __init__(self, pluginmanager, *, invocation_params=None) -> None:
from .argparsing import Parser, FILE_OR_DIR
if invocation_params is None:
@ -793,11 +788,11 @@ class Config:
config.pluginmanager.consider_pluginarg(x)
return config
def _processopt(self, opt):
def _processopt(self, opt: "Argument") -> None:
for name in opt._short_opts + opt._long_opts:
self._opt2dest[name] = opt.dest
if hasattr(opt, "default") and opt.dest:
if hasattr(opt, "default"):
if not hasattr(self.option, opt.dest):
setattr(self.option, opt.dest, opt.default)
@ -805,7 +800,7 @@ class Config:
def pytest_load_initial_conftests(self, early_config):
self.pluginmanager._set_initial_conftests(early_config.known_args_namespace)
def _initini(self, args) -> None:
def _initini(self, args: Sequence[str]) -> None:
ns, unknown_args = self._parser.parse_known_and_unknown_args(
args, namespace=copy.copy(self.option)
)
@ -822,7 +817,7 @@ class Config:
self._parser.addini("minversion", "minimally required pytest version")
self._override_ini = ns.override_ini or ()
def _consider_importhook(self, args):
def _consider_importhook(self, args: Sequence[str]) -> None:
"""Install the PEP 302 import hook if using assertion rewriting.
Needs to parse the --assert=<mode> option from the commandline
@ -862,19 +857,19 @@ class Config:
for name in _iter_rewritable_modules(package_files):
hook.mark_rewrite(name)
def _validate_args(self, args, via):
def _validate_args(self, args: List[str], via: str) -> List[str]:
"""Validate known args."""
self._parser._config_source_hint = via
self._parser._config_source_hint = via # type: ignore
try:
self._parser.parse_known_and_unknown_args(
args, namespace=copy.copy(self.option)
)
finally:
del self._parser._config_source_hint
del self._parser._config_source_hint # type: ignore
return args
def _preparse(self, args, addopts=True):
def _preparse(self, args: List[str], addopts: bool = True) -> None:
if addopts:
env_addopts = os.environ.get("PYTEST_ADDOPTS", "")
if len(env_addopts):
@ -938,7 +933,7 @@ class Config:
)
)
def parse(self, args, addopts=True):
def parse(self, args: List[str], addopts: bool = True) -> None:
# parse given cmdline arguments into this config object.
assert not hasattr(
self, "args"
@ -949,7 +944,7 @@ class Config:
self._preparse(args, addopts=addopts)
# XXX deprecated hook:
self.hook.pytest_cmdline_preparse(config=self, args=args)
self._parser.after_preparse = True
self._parser.after_preparse = True # type: ignore
try:
args = self._parser.parse_setoption(
args, self.option, namespace=self.option
@ -974,7 +969,7 @@ class Config:
def getini(self, name: str):
""" return configuration value from an :ref:`ini file <inifiles>`. If the
specified name hasn't been registered through a prior
:py:func:`parser.addini <_pytest.config.Parser.addini>`
:py:func:`parser.addini <_pytest.config.argparsing.Parser.addini>`
call (usually from a plugin), a ValueError is raised. """
try:
return self._inicache[name]

View File

@ -3,15 +3,24 @@ import sys
import warnings
from gettext import gettext
from typing import Any
from typing import Callable
from typing import cast
from typing import Dict
from typing import List
from typing import Mapping
from typing import Optional
from typing import Sequence
from typing import Tuple
from typing import Union
import py
from _pytest.config.exceptions import UsageError
if False: # TYPE_CHECKING
from typing import NoReturn
from typing_extensions import Literal # noqa: F401
FILE_OR_DIR = "file_or_dir"
@ -22,9 +31,13 @@ class Parser:
there's an error processing the command line arguments.
"""
prog = None
prog = None # type: Optional[str]
def __init__(self, usage=None, processopt=None):
def __init__(
self,
usage: Optional[str] = None,
processopt: Optional[Callable[["Argument"], None]] = None,
) -> None:
self._anonymous = OptionGroup("custom options", parser=self)
self._groups = [] # type: List[OptionGroup]
self._processopt = processopt
@ -33,12 +46,14 @@ class Parser:
self._ininames = [] # type: List[str]
self.extra_info = {} # type: Dict[str, Any]
def processoption(self, option):
def processoption(self, option: "Argument") -> None:
if self._processopt:
if option.dest:
self._processopt(option)
def getgroup(self, name, description="", after=None):
def getgroup(
self, name: str, description: str = "", after: Optional[str] = None
) -> "OptionGroup":
""" get (or create) a named option Group.
:name: name of the option group.
@ -47,7 +62,7 @@ class Parser:
The returned group object has an ``addoption`` method with the same
signature as :py:func:`parser.addoption
<_pytest.config.Parser.addoption>` but will be shown in the
<_pytest.config.argparsing.Parser.addoption>` but will be shown in the
respective group in the output of ``pytest. --help``.
"""
for group in self._groups:
@ -61,13 +76,13 @@ class Parser:
self._groups.insert(i + 1, group)
return group
def addoption(self, *opts, **attrs):
def addoption(self, *opts: str, **attrs: Any) -> None:
""" register a command line option.
:opts: option names, can be short or long options.
:attrs: same attributes which the ``add_option()`` function of the
:attrs: same attributes which the ``add_argument()`` function of the
`argparse library
<http://docs.python.org/2/library/argparse.html>`_
<https://docs.python.org/library/argparse.html>`_
accepts.
After command line parsing options are available on the pytest config
@ -77,7 +92,11 @@ class Parser:
"""
self._anonymous.addoption(*opts, **attrs)
def parse(self, args, namespace=None):
def parse(
self,
args: Sequence[Union[str, py.path.local]],
namespace: Optional[argparse.Namespace] = None,
) -> argparse.Namespace:
from _pytest._argcomplete import try_argcomplete
self.optparser = self._getparser()
@ -98,27 +117,37 @@ class Parser:
n = option.names()
a = option.attrs()
arggroup.add_argument(*n, **a)
file_or_dir_arg = optparser.add_argument(FILE_OR_DIR, nargs="*")
# bash like autocompletion for dirs (appending '/')
# Type ignored because typeshed doesn't know about argcomplete.
optparser.add_argument( # type: ignore
FILE_OR_DIR, nargs="*"
).completer = filescompleter
file_or_dir_arg.completer = filescompleter # type: ignore
return optparser
def parse_setoption(self, args, option, namespace=None):
def parse_setoption(
self,
args: Sequence[Union[str, py.path.local]],
option: argparse.Namespace,
namespace: Optional[argparse.Namespace] = None,
) -> List[str]:
parsedoption = self.parse(args, namespace=namespace)
for name, value in parsedoption.__dict__.items():
setattr(option, name, value)
return getattr(parsedoption, FILE_OR_DIR)
return cast(List[str], getattr(parsedoption, FILE_OR_DIR))
def parse_known_args(self, args, namespace=None) -> argparse.Namespace:
def parse_known_args(
self,
args: Sequence[Union[str, py.path.local]],
namespace: Optional[argparse.Namespace] = None,
) -> argparse.Namespace:
"""parses and returns a namespace object with known arguments at this
point.
"""
return self.parse_known_and_unknown_args(args, namespace=namespace)[0]
def parse_known_and_unknown_args(
self, args, namespace=None
self,
args: Sequence[Union[str, py.path.local]],
namespace: Optional[argparse.Namespace] = None,
) -> Tuple[argparse.Namespace, List[str]]:
"""parses and returns a namespace object with known arguments, and
the remaining arguments unknown at this point.
@ -127,7 +156,13 @@ class Parser:
args = [str(x) if isinstance(x, py.path.local) else x for x in args]
return optparser.parse_known_args(args, namespace=namespace)
def addini(self, name, help, type=None, default=None):
def addini(
self,
name: str,
help: str,
type: Optional["Literal['pathlist', 'args', 'linelist', 'bool']"] = None,
default=None,
) -> None:
""" register an ini-file option.
:name: name of the ini-variable
@ -149,11 +184,11 @@ class ArgumentError(Exception):
inconsistent arguments.
"""
def __init__(self, msg, option):
def __init__(self, msg: str, option: Union["Argument", str]) -> None:
self.msg = msg
self.option_id = str(option)
def __str__(self):
def __str__(self) -> str:
if self.option_id:
return "option {}: {}".format(self.option_id, self.msg)
else:
@ -170,12 +205,11 @@ class Argument:
_typ_map = {"int": int, "string": str, "float": float, "complex": complex}
def __init__(self, *names, **attrs):
def __init__(self, *names: str, **attrs: Any) -> None:
"""store parms in private vars for use in add_argument"""
self._attrs = attrs
self._short_opts = [] # type: List[str]
self._long_opts = [] # type: List[str]
self.dest = attrs.get("dest")
if "%default" in (attrs.get("help") or ""):
warnings.warn(
'pytest now uses argparse. "%default" should be'
@ -221,23 +255,25 @@ class Argument:
except KeyError:
pass
self._set_opt_strings(names)
if not self.dest:
if self._long_opts:
self.dest = self._long_opts[0][2:].replace("-", "_")
else:
try:
self.dest = self._short_opts[0][1:]
except IndexError:
raise ArgumentError("need a long or short option", self)
dest = attrs.get("dest") # type: Optional[str]
if dest:
self.dest = dest
elif self._long_opts:
self.dest = self._long_opts[0][2:].replace("-", "_")
else:
try:
self.dest = self._short_opts[0][1:]
except IndexError:
self.dest = "???" # Needed for the error repr.
raise ArgumentError("need a long or short option", self)
def names(self):
def names(self) -> List[str]:
return self._short_opts + self._long_opts
def attrs(self):
def attrs(self) -> Mapping[str, Any]:
# update any attributes set by processopt
attrs = "default dest help".split()
if self.dest:
attrs.append(self.dest)
attrs.append(self.dest)
for attr in attrs:
try:
self._attrs[attr] = getattr(self, attr)
@ -250,7 +286,7 @@ class Argument:
self._attrs["help"] = a
return self._attrs
def _set_opt_strings(self, opts):
def _set_opt_strings(self, opts: Sequence[str]) -> None:
"""directly from optparse
might not be necessary as this is passed to argparse later on"""
@ -293,13 +329,15 @@ class Argument:
class OptionGroup:
def __init__(self, name, description="", parser=None):
def __init__(
self, name: str, description: str = "", parser: Optional[Parser] = None
) -> None:
self.name = name
self.description = description
self.options = [] # type: List[Argument]
self.parser = parser
def addoption(self, *optnames, **attrs):
def addoption(self, *optnames: str, **attrs: Any) -> None:
""" add an option to this group.
if a shortened version of a long option is specified it will
@ -315,11 +353,11 @@ class OptionGroup:
option = Argument(*optnames, **attrs)
self._addoption_instance(option, shortupper=False)
def _addoption(self, *optnames, **attrs):
def _addoption(self, *optnames: str, **attrs: Any) -> None:
option = Argument(*optnames, **attrs)
self._addoption_instance(option, shortupper=True)
def _addoption_instance(self, option, shortupper=False):
def _addoption_instance(self, option: "Argument", shortupper: bool = False) -> None:
if not shortupper:
for opt in option._short_opts:
if opt[0] == "-" and opt[1].islower():
@ -330,9 +368,12 @@ class OptionGroup:
class MyOptionParser(argparse.ArgumentParser):
def __init__(self, parser, extra_info=None, prog=None):
if not extra_info:
extra_info = {}
def __init__(
self,
parser: Parser,
extra_info: Optional[Dict[str, Any]] = None,
prog: Optional[str] = None,
) -> None:
self._parser = parser
argparse.ArgumentParser.__init__(
self,
@ -344,34 +385,42 @@ class MyOptionParser(argparse.ArgumentParser):
)
# extra_info is a dict of (param -> value) to display if there's
# an usage error to provide more contextual information to the user
self.extra_info = extra_info
self.extra_info = extra_info if extra_info else {}
def error(self, message):
def error(self, message: str) -> "NoReturn":
"""Transform argparse error message into UsageError."""
msg = "{}: error: {}".format(self.prog, message)
if hasattr(self._parser, "_config_source_hint"):
msg = "{} ({})".format(msg, self._parser._config_source_hint)
# Type ignored because the attribute is set dynamically.
msg = "{} ({})".format(msg, self._parser._config_source_hint) # type: ignore
raise UsageError(self.format_usage() + msg)
def parse_args(self, args=None, namespace=None):
# Type ignored because typeshed has a very complex type in the superclass.
def parse_args( # type: ignore
self,
args: Optional[Sequence[str]] = None,
namespace: Optional[argparse.Namespace] = None,
) -> argparse.Namespace:
"""allow splitting of positional arguments"""
args, argv = self.parse_known_args(args, namespace)
if argv:
for arg in argv:
parsed, unrecognized = self.parse_known_args(args, namespace)
if unrecognized:
for arg in unrecognized:
if arg and arg[0] == "-":
lines = ["unrecognized arguments: %s" % (" ".join(argv))]
lines = ["unrecognized arguments: %s" % (" ".join(unrecognized))]
for k, v in sorted(self.extra_info.items()):
lines.append(" {}: {}".format(k, v))
self.error("\n".join(lines))
getattr(args, FILE_OR_DIR).extend(argv)
return args
getattr(parsed, FILE_OR_DIR).extend(unrecognized)
return parsed
if sys.version_info[:2] < (3, 9): # pragma: no cover
# Backport of https://github.com/python/cpython/pull/14316 so we can
# disable long --argument abbreviations without breaking short flags.
def _parse_optional(self, arg_string):
def _parse_optional(
self, arg_string: str
) -> Optional[Tuple[Optional[argparse.Action], str, Optional[str]]]:
if not arg_string:
return None
if not arg_string[0] in self.prefix_chars:
@ -395,7 +444,7 @@ class MyOptionParser(argparse.ArgumentParser):
options = ", ".join(option for _, option, _ in option_tuples)
self.error(msg % {"option": arg_string, "matches": options})
elif len(option_tuples) == 1:
option_tuple, = option_tuples
(option_tuple,) = option_tuples
return option_tuple
if self._negative_number_matcher.match(arg_string):
if not self._has_negative_number_optionals:
@ -409,49 +458,45 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter):
"""shorten help for long options that differ only in extra hyphens
- collapse **long** options that are the same except for extra hyphens
- special action attribute map_long_option allows suppressing additional
long options
- shortcut if there are only two options and one of them is a short one
- cache result on action object as this is called at least 2 times
"""
def __init__(self, *args, **kwargs):
def __init__(self, *args: Any, **kwargs: Any) -> None:
"""Use more accurate terminal width via pylib."""
if "width" not in kwargs:
kwargs["width"] = py.io.get_terminal_width()
super().__init__(*args, **kwargs)
def _format_action_invocation(self, action):
def _format_action_invocation(self, action: argparse.Action) -> str:
orgstr = argparse.HelpFormatter._format_action_invocation(self, action)
if orgstr and orgstr[0] != "-": # only optional arguments
return orgstr
res = getattr(action, "_formatted_action_invocation", None)
res = getattr(
action, "_formatted_action_invocation", None
) # type: Optional[str]
if res:
return res
options = orgstr.split(", ")
if len(options) == 2 and (len(options[0]) == 2 or len(options[1]) == 2):
# a shortcut for '-h, --help' or '--abc', '-a'
action._formatted_action_invocation = orgstr
action._formatted_action_invocation = orgstr # type: ignore
return orgstr
return_list = []
option_map = getattr(action, "map_long_option", {})
if option_map is None:
option_map = {}
short_long = {} # type: Dict[str, str]
for option in options:
if len(option) == 2 or option[2] == " ":
continue
if not option.startswith("--"):
raise ArgumentError(
'long optional argument without "--": [%s]' % (option), self
'long optional argument without "--": [%s]' % (option), option
)
xxoption = option[2:]
if xxoption.split()[0] not in option_map:
shortened = xxoption.replace("-", "")
if shortened not in short_long or len(short_long[shortened]) < len(
xxoption
):
short_long[shortened] = xxoption
shortened = xxoption.replace("-", "")
if shortened not in short_long or len(short_long[shortened]) < len(
xxoption
):
short_long[shortened] = xxoption
# now short_long has been filled out to the longest with dashes
# **and** we keep the right option ordering from add_argument
for option in options:
@ -459,5 +504,6 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter):
return_list.append(option)
if option[2:] == short_long.get(option.replace("-", "")):
return_list.append(option.replace(" ", "=", 1))
action._formatted_action_invocation = ", ".join(return_list)
return action._formatted_action_invocation
formatted_action_invocation = ", ".join(return_list)
action._formatted_action_invocation = formatted_action_invocation # type: ignore
return formatted_action_invocation

View File

@ -9,6 +9,7 @@ All constants defined in this module should be either PytestWarning instances or
in case of warnings which need to format their messages.
"""
from _pytest.warning_types import PytestDeprecationWarning
from _pytest.warning_types import UnformattedWarning
# set of plugins which have been integrated into the core; we use this list to ignore
# them during registration to avoid conflicts
@ -26,7 +27,7 @@ FUNCARGNAMES = PytestDeprecationWarning(
RESULT_LOG = PytestDeprecationWarning(
"--result-log is deprecated and scheduled for removal in pytest 6.0.\n"
"--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."
)
@ -34,3 +35,13 @@ FIXTURE_POSITIONAL_ARGUMENTS = PytestDeprecationWarning(
"Passing arguments to pytest.fixture() as positional arguments is deprecated - pass them "
"as a keyword argument instead."
)
NODE_USE_FROM_PARENT = UnformattedWarning(
PytestDeprecationWarning,
"direct construction of {name} has been deprecated, please use {name}.from_parent",
)
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."
)

View File

@ -108,9 +108,9 @@ def pytest_collect_file(path, parent):
config = parent.config
if path.ext == ".py":
if config.option.doctestmodules and not _is_setup_py(config, path, parent):
return DoctestModule(path, parent)
return DoctestModule.from_parent(parent, fspath=path)
elif _is_doctest(config, path, parent):
return DoctestTextfile(path, parent)
return DoctestTextfile.from_parent(parent, fspath=path)
def _is_setup_py(config, path, parent):
@ -215,6 +215,10 @@ class DoctestItem(pytest.Item):
self.obj = None
self.fixture_request = None
@classmethod
def from_parent(cls, parent, *, name, runner, dtest):
return cls._create(name=name, parent=parent, runner=runner, dtest=dtest)
def setup(self):
if self.dtest is not None:
self.fixture_request = _setup_fixtures(self)
@ -370,7 +374,9 @@ class DoctestTextfile(pytest.Module):
parser = doctest.DocTestParser()
test = parser.get_doctest(text, globs, name, filename, 0)
if test.examples:
yield DoctestItem(test.name, self, runner, test)
yield DoctestItem.from_parent(
self, name=test.name, runner=runner, dtest=test
)
def _check_all_skipped(test):
@ -467,7 +473,9 @@ class DoctestModule(pytest.Module):
for test in finder.find(module, module.__name__):
if test.examples: # skip empty doctests
yield DoctestItem(test.name, self, runner, test)
yield DoctestItem.from_parent(
self, name=test.name, runner=runner, dtest=test
)
def _setup_fixtures(doctest_item):

View File

@ -18,7 +18,6 @@ from _pytest._code.code import FormattedExcinfo
from _pytest._code.code import TerminalRepr
from _pytest.compat import _format_args
from _pytest.compat import _PytestWrapper
from _pytest.compat import FuncargnamesCompatAttr
from _pytest.compat import get_real_func
from _pytest.compat import get_real_method
from _pytest.compat import getfslineno
@ -29,6 +28,7 @@ from _pytest.compat import is_generator
from _pytest.compat import NOTSET
from _pytest.compat import safe_getattr
from _pytest.deprecated import FIXTURE_POSITIONAL_ARGUMENTS
from _pytest.deprecated import FUNCARGNAMES
from _pytest.outcomes import fail
from _pytest.outcomes import TEST_OUTCOME
@ -36,6 +36,7 @@ if False: # TYPE_CHECKING
from typing import Type
from _pytest import nodes
from _pytest.main import Session
@attr.s(frozen=True)
@ -44,7 +45,7 @@ class PseudoFixtureDef:
scope = attr.ib()
def pytest_sessionstart(session):
def pytest_sessionstart(session: "Session"):
import _pytest.python
import _pytest.nodes
@ -336,7 +337,7 @@ class FuncFixtureInfo:
self.names_closure[:] = sorted(closure, key=self.names_closure.index)
class FixtureRequest(FuncargnamesCompatAttr):
class FixtureRequest:
""" A request for a fixture from a test or fixture function.
A request object gives access to the requesting test context
@ -363,6 +364,12 @@ class FixtureRequest(FuncargnamesCompatAttr):
result.extend(set(self._fixture_defs).difference(result))
return result
@property
def funcargnames(self):
""" alias attribute for ``fixturenames`` for pre-2.3 compatibility"""
warnings.warn(FUNCARGNAMES, stacklevel=2)
return self.fixturenames
@property
def node(self):
""" underlying collection node (depends on current request scope)"""
@ -504,13 +511,11 @@ class FixtureRequest(FuncargnamesCompatAttr):
values.append(fixturedef)
current = current._parent_request
def _compute_fixture_value(self, fixturedef):
def _compute_fixture_value(self, fixturedef: "FixtureDef") -> None:
"""
Creates a SubRequest based on "self" and calls the execute method of the given fixturedef object. This will
force the FixtureDef object to throw away any previous results and compute a new fixture value, which
will be stored into the FixtureDef object itself.
:param FixtureDef fixturedef:
"""
# prepare a subrequest object before calling fixture function
# (latter managed by fixturedef)
@ -538,9 +543,8 @@ class FixtureRequest(FuncargnamesCompatAttr):
if has_params:
frame = inspect.stack()[3]
frameinfo = inspect.getframeinfo(frame[0])
source_path = frameinfo.filename
source_path = py.path.local(frameinfo.filename)
source_lineno = frameinfo.lineno
source_path = py.path.local(source_path)
if source_path.relto(funcitem.config.rootdir):
source_path = source_path.relto(funcitem.config.rootdir)
msg = (

View File

@ -45,10 +45,10 @@ def pytest_addoption(parser, pluginmanager):
files situated at the tests root directory due to how pytest
:ref:`discovers plugins during startup <pluginorder>`.
:arg _pytest.config.Parser parser: To add command line options, call
:py:func:`parser.addoption(...) <_pytest.config.Parser.addoption>`.
:arg _pytest.config.argparsing.Parser parser: To add command line options, call
:py:func:`parser.addoption(...) <_pytest.config.argparsing.Parser.addoption>`.
To add ini-file values call :py:func:`parser.addini(...)
<_pytest.config.Parser.addini>`.
<_pytest.config.argparsing.Parser.addini>`.
:arg _pytest.config.PytestPluginManager pluginmanager: pytest plugin manager,
which can be used to install :py:func:`hookspec`'s or :py:func:`hookimpl`'s
@ -148,7 +148,7 @@ def pytest_load_initial_conftests(early_config, parser, args):
:param _pytest.config.Config early_config: pytest config object
:param list[str] args: list of arguments passed on the command line
:param _pytest.config.Parser parser: to add command line options
:param _pytest.config.argparsing.Parser parser: to add command line options
"""
@ -562,7 +562,7 @@ def pytest_terminal_summary(terminalreporter, exitstatus, config):
@hookspec(historic=True)
def pytest_warning_captured(warning_message, when, item):
def pytest_warning_captured(warning_message, when, item, location):
"""
Process a warning captured by the internal pytest warnings plugin.
@ -582,6 +582,10 @@ def pytest_warning_captured(warning_message, when, item):
in a future release.
The item being executed if ``when`` is ``"runtest"``, otherwise ``None``.
:param tuple location:
Holds information about the execution context of the captured warning (filename, linenumber, function).
``function`` evaluates to <module> when the execution context is at the module level.
"""

View File

@ -19,8 +19,10 @@ from datetime import datetime
import py
import pytest
from _pytest import deprecated
from _pytest import nodes
from _pytest.config import filename_arg
from _pytest.warnings import _issue_warning_captured
class Junit(py.xml.Namespace):
@ -421,9 +423,7 @@ def pytest_addoption(parser):
default="total",
) # choices=['total', 'call'])
parser.addini(
"junit_family",
"Emit XML for schema: one of legacy|xunit1|xunit2",
default="xunit1",
"junit_family", "Emit XML for schema: one of legacy|xunit1|xunit2", default=None
)
@ -431,13 +431,17 @@ def pytest_configure(config):
xmlpath = config.option.xmlpath
# prevent opening xmllog on slave nodes (xdist)
if xmlpath and not hasattr(config, "slaveinput"):
junit_family = config.getini("junit_family")
if not junit_family:
_issue_warning_captured(deprecated.JUNIT_XML_DEFAULT_FAMILY, config.hook, 2)
junit_family = "xunit1"
config._xml = LogXML(
xmlpath,
config.option.junitprefix,
config.getini("junit_suite_name"),
config.getini("junit_logging"),
config.getini("junit_duration_report"),
config.getini("junit_family"),
junit_family,
config.getini("junit_log_passing_tests"),
)
config.pluginmanager.register(config._xml)

View File

@ -15,6 +15,7 @@ from _pytest import nodes
from _pytest.config import directory_arg
from _pytest.config import hookimpl
from _pytest.config import UsageError
from _pytest.fixtures import FixtureManager
from _pytest.outcomes import exit
from _pytest.runner import collect_one_node
from _pytest.runner import SetupState
@ -184,7 +185,7 @@ def pytest_addoption(parser):
def wrap_session(config, doit):
"""Skeleton command line program"""
session = Session(config)
session = Session.from_config(config)
session.exitstatus = ExitCode.OK
initstate = 0
try:
@ -372,6 +373,7 @@ class Session(nodes.FSCollector):
Interrupted = Interrupted
Failed = Failed
_setupstate = None # type: SetupState
_fixturemanager = None # type: FixtureManager
def __init__(self, config):
nodes.FSCollector.__init__(
@ -395,6 +397,10 @@ class Session(nodes.FSCollector):
self.config.pluginmanager.register(self, name="session")
@classmethod
def from_config(cls, config):
return cls._create(config)
def __repr__(self):
return "<%s %s exitstatus=%r testsfailed=%d testscollected=%d>" % (
self.__class__.__name__,

View File

@ -2,6 +2,8 @@ import inspect
import warnings
from collections import namedtuple
from collections.abc import MutableMapping
from typing import List
from typing import Optional
from typing import Set
import attr
@ -144,7 +146,15 @@ class Mark:
#: keyword arguments of the mark decorator
kwargs = attr.ib() # Dict[str, object]
def combined_with(self, other):
#: source Mark for ids with parametrize Marks
_param_ids_from = attr.ib(type=Optional["Mark"], default=None, repr=False)
#: resolved/generated ids with parametrize Marks
_param_ids_generated = attr.ib(type=Optional[List[str]], default=None, repr=False)
def _has_param_ids(self):
return "ids" in self.kwargs or len(self.args) >= 4
def combined_with(self, other: "Mark") -> "Mark":
"""
:param other: the mark to combine with
:type other: Mark
@ -153,8 +163,20 @@ class Mark:
combines by appending args and merging the mappings
"""
assert self.name == other.name
# Remember source of ids with parametrize Marks.
param_ids_from = None # type: Optional[Mark]
if self.name == "parametrize":
if other._has_param_ids():
param_ids_from = other
elif self._has_param_ids():
param_ids_from = self
return Mark(
self.name, self.args + other.args, dict(self.kwargs, **other.kwargs)
self.name,
self.args + other.args,
dict(self.kwargs, **other.kwargs),
param_ids_from=param_ids_from,
)
@ -314,13 +336,19 @@ class MarkGenerator:
"{!r} not found in `markers` configuration option".format(name),
pytrace=False,
)
else:
warnings.warn(
"Unknown pytest.mark.%s - is this a typo? You can register "
"custom marks to avoid this warning - for details, see "
"https://docs.pytest.org/en/latest/mark.html" % name,
PytestUnknownMarkWarning,
)
# Raise a specific error for common misspellings of "parametrize".
if name in ["parameterize", "parametrise", "parameterise"]:
__tracebackhide__ = True
fail("Unknown '{}' mark, did you mean 'parametrize'?".format(name))
warnings.warn(
"Unknown pytest.mark.%s - is this a typo? You can register "
"custom marks to avoid this warning - for details, see "
"https://docs.pytest.org/en/latest/mark.html" % name,
PytestUnknownMarkWarning,
2,
)
return MarkDecorator(Mark(name, (), {}))

View File

@ -15,7 +15,10 @@ import _pytest._code
from _pytest._code.code import ExceptionChainRepr
from _pytest._code.code import ExceptionInfo
from _pytest._code.code import ReprExceptionInfo
from _pytest.compat import cached_property
from _pytest.compat import getfslineno
from _pytest.config import Config
from _pytest.deprecated import NODE_USE_FROM_PARENT
from _pytest.fixtures import FixtureDef
from _pytest.fixtures import FixtureLookupError
from _pytest.fixtures import FixtureLookupErrorRepr
@ -71,18 +74,27 @@ def ischildnode(baseid, nodeid):
return node_parts[: len(base_parts)] == base_parts
class Node:
class NodeMeta(type):
def __call__(self, *k, **kw):
warnings.warn(NODE_USE_FROM_PARENT.format(name=self.__name__), stacklevel=2)
return super().__call__(*k, **kw)
def _create(self, *k, **kw):
return super().__call__(*k, **kw)
class Node(metaclass=NodeMeta):
""" base class for Collector and Item the test collection tree.
Collector subclasses have children, Items are terminal nodes."""
def __init__(
self,
name,
parent=None,
config=None,
parent: Optional["Node"] = None,
config: Optional[Config] = None,
session: Optional["Session"] = None,
fspath=None,
nodeid=None,
fspath: Optional[py.path.local] = None,
nodeid: Optional[str] = None,
) -> None:
#: a unique name within the scope of the parent node
self.name = name
@ -91,14 +103,20 @@ class Node:
self.parent = parent
#: the pytest config object
self.config = config or parent.config
if config:
self.config = config
else:
if not parent:
raise TypeError("config or parent must be provided")
self.config = parent.config
#: the session this node is part of
if session is None:
assert parent.session is not None
self.session = parent.session
else:
if session:
self.session = session
else:
if not parent:
raise TypeError("session or parent must be provided")
self.session = parent.session
#: filesystem path where this node was collected from (can be None)
self.fspath = fspath or getattr(parent, "fspath", None)
@ -119,10 +137,16 @@ class Node:
assert "::()" not in nodeid
self._nodeid = nodeid
else:
if not self.parent:
raise TypeError("nodeid or parent must be provided")
self._nodeid = self.parent.nodeid
if self.name != "()":
self._nodeid += "::" + self.name
@classmethod
def from_parent(cls, parent, *, name):
return cls._create(parent=parent, name=name)
@property
def ihook(self):
""" fspath sensitive hook proxy used to call pytest hooks"""
@ -182,7 +206,7 @@ class Node:
""" return list of all parent collectors up to self,
starting from root of collection tree. """
chain = []
item = self
item = self # type: Optional[Node]
while item is not None:
chain.append(item)
item = item.parent
@ -263,7 +287,7 @@ class Node:
def getparent(self, cls):
""" get the next parent node (including ourself)
which is an instance of the given class"""
current = self
current = self # type: Optional[Node]
while current and not isinstance(current, cls):
current = current.parent
return current
@ -355,12 +379,14 @@ class Collector(Node):
def repr_failure(self, excinfo):
""" represent a collection failure. """
if excinfo.errisinstance(self.CollectError):
if excinfo.errisinstance(self.CollectError) and not self.config.getoption(
"fulltrace", False
):
exc = excinfo.value
return str(exc.args[0])
# Respect explicit tbstyle option, but default to "short"
# (None._repr_failure_py defaults to "long" without "fulltrace" option).
# (_repr_failure_py uses "long" with "fulltrace" option always).
tbstyle = self.config.getoption("tbstyle", "auto")
if tbstyle == "auto":
tbstyle = "short"
@ -406,6 +432,10 @@ class FSCollector(Collector):
super().__init__(name, parent, config, session, nodeid=nodeid, fspath=fspath)
@classmethod
def from_parent(cls, parent, *, fspath):
return cls._create(parent=parent, fspath=fspath)
class File(FSCollector):
""" base class for collecting tests from a file. """
@ -448,17 +478,9 @@ class Item(Node):
def reportinfo(self) -> Tuple[str, Optional[int], str]:
return self.fspath, None, ""
@property
@cached_property
def location(self) -> Tuple[str, Optional[int], str]:
try:
return self._location
except AttributeError:
location = self.reportinfo()
fspath = self.session._node_location_to_relpath(location[0])
assert type(location[2]) is str
self._location = (
fspath,
location[1],
location[2],
) # type: Tuple[str, Optional[int], str]
return self._location
location = self.reportinfo()
fspath = self.session._node_location_to_relpath(location[0])
assert type(location[2]) is str
return (fspath, location[1], location[2])

View File

@ -312,7 +312,7 @@ class HookRecorder:
return self.getfailures("pytest_collectreport")
def listoutcomes(
self
self,
) -> Tuple[List[TestReport], List[TestReport], List[TestReport]]:
passed = []
skipped = []
@ -332,10 +332,17 @@ class HookRecorder:
return [len(x) for x in self.listoutcomes()]
def assertoutcome(self, passed: int = 0, skipped: int = 0, failed: int = 0) -> None:
realpassed, realskipped, realfailed = self.listoutcomes()
assert passed == len(realpassed)
assert skipped == len(realskipped)
assert failed == len(realfailed)
__tracebackhide__ = True
outcomes = self.listoutcomes()
realpassed, realskipped, realfailed = outcomes
obtained = {
"passed": len(realpassed),
"skipped": len(realskipped),
"failed": len(realfailed),
}
expected = {"passed": passed, "skipped": skipped, "failed": failed}
assert obtained == expected, outcomes
def clear(self) -> None:
self.calls[:] = []
@ -441,8 +448,9 @@ class RunResult:
) -> None:
"""Assert that the specified outcomes appear with the respective
numbers (0 means it didn't occur) in the text output from a test run.
"""
__tracebackhide__ = True
d = self.parseoutcomes()
obtained = {
"passed": d.get("passed", 0),
@ -536,10 +544,12 @@ class Testdir:
mp.delenv("TOX_ENV_DIR", raising=False)
# Discard outer pytest options.
mp.delenv("PYTEST_ADDOPTS", raising=False)
# Environment (updates) for inner runs.
# Ensure no user config is used.
tmphome = str(self.tmpdir)
self._env_run_update = {"HOME": tmphome, "USERPROFILE": tmphome}
mp.setenv("HOME", tmphome)
mp.setenv("USERPROFILE", tmphome)
# Do not use colors for inner runs by default.
mp.setenv("PY_COLORS", "0")
def __repr__(self):
return "<Testdir {!r}>".format(self.tmpdir)
@ -735,7 +745,7 @@ class Testdir:
:param arg: a :py:class:`py.path.local` instance of the file
"""
session = Session(config)
session = Session.from_config(config)
assert "::" not in str(arg)
p = py.path.local(arg)
config.hook.pytest_sessionstart(session=session)
@ -753,7 +763,7 @@ class Testdir:
"""
config = self.parseconfigure(path)
session = Session(config)
session = Session.from_config(config)
x = session.fspath.bestrelpath(path)
config.hook.pytest_sessionstart(session=session)
res = session.perform_collect([x], genitems=False)[0]
@ -845,12 +855,6 @@ class Testdir:
plugins = list(plugins)
finalizers = []
try:
# Do not load user config (during runs only).
mp_run = MonkeyPatch()
for k, v in self._env_run_update.items():
mp_run.setenv(k, v)
finalizers.append(mp_run.undo)
# Any sys.module or sys.path changes done while running pytest
# inline should be reverted after the test run completes to avoid
# clashing with later inline tests run within the same pytest test,
@ -1083,7 +1087,6 @@ class Testdir:
env["PYTHONPATH"] = os.pathsep.join(
filter(None, [os.getcwd(), env.get("PYTHONPATH", "")])
)
env.update(self._env_run_update)
kw["env"] = env
if stdin is Testdir.CLOSE_STDIN:
@ -1253,11 +1256,7 @@ class Testdir:
pytest.skip("pexpect.spawn not available")
logfile = self.tmpdir.join("spawn.out").open("wb")
# Do not load user config.
env = os.environ.copy()
env.update(self._env_run_update)
child = pexpect.spawn(cmd, logfile=logfile, env=env)
child = pexpect.spawn(cmd, logfile=logfile)
self.request.addfinalizer(logfile.close)
child.timeout = expect_timeout
return child
@ -1430,8 +1429,10 @@ class LineMatcher:
self._log("{:>{width}}".format("and:", width=wnick), repr(nextline))
extralines.append(nextline)
else:
self._log("remains unmatched: {!r}".format(line))
pytest.fail(self._log_text.lstrip())
msg = "remains unmatched: {!r}".format(line)
self._log(msg)
self._fail(msg)
self._log_output = []
def no_fnmatch_line(self, pat):
"""Ensure captured lines do not match the given pattern, using ``fnmatch.fnmatch``.
@ -1457,18 +1458,21 @@ class LineMatcher:
__tracebackhide__ = True
nomatch_printed = False
wnick = len(match_nickname) + 1
try:
for line in self.lines:
if match_func(line, pat):
self._log("%s:" % match_nickname, repr(pat))
self._log("{:>{width}}".format("with:", width=wnick), repr(line))
pytest.fail(self._log_text.lstrip())
else:
if not nomatch_printed:
self._log(
"{:>{width}}".format("nomatch:", width=wnick), repr(pat)
)
nomatch_printed = True
self._log("{:>{width}}".format("and:", width=wnick), repr(line))
finally:
self._log_output = []
for line in self.lines:
if match_func(line, pat):
msg = "{}: {!r}".format(match_nickname, pat)
self._log(msg)
self._log("{:>{width}}".format("with:", width=wnick), repr(line))
self._fail(msg)
else:
if not nomatch_printed:
self._log("{:>{width}}".format("nomatch:", width=wnick), repr(pat))
nomatch_printed = True
self._log("{:>{width}}".format("and:", width=wnick), repr(line))
self._log_output = []
def _fail(self, msg):
__tracebackhide__ = True
log_text = self._log_text
self._log_output = []
pytest.fail(log_text)

View File

@ -9,6 +9,8 @@ from collections import Counter
from collections.abc import Sequence
from functools import partial
from textwrap import dedent
from typing import List
from typing import Optional
from typing import Tuple
import py
@ -31,9 +33,11 @@ from _pytest.compat import safe_getattr
from _pytest.compat import safe_isclass
from _pytest.compat import STRING_TYPES
from _pytest.config import hookimpl
from _pytest.deprecated import FUNCARGNAMES
from _pytest.main import FSHookProxy
from _pytest.mark import MARK_GEN
from _pytest.mark.structures import get_unpacked_marks
from _pytest.mark.structures import Mark
from _pytest.mark.structures import normalize_mark_list
from _pytest.outcomes import fail
from _pytest.outcomes import skip
@ -119,15 +123,8 @@ def pytest_cmdline_main(config):
def pytest_generate_tests(metafunc):
# those alternative spellings are common - raise a specific error to alert
# the user
alt_spellings = ["parameterize", "parametrise", "parameterise"]
for mark_name in alt_spellings:
if metafunc.definition.get_closest_marker(mark_name):
msg = "{0} has '{1}' mark, spelling should be 'parametrize'"
fail(msg.format(metafunc.function.__name__, mark_name), pytrace=False)
for marker in metafunc.definition.iter_markers(name="parametrize"):
metafunc.parametrize(*marker.args, **marker.kwargs)
metafunc.parametrize(*marker.args, **marker.kwargs, _param_mark=marker)
def pytest_configure(config):
@ -193,8 +190,8 @@ def path_matches_patterns(path, patterns):
def pytest_pycollect_makemodule(path, parent):
if path.basename == "__init__.py":
return Package(path, parent)
return Module(path, parent)
return Package.from_parent(parent, fspath=path)
return Module.from_parent(parent, fspath=path)
@hookimpl(hookwrapper=True)
@ -206,7 +203,7 @@ def pytest_pycollect_makeitem(collector, name, obj):
# nothing was collected elsewhere, let's do it here
if safe_isclass(obj):
if collector.istestclass(obj, name):
outcome.force_result(Class(name, parent=collector))
outcome.force_result(Class.from_parent(collector, name=name, obj=obj))
elif collector.istestfunction(obj, name):
# mock seems to store unbound methods (issue473), normalize it
obj = getattr(obj, "__func__", obj)
@ -225,7 +222,7 @@ def pytest_pycollect_makeitem(collector, name, obj):
)
elif getattr(obj, "__test__", True):
if is_generator(obj):
res = Function(name, parent=collector)
res = Function.from_parent(collector, name=name)
reason = "yield tests were removed in pytest 4.0 - {name} will be ignored".format(
name=name
)
@ -236,10 +233,6 @@ def pytest_pycollect_makeitem(collector, name, obj):
outcome.force_result(res)
def pytest_make_parametrize_id(config, val, argname=None):
return None
class PyobjContext:
module = pyobj_property("Module")
cls = pyobj_property("Class")
@ -286,8 +279,7 @@ class PyobjMixin(PyobjContext):
break
parts.append(name)
parts.reverse()
s = ".".join(parts)
return s.replace(".[", "[")
return ".".join(parts)
def reportinfo(self) -> Tuple[str, int, str]:
# XXX caching?
@ -389,7 +381,7 @@ class PyCollector(PyobjMixin, nodes.Collector):
cls = clscol and clscol.obj or None
fm = self.session._fixturemanager
definition = FunctionDefinition(name=name, parent=self, callobj=funcobj)
definition = FunctionDefinition.from_parent(self, name=name, callobj=funcobj)
fixtureinfo = fm.getfixtureinfo(definition, funcobj, cls)
metafunc = Metafunc(
@ -404,7 +396,7 @@ class PyCollector(PyobjMixin, nodes.Collector):
self.ihook.pytest_generate_tests.call_extra(methods, dict(metafunc=metafunc))
if not metafunc._calls:
yield Function(name, parent=self, fixtureinfo=fixtureinfo)
yield Function.from_parent(self, name=name, fixtureinfo=fixtureinfo)
else:
# add funcargs() as fixturedefs to fixtureinfo.arg2fixturedefs
fixtures.add_funcarg_pseudo_fixture_def(self, metafunc, fm)
@ -416,9 +408,9 @@ class PyCollector(PyobjMixin, nodes.Collector):
for callspec in metafunc._calls:
subname = "{}[{}]".format(name, callspec.id)
yield Function(
yield Function.from_parent(
self,
name=subname,
parent=self,
callspec=callspec,
callobj=funcobj,
fixtureinfo=fixtureinfo,
@ -634,7 +626,7 @@ class Package(Module):
if init_module.check(file=1) and path_matches_patterns(
init_module, self.config.getini("python_files")
):
yield Module(init_module, self)
yield Module.from_parent(self, fspath=init_module)
pkg_prefixes = set()
for path in this_path.visit(rec=self._recurse, bf=True, sort=True):
# We will visit our own __init__.py file, in which case we skip it.
@ -685,6 +677,10 @@ def _get_first_non_fixture_func(obj, names):
class Class(PyCollector):
""" Collector for test methods. """
@classmethod
def from_parent(cls, parent, *, name, obj=None):
return cls._create(name=name, parent=parent)
def collect(self):
if not safe_getattr(self.obj, "__test__", True):
return []
@ -710,7 +706,7 @@ class Class(PyCollector):
self._inject_setup_class_fixture()
self._inject_setup_method_fixture()
return [Instance(name="()", parent=self)]
return [Instance.from_parent(self, name="()")]
def _inject_setup_class_fixture(self):
"""Injects a hidden autouse, class scoped fixture into the collected class object
@ -882,7 +878,7 @@ class CallSpec2:
self.marks.extend(normalize_mark_list(marks))
class Metafunc(fixtures.FuncargnamesCompatAttr):
class Metafunc:
"""
Metafunc objects are passed to the :func:`pytest_generate_tests <_pytest.hookspec.pytest_generate_tests>` hook.
They help to inspect a test function and to generate tests according to
@ -890,11 +886,14 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
test function is defined.
"""
def __init__(self, definition, fixtureinfo, config, cls=None, module=None):
assert (
isinstance(definition, FunctionDefinition)
or type(definition).__name__ == "DefinitionMock"
)
def __init__(
self,
definition: "FunctionDefinition",
fixtureinfo,
config,
cls=None,
module=None,
) -> None:
self.definition = definition
#: access to the :class:`_pytest.config.Config` object for the test session
@ -912,11 +911,25 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
#: class object where the test function is defined in or ``None``.
self.cls = cls
self._calls = []
self._ids = set()
self._calls = [] # type: List[CallSpec2]
self._arg2fixturedefs = fixtureinfo.name2fixturedefs
def parametrize(self, argnames, argvalues, indirect=False, ids=None, scope=None):
@property
def funcargnames(self):
""" alias attribute for ``fixturenames`` for pre-2.3 compatibility"""
warnings.warn(FUNCARGNAMES, stacklevel=2)
return self.fixturenames
def parametrize(
self,
argnames,
argvalues,
indirect=False,
ids=None,
scope=None,
*,
_param_mark: Optional[Mark] = None
):
""" Add new invocations to the underlying test function using the list
of argvalues for the given argnames. Parametrization is performed
during the collection phase. If you need to setup expensive resources
@ -939,13 +952,22 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
function so that it can perform more expensive setups during the
setup phase of a test rather than at collection time.
:arg ids: list of string ids, or a callable.
If strings, each is corresponding to the argvalues so that they are
part of the test id. If None is given as id of specific test, the
automatically generated id for that argument will be used.
If callable, it should take one argument (a single argvalue) and return
a string or return None. If None, the automatically generated id for that
argument will be used.
:arg ids: sequence of (or generator for) ids for ``argvalues``,
or a callable to return part of the id for each argvalue.
With sequences (and generators like ``itertools.count()``) the
returned ids should be of type ``string``, ``int``, ``float``,
``bool``, or ``None``.
They are mapped to the corresponding index in ``argvalues``.
``None`` means to use the auto-generated id.
If it is a callable it will be called for each entry in
``argvalues``, and the return value is used as part of the
auto-generated id for the whole set (where parts are joined with
dashes ("-")).
This is useful to provide more specific ids for certain items, e.g.
dates. Returning ``None`` will use an auto-generated id.
If no ids are provided they will be generated automatically from
the argvalues.
@ -966,6 +988,12 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
)
del argvalues
if "request" in argnames:
fail(
"'request' is a reserved name and cannot be used in @pytest.mark.parametrize",
pytrace=False,
)
if scope is None:
scope = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect)
@ -973,8 +1001,18 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
arg_values_types = self._resolve_arg_value_types(argnames, indirect)
# Use any already (possibly) generated ids with parametrize Marks.
if _param_mark and _param_mark._param_ids_from:
generated_ids = _param_mark._param_ids_from._param_ids_generated
if generated_ids is not None:
ids = generated_ids
ids = self._resolve_arg_ids(argnames, ids, parameters, item=self.definition)
# Store used (possibly generated) ids with parametrize Marks.
if _param_mark and _param_mark._param_ids_from and generated_ids is None:
object.__setattr__(_param_mark._param_ids_from, "_param_ids_generated", ids)
scopenum = scope2index(
scope, descr="parametrize() call in {}".format(self.function.__name__)
)
@ -1009,27 +1047,48 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
:rtype: List[str]
:return: the list of ids for each argname given
"""
from _pytest._io.saferepr import saferepr
idfn = None
if callable(ids):
idfn = ids
ids = None
if ids:
func_name = self.function.__name__
if len(ids) != len(parameters):
msg = "In {}: {} parameter sets specified, with different number of ids: {}"
fail(msg.format(func_name, len(parameters), len(ids)), pytrace=False)
for id_value in ids:
if id_value is not None and not isinstance(id_value, str):
msg = "In {}: ids must be list of strings, found: {} (type: {!r})"
fail(
msg.format(func_name, saferepr(id_value), type(id_value)),
pytrace=False,
)
ids = self._validate_ids(ids, parameters, func_name)
ids = idmaker(argnames, parameters, idfn, ids, self.config, item=item)
return ids
def _validate_ids(self, ids, parameters, func_name):
try:
len(ids)
except TypeError:
try:
it = iter(ids)
except TypeError:
raise TypeError("ids must be a callable, sequence or generator")
else:
import itertools
new_ids = list(itertools.islice(it, len(parameters)))
else:
new_ids = list(ids)
if len(new_ids) != len(parameters):
msg = "In {}: {} parameter sets specified, with different number of ids: {}"
fail(msg.format(func_name, len(parameters), len(ids)), pytrace=False)
for idx, id_value in enumerate(new_ids):
if id_value is not None:
if isinstance(id_value, (float, int, bool)):
new_ids[idx] = str(id_value)
elif not isinstance(id_value, str):
from _pytest._io.saferepr import saferepr
msg = "In {}: ids must be list of string/float/int/bool, found: {} (type: {!r}) at index {}"
fail(
msg.format(func_name, saferepr(id_value), type(id_value), idx),
pytrace=False,
)
return new_ids
def _resolve_arg_value_types(self, argnames, indirect):
"""Resolves if each parametrized argument must be considered a parameter to a fixture or a "funcarg"
to the function, based on the ``indirect`` parameter of the parametrized() call.
@ -1143,8 +1202,7 @@ def _idval(val, argname, idx, idfn, item, config):
if generated_id is not None:
val = generated_id
except Exception as e:
# See issue https://github.com/pytest-dev/pytest/issues/2169
msg = "{}: error raised while trying to determine id of parameter '{}' at position {}\n"
msg = "{}: error raised while trying to determine id of parameter '{}' at position {}"
msg = msg.format(item.nodeid, argname, idx)
raise ValueError(msg) from e
elif config:
@ -1333,7 +1391,7 @@ def write_docstring(tw, doc, indent=" "):
tw.write(indent + line + "\n")
class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr):
class Function(FunctionMixin, nodes.Item):
""" a Function Item is responsible for setting up and executing a
Python test function.
"""
@ -1399,6 +1457,10 @@ class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr):
#: .. versionadded:: 3.0
self.originalname = originalname
@classmethod
def from_parent(cls, parent, **kw):
return cls._create(parent=parent, **kw)
def _initrequest(self):
self.funcargs = {}
self._request = fixtures.FixtureRequest(self)
@ -1420,6 +1482,12 @@ class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr):
"(compatonly) for code expecting pytest-2.2 style request objects"
return self
@property
def funcargnames(self):
""" alias attribute for ``fixturenames`` for pre-2.3 compatibility"""
warnings.warn(FUNCARGNAMES, stacklevel=2)
return self.fixturenames
def runtest(self):
""" execute the underlying test function. """
self.ihook.pytest_pyfunc_call(pyfuncitem=self)

View File

@ -552,7 +552,7 @@ def raises(
@overload # noqa: F811
def raises(
def raises( # noqa: F811
expected_exception: Union["Type[_E]", Tuple["Type[_E]", ...]],
func: Callable,
*args: Any,

Some files were not shown because too many files have changed in this diff Show More