Merge branch 'features' into unittest-debug
This commit is contained in:
commit
41b7b109e9
|
@ -2,15 +2,22 @@
|
||||||
Thanks for submitting a PR, your contribution is really appreciated!
|
Thanks for submitting a PR, your contribution is really appreciated!
|
||||||
|
|
||||||
Here is a quick checklist that should be present in PRs.
|
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 `master` branch for bug fixes, documentation updates and trivial changes.
|
||||||
- [ ] Target the `features` branch for new features, improvements, and removals/deprecations.
|
- [ ] Target the `features` branch for new features, improvements, and removals/deprecations.
|
||||||
- [ ] Include documentation when adding new features.
|
- [ ] Include documentation when adding new features.
|
||||||
- [ ] Include new tests or update existing tests when applicable.
|
- [ ] 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.
|
- [ ] 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.
|
||||||
|
-->
|
||||||
|
|
|
@ -31,6 +31,7 @@ dist/
|
||||||
issue/
|
issue/
|
||||||
env/
|
env/
|
||||||
.env/
|
.env/
|
||||||
|
.venv/
|
||||||
3rdparty/
|
3rdparty/
|
||||||
.tox
|
.tox
|
||||||
.cache
|
.cache
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
exclude: doc/en/example/py2py3/test_py2.py
|
exclude: doc/en/example/py2py3/test_py2.py
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 19.3b0
|
rev: 19.10b0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
args: [--safe, --quiet]
|
args: [--safe, --quiet]
|
||||||
|
@ -37,12 +37,8 @@ repos:
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
args: [--py3-plus]
|
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
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||||
rev: v0.740
|
rev: v0.750
|
||||||
hooks:
|
hooks:
|
||||||
- id: mypy
|
- id: mypy
|
||||||
files: ^(src/|testing/)
|
files: ^(src/|testing/)
|
||||||
|
|
|
@ -108,7 +108,7 @@ before_script:
|
||||||
export _PYTEST_TOX_EXTRA_DEP=coverage-enable-subprocess
|
export _PYTEST_TOX_EXTRA_DEP=coverage-enable-subprocess
|
||||||
fi
|
fi
|
||||||
|
|
||||||
script: tox -vv
|
script: tox
|
||||||
|
|
||||||
after_success:
|
after_success:
|
||||||
- |
|
- |
|
||||||
|
|
5
AUTHORS
5
AUTHORS
|
@ -70,6 +70,7 @@ Daniel Hahler
|
||||||
Daniel Nuri
|
Daniel Nuri
|
||||||
Daniel Wandschneider
|
Daniel Wandschneider
|
||||||
Danielle Jenkins
|
Danielle Jenkins
|
||||||
|
Daniil Galiev
|
||||||
Dave Hunt
|
Dave Hunt
|
||||||
David Díaz-Barquero
|
David Díaz-Barquero
|
||||||
David Mohr
|
David Mohr
|
||||||
|
@ -134,6 +135,7 @@ Jordan Guymon
|
||||||
Jordan Moldow
|
Jordan Moldow
|
||||||
Jordan Speicher
|
Jordan Speicher
|
||||||
Joseph Hunkeler
|
Joseph Hunkeler
|
||||||
|
Josh Karpel
|
||||||
Joshua Bronson
|
Joshua Bronson
|
||||||
Jurko Gospodnetić
|
Jurko Gospodnetić
|
||||||
Justyna Janczyszyn
|
Justyna Janczyszyn
|
||||||
|
@ -163,6 +165,7 @@ Marcelo Duarte Trevisani
|
||||||
Marcin Bachry
|
Marcin Bachry
|
||||||
Marco Gorelli
|
Marco Gorelli
|
||||||
Mark Abramowitz
|
Mark Abramowitz
|
||||||
|
Mark Dickinson
|
||||||
Markus Unterwaditzer
|
Markus Unterwaditzer
|
||||||
Martijn Faassen
|
Martijn Faassen
|
||||||
Martin Altmayer
|
Martin Altmayer
|
||||||
|
@ -204,6 +207,7 @@ Oscar Benjamin
|
||||||
Patrick Hayes
|
Patrick Hayes
|
||||||
Paweł Adamczak
|
Paweł Adamczak
|
||||||
Pedro Algarvio
|
Pedro Algarvio
|
||||||
|
Philipp Loose
|
||||||
Pieter Mulder
|
Pieter Mulder
|
||||||
Piotr Banaszkiewicz
|
Piotr Banaszkiewicz
|
||||||
Pulkit Goyal
|
Pulkit Goyal
|
||||||
|
@ -263,6 +267,7 @@ Virgil Dupras
|
||||||
Vitaly Lashmanov
|
Vitaly Lashmanov
|
||||||
Vlad Dragos
|
Vlad Dragos
|
||||||
Volodymyr Piskun
|
Volodymyr Piskun
|
||||||
|
Wei Lin
|
||||||
Wil Cooley
|
Wil Cooley
|
||||||
William Lee
|
William Lee
|
||||||
Wim Glenn
|
Wim Glenn
|
||||||
|
|
249
CHANGELOG.rst
249
CHANGELOG.rst
|
@ -18,6 +18,238 @@ with advance notice in the **Deprecations** section of releases.
|
||||||
|
|
||||||
.. towncrier release notes start
|
.. 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)
|
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.
|
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
|
- `#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 ``<=``
|
- ``pytest.approx`` no longer supports ``>``, ``>=``, ``<`` and ``<=``
|
||||||
operators to avoid surprising/inconsistent behavior. See `the approx docs
|
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>`_)
|
information. (`#2003 <https://github.com/pytest-dev/pytest/issues/2003>`_)
|
||||||
|
|
||||||
- All old-style specific behavior in current classes in the pytest's API is
|
- 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
|
* Fix (`#1422`_): junit record_xml_property doesn't allow multiple records
|
||||||
with same name.
|
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
|
.. _#1609: https://github.com/pytest-dev/pytest/issues/1609
|
||||||
.. _#1422: https://github.com/pytest-dev/pytest/issues/1422
|
.. _#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.
|
- add ability to set command line options by environment variable PYTEST_ADDOPTS.
|
||||||
|
|
||||||
- added documentation on the new pytest-dev teams on bitbucket and
|
- 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.
|
Thanks to Anatoly for pushing and initial work on this.
|
||||||
|
|
||||||
- fix issue650: new option ``--docttest-ignore-import-errors`` which
|
- 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
|
- yielded test functions will now have autouse-fixtures active but
|
||||||
cannot accept fixtures as funcargs - it's anyway recommended to
|
cannot accept fixtures as funcargs - it's anyway recommended to
|
||||||
rather use the post-2.0 parametrize features instead of yield, see:
|
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
|
- 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
|
if defined in an a/conftest.py file and tests in a/tests/test_some.py
|
||||||
- fix issue226 - LIFO ordering for fixture teardowns
|
- fix issue226 - LIFO ordering for fixture teardowns
|
||||||
|
@ -6211,7 +6444,7 @@ Bug fixes:
|
||||||
- pluginmanager.register(...) now raises ValueError if the
|
- pluginmanager.register(...) now raises ValueError if the
|
||||||
plugin has been already registered or the name is taken
|
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
|
especially with respect to the "magic" history, also mention
|
||||||
pytest-django, trial and unittest integration.
|
pytest-django, trial and unittest integration.
|
||||||
|
|
||||||
|
@ -6324,7 +6557,7 @@ Bug fixes:
|
||||||
or through plugin hooks. Also introduce a "--strict" option which
|
or through plugin hooks. Also introduce a "--strict" option which
|
||||||
will treat unregistered markers as errors
|
will treat unregistered markers as errors
|
||||||
allowing to avoid typos and maintain a well described set of markers
|
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.
|
and its links.
|
||||||
- issue50: introduce "-m marker" option to select tests based on markers
|
- issue50: introduce "-m marker" option to select tests based on markers
|
||||||
(this is a stricter and more predictable version of '-k' in that "-m"
|
(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
|
- refinements to "collecting" output on non-ttys
|
||||||
- refine internal plugin registration and --traceconfig output
|
- refine internal plugin registration and --traceconfig output
|
||||||
- introduce a mechanism to prevent/unregister plugins from the
|
- 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
|
- activate resultlog plugin by default
|
||||||
- fix regression wrt yielded tests which due to the
|
- fix regression wrt yielded tests which due to the
|
||||||
collection-before-running semantics were not
|
collection-before-running semantics were not
|
||||||
|
|
|
@ -262,6 +262,19 @@ Here is a simple overview, with pytest-specific bits:
|
||||||
|
|
||||||
When committing, ``pre-commit`` will re-format the files if necessary.
|
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)::
|
#. Commit and push once your tests pass and you are happy with your change(s)::
|
||||||
|
|
||||||
$ git commit -a -m "<commit message>"
|
$ git commit -a -m "<commit message>"
|
||||||
|
|
|
@ -57,7 +57,7 @@ jobs:
|
||||||
export COVERAGE_FILE="$PWD/.coverage"
|
export COVERAGE_FILE="$PWD/.coverage"
|
||||||
export COVERAGE_PROCESS_START="$PWD/.coveragerc"
|
export COVERAGE_PROCESS_START="$PWD/.coveragerc"
|
||||||
fi
|
fi
|
||||||
python -m tox -e $(tox.env) -vv
|
python -m tox -e $(tox.env)
|
||||||
displayName: 'Run tests'
|
displayName: 'Run tests'
|
||||||
|
|
||||||
- task: PublishTestResults@2
|
- task: PublishTestResults@2
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
``pytest.mark.parametrize`` accepts integers for ``ids`` again, converting it to strings.
|
|
@ -1 +0,0 @@
|
||||||
Fix line offset mismatch with skipped tests in terminal summary.
|
|
|
@ -0,0 +1 @@
|
||||||
|
Fixed some warning reports produced by pytest to point to the correct location of the warning in the user's code.
|
|
@ -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.
|
|
|
@ -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.
|
|
|
@ -1,2 +0,0 @@
|
||||||
``RunResult`` from ``pytester`` now displays the mnemonic of the ``ret`` attribute when it is a
|
|
||||||
valid ``pytest.ExitCode`` value.
|
|
|
@ -1 +0,0 @@
|
||||||
Use multiple colors with terminal summary statistics.
|
|
|
@ -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.
|
|
|
@ -1 +0,0 @@
|
||||||
Quitting from debuggers is now properly handled in ``doctest`` items.
|
|
|
@ -0,0 +1 @@
|
||||||
|
pytester: fix ``no_fnmatch_line`` when used after positive matching.
|
|
@ -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.
|
|
|
@ -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 ]
|
|
|
@ -0,0 +1 @@
|
||||||
|
Report ``PytestUnknownMarkWarning`` at the level of the user's code, not ``pytest``'s.
|
|
@ -1 +0,0 @@
|
||||||
Display untruncated assertion message with ``-vv``.
|
|
|
@ -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.
|
|
@ -0,0 +1 @@
|
||||||
|
The ``pytest_warning_captured`` hook now receives a ``location`` parameter with the code location that generated the warning.
|
|
@ -1 +0,0 @@
|
||||||
Fix plurality mismatch in test summary (e.g. display "1 error" instead of "1 errors").
|
|
|
@ -1,2 +0,0 @@
|
||||||
``Config.InvocationParams.args`` is now always a ``tuple`` to better convey that it should be
|
|
||||||
immutable and avoid accidental modifications.
|
|
|
@ -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).
|
|
|
@ -1 +0,0 @@
|
||||||
Align prefixes in output of pytester's ``LineMatcher``.
|
|
|
@ -1,3 +0,0 @@
|
||||||
The ``PytestDoctestRunner`` is properly invalidated when unconfiguring the doctest plugin.
|
|
||||||
|
|
||||||
This is important when used with ``pytester``'s ``runpytest_inprocess``.
|
|
|
@ -1 +0,0 @@
|
||||||
BaseExceptions are handled in ``saferepr``, which includes ``pytest.fail.Exception`` etc.
|
|
|
@ -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.
|
|
|
@ -1 +0,0 @@
|
||||||
Collection errors are reported as errors (and not failures like before) in the terminal's short test summary.
|
|
|
@ -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.
|
|
|
@ -1 +0,0 @@
|
||||||
``pytester.spawn`` does not skip/xfail tests on FreeBSD anymore unconditionally.
|
|
|
@ -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``.
|
|
|
@ -1 +0,0 @@
|
||||||
The "[XXX%]" indicator in the test summary is colored according to the final (new) multi-colored line's main color.
|
|
|
@ -1 +0,0 @@
|
||||||
Fix ``--trace`` when used with parametrized functions.
|
|
|
@ -1 +0,0 @@
|
||||||
Add ``--co`` as a synonym to ``--collect-only``.
|
|
|
@ -1 +0,0 @@
|
||||||
``atomicwrites`` is now only used on Windows, fixing a performance regression with assertion rewriting on Unix.
|
|
|
@ -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.
|
|
|
@ -0,0 +1 @@
|
||||||
|
pytester: the ``testdir`` fixture respects environment settings from the ``monkeypatch`` fixture for inner runs.
|
|
@ -0,0 +1 @@
|
||||||
|
Improve check for misspelling of ``pytest.mark.parametrize``.
|
|
@ -0,0 +1 @@
|
||||||
|
``--fulltrace`` is honored with collection errors.
|
|
@ -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.
|
|
@ -0,0 +1 @@
|
||||||
|
``pytest.mark.parametrize`` supports iterators and generators for ``ids``.
|
|
@ -1,12 +1,14 @@
|
||||||
This directory contains "newsfragments" which are short files that contain a small **ReST**-formatted
|
This directory contains "newsfragments" which are short files that contain a small **ReST**-formatted
|
||||||
text that will be added to the next ``CHANGELOG``.
|
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.
|
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
|
Each file should be named like ``<ISSUE>.<TYPE>.rst``, where
|
||||||
``<ISSUE>`` is an issue number, and ``<TYPE>`` is one of:
|
``<ISSUE>`` is an issue number, and ``<TYPE>`` is one of:
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
``repr`` of ``ExceptionInfo`` objects has been improved to honor the ``__repr__`` method of the underlying exception.
|
|
|
@ -6,6 +6,9 @@ Release announcements
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
|
|
||||||
|
release-5.3.0
|
||||||
|
release-5.2.4
|
||||||
|
release-5.2.3
|
||||||
release-5.2.2
|
release-5.2.2
|
||||||
release-5.2.1
|
release-5.2.1
|
||||||
release-5.2.0
|
release-5.2.0
|
||||||
|
|
|
@ -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
|
has been removed, resulting in a much smaller and cleaner
|
||||||
implementation. See the new docs with examples here:
|
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
|
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
|
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)
|
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
|
- new and better reporting information in assert expressions
|
||||||
if comparing lists, sequences or strings.
|
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),
|
- new configuration through ini-files (setup.cfg or tox.ini recognized),
|
||||||
for example::
|
for example::
|
||||||
|
@ -50,7 +50,7 @@ New Features
|
||||||
norecursedirs = .hg data* # don't ever recurse in such dirs
|
norecursedirs = .hg data* # don't ever recurse in such dirs
|
||||||
addopts = -x --pyargs # add these command line options by default
|
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
|
- improved standard unittest support. In general py.test should now
|
||||||
better be able to run custom unittest.TestCases like twisted trial
|
better be able to run custom unittest.TestCases like twisted trial
|
||||||
|
|
|
@ -57,7 +57,7 @@ Changes between 2.0.0 and 2.0.1
|
||||||
- refinements to "collecting" output on non-ttys
|
- refinements to "collecting" output on non-ttys
|
||||||
- refine internal plugin registration and --traceconfig output
|
- refine internal plugin registration and --traceconfig output
|
||||||
- introduce a mechanism to prevent/unregister plugins from the
|
- 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
|
- activate resultlog plugin by default
|
||||||
- fix regression wrt yielded tests which due to the
|
- fix regression wrt yielded tests which due to the
|
||||||
collection-before-running semantics were not
|
collection-before-running semantics were not
|
||||||
|
|
|
@ -9,7 +9,7 @@ with these improvements:
|
||||||
|
|
||||||
- new @pytest.mark.parametrize decorator to run tests with different arguments
|
- new @pytest.mark.parametrize decorator to run tests with different arguments
|
||||||
- new metafunc.parametrize() API for parametrizing arguments independently
|
- 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
|
- NOTE that parametrize() related APIs are still a bit experimental
|
||||||
and might change in future releases.
|
and might change in future releases.
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ with these improvements:
|
||||||
- "-m markexpr" option for selecting tests according to their mark
|
- "-m markexpr" option for selecting tests according to their mark
|
||||||
- a new "markers" ini-variable for registering test markers for your project
|
- a new "markers" ini-variable for registering test markers for your project
|
||||||
- the new "--strict" bails out with an error if using unregistered markers.
|
- 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
|
* duration profiling: new "--duration=N" option showing the N slowest test
|
||||||
execution or setup/teardown calls. This is most useful if you want to
|
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
|
or through plugin hooks. Also introduce a "--strict" option which
|
||||||
will treat unregistered markers as errors
|
will treat unregistered markers as errors
|
||||||
allowing to avoid typos and maintain a well described set of markers
|
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.
|
and its links.
|
||||||
- issue50: introduce "-m marker" option to select tests based on markers
|
- issue50: introduce "-m marker" option to select tests based on markers
|
||||||
(this is a stricter and more predictable version of "-k" in that "-m"
|
(this is a stricter and more predictable version of "-k" in that "-m"
|
||||||
|
|
|
@ -13,12 +13,12 @@ re-useable fixture design.
|
||||||
|
|
||||||
For detailed info and tutorial-style examples, see:
|
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
|
Moreover, there is now support for using pytest fixtures/funcargs with
|
||||||
unittest-style suites, see here for examples:
|
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"
|
Besides, more unittest-test suites are now expected to "simply work"
|
||||||
with pytest.
|
with pytest.
|
||||||
|
@ -29,11 +29,11 @@ pytest-2.2.4.
|
||||||
|
|
||||||
If you are interested in the precise reasoning (including examples) of the
|
If you are interested in the precise reasoning (including examples) of the
|
||||||
pytest-2.3 fixture evolution, please consult
|
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:
|
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:
|
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
|
- pluginmanager.register(...) now raises ValueError if the
|
||||||
plugin has been already registered or the name is taken
|
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
|
especially with respect to the "magic" history, also mention
|
||||||
pytest-django, trial and unittest integration.
|
pytest-django, trial and unittest integration.
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ comes with the following fixes and features:
|
||||||
- yielded test functions will now have autouse-fixtures active but
|
- yielded test functions will now have autouse-fixtures active but
|
||||||
cannot accept fixtures as funcargs - it's anyway recommended to
|
cannot accept fixtures as funcargs - it's anyway recommended to
|
||||||
rather use the post-2.0 parametrize features instead of yield, see:
|
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
|
- 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
|
if defined in an a/conftest.py file and tests in a/tests/test_some.py
|
||||||
- fix issue226 - LIFO ordering for fixture teardowns
|
- fix issue226 - LIFO ordering for fixture teardowns
|
||||||
|
|
|
@ -7,7 +7,7 @@ from a few supposedly very minor incompatibilities. See below for
|
||||||
a full list of details. A few feature highlights:
|
a full list of details. A few feature highlights:
|
||||||
|
|
||||||
- new yield-style fixtures `pytest.yield_fixture
|
- 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.
|
existing with-style context managers in fixture functions.
|
||||||
|
|
||||||
- improved pdb support: ``import pdb ; pdb.set_trace()`` now works
|
- improved pdb support: ``import pdb ; pdb.set_trace()`` now works
|
||||||
|
|
|
@ -52,7 +52,7 @@ holger krekel
|
||||||
- add ability to set command line options by environment variable PYTEST_ADDOPTS.
|
- add ability to set command line options by environment variable PYTEST_ADDOPTS.
|
||||||
|
|
||||||
- added documentation on the new pytest-dev teams on bitbucket and
|
- 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.
|
Thanks to Anatoly for pushing and initial work on this.
|
||||||
|
|
||||||
- fix issue650: new option ``--docttest-ignore-import-errors`` which
|
- fix issue650: new option ``--docttest-ignore-import-errors`` which
|
||||||
|
|
|
@ -131,7 +131,7 @@ The py.test Development Team
|
||||||
with same name.
|
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
|
.. _#1422: https://github.com/pytest-dev/pytest/issues/1422
|
||||||
.. _#1379: https://github.com/pytest-dev/pytest/issues/1379
|
.. _#1379: https://github.com/pytest-dev/pytest/issues/1379
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -92,7 +92,7 @@ exclude_patterns = [
|
||||||
|
|
||||||
|
|
||||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
# 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.
|
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||||
# add_function_parentheses = True
|
# add_function_parentheses = True
|
||||||
|
@ -112,6 +112,19 @@ pygments_style = "sphinx"
|
||||||
# A list of ignored prefixes for module index sorting.
|
# A list of ignored prefixes for module index sorting.
|
||||||
# modindex_common_prefix = []
|
# 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 ---------------------------------------------------
|
# -- Options for HTML output ---------------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,6 @@ Full pytest documentation
|
||||||
unittest
|
unittest
|
||||||
nose
|
nose
|
||||||
xunit_setup
|
xunit_setup
|
||||||
report_log
|
|
||||||
plugins
|
plugins
|
||||||
writing_plugins
|
writing_plugins
|
||||||
logging
|
logging
|
||||||
|
|
|
@ -20,6 +20,37 @@ Below is a complete list of all pytest features which are considered deprecated.
|
||||||
:ref:`standard warning filters <warnings>`.
|
: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``
|
``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
|
analysed at runtime, but it uses a custom format which requires users to implement their own
|
||||||
parser.
|
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.
|
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
|
The plan is remove the ``--result-log`` option in pytest 6.0 if ``pytest-reportlog`` proves satisfactory
|
||||||
to all users and is deemed stable.
|
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
|
Removed Features
|
||||||
|
|
|
@ -622,7 +622,7 @@ then you will see two tests skipped and two executed tests as expected:
|
||||||
test_plat.py s.s. [100%]
|
test_plat.py s.s. [100%]
|
||||||
|
|
||||||
========================= short test summary info ==========================
|
========================= 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 =======================
|
======================= 2 passed, 2 skipped in 0.12s =======================
|
||||||
|
|
||||||
Note that if you specify a platform via the marker-command line option like this:
|
Note that if you specify a platform via the marker-command line option like this:
|
||||||
|
|
|
@ -4,7 +4,7 @@ import pytest
|
||||||
|
|
||||||
def pytest_collect_file(parent, path):
|
def pytest_collect_file(parent, path):
|
||||||
if path.ext == ".yaml" and path.basename.startswith("test"):
|
if path.ext == ".yaml" and path.basename.startswith("test"):
|
||||||
return YamlFile(path, parent)
|
return YamlFile.from_parent(parent, fspath=path)
|
||||||
|
|
||||||
|
|
||||||
class YamlFile(pytest.File):
|
class YamlFile(pytest.File):
|
||||||
|
@ -13,7 +13,7 @@ class YamlFile(pytest.File):
|
||||||
|
|
||||||
raw = yaml.safe_load(self.fspath.open())
|
raw = yaml.safe_load(self.fspath.open())
|
||||||
for name, spec in sorted(raw.items()):
|
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):
|
class YamlItem(pytest.Item):
|
||||||
|
|
|
@ -475,10 +475,10 @@ Running it results in some skips if we don't have all the python interpreters in
|
||||||
.. code-block:: pytest
|
.. code-block:: pytest
|
||||||
|
|
||||||
. $ pytest -rs -q multipython.py
|
. $ pytest -rs -q multipython.py
|
||||||
ssssssssssssssssssssssss... [100%]
|
ssssssssssss...ssssssssssss [100%]
|
||||||
========================= short test summary info ==========================
|
========================= short test summary info ==========================
|
||||||
SKIPPED [12] $REGENDOC_TMPDIR/CWD/multipython.py:30: 'python3.5' not found
|
SKIPPED [12] $REGENDOC_TMPDIR/CWD/multipython.py:29: '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.7' not found
|
||||||
3 passed, 24 skipped in 0.12s
|
3 passed, 24 skipped in 0.12s
|
||||||
|
|
||||||
Indirect parametrization of optional implementations/imports
|
Indirect parametrization of optional implementations/imports
|
||||||
|
@ -547,7 +547,7 @@ If you run this with reporting for skips enabled:
|
||||||
test_module.py .s [100%]
|
test_module.py .s [100%]
|
||||||
|
|
||||||
========================= short test summary info ==========================
|
========================= 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 =======================
|
======================= 1 passed, 1 skipped in 0.12s =======================
|
||||||
|
|
||||||
You'll see that we don't have an ``opt2`` module and thus the second test run
|
You'll see that we don't have an ``opt2`` module and thus the second test run
|
||||||
|
|
|
@ -13,4 +13,4 @@ class DummyCollector(pytest.collect.File):
|
||||||
def pytest_pycollect_makemodule(path, parent):
|
def pytest_pycollect_makemodule(path, parent):
|
||||||
bn = path.basename
|
bn = path.basename
|
||||||
if "py3" in bn and not py3 or ("py2" in bn and py3):
|
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)
|
||||||
|
|
|
@ -436,7 +436,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
items = [1, 2, 3]
|
items = [1, 2, 3]
|
||||||
print("items is {!r}".format(items))
|
print("items is {!r}".format(items))
|
||||||
> a, b = items.pop()
|
> a, b = items.pop()
|
||||||
E TypeError: cannot unpack non-iterable int object
|
E TypeError: 'int' object is not iterable
|
||||||
|
|
||||||
failure_demo.py:181: TypeError
|
failure_demo.py:181: TypeError
|
||||||
--------------------------- Captured stdout call ---------------------------
|
--------------------------- 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):
|
def test_z2_type_error(self):
|
||||||
items = 3
|
items = 3
|
||||||
> a, b = items
|
> a, b = items
|
||||||
E TypeError: cannot unpack non-iterable int object
|
E TypeError: 'int' object is not iterable
|
||||||
|
|
||||||
failure_demo.py:222: TypeError
|
failure_demo.py:222: TypeError
|
||||||
______________________ TestMoreErrors.test_startswith ______________________
|
______________________ TestMoreErrors.test_startswith ______________________
|
||||||
|
|
|
@ -300,36 +300,33 @@ behave differently if called from a test. But if you
|
||||||
absolutely must find out if your application code is
|
absolutely must find out if your application code is
|
||||||
running from a test you can do something like this:
|
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
|
.. code-block:: python
|
||||||
|
|
||||||
# content of conftest.py
|
# content of conftest.py
|
||||||
|
|
||||||
|
|
||||||
def pytest_configure(config):
|
def pytest_configure(config):
|
||||||
import sys
|
your_module._called_from_test = True
|
||||||
|
|
||||||
sys._called_from_test = True
|
and then check for the ``your_module._called_from_test`` flag:
|
||||||
|
|
||||||
|
|
||||||
def pytest_unconfigure(config):
|
|
||||||
import sys
|
|
||||||
|
|
||||||
del sys._called_from_test
|
|
||||||
|
|
||||||
and then check for the ``sys._called_from_test`` flag:
|
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
if hasattr(sys, "_called_from_test"):
|
if your_module._called_from_test:
|
||||||
# called from within a test run
|
# called from within a test run
|
||||||
...
|
...
|
||||||
else:
|
else:
|
||||||
# called "normally"
|
# called "normally"
|
||||||
...
|
...
|
||||||
|
|
||||||
accordingly in your application. It's also a good idea
|
accordingly in your application.
|
||||||
to use your own application module rather than ``sys``
|
|
||||||
for handling flag.
|
|
||||||
|
|
||||||
Adding info to test report header
|
Adding info to test report header
|
||||||
--------------------------------------------------------------
|
--------------------------------------------------------------
|
||||||
|
@ -446,7 +443,7 @@ Now we can profile which test functions execute the slowest:
|
||||||
========================= slowest 3 test durations =========================
|
========================= slowest 3 test durations =========================
|
||||||
0.30s call test_some_are_slow.py::test_funcslow2
|
0.30s call test_some_are_slow.py::test_funcslow2
|
||||||
0.20s call test_some_are_slow.py::test_funcslow1
|
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 =============================
|
============================ 3 passed in 0.12s =============================
|
||||||
|
|
||||||
incremental testing - test steps
|
incremental testing - test steps
|
||||||
|
|
|
@ -9,9 +9,9 @@ pytest fixtures: explicit, modular, scalable
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. _`xUnit`: http://en.wikipedia.org/wiki/XUnit
|
.. _`xUnit`: https://en.wikipedia.org/wiki/XUnit
|
||||||
.. _`purpose of test fixtures`: http://en.wikipedia.org/wiki/Test_fixture#Software
|
.. _`purpose of test fixtures`: https://en.wikipedia.org/wiki/Test_fixture#Software
|
||||||
.. _`Dependency injection`: http://en.wikipedia.org/wiki/Dependency_injection
|
.. _`Dependency injection`: https://en.wikipedia.org/wiki/Dependency_injection
|
||||||
|
|
||||||
The `purpose of test fixtures`_ is to provide a fixed baseline
|
The `purpose of test fixtures`_ is to provide a fixed baseline
|
||||||
upon which tests can reliably and repeatedly execute. pytest fixtures
|
upon which tests can reliably and repeatedly execute. pytest fixtures
|
||||||
|
|
|
@ -28,7 +28,7 @@ Install ``pytest``
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
$ pytest --version
|
$ 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`:
|
.. _`simpletest`:
|
||||||
|
|
||||||
|
|
|
@ -73,7 +73,6 @@ Some organisations using pytest
|
||||||
|
|
||||||
* `Square Kilometre Array, Cape Town <http://ska.ac.za/>`_
|
* `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
|
* `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/>`_
|
* `Shootq <http://web.shootq.com/>`_
|
||||||
* `Stups department of Heinrich Heine University Duesseldorf <http://www.stups.uni-duesseldorf.de/projects.php>`_
|
* `Stups department of Heinrich Heine University Duesseldorf <http://www.stups.uni-duesseldorf.de/projects.php>`_
|
||||||
* cellzome
|
* cellzome
|
||||||
|
|
|
@ -59,7 +59,7 @@ pytest.raises
|
||||||
|
|
||||||
**Tutorial**: :ref:`assertraises`.
|
**Tutorial**: :ref:`assertraises`.
|
||||||
|
|
||||||
.. autofunction:: pytest.raises(expected_exception: Exception, [match])
|
.. autofunction:: pytest.raises(expected_exception: Exception [, *, match])
|
||||||
:with: excinfo
|
:with: excinfo
|
||||||
|
|
||||||
pytest.deprecated_call
|
pytest.deprecated_call
|
||||||
|
|
|
@ -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"}
|
|
|
@ -64,7 +64,7 @@ Talks and blog postings
|
||||||
- `pytest introduction from Brian Okken (January 2013)
|
- `pytest introduction from Brian Okken (January 2013)
|
||||||
<http://pythontesting.net/framework/pytest-introduction/>`_
|
<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>`_
|
- `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)
|
- `monkey patching done right`_ (blog post, consult `monkeypatch plugin`_ for up-to-date API)
|
||||||
|
|
|
@ -66,8 +66,8 @@ To stop the testing process after the first (N) failures:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
pytest -x # stop after first failure
|
pytest -x # stop after first failure
|
||||||
pytest --maxfail=2 # stop after two failures
|
pytest --maxfail=2 # stop after two failures
|
||||||
|
|
||||||
.. _select-tests:
|
.. _select-tests:
|
||||||
|
|
||||||
|
@ -241,7 +241,7 @@ Example:
|
||||||
|
|
||||||
test_example.py:14: AssertionError
|
test_example.py:14: AssertionError
|
||||||
========================= short test summary info ==========================
|
========================= 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
|
XFAIL test_example.py::test_xfail
|
||||||
reason: xfailing this test
|
reason: xfailing this test
|
||||||
XPASS test_example.py::test_xpass always xfail
|
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
|
test_example.py:14: AssertionError
|
||||||
========================= short test summary info ==========================
|
========================= short test summary info ==========================
|
||||||
FAILED test_example.py::test_fail - assert 0
|
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 ===
|
== 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
|
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.
|
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>`__
|
See `the deprecation docs <https://docs.pytest.org/en/latest/deprecations.html#result-log-result-log>`__
|
||||||
for more information.
|
for more information.
|
||||||
|
|
|
@ -41,7 +41,7 @@ Running pytest now produces this output:
|
||||||
warnings.warn(UserWarning("api v1, should use functions from v2"))
|
warnings.warn(UserWarning("api v1, should use functions from v2"))
|
||||||
|
|
||||||
-- Docs: https://docs.pytest.org/en/latest/warnings.html
|
-- 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
|
The ``-W`` flag can be passed to control which warnings will be displayed or even turn
|
||||||
them into errors:
|
them into errors:
|
||||||
|
@ -407,7 +407,7 @@ defines an ``__init__`` constructor, as this prevents the class from being insta
|
||||||
class Test:
|
class Test:
|
||||||
|
|
||||||
-- Docs: https://docs.pytest.org/en/latest/warnings.html
|
-- 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.
|
These warnings might be filtered using the same builtin mechanisms used to filter other types of warnings.
|
||||||
|
|
||||||
|
|
|
@ -442,7 +442,7 @@ additionally it is possible to copy examples for an example folder before runnin
|
||||||
testdir.copy_example("test_example.py")
|
testdir.copy_example("test_example.py")
|
||||||
|
|
||||||
-- Docs: https://docs.pytest.org/en/latest/warnings.html
|
-- 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
|
For more information about the result object that ``runpytest()`` returns, and
|
||||||
the methods that it provides please check out the :py:class:`RunResult
|
the methods that it provides please check out the :py:class:`RunResult
|
||||||
|
|
|
@ -79,12 +79,19 @@ def fix_formatting():
|
||||||
call(["pre-commit", "run", "--all-files"])
|
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):
|
def pre_release(version):
|
||||||
"""Generates new docs, release announcements and creates a local tag."""
|
"""Generates new docs, release announcements and creates a local tag."""
|
||||||
announce(version)
|
announce(version)
|
||||||
regen()
|
regen()
|
||||||
changelog(version, write_out=True)
|
changelog(version, write_out=True)
|
||||||
fix_formatting()
|
fix_formatting()
|
||||||
|
check_links()
|
||||||
|
|
||||||
msg = "Preparing release version {}".format(version)
|
msg = "Preparing release version {}".format(version)
|
||||||
check_call(["git", "commit", "-a", "-m", msg])
|
check_call(["git", "commit", "-a", "-m", msg])
|
||||||
|
|
|
@ -57,12 +57,13 @@ upload-dir = doc/en/build/html
|
||||||
|
|
||||||
[check-manifest]
|
[check-manifest]
|
||||||
ignore =
|
ignore =
|
||||||
_pytest/_version.py
|
src/_pytest/_version.py
|
||||||
|
|
||||||
[devpi:upload]
|
[devpi:upload]
|
||||||
formats = sdist.tgz,bdist_wheel
|
formats = sdist.tgz,bdist_wheel
|
||||||
|
|
||||||
[mypy]
|
[mypy]
|
||||||
|
mypy_path = src
|
||||||
ignore_missing_imports = True
|
ignore_missing_imports = True
|
||||||
no_implicit_optional = True
|
no_implicit_optional = True
|
||||||
strict_equality = True
|
strict_equality = True
|
||||||
|
|
|
@ -53,19 +53,22 @@ If things do not work right away:
|
||||||
which should throw a KeyError: 'COMPLINE' (which is properly set by the
|
which should throw a KeyError: 'COMPLINE' (which is properly set by the
|
||||||
global argcomplete script).
|
global argcomplete script).
|
||||||
"""
|
"""
|
||||||
|
import argparse
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from glob import glob
|
from glob import glob
|
||||||
|
from typing import Any
|
||||||
|
from typing import List
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
class FastFilesCompleter:
|
class FastFilesCompleter:
|
||||||
"Fast file completer class"
|
"Fast file completer class"
|
||||||
|
|
||||||
def __init__(self, directories=True):
|
def __init__(self, directories: bool = True) -> None:
|
||||||
self.directories = directories
|
self.directories = directories
|
||||||
|
|
||||||
def __call__(self, prefix, **kwargs):
|
def __call__(self, prefix: str, **kwargs: Any) -> List[str]:
|
||||||
"""only called on non option completions"""
|
"""only called on non option completions"""
|
||||||
if os.path.sep in prefix[1:]:
|
if os.path.sep in prefix[1:]:
|
||||||
prefix_dir = len(os.path.dirname(prefix) + os.path.sep)
|
prefix_dir = len(os.path.dirname(prefix) + os.path.sep)
|
||||||
|
@ -94,13 +97,13 @@ if os.environ.get("_ARGCOMPLETE"):
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
filescompleter = FastFilesCompleter() # type: Optional[FastFilesCompleter]
|
filescompleter = FastFilesCompleter() # type: Optional[FastFilesCompleter]
|
||||||
|
|
||||||
def try_argcomplete(parser):
|
def try_argcomplete(parser: argparse.ArgumentParser) -> None:
|
||||||
argcomplete.autocomplete(parser, always_complete_options=False)
|
argcomplete.autocomplete(parser, always_complete_options=False)
|
||||||
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
def try_argcomplete(parser):
|
def try_argcomplete(parser: argparse.ArgumentParser) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
filescompleter = None
|
filescompleter = None
|
||||||
|
|
|
@ -7,13 +7,17 @@ from inspect import CO_VARKEYWORDS
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from traceback import format_exception_only
|
from traceback import format_exception_only
|
||||||
from types import CodeType
|
from types import CodeType
|
||||||
|
from types import FrameType
|
||||||
from types import TracebackType
|
from types import TracebackType
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
from typing import Callable
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from typing import Generic
|
from typing import Generic
|
||||||
|
from typing import Iterable
|
||||||
from typing import List
|
from typing import List
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from typing import Pattern
|
from typing import Pattern
|
||||||
|
from typing import Sequence
|
||||||
from typing import Set
|
from typing import Set
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
from typing import TypeVar
|
from typing import TypeVar
|
||||||
|
@ -27,9 +31,16 @@ import py
|
||||||
import _pytest
|
import _pytest
|
||||||
from _pytest._io.saferepr import safeformat
|
from _pytest._io.saferepr import safeformat
|
||||||
from _pytest._io.saferepr import saferepr
|
from _pytest._io.saferepr import saferepr
|
||||||
|
from _pytest.compat import overload
|
||||||
|
|
||||||
if False: # TYPE_CHECKING
|
if False: # TYPE_CHECKING
|
||||||
from typing import Type
|
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:
|
class Code:
|
||||||
|
@ -38,13 +49,12 @@ class Code:
|
||||||
def __init__(self, rawcode) -> None:
|
def __init__(self, rawcode) -> None:
|
||||||
if not hasattr(rawcode, "co_filename"):
|
if not hasattr(rawcode, "co_filename"):
|
||||||
rawcode = getrawcode(rawcode)
|
rawcode = getrawcode(rawcode)
|
||||||
try:
|
if not isinstance(rawcode, CodeType):
|
||||||
self.filename = rawcode.co_filename
|
|
||||||
self.firstlineno = rawcode.co_firstlineno - 1
|
|
||||||
self.name = rawcode.co_name
|
|
||||||
except AttributeError:
|
|
||||||
raise TypeError("not a code object: {!r}".format(rawcode))
|
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):
|
def __eq__(self, other):
|
||||||
return self.raw == other.raw
|
return self.raw == other.raw
|
||||||
|
@ -72,7 +82,7 @@ class Code:
|
||||||
return p
|
return p
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fullsource(self):
|
def fullsource(self) -> Optional["Source"]:
|
||||||
""" return a _pytest._code.Source object for the full source file of the code
|
""" return a _pytest._code.Source object for the full source file of the code
|
||||||
"""
|
"""
|
||||||
from _pytest._code import source
|
from _pytest._code import source
|
||||||
|
@ -80,7 +90,7 @@ class Code:
|
||||||
full, _ = source.findsource(self.raw)
|
full, _ = source.findsource(self.raw)
|
||||||
return full
|
return full
|
||||||
|
|
||||||
def source(self):
|
def source(self) -> "Source":
|
||||||
""" return a _pytest._code.Source object for the code object's source only
|
""" return a _pytest._code.Source object for the code object's source only
|
||||||
"""
|
"""
|
||||||
# return source only for that part of code
|
# return source only for that part of code
|
||||||
|
@ -88,7 +98,7 @@ class Code:
|
||||||
|
|
||||||
return _pytest._code.Source(self.raw)
|
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
|
""" return a tuple with the argument names for the code object
|
||||||
|
|
||||||
if 'var' is set True also return the names of the variable and
|
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
|
"""Wrapper around a Python frame holding f_locals and f_globals
|
||||||
in which expressions can be evaluated."""
|
in which expressions can be evaluated."""
|
||||||
|
|
||||||
def __init__(self, frame):
|
def __init__(self, frame: FrameType) -> None:
|
||||||
self.lineno = frame.f_lineno - 1
|
self.lineno = frame.f_lineno - 1
|
||||||
self.f_globals = frame.f_globals
|
self.f_globals = frame.f_globals
|
||||||
self.f_locals = frame.f_locals
|
self.f_locals = frame.f_locals
|
||||||
|
@ -115,7 +125,7 @@ class Frame:
|
||||||
self.code = Code(frame.f_code)
|
self.code = Code(frame.f_code)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def statement(self):
|
def statement(self) -> "Source":
|
||||||
""" statement this frame is at """
|
""" statement this frame is at """
|
||||||
import _pytest._code
|
import _pytest._code
|
||||||
|
|
||||||
|
@ -134,7 +144,7 @@ class Frame:
|
||||||
f_locals.update(vars)
|
f_locals.update(vars)
|
||||||
return eval(code, self.f_globals, f_locals)
|
return eval(code, self.f_globals, f_locals)
|
||||||
|
|
||||||
def exec_(self, code, **vars):
|
def exec_(self, code, **vars) -> None:
|
||||||
""" exec 'code' in the frame
|
""" exec 'code' in the frame
|
||||||
|
|
||||||
'vars' are optional; additional local variables
|
'vars' are optional; additional local variables
|
||||||
|
@ -143,7 +153,7 @@ class Frame:
|
||||||
f_locals.update(vars)
|
f_locals.update(vars)
|
||||||
exec(code, self.f_globals, f_locals)
|
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 a 'safe' (non-recursive, one-line) string repr for 'object'
|
||||||
"""
|
"""
|
||||||
return saferepr(object)
|
return saferepr(object)
|
||||||
|
@ -151,7 +161,7 @@ class Frame:
|
||||||
def is_true(self, object):
|
def is_true(self, object):
|
||||||
return object
|
return object
|
||||||
|
|
||||||
def getargs(self, var=False):
|
def getargs(self, var: bool = False):
|
||||||
""" return a list of tuples (name, value) for all arguments
|
""" return a list of tuples (name, value) for all arguments
|
||||||
|
|
||||||
if 'var' is set True also include the variable and keyword
|
if 'var' is set True also include the variable and keyword
|
||||||
|
@ -169,35 +179,34 @@ class Frame:
|
||||||
class TracebackEntry:
|
class TracebackEntry:
|
||||||
""" a single entry in a traceback """
|
""" a single entry in a traceback """
|
||||||
|
|
||||||
_repr_style = None
|
_repr_style = None # type: Optional[Literal["short", "long"]]
|
||||||
exprinfo = None
|
exprinfo = None
|
||||||
|
|
||||||
def __init__(self, rawentry, excinfo=None):
|
def __init__(self, rawentry: TracebackType, excinfo=None) -> None:
|
||||||
self._excinfo = excinfo
|
self._excinfo = excinfo
|
||||||
self._rawentry = rawentry
|
self._rawentry = rawentry
|
||||||
self.lineno = rawentry.tb_lineno - 1
|
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")
|
assert mode in ("short", "long")
|
||||||
self._repr_style = mode
|
self._repr_style = mode
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def frame(self):
|
def frame(self) -> Frame:
|
||||||
import _pytest._code
|
return Frame(self._rawentry.tb_frame)
|
||||||
|
|
||||||
return _pytest._code.Frame(self._rawentry.tb_frame)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def relline(self):
|
def relline(self) -> int:
|
||||||
return self.lineno - self.frame.code.firstlineno
|
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)
|
return "<TracebackEntry %s:%d>" % (self.frame.code.path, self.lineno + 1)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def statement(self):
|
def statement(self) -> "Source":
|
||||||
""" _pytest._code.Source object for the current statement """
|
""" _pytest._code.Source object for the current statement """
|
||||||
source = self.frame.code.fullsource
|
source = self.frame.code.fullsource
|
||||||
|
assert source is not None
|
||||||
return source.getstatement(self.lineno)
|
return source.getstatement(self.lineno)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -206,14 +215,14 @@ class TracebackEntry:
|
||||||
return self.frame.code.path
|
return self.frame.code.path
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def locals(self):
|
def locals(self) -> Dict[str, Any]:
|
||||||
""" locals of underlying frame """
|
""" locals of underlying frame """
|
||||||
return self.frame.f_locals
|
return self.frame.f_locals
|
||||||
|
|
||||||
def getfirstlinesource(self):
|
def getfirstlinesource(self) -> int:
|
||||||
return self.frame.code.firstlineno
|
return self.frame.code.firstlineno
|
||||||
|
|
||||||
def getsource(self, astcache=None):
|
def getsource(self, astcache=None) -> Optional["Source"]:
|
||||||
""" return failing source code. """
|
""" return failing source code. """
|
||||||
# we use the passed in astcache to not reparse asttrees
|
# we use the passed in astcache to not reparse asttrees
|
||||||
# within exception info printing
|
# within exception info printing
|
||||||
|
@ -258,7 +267,7 @@ class TracebackEntry:
|
||||||
return tbh(None if self._excinfo is None else self._excinfo())
|
return tbh(None if self._excinfo is None else self._excinfo())
|
||||||
return tbh
|
return tbh
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
try:
|
try:
|
||||||
fn = str(self.path)
|
fn = str(self.path)
|
||||||
except py.error.Error:
|
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)
|
return " File %r:%d in %s\n %s\n" % (fn, self.lineno + 1, name, line)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self) -> str:
|
||||||
""" co_name of underlying code """
|
""" co_name of underlying code """
|
||||||
return self.frame.code.raw.co_name
|
return self.frame.code.raw.co_name
|
||||||
|
|
||||||
|
|
||||||
class Traceback(list):
|
class Traceback(List[TracebackEntry]):
|
||||||
""" Traceback objects encapsulate and offer higher level
|
""" Traceback objects encapsulate and offer higher level
|
||||||
access to Traceback entries.
|
access to Traceback entries.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Entry = TracebackEntry
|
def __init__(
|
||||||
|
self,
|
||||||
def __init__(self, tb, excinfo=None):
|
tb: Union[TracebackType, Iterable[TracebackEntry]],
|
||||||
|
excinfo: Optional["ReferenceType[ExceptionInfo]"] = None,
|
||||||
|
) -> None:
|
||||||
""" initialize from given python traceback object and ExceptionInfo """
|
""" initialize from given python traceback object and ExceptionInfo """
|
||||||
self._excinfo = excinfo
|
self._excinfo = excinfo
|
||||||
if hasattr(tb, "tb_next"):
|
if isinstance(tb, TracebackType):
|
||||||
|
|
||||||
def f(cur):
|
def f(cur: TracebackType) -> Iterable[TracebackEntry]:
|
||||||
while cur is not None:
|
cur_ = cur # type: Optional[TracebackType]
|
||||||
yield self.Entry(cur, excinfo=excinfo)
|
while cur_ is not None:
|
||||||
cur = cur.tb_next
|
yield TracebackEntry(cur_, excinfo=excinfo)
|
||||||
|
cur_ = cur_.tb_next
|
||||||
|
|
||||||
list.__init__(self, f(tb))
|
super().__init__(f(tb))
|
||||||
else:
|
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
|
""" return a Traceback instance wrapping part of this Traceback
|
||||||
|
|
||||||
by providing any combination of path, lineno and firstlineno, the
|
by providing any combination of path, lineno and firstlineno, the
|
||||||
|
@ -325,13 +343,25 @@ class Traceback(list):
|
||||||
return Traceback(x._rawentry, self._excinfo)
|
return Traceback(x._rawentry, self._excinfo)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __getitem__(self, key):
|
@overload
|
||||||
val = super().__getitem__(key)
|
def __getitem__(self, key: int) -> TracebackEntry:
|
||||||
if isinstance(key, type(slice(0))):
|
raise NotImplementedError()
|
||||||
val = self.__class__(val)
|
|
||||||
return val
|
|
||||||
|
|
||||||
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
|
""" return a Traceback instance with certain items removed
|
||||||
|
|
||||||
fn is a function that gets a single argument, a TracebackEntry
|
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)
|
return Traceback(filter(fn, self), self._excinfo)
|
||||||
|
|
||||||
def getcrashentry(self):
|
def getcrashentry(self) -> TracebackEntry:
|
||||||
""" return last non-hidden traceback entry that lead
|
""" return last non-hidden traceback entry that lead
|
||||||
to the exception of a traceback.
|
to the exception of a traceback.
|
||||||
"""
|
"""
|
||||||
|
@ -353,7 +383,7 @@ class Traceback(list):
|
||||||
return entry
|
return entry
|
||||||
return self[-1]
|
return self[-1]
|
||||||
|
|
||||||
def recursionindex(self):
|
def recursionindex(self) -> Optional[int]:
|
||||||
""" return the index of the frame/TracebackEntry where recursion
|
""" return the index of the frame/TracebackEntry where recursion
|
||||||
originates if appropriate, None if no recursion occurred
|
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[1] is not None, "no current exception"
|
||||||
assert tup[2] is not None, "no current exception"
|
assert tup[2] is not None, "no current exception"
|
||||||
exc_info = (tup[0], tup[1], tup[2])
|
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
|
@classmethod
|
||||||
def for_later(cls) -> "ExceptionInfo[_E]":
|
def for_later(cls) -> "ExceptionInfo[_E]":
|
||||||
|
@ -543,7 +573,7 @@ class ExceptionInfo(Generic[_E]):
|
||||||
def getrepr(
|
def getrepr(
|
||||||
self,
|
self,
|
||||||
showlocals: bool = False,
|
showlocals: bool = False,
|
||||||
style: str = "long",
|
style: "_TracebackStyle" = "long",
|
||||||
abspath: bool = False,
|
abspath: bool = False,
|
||||||
tbfilter: bool = True,
|
tbfilter: bool = True,
|
||||||
funcargs: bool = False,
|
funcargs: bool = False,
|
||||||
|
@ -621,16 +651,16 @@ class FormattedExcinfo:
|
||||||
flow_marker = ">"
|
flow_marker = ">"
|
||||||
fail_marker = "E"
|
fail_marker = "E"
|
||||||
|
|
||||||
showlocals = attr.ib(default=False)
|
showlocals = attr.ib(type=bool, default=False)
|
||||||
style = attr.ib(default="long")
|
style = attr.ib(type="_TracebackStyle", default="long")
|
||||||
abspath = attr.ib(default=True)
|
abspath = attr.ib(type=bool, default=True)
|
||||||
tbfilter = attr.ib(default=True)
|
tbfilter = attr.ib(type=bool, default=True)
|
||||||
funcargs = attr.ib(default=False)
|
funcargs = attr.ib(type=bool, default=False)
|
||||||
truncate_locals = attr.ib(default=True)
|
truncate_locals = attr.ib(type=bool, default=True)
|
||||||
chain = attr.ib(default=True)
|
chain = attr.ib(type=bool, default=True)
|
||||||
astcache = attr.ib(default=attr.Factory(dict), init=False, repr=False)
|
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
|
# figure out indent for given source
|
||||||
try:
|
try:
|
||||||
s = str(source.getstatement(len(source) - 1))
|
s = str(source.getstatement(len(source) - 1))
|
||||||
|
@ -645,20 +675,27 @@ class FormattedExcinfo:
|
||||||
return 0
|
return 0
|
||||||
return 4 + (len(s) - len(s.lstrip()))
|
return 4 + (len(s) - len(s.lstrip()))
|
||||||
|
|
||||||
def _getentrysource(self, entry):
|
def _getentrysource(self, entry: TracebackEntry) -> Optional["Source"]:
|
||||||
source = entry.getsource(self.astcache)
|
source = entry.getsource(self.astcache)
|
||||||
if source is not None:
|
if source is not None:
|
||||||
source = source.deindent()
|
source = source.deindent()
|
||||||
return source
|
return source
|
||||||
|
|
||||||
def repr_args(self, entry):
|
def repr_args(self, entry: TracebackEntry) -> Optional["ReprFuncArgs"]:
|
||||||
if self.funcargs:
|
if self.funcargs:
|
||||||
args = []
|
args = []
|
||||||
for argname, argvalue in entry.frame.getargs(var=True):
|
for argname, argvalue in entry.frame.getargs(var=True):
|
||||||
args.append((argname, saferepr(argvalue)))
|
args.append((argname, saferepr(argvalue)))
|
||||||
return ReprFuncArgs(args)
|
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. """
|
""" return formatted and marked up source lines. """
|
||||||
import _pytest._code
|
import _pytest._code
|
||||||
|
|
||||||
|
@ -682,19 +719,21 @@ class FormattedExcinfo:
|
||||||
lines.extend(self.get_exconly(excinfo, indent=indent, markall=True))
|
lines.extend(self.get_exconly(excinfo, indent=indent, markall=True))
|
||||||
return lines
|
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 = []
|
lines = []
|
||||||
indent = " " * indent
|
indentstr = " " * indent
|
||||||
# get the real exception information out
|
# get the real exception information out
|
||||||
exlines = excinfo.exconly(tryshort=True).split("\n")
|
exlines = excinfo.exconly(tryshort=True).split("\n")
|
||||||
failindent = self.fail_marker + indent[1:]
|
failindent = self.fail_marker + indentstr[1:]
|
||||||
for line in exlines:
|
for line in exlines:
|
||||||
lines.append(failindent + line)
|
lines.append(failindent + line)
|
||||||
if not markall:
|
if not markall:
|
||||||
failindent = indent
|
failindent = indentstr
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
def repr_locals(self, locals):
|
def repr_locals(self, locals: Dict[str, object]) -> Optional["ReprLocals"]:
|
||||||
if self.showlocals:
|
if self.showlocals:
|
||||||
lines = []
|
lines = []
|
||||||
keys = [loc for loc in locals if loc[0] != "@"]
|
keys = [loc for loc in locals if loc[0] != "@"]
|
||||||
|
@ -719,8 +758,11 @@ class FormattedExcinfo:
|
||||||
# # XXX
|
# # XXX
|
||||||
# pprint.pprint(value, stream=self.excinfowriter)
|
# pprint.pprint(value, stream=self.excinfowriter)
|
||||||
return ReprLocals(lines)
|
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
|
import _pytest._code
|
||||||
|
|
||||||
source = self._getentrysource(entry)
|
source = self._getentrysource(entry)
|
||||||
|
@ -731,9 +773,7 @@ class FormattedExcinfo:
|
||||||
line_index = entry.lineno - entry.getfirstlinesource()
|
line_index = entry.lineno - entry.getfirstlinesource()
|
||||||
|
|
||||||
lines = [] # type: List[str]
|
lines = [] # type: List[str]
|
||||||
style = entry._repr_style
|
style = entry._repr_style if entry._repr_style is not None else self.style
|
||||||
if style is None:
|
|
||||||
style = self.style
|
|
||||||
if style in ("short", "long"):
|
if style in ("short", "long"):
|
||||||
short = style == "short"
|
short = style == "short"
|
||||||
reprargs = self.repr_args(entry) if not short else None
|
reprargs = self.repr_args(entry) if not short else None
|
||||||
|
@ -763,7 +803,7 @@ class FormattedExcinfo:
|
||||||
path = np
|
path = np
|
||||||
return path
|
return path
|
||||||
|
|
||||||
def repr_traceback(self, excinfo):
|
def repr_traceback(self, excinfo: ExceptionInfo) -> "ReprTraceback":
|
||||||
traceback = excinfo.traceback
|
traceback = excinfo.traceback
|
||||||
if self.tbfilter:
|
if self.tbfilter:
|
||||||
traceback = traceback.filter()
|
traceback = traceback.filter()
|
||||||
|
@ -781,7 +821,9 @@ class FormattedExcinfo:
|
||||||
entries.append(reprentry)
|
entries.append(reprentry)
|
||||||
return ReprTraceback(entries, extraline, style=self.style)
|
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
|
Truncate the given recursive traceback trying to find the starting point
|
||||||
of the recursion.
|
of the recursion.
|
||||||
|
@ -808,7 +850,9 @@ class FormattedExcinfo:
|
||||||
max_frames=max_frames,
|
max_frames=max_frames,
|
||||||
total=len(traceback),
|
total=len(traceback),
|
||||||
) # type: Optional[str]
|
) # 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:
|
else:
|
||||||
if recursionindex is not None:
|
if recursionindex is not None:
|
||||||
extraline = "!!! Recursion detected (same locals & position)"
|
extraline = "!!! Recursion detected (same locals & position)"
|
||||||
|
@ -865,7 +909,7 @@ class FormattedExcinfo:
|
||||||
|
|
||||||
|
|
||||||
class TerminalRepr:
|
class TerminalRepr:
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
# FYI this is called from pytest-xdist's serialization of exception
|
# FYI this is called from pytest-xdist's serialization of exception
|
||||||
# information.
|
# information.
|
||||||
io = StringIO()
|
io = StringIO()
|
||||||
|
@ -873,7 +917,7 @@ class TerminalRepr:
|
||||||
self.toterminal(tw)
|
self.toterminal(tw)
|
||||||
return io.getvalue().strip()
|
return io.getvalue().strip()
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
return "<{} instance at {:0x}>".format(self.__class__, id(self))
|
return "<{} instance at {:0x}>".format(self.__class__, id(self))
|
||||||
|
|
||||||
def toterminal(self, tw) -> None:
|
def toterminal(self, tw) -> None:
|
||||||
|
@ -884,7 +928,7 @@ class ExceptionRepr(TerminalRepr):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.sections = [] # type: List[Tuple[str, str, str]]
|
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))
|
self.sections.append((name, content, sep))
|
||||||
|
|
||||||
def toterminal(self, tw) -> None:
|
def toterminal(self, tw) -> None:
|
||||||
|
@ -894,7 +938,12 @@ class ExceptionRepr(TerminalRepr):
|
||||||
|
|
||||||
|
|
||||||
class ExceptionChainRepr(ExceptionRepr):
|
class ExceptionChainRepr(ExceptionRepr):
|
||||||
def __init__(self, chain):
|
def __init__(
|
||||||
|
self,
|
||||||
|
chain: Sequence[
|
||||||
|
Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]]
|
||||||
|
],
|
||||||
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.chain = chain
|
self.chain = chain
|
||||||
# reprcrash and reprtraceback of the outermost (the newest) exception
|
# reprcrash and reprtraceback of the outermost (the newest) exception
|
||||||
|
@ -912,7 +961,9 @@ class ExceptionChainRepr(ExceptionRepr):
|
||||||
|
|
||||||
|
|
||||||
class ReprExceptionInfo(ExceptionRepr):
|
class ReprExceptionInfo(ExceptionRepr):
|
||||||
def __init__(self, reprtraceback, reprcrash):
|
def __init__(
|
||||||
|
self, reprtraceback: "ReprTraceback", reprcrash: "ReprFileLocation"
|
||||||
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.reprtraceback = reprtraceback
|
self.reprtraceback = reprtraceback
|
||||||
self.reprcrash = reprcrash
|
self.reprcrash = reprcrash
|
||||||
|
@ -925,7 +976,12 @@ class ReprExceptionInfo(ExceptionRepr):
|
||||||
class ReprTraceback(TerminalRepr):
|
class ReprTraceback(TerminalRepr):
|
||||||
entrysep = "_ "
|
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.reprentries = reprentries
|
||||||
self.extraline = extraline
|
self.extraline = extraline
|
||||||
self.style = style
|
self.style = style
|
||||||
|
@ -950,16 +1006,16 @@ class ReprTraceback(TerminalRepr):
|
||||||
|
|
||||||
|
|
||||||
class ReprTracebackNative(ReprTraceback):
|
class ReprTracebackNative(ReprTraceback):
|
||||||
def __init__(self, tblines):
|
def __init__(self, tblines: Sequence[str]) -> None:
|
||||||
self.style = "native"
|
self.style = "native"
|
||||||
self.reprentries = [ReprEntryNative(tblines)]
|
self.reprentries = [ReprEntryNative(tblines)]
|
||||||
self.extraline = None
|
self.extraline = None
|
||||||
|
|
||||||
|
|
||||||
class ReprEntryNative(TerminalRepr):
|
class ReprEntryNative(TerminalRepr):
|
||||||
style = "native"
|
style = "native" # type: _TracebackStyle
|
||||||
|
|
||||||
def __init__(self, tblines):
|
def __init__(self, tblines: Sequence[str]) -> None:
|
||||||
self.lines = tblines
|
self.lines = tblines
|
||||||
|
|
||||||
def toterminal(self, tw) -> None:
|
def toterminal(self, tw) -> None:
|
||||||
|
@ -967,7 +1023,14 @@ class ReprEntryNative(TerminalRepr):
|
||||||
|
|
||||||
|
|
||||||
class ReprEntry(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.lines = lines
|
||||||
self.reprfuncargs = reprfuncargs
|
self.reprfuncargs = reprfuncargs
|
||||||
self.reprlocals = reprlocals
|
self.reprlocals = reprlocals
|
||||||
|
@ -976,6 +1039,7 @@ class ReprEntry(TerminalRepr):
|
||||||
|
|
||||||
def toterminal(self, tw) -> None:
|
def toterminal(self, tw) -> None:
|
||||||
if self.style == "short":
|
if self.style == "short":
|
||||||
|
assert self.reprfileloc is not None
|
||||||
self.reprfileloc.toterminal(tw)
|
self.reprfileloc.toterminal(tw)
|
||||||
for line in self.lines:
|
for line in self.lines:
|
||||||
red = line.startswith("E ")
|
red = line.startswith("E ")
|
||||||
|
@ -994,14 +1058,14 @@ class ReprEntry(TerminalRepr):
|
||||||
tw.line("")
|
tw.line("")
|
||||||
self.reprfileloc.toterminal(tw)
|
self.reprfileloc.toterminal(tw)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
return "{}\n{}\n{}".format(
|
return "{}\n{}\n{}".format(
|
||||||
"\n".join(self.lines), self.reprlocals, self.reprfileloc
|
"\n".join(self.lines), self.reprlocals, self.reprfileloc
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ReprFileLocation(TerminalRepr):
|
class ReprFileLocation(TerminalRepr):
|
||||||
def __init__(self, path, lineno, message):
|
def __init__(self, path, lineno: int, message: str) -> None:
|
||||||
self.path = str(path)
|
self.path = str(path)
|
||||||
self.lineno = lineno
|
self.lineno = lineno
|
||||||
self.message = message
|
self.message = message
|
||||||
|
@ -1018,7 +1082,7 @@ class ReprFileLocation(TerminalRepr):
|
||||||
|
|
||||||
|
|
||||||
class ReprLocals(TerminalRepr):
|
class ReprLocals(TerminalRepr):
|
||||||
def __init__(self, lines):
|
def __init__(self, lines: Sequence[str]) -> None:
|
||||||
self.lines = lines
|
self.lines = lines
|
||||||
|
|
||||||
def toterminal(self, tw) -> None:
|
def toterminal(self, tw) -> None:
|
||||||
|
@ -1027,7 +1091,7 @@ class ReprLocals(TerminalRepr):
|
||||||
|
|
||||||
|
|
||||||
class ReprFuncArgs(TerminalRepr):
|
class ReprFuncArgs(TerminalRepr):
|
||||||
def __init__(self, args):
|
def __init__(self, args: Sequence[Tuple[str, object]]) -> None:
|
||||||
self.args = args
|
self.args = args
|
||||||
|
|
||||||
def toterminal(self, tw) -> None:
|
def toterminal(self, tw) -> None:
|
||||||
|
@ -1049,13 +1113,11 @@ class ReprFuncArgs(TerminalRepr):
|
||||||
tw.line("")
|
tw.line("")
|
||||||
|
|
||||||
|
|
||||||
def getrawcode(obj, trycall=True):
|
def getrawcode(obj, trycall: bool = True):
|
||||||
""" return code object for given function. """
|
""" return code object for given function. """
|
||||||
try:
|
try:
|
||||||
return obj.__code__
|
return obj.__code__
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
obj = getattr(obj, "im_func", obj)
|
|
||||||
obj = getattr(obj, "func_code", obj)
|
|
||||||
obj = getattr(obj, "f_code", obj)
|
obj = getattr(obj, "f_code", obj)
|
||||||
obj = getattr(obj, "__code__", obj)
|
obj = getattr(obj, "__code__", obj)
|
||||||
if trycall and not hasattr(obj, "co_firstlineno"):
|
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()
|
_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:
|
"""Return True if a TracebackEntry instance should be removed from tracebacks:
|
||||||
* dynamically generated code (no code to show up for it);
|
* dynamically generated code (no code to show up for it);
|
||||||
* internal traceback from pytest or its internal libraries, py and pluggy.
|
* internal traceback from pytest or its internal libraries, py and pluggy.
|
||||||
|
|
|
@ -8,6 +8,7 @@ import warnings
|
||||||
from ast import PyCF_ONLY_AST as _AST_FLAG
|
from ast import PyCF_ONLY_AST as _AST_FLAG
|
||||||
from bisect import bisect_right
|
from bisect import bisect_right
|
||||||
from types import FrameType
|
from types import FrameType
|
||||||
|
from typing import Iterator
|
||||||
from typing import List
|
from typing import List
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from typing import Sequence
|
from typing import Sequence
|
||||||
|
@ -60,7 +61,7 @@ class Source:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@overload # noqa: F811
|
@overload # noqa: F811
|
||||||
def __getitem__(self, key: slice) -> "Source":
|
def __getitem__(self, key: slice) -> "Source": # noqa: F811
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def __getitem__(self, key: Union[int, slice]) -> Union[str, "Source"]: # noqa: F811
|
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]
|
newsource.lines = self.lines[key.start : key.stop]
|
||||||
return newsource
|
return newsource
|
||||||
|
|
||||||
|
def __iter__(self) -> Iterator[str]:
|
||||||
|
return iter(self.lines)
|
||||||
|
|
||||||
def __len__(self) -> int:
|
def __len__(self) -> int:
|
||||||
return len(self.lines)
|
return len(self.lines)
|
||||||
|
|
||||||
|
@ -335,7 +339,9 @@ def getstatementrange_ast(
|
||||||
block_finder.started = source.lines[start][0].isspace()
|
block_finder.started = source.lines[start][0].isspace()
|
||||||
it = ((x + "\n") for x in source.lines[start:end])
|
it = ((x + "\n") for x in source.lines[start:end])
|
||||||
try:
|
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)
|
block_finder.tokeneater(*tok)
|
||||||
except (inspect.EndOfBlock, IndentationError):
|
except (inspect.EndOfBlock, IndentationError):
|
||||||
end = block_finder.last + start
|
end = block_finder.last + start
|
||||||
|
|
|
@ -80,3 +80,24 @@ def saferepr(obj: Any, maxsize: int = 240) -> str:
|
||||||
around the Repr/reprlib functionality of the standard 2.6 lib.
|
around the Repr/reprlib functionality of the standard 2.6 lib.
|
||||||
"""
|
"""
|
||||||
return SafeRepr(maxsize).repr(obj)
|
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)
|
||||||
|
|
|
@ -13,7 +13,6 @@ import struct
|
||||||
import sys
|
import sys
|
||||||
import tokenize
|
import tokenize
|
||||||
import types
|
import types
|
||||||
from pathlib import Path
|
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from typing import List
|
from typing import List
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
@ -28,6 +27,7 @@ from _pytest.assertion.util import ( # noqa: F401
|
||||||
)
|
)
|
||||||
from _pytest.compat import fspath
|
from _pytest.compat import fspath
|
||||||
from _pytest.pathlib import fnmatch_ex
|
from _pytest.pathlib import fnmatch_ex
|
||||||
|
from _pytest.pathlib import Path
|
||||||
from _pytest.pathlib import PurePath
|
from _pytest.pathlib import PurePath
|
||||||
|
|
||||||
# pytest caches rewritten pycs in pycache dirs
|
# 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
|
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))
|
msg = self.pop_format_context(ast.Str(explanation))
|
||||||
|
|
||||||
# Failed
|
# Failed
|
||||||
|
@ -860,7 +861,6 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||||
else: # Original assertion rewriting
|
else: # Original assertion rewriting
|
||||||
# Create failure message.
|
# Create failure message.
|
||||||
body = self.expl_stmts
|
body = self.expl_stmts
|
||||||
negation = ast.UnaryOp(ast.Not(), top_condition)
|
|
||||||
self.statements.append(ast.If(negation, body, []))
|
self.statements.append(ast.If(negation, body, []))
|
||||||
if assert_.msg:
|
if assert_.msg:
|
||||||
assertmsg = self.helper("_format_assertmsg", assert_.msg)
|
assertmsg = self.helper("_format_assertmsg", assert_.msg)
|
||||||
|
|
|
@ -13,6 +13,7 @@ from typing import Tuple
|
||||||
|
|
||||||
import _pytest._code
|
import _pytest._code
|
||||||
from _pytest import outcomes
|
from _pytest import outcomes
|
||||||
|
from _pytest._io.saferepr import _pformat_dispatch
|
||||||
from _pytest._io.saferepr import safeformat
|
from _pytest._io.saferepr import safeformat
|
||||||
from _pytest._io.saferepr import saferepr
|
from _pytest._io.saferepr import saferepr
|
||||||
from _pytest.compat import ATTRS_EQ_FIELD
|
from _pytest.compat import ATTRS_EQ_FIELD
|
||||||
|
@ -270,15 +271,8 @@ def _compare_eq_iterable(
|
||||||
lines_left = len(left_formatting)
|
lines_left = len(left_formatting)
|
||||||
lines_right = len(right_formatting)
|
lines_right = len(right_formatting)
|
||||||
if lines_left != lines_right:
|
if lines_left != lines_right:
|
||||||
if lines_left > lines_right:
|
left_formatting = _pformat_dispatch(left).splitlines()
|
||||||
max_width = min(len(x) for x in left_formatting)
|
right_formatting = _pformat_dispatch(right).splitlines()
|
||||||
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)
|
|
||||||
|
|
||||||
if lines_left > 1 or lines_right > 1:
|
if lines_left > 1 or lines_right > 1:
|
||||||
_surrounding_parens_on_own_lines(left_formatting)
|
_surrounding_parens_on_own_lines(left_formatting)
|
||||||
|
|
|
@ -125,13 +125,14 @@ class Cache:
|
||||||
return
|
return
|
||||||
if not cache_dir_exists_already:
|
if not cache_dir_exists_already:
|
||||||
self._ensure_supporting_files()
|
self._ensure_supporting_files()
|
||||||
|
data = json.dumps(value, indent=2, sort_keys=True)
|
||||||
try:
|
try:
|
||||||
f = path.open("w")
|
f = path.open("w")
|
||||||
except (IOError, OSError):
|
except (IOError, OSError):
|
||||||
self.warn("cache could not write path {path}", path=path)
|
self.warn("cache could not write path {path}", path=path)
|
||||||
else:
|
else:
|
||||||
with f:
|
with f:
|
||||||
json.dump(value, f, indent=2, sort_keys=True)
|
f.write(data)
|
||||||
|
|
||||||
def _ensure_supporting_files(self):
|
def _ensure_supporting_files(self):
|
||||||
"""Create supporting files in the cache dir that are not really part of the cache."""
|
"""Create supporting files in the cache dir that are not really part of the cache."""
|
||||||
|
|
|
@ -10,7 +10,14 @@ import sys
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from inspect import Parameter
|
from inspect import Parameter
|
||||||
from inspect import signature
|
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 overload
|
||||||
|
from typing import Tuple
|
||||||
|
from typing import TypeVar
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
import py
|
import py
|
||||||
|
@ -20,6 +27,13 @@ from _pytest._io.saferepr import saferepr
|
||||||
from _pytest.outcomes import fail
|
from _pytest.outcomes import fail
|
||||||
from _pytest.outcomes import TEST_OUTCOME
|
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()
|
NOTSET = object()
|
||||||
|
|
||||||
|
@ -29,12 +43,12 @@ MODULE_NOT_FOUND_ERROR = (
|
||||||
|
|
||||||
|
|
||||||
if sys.version_info >= (3, 8):
|
if sys.version_info >= (3, 8):
|
||||||
from importlib import metadata as importlib_metadata # noqa: F401
|
from importlib import metadata as importlib_metadata
|
||||||
else:
|
else:
|
||||||
import importlib_metadata # noqa: F401
|
import importlib_metadata # noqa: F401
|
||||||
|
|
||||||
|
|
||||||
def _format_args(func):
|
def _format_args(func: Callable[..., Any]) -> str:
|
||||||
return str(signature(func))
|
return str(signature(func))
|
||||||
|
|
||||||
|
|
||||||
|
@ -55,12 +69,12 @@ else:
|
||||||
fspath = os.fspath
|
fspath = os.fspath
|
||||||
|
|
||||||
|
|
||||||
def is_generator(func):
|
def is_generator(func: object) -> bool:
|
||||||
genfunc = inspect.isgeneratorfunction(func)
|
genfunc = inspect.isgeneratorfunction(func)
|
||||||
return genfunc and not iscoroutinefunction(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
|
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
|
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)
|
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)
|
function = get_real_func(function)
|
||||||
fn = py.path.local(inspect.getfile(function))
|
fn = py.path.local(inspect.getfile(function))
|
||||||
lineno = function.__code__.co_firstlineno
|
lineno = function.__code__.co_firstlineno
|
||||||
|
@ -82,7 +96,7 @@ def getlocation(function, curdir=None):
|
||||||
return "%s:%d" % (fn, lineno + 1)
|
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) """
|
""" return number of arguments used up by mock arguments (if any) """
|
||||||
patchings = getattr(function, "patchings", None)
|
patchings = getattr(function, "patchings", None)
|
||||||
if not patchings:
|
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.
|
"""Returns the names of a function's mandatory arguments.
|
||||||
|
|
||||||
This should return the names of all function arguments that:
|
This should return the names of all function arguments that:
|
||||||
|
@ -169,7 +189,7 @@ else:
|
||||||
from contextlib import nullcontext # noqa
|
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,
|
# Note: this code intentionally mirrors the code at the beginning of getfuncargnames,
|
||||||
# to get the arguments which were excluded from its result because they had default values
|
# to get the arguments which were excluded from its result because they had default values
|
||||||
return tuple(
|
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)
|
return s.translate(_non_printable_ascii_translate_table)
|
||||||
|
|
||||||
|
|
||||||
STRING_TYPES = bytes, str
|
STRING_TYPES = bytes, str
|
||||||
|
|
||||||
|
|
||||||
def _bytes_to_ascii(val):
|
def _bytes_to_ascii(val: bytes) -> str:
|
||||||
return val.decode("ascii", "backslashreplace")
|
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
|
"""If val is pure ascii, returns it as a str(). Otherwise, escapes
|
||||||
bytes objects into a sequence of escaped bytes:
|
bytes objects into a sequence of escaped bytes:
|
||||||
|
|
||||||
|
@ -296,7 +316,7 @@ def getimfunc(func):
|
||||||
return 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.
|
""" Like getattr but return default upon any Exception or any OutcomeException.
|
||||||
|
|
||||||
Attribute access can potentially fail for 'evil' Python objects.
|
Attribute access can potentially fail for 'evil' Python objects.
|
||||||
|
@ -310,7 +330,7 @@ def safe_getattr(object, name, default):
|
||||||
return default
|
return default
|
||||||
|
|
||||||
|
|
||||||
def safe_isclass(obj):
|
def safe_isclass(obj: object) -> bool:
|
||||||
"""Ignore any exception via isinstance on Python 3."""
|
"""Ignore any exception via isinstance on Python 3."""
|
||||||
try:
|
try:
|
||||||
return inspect.isclass(obj)
|
return inspect.isclass(obj)
|
||||||
|
@ -331,39 +351,26 @@ COLLECT_FAKEMODULE_ATTRIBUTES = (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _setup_collect_fakemodule():
|
def _setup_collect_fakemodule() -> None:
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
pytest.collect = ModuleType("pytest.collect")
|
# Types ignored because the module is created dynamically.
|
||||||
pytest.collect.__all__ = [] # used for setns
|
pytest.collect = ModuleType("pytest.collect") # type: ignore
|
||||||
|
pytest.collect.__all__ = [] # type: ignore # used for setns
|
||||||
for attr_name in COLLECT_FAKEMODULE_ATTRIBUTES:
|
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):
|
class CaptureIO(io.TextIOWrapper):
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
super().__init__(io.BytesIO(), encoding="UTF-8", newline="", write_through=True)
|
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")
|
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
|
if sys.version_info < (3, 5, 2): # pragma: no cover
|
||||||
|
|
||||||
def overload(f): # noqa: F811
|
def overload(f): # noqa: F811
|
||||||
|
@ -374,3 +381,33 @@ if getattr(attr, "__version_info__", ()) >= (19, 2):
|
||||||
ATTRS_EQ_FIELD = "eq"
|
ATTRS_EQ_FIELD = "eq"
|
||||||
else:
|
else:
|
||||||
ATTRS_EQ_FIELD = "cmp"
|
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
|
||||||
|
|
|
@ -8,7 +8,6 @@ import sys
|
||||||
import types
|
import types
|
||||||
import warnings
|
import warnings
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from pathlib import Path
|
|
||||||
from types import TracebackType
|
from types import TracebackType
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
|
@ -40,11 +39,14 @@ from _pytest._code import filter_traceback
|
||||||
from _pytest.compat import importlib_metadata
|
from _pytest.compat import importlib_metadata
|
||||||
from _pytest.outcomes import fail
|
from _pytest.outcomes import fail
|
||||||
from _pytest.outcomes import Skipped
|
from _pytest.outcomes import Skipped
|
||||||
|
from _pytest.pathlib import Path
|
||||||
from _pytest.warning_types import PytestConfigWarning
|
from _pytest.warning_types import PytestConfigWarning
|
||||||
|
|
||||||
if False: # TYPE_CHECKING
|
if False: # TYPE_CHECKING
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
|
from .argparsing import Argument
|
||||||
|
|
||||||
|
|
||||||
hookimpl = HookimplMarker("pytest")
|
hookimpl = HookimplMarker("pytest")
|
||||||
hookspec = HookspecMarker("pytest")
|
hookspec = HookspecMarker("pytest")
|
||||||
|
@ -131,13 +133,7 @@ def directory_arg(path, optname):
|
||||||
|
|
||||||
|
|
||||||
# Plugins that cannot be disabled via "-p no:X" currently.
|
# Plugins that cannot be disabled via "-p no:X" currently.
|
||||||
essential_plugins = ( # fmt: off
|
essential_plugins = ("mark", "main", "runner", "fixtures", "helpconfig") # Provides -p.
|
||||||
"mark",
|
|
||||||
"main",
|
|
||||||
"runner",
|
|
||||||
"fixtures",
|
|
||||||
"helpconfig", # Provides -p.
|
|
||||||
) # fmt: on
|
|
||||||
|
|
||||||
default_plugins = essential_plugins + (
|
default_plugins = essential_plugins + (
|
||||||
"python",
|
"python",
|
||||||
|
@ -154,7 +150,6 @@ default_plugins = essential_plugins + (
|
||||||
"assertion",
|
"assertion",
|
||||||
"junitxml",
|
"junitxml",
|
||||||
"resultlog",
|
"resultlog",
|
||||||
"report_log",
|
|
||||||
"doctest",
|
"doctest",
|
||||||
"cacheprovider",
|
"cacheprovider",
|
||||||
"freeze_support",
|
"freeze_support",
|
||||||
|
@ -588,7 +583,7 @@ class PytestPluginManager(PluginManager):
|
||||||
_issue_warning_captured(
|
_issue_warning_captured(
|
||||||
PytestConfigWarning("skipped plugin {!r}: {}".format(modname, e.msg)),
|
PytestConfigWarning("skipped plugin {!r}: {}".format(modname, e.msg)),
|
||||||
self.hook,
|
self.hook,
|
||||||
stacklevel=1,
|
stacklevel=2,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
mod = sys.modules[importspec]
|
mod = sys.modules[importspec]
|
||||||
|
@ -680,7 +675,7 @@ class Config:
|
||||||
plugins = attr.ib()
|
plugins = attr.ib()
|
||||||
dir = attr.ib(type=Path)
|
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
|
from .argparsing import Parser, FILE_OR_DIR
|
||||||
|
|
||||||
if invocation_params is None:
|
if invocation_params is None:
|
||||||
|
@ -793,11 +788,11 @@ class Config:
|
||||||
config.pluginmanager.consider_pluginarg(x)
|
config.pluginmanager.consider_pluginarg(x)
|
||||||
return config
|
return config
|
||||||
|
|
||||||
def _processopt(self, opt):
|
def _processopt(self, opt: "Argument") -> None:
|
||||||
for name in opt._short_opts + opt._long_opts:
|
for name in opt._short_opts + opt._long_opts:
|
||||||
self._opt2dest[name] = opt.dest
|
self._opt2dest[name] = opt.dest
|
||||||
|
|
||||||
if hasattr(opt, "default") and opt.dest:
|
if hasattr(opt, "default"):
|
||||||
if not hasattr(self.option, opt.dest):
|
if not hasattr(self.option, opt.dest):
|
||||||
setattr(self.option, opt.dest, opt.default)
|
setattr(self.option, opt.dest, opt.default)
|
||||||
|
|
||||||
|
@ -805,7 +800,7 @@ class Config:
|
||||||
def pytest_load_initial_conftests(self, early_config):
|
def pytest_load_initial_conftests(self, early_config):
|
||||||
self.pluginmanager._set_initial_conftests(early_config.known_args_namespace)
|
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(
|
ns, unknown_args = self._parser.parse_known_and_unknown_args(
|
||||||
args, namespace=copy.copy(self.option)
|
args, namespace=copy.copy(self.option)
|
||||||
)
|
)
|
||||||
|
@ -822,7 +817,7 @@ class Config:
|
||||||
self._parser.addini("minversion", "minimally required pytest version")
|
self._parser.addini("minversion", "minimally required pytest version")
|
||||||
self._override_ini = ns.override_ini or ()
|
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.
|
"""Install the PEP 302 import hook if using assertion rewriting.
|
||||||
|
|
||||||
Needs to parse the --assert=<mode> option from the commandline
|
Needs to parse the --assert=<mode> option from the commandline
|
||||||
|
@ -862,19 +857,19 @@ class Config:
|
||||||
for name in _iter_rewritable_modules(package_files):
|
for name in _iter_rewritable_modules(package_files):
|
||||||
hook.mark_rewrite(name)
|
hook.mark_rewrite(name)
|
||||||
|
|
||||||
def _validate_args(self, args, via):
|
def _validate_args(self, args: List[str], via: str) -> List[str]:
|
||||||
"""Validate known args."""
|
"""Validate known args."""
|
||||||
self._parser._config_source_hint = via
|
self._parser._config_source_hint = via # type: ignore
|
||||||
try:
|
try:
|
||||||
self._parser.parse_known_and_unknown_args(
|
self._parser.parse_known_and_unknown_args(
|
||||||
args, namespace=copy.copy(self.option)
|
args, namespace=copy.copy(self.option)
|
||||||
)
|
)
|
||||||
finally:
|
finally:
|
||||||
del self._parser._config_source_hint
|
del self._parser._config_source_hint # type: ignore
|
||||||
|
|
||||||
return args
|
return args
|
||||||
|
|
||||||
def _preparse(self, args, addopts=True):
|
def _preparse(self, args: List[str], addopts: bool = True) -> None:
|
||||||
if addopts:
|
if addopts:
|
||||||
env_addopts = os.environ.get("PYTEST_ADDOPTS", "")
|
env_addopts = os.environ.get("PYTEST_ADDOPTS", "")
|
||||||
if len(env_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.
|
# parse given cmdline arguments into this config object.
|
||||||
assert not hasattr(
|
assert not hasattr(
|
||||||
self, "args"
|
self, "args"
|
||||||
|
@ -949,7 +944,7 @@ class Config:
|
||||||
self._preparse(args, addopts=addopts)
|
self._preparse(args, addopts=addopts)
|
||||||
# XXX deprecated hook:
|
# XXX deprecated hook:
|
||||||
self.hook.pytest_cmdline_preparse(config=self, args=args)
|
self.hook.pytest_cmdline_preparse(config=self, args=args)
|
||||||
self._parser.after_preparse = True
|
self._parser.after_preparse = True # type: ignore
|
||||||
try:
|
try:
|
||||||
args = self._parser.parse_setoption(
|
args = self._parser.parse_setoption(
|
||||||
args, self.option, namespace=self.option
|
args, self.option, namespace=self.option
|
||||||
|
@ -974,7 +969,7 @@ class Config:
|
||||||
def getini(self, name: str):
|
def getini(self, name: str):
|
||||||
""" return configuration value from an :ref:`ini file <inifiles>`. If the
|
""" return configuration value from an :ref:`ini file <inifiles>`. If the
|
||||||
specified name hasn't been registered through a prior
|
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. """
|
call (usually from a plugin), a ValueError is raised. """
|
||||||
try:
|
try:
|
||||||
return self._inicache[name]
|
return self._inicache[name]
|
||||||
|
|
|
@ -3,15 +3,24 @@ import sys
|
||||||
import warnings
|
import warnings
|
||||||
from gettext import gettext
|
from gettext import gettext
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
from typing import Callable
|
||||||
|
from typing import cast
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from typing import List
|
from typing import List
|
||||||
|
from typing import Mapping
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
from typing import Sequence
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
import py
|
import py
|
||||||
|
|
||||||
from _pytest.config.exceptions import UsageError
|
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"
|
FILE_OR_DIR = "file_or_dir"
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,9 +31,13 @@ class Parser:
|
||||||
there's an error processing the command line arguments.
|
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._anonymous = OptionGroup("custom options", parser=self)
|
||||||
self._groups = [] # type: List[OptionGroup]
|
self._groups = [] # type: List[OptionGroup]
|
||||||
self._processopt = processopt
|
self._processopt = processopt
|
||||||
|
@ -33,12 +46,14 @@ class Parser:
|
||||||
self._ininames = [] # type: List[str]
|
self._ininames = [] # type: List[str]
|
||||||
self.extra_info = {} # type: Dict[str, Any]
|
self.extra_info = {} # type: Dict[str, Any]
|
||||||
|
|
||||||
def processoption(self, option):
|
def processoption(self, option: "Argument") -> None:
|
||||||
if self._processopt:
|
if self._processopt:
|
||||||
if option.dest:
|
if option.dest:
|
||||||
self._processopt(option)
|
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.
|
""" get (or create) a named option Group.
|
||||||
|
|
||||||
:name: name of the 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
|
The returned group object has an ``addoption`` method with the same
|
||||||
signature as :py:func:`parser.addoption
|
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``.
|
respective group in the output of ``pytest. --help``.
|
||||||
"""
|
"""
|
||||||
for group in self._groups:
|
for group in self._groups:
|
||||||
|
@ -61,13 +76,13 @@ class Parser:
|
||||||
self._groups.insert(i + 1, group)
|
self._groups.insert(i + 1, group)
|
||||||
return group
|
return group
|
||||||
|
|
||||||
def addoption(self, *opts, **attrs):
|
def addoption(self, *opts: str, **attrs: Any) -> None:
|
||||||
""" register a command line option.
|
""" register a command line option.
|
||||||
|
|
||||||
:opts: option names, can be short or long options.
|
: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
|
`argparse library
|
||||||
<http://docs.python.org/2/library/argparse.html>`_
|
<https://docs.python.org/library/argparse.html>`_
|
||||||
accepts.
|
accepts.
|
||||||
|
|
||||||
After command line parsing options are available on the pytest config
|
After command line parsing options are available on the pytest config
|
||||||
|
@ -77,7 +92,11 @@ class Parser:
|
||||||
"""
|
"""
|
||||||
self._anonymous.addoption(*opts, **attrs)
|
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
|
from _pytest._argcomplete import try_argcomplete
|
||||||
|
|
||||||
self.optparser = self._getparser()
|
self.optparser = self._getparser()
|
||||||
|
@ -98,27 +117,37 @@ class Parser:
|
||||||
n = option.names()
|
n = option.names()
|
||||||
a = option.attrs()
|
a = option.attrs()
|
||||||
arggroup.add_argument(*n, **a)
|
arggroup.add_argument(*n, **a)
|
||||||
|
file_or_dir_arg = optparser.add_argument(FILE_OR_DIR, nargs="*")
|
||||||
# bash like autocompletion for dirs (appending '/')
|
# bash like autocompletion for dirs (appending '/')
|
||||||
# Type ignored because typeshed doesn't know about argcomplete.
|
# Type ignored because typeshed doesn't know about argcomplete.
|
||||||
optparser.add_argument( # type: ignore
|
file_or_dir_arg.completer = filescompleter # type: ignore
|
||||||
FILE_OR_DIR, nargs="*"
|
|
||||||
).completer = filescompleter
|
|
||||||
return optparser
|
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)
|
parsedoption = self.parse(args, namespace=namespace)
|
||||||
for name, value in parsedoption.__dict__.items():
|
for name, value in parsedoption.__dict__.items():
|
||||||
setattr(option, name, value)
|
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
|
"""parses and returns a namespace object with known arguments at this
|
||||||
point.
|
point.
|
||||||
"""
|
"""
|
||||||
return self.parse_known_and_unknown_args(args, namespace=namespace)[0]
|
return self.parse_known_and_unknown_args(args, namespace=namespace)[0]
|
||||||
|
|
||||||
def parse_known_and_unknown_args(
|
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]]:
|
) -> Tuple[argparse.Namespace, List[str]]:
|
||||||
"""parses and returns a namespace object with known arguments, and
|
"""parses and returns a namespace object with known arguments, and
|
||||||
the remaining arguments unknown at this point.
|
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]
|
args = [str(x) if isinstance(x, py.path.local) else x for x in args]
|
||||||
return optparser.parse_known_args(args, namespace=namespace)
|
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.
|
""" register an ini-file option.
|
||||||
|
|
||||||
:name: name of the ini-variable
|
:name: name of the ini-variable
|
||||||
|
@ -149,11 +184,11 @@ class ArgumentError(Exception):
|
||||||
inconsistent arguments.
|
inconsistent arguments.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, msg, option):
|
def __init__(self, msg: str, option: Union["Argument", str]) -> None:
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
self.option_id = str(option)
|
self.option_id = str(option)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
if self.option_id:
|
if self.option_id:
|
||||||
return "option {}: {}".format(self.option_id, self.msg)
|
return "option {}: {}".format(self.option_id, self.msg)
|
||||||
else:
|
else:
|
||||||
|
@ -170,12 +205,11 @@ class Argument:
|
||||||
|
|
||||||
_typ_map = {"int": int, "string": str, "float": float, "complex": complex}
|
_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"""
|
"""store parms in private vars for use in add_argument"""
|
||||||
self._attrs = attrs
|
self._attrs = attrs
|
||||||
self._short_opts = [] # type: List[str]
|
self._short_opts = [] # type: List[str]
|
||||||
self._long_opts = [] # type: List[str]
|
self._long_opts = [] # type: List[str]
|
||||||
self.dest = attrs.get("dest")
|
|
||||||
if "%default" in (attrs.get("help") or ""):
|
if "%default" in (attrs.get("help") or ""):
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
'pytest now uses argparse. "%default" should be'
|
'pytest now uses argparse. "%default" should be'
|
||||||
|
@ -221,23 +255,25 @@ class Argument:
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
self._set_opt_strings(names)
|
self._set_opt_strings(names)
|
||||||
if not self.dest:
|
dest = attrs.get("dest") # type: Optional[str]
|
||||||
if self._long_opts:
|
if dest:
|
||||||
self.dest = self._long_opts[0][2:].replace("-", "_")
|
self.dest = dest
|
||||||
else:
|
elif self._long_opts:
|
||||||
try:
|
self.dest = self._long_opts[0][2:].replace("-", "_")
|
||||||
self.dest = self._short_opts[0][1:]
|
else:
|
||||||
except IndexError:
|
try:
|
||||||
raise ArgumentError("need a long or short option", self)
|
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
|
return self._short_opts + self._long_opts
|
||||||
|
|
||||||
def attrs(self):
|
def attrs(self) -> Mapping[str, Any]:
|
||||||
# update any attributes set by processopt
|
# update any attributes set by processopt
|
||||||
attrs = "default dest help".split()
|
attrs = "default dest help".split()
|
||||||
if self.dest:
|
attrs.append(self.dest)
|
||||||
attrs.append(self.dest)
|
|
||||||
for attr in attrs:
|
for attr in attrs:
|
||||||
try:
|
try:
|
||||||
self._attrs[attr] = getattr(self, attr)
|
self._attrs[attr] = getattr(self, attr)
|
||||||
|
@ -250,7 +286,7 @@ class Argument:
|
||||||
self._attrs["help"] = a
|
self._attrs["help"] = a
|
||||||
return self._attrs
|
return self._attrs
|
||||||
|
|
||||||
def _set_opt_strings(self, opts):
|
def _set_opt_strings(self, opts: Sequence[str]) -> None:
|
||||||
"""directly from optparse
|
"""directly from optparse
|
||||||
|
|
||||||
might not be necessary as this is passed to argparse later on"""
|
might not be necessary as this is passed to argparse later on"""
|
||||||
|
@ -293,13 +329,15 @@ class Argument:
|
||||||
|
|
||||||
|
|
||||||
class OptionGroup:
|
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.name = name
|
||||||
self.description = description
|
self.description = description
|
||||||
self.options = [] # type: List[Argument]
|
self.options = [] # type: List[Argument]
|
||||||
self.parser = parser
|
self.parser = parser
|
||||||
|
|
||||||
def addoption(self, *optnames, **attrs):
|
def addoption(self, *optnames: str, **attrs: Any) -> None:
|
||||||
""" add an option to this group.
|
""" add an option to this group.
|
||||||
|
|
||||||
if a shortened version of a long option is specified it will
|
if a shortened version of a long option is specified it will
|
||||||
|
@ -315,11 +353,11 @@ class OptionGroup:
|
||||||
option = Argument(*optnames, **attrs)
|
option = Argument(*optnames, **attrs)
|
||||||
self._addoption_instance(option, shortupper=False)
|
self._addoption_instance(option, shortupper=False)
|
||||||
|
|
||||||
def _addoption(self, *optnames, **attrs):
|
def _addoption(self, *optnames: str, **attrs: Any) -> None:
|
||||||
option = Argument(*optnames, **attrs)
|
option = Argument(*optnames, **attrs)
|
||||||
self._addoption_instance(option, shortupper=True)
|
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:
|
if not shortupper:
|
||||||
for opt in option._short_opts:
|
for opt in option._short_opts:
|
||||||
if opt[0] == "-" and opt[1].islower():
|
if opt[0] == "-" and opt[1].islower():
|
||||||
|
@ -330,9 +368,12 @@ class OptionGroup:
|
||||||
|
|
||||||
|
|
||||||
class MyOptionParser(argparse.ArgumentParser):
|
class MyOptionParser(argparse.ArgumentParser):
|
||||||
def __init__(self, parser, extra_info=None, prog=None):
|
def __init__(
|
||||||
if not extra_info:
|
self,
|
||||||
extra_info = {}
|
parser: Parser,
|
||||||
|
extra_info: Optional[Dict[str, Any]] = None,
|
||||||
|
prog: Optional[str] = None,
|
||||||
|
) -> None:
|
||||||
self._parser = parser
|
self._parser = parser
|
||||||
argparse.ArgumentParser.__init__(
|
argparse.ArgumentParser.__init__(
|
||||||
self,
|
self,
|
||||||
|
@ -344,34 +385,42 @@ class MyOptionParser(argparse.ArgumentParser):
|
||||||
)
|
)
|
||||||
# extra_info is a dict of (param -> value) to display if there's
|
# extra_info is a dict of (param -> value) to display if there's
|
||||||
# an usage error to provide more contextual information to the user
|
# 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."""
|
"""Transform argparse error message into UsageError."""
|
||||||
msg = "{}: error: {}".format(self.prog, message)
|
msg = "{}: error: {}".format(self.prog, message)
|
||||||
|
|
||||||
if hasattr(self._parser, "_config_source_hint"):
|
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)
|
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"""
|
"""allow splitting of positional arguments"""
|
||||||
args, argv = self.parse_known_args(args, namespace)
|
parsed, unrecognized = self.parse_known_args(args, namespace)
|
||||||
if argv:
|
if unrecognized:
|
||||||
for arg in argv:
|
for arg in unrecognized:
|
||||||
if arg and arg[0] == "-":
|
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()):
|
for k, v in sorted(self.extra_info.items()):
|
||||||
lines.append(" {}: {}".format(k, v))
|
lines.append(" {}: {}".format(k, v))
|
||||||
self.error("\n".join(lines))
|
self.error("\n".join(lines))
|
||||||
getattr(args, FILE_OR_DIR).extend(argv)
|
getattr(parsed, FILE_OR_DIR).extend(unrecognized)
|
||||||
return args
|
return parsed
|
||||||
|
|
||||||
if sys.version_info[:2] < (3, 9): # pragma: no cover
|
if sys.version_info[:2] < (3, 9): # pragma: no cover
|
||||||
# Backport of https://github.com/python/cpython/pull/14316 so we can
|
# Backport of https://github.com/python/cpython/pull/14316 so we can
|
||||||
# disable long --argument abbreviations without breaking short flags.
|
# 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:
|
if not arg_string:
|
||||||
return None
|
return None
|
||||||
if not arg_string[0] in self.prefix_chars:
|
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)
|
options = ", ".join(option for _, option, _ in option_tuples)
|
||||||
self.error(msg % {"option": arg_string, "matches": options})
|
self.error(msg % {"option": arg_string, "matches": options})
|
||||||
elif len(option_tuples) == 1:
|
elif len(option_tuples) == 1:
|
||||||
option_tuple, = option_tuples
|
(option_tuple,) = option_tuples
|
||||||
return option_tuple
|
return option_tuple
|
||||||
if self._negative_number_matcher.match(arg_string):
|
if self._negative_number_matcher.match(arg_string):
|
||||||
if not self._has_negative_number_optionals:
|
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
|
"""shorten help for long options that differ only in extra hyphens
|
||||||
|
|
||||||
- collapse **long** options that are the same except for 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
|
- 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
|
- 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."""
|
"""Use more accurate terminal width via pylib."""
|
||||||
if "width" not in kwargs:
|
if "width" not in kwargs:
|
||||||
kwargs["width"] = py.io.get_terminal_width()
|
kwargs["width"] = py.io.get_terminal_width()
|
||||||
super().__init__(*args, **kwargs)
|
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)
|
orgstr = argparse.HelpFormatter._format_action_invocation(self, action)
|
||||||
if orgstr and orgstr[0] != "-": # only optional arguments
|
if orgstr and orgstr[0] != "-": # only optional arguments
|
||||||
return orgstr
|
return orgstr
|
||||||
res = getattr(action, "_formatted_action_invocation", None)
|
res = getattr(
|
||||||
|
action, "_formatted_action_invocation", None
|
||||||
|
) # type: Optional[str]
|
||||||
if res:
|
if res:
|
||||||
return res
|
return res
|
||||||
options = orgstr.split(", ")
|
options = orgstr.split(", ")
|
||||||
if len(options) == 2 and (len(options[0]) == 2 or len(options[1]) == 2):
|
if len(options) == 2 and (len(options[0]) == 2 or len(options[1]) == 2):
|
||||||
# a shortcut for '-h, --help' or '--abc', '-a'
|
# a shortcut for '-h, --help' or '--abc', '-a'
|
||||||
action._formatted_action_invocation = orgstr
|
action._formatted_action_invocation = orgstr # type: ignore
|
||||||
return orgstr
|
return orgstr
|
||||||
return_list = []
|
return_list = []
|
||||||
option_map = getattr(action, "map_long_option", {})
|
|
||||||
if option_map is None:
|
|
||||||
option_map = {}
|
|
||||||
short_long = {} # type: Dict[str, str]
|
short_long = {} # type: Dict[str, str]
|
||||||
for option in options:
|
for option in options:
|
||||||
if len(option) == 2 or option[2] == " ":
|
if len(option) == 2 or option[2] == " ":
|
||||||
continue
|
continue
|
||||||
if not option.startswith("--"):
|
if not option.startswith("--"):
|
||||||
raise ArgumentError(
|
raise ArgumentError(
|
||||||
'long optional argument without "--": [%s]' % (option), self
|
'long optional argument without "--": [%s]' % (option), option
|
||||||
)
|
)
|
||||||
xxoption = option[2:]
|
xxoption = option[2:]
|
||||||
if xxoption.split()[0] not in option_map:
|
shortened = xxoption.replace("-", "")
|
||||||
shortened = xxoption.replace("-", "")
|
if shortened not in short_long or len(short_long[shortened]) < len(
|
||||||
if shortened not in short_long or len(short_long[shortened]) < len(
|
xxoption
|
||||||
xxoption
|
):
|
||||||
):
|
short_long[shortened] = xxoption
|
||||||
short_long[shortened] = xxoption
|
|
||||||
# now short_long has been filled out to the longest with dashes
|
# now short_long has been filled out to the longest with dashes
|
||||||
# **and** we keep the right option ordering from add_argument
|
# **and** we keep the right option ordering from add_argument
|
||||||
for option in options:
|
for option in options:
|
||||||
|
@ -459,5 +504,6 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter):
|
||||||
return_list.append(option)
|
return_list.append(option)
|
||||||
if option[2:] == short_long.get(option.replace("-", "")):
|
if option[2:] == short_long.get(option.replace("-", "")):
|
||||||
return_list.append(option.replace(" ", "=", 1))
|
return_list.append(option.replace(" ", "=", 1))
|
||||||
action._formatted_action_invocation = ", ".join(return_list)
|
formatted_action_invocation = ", ".join(return_list)
|
||||||
return action._formatted_action_invocation
|
action._formatted_action_invocation = formatted_action_invocation # type: ignore
|
||||||
|
return formatted_action_invocation
|
||||||
|
|
|
@ -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.
|
in case of warnings which need to format their messages.
|
||||||
"""
|
"""
|
||||||
from _pytest.warning_types import PytestDeprecationWarning
|
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
|
# set of plugins which have been integrated into the core; we use this list to ignore
|
||||||
# them during registration to avoid conflicts
|
# them during registration to avoid conflicts
|
||||||
|
@ -26,7 +27,7 @@ FUNCARGNAMES = PytestDeprecationWarning(
|
||||||
|
|
||||||
|
|
||||||
RESULT_LOG = 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."
|
"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 "
|
"Passing arguments to pytest.fixture() as positional arguments is deprecated - pass them "
|
||||||
"as a keyword argument instead."
|
"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."
|
||||||
|
)
|
||||||
|
|
|
@ -108,9 +108,9 @@ def pytest_collect_file(path, parent):
|
||||||
config = parent.config
|
config = parent.config
|
||||||
if path.ext == ".py":
|
if path.ext == ".py":
|
||||||
if config.option.doctestmodules and not _is_setup_py(config, path, parent):
|
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):
|
elif _is_doctest(config, path, parent):
|
||||||
return DoctestTextfile(path, parent)
|
return DoctestTextfile.from_parent(parent, fspath=path)
|
||||||
|
|
||||||
|
|
||||||
def _is_setup_py(config, path, parent):
|
def _is_setup_py(config, path, parent):
|
||||||
|
@ -215,6 +215,10 @@ class DoctestItem(pytest.Item):
|
||||||
self.obj = None
|
self.obj = None
|
||||||
self.fixture_request = 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):
|
def setup(self):
|
||||||
if self.dtest is not None:
|
if self.dtest is not None:
|
||||||
self.fixture_request = _setup_fixtures(self)
|
self.fixture_request = _setup_fixtures(self)
|
||||||
|
@ -370,7 +374,9 @@ class DoctestTextfile(pytest.Module):
|
||||||
parser = doctest.DocTestParser()
|
parser = doctest.DocTestParser()
|
||||||
test = parser.get_doctest(text, globs, name, filename, 0)
|
test = parser.get_doctest(text, globs, name, filename, 0)
|
||||||
if test.examples:
|
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):
|
def _check_all_skipped(test):
|
||||||
|
@ -467,7 +473,9 @@ class DoctestModule(pytest.Module):
|
||||||
|
|
||||||
for test in finder.find(module, module.__name__):
|
for test in finder.find(module, module.__name__):
|
||||||
if test.examples: # skip empty doctests
|
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):
|
def _setup_fixtures(doctest_item):
|
||||||
|
|
|
@ -18,7 +18,6 @@ from _pytest._code.code import FormattedExcinfo
|
||||||
from _pytest._code.code import TerminalRepr
|
from _pytest._code.code import TerminalRepr
|
||||||
from _pytest.compat import _format_args
|
from _pytest.compat import _format_args
|
||||||
from _pytest.compat import _PytestWrapper
|
from _pytest.compat import _PytestWrapper
|
||||||
from _pytest.compat import FuncargnamesCompatAttr
|
|
||||||
from _pytest.compat import get_real_func
|
from _pytest.compat import get_real_func
|
||||||
from _pytest.compat import get_real_method
|
from _pytest.compat import get_real_method
|
||||||
from _pytest.compat import getfslineno
|
from _pytest.compat import getfslineno
|
||||||
|
@ -29,6 +28,7 @@ from _pytest.compat import is_generator
|
||||||
from _pytest.compat import NOTSET
|
from _pytest.compat import NOTSET
|
||||||
from _pytest.compat import safe_getattr
|
from _pytest.compat import safe_getattr
|
||||||
from _pytest.deprecated import FIXTURE_POSITIONAL_ARGUMENTS
|
from _pytest.deprecated import FIXTURE_POSITIONAL_ARGUMENTS
|
||||||
|
from _pytest.deprecated import FUNCARGNAMES
|
||||||
from _pytest.outcomes import fail
|
from _pytest.outcomes import fail
|
||||||
from _pytest.outcomes import TEST_OUTCOME
|
from _pytest.outcomes import TEST_OUTCOME
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@ if False: # TYPE_CHECKING
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
from _pytest import nodes
|
from _pytest import nodes
|
||||||
|
from _pytest.main import Session
|
||||||
|
|
||||||
|
|
||||||
@attr.s(frozen=True)
|
@attr.s(frozen=True)
|
||||||
|
@ -44,7 +45,7 @@ class PseudoFixtureDef:
|
||||||
scope = attr.ib()
|
scope = attr.ib()
|
||||||
|
|
||||||
|
|
||||||
def pytest_sessionstart(session):
|
def pytest_sessionstart(session: "Session"):
|
||||||
import _pytest.python
|
import _pytest.python
|
||||||
import _pytest.nodes
|
import _pytest.nodes
|
||||||
|
|
||||||
|
@ -336,7 +337,7 @@ class FuncFixtureInfo:
|
||||||
self.names_closure[:] = sorted(closure, key=self.names_closure.index)
|
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 for a fixture from a test or fixture function.
|
||||||
|
|
||||||
A request object gives access to the requesting test context
|
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))
|
result.extend(set(self._fixture_defs).difference(result))
|
||||||
return 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
|
@property
|
||||||
def node(self):
|
def node(self):
|
||||||
""" underlying collection node (depends on current request scope)"""
|
""" underlying collection node (depends on current request scope)"""
|
||||||
|
@ -504,13 +511,11 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||||
values.append(fixturedef)
|
values.append(fixturedef)
|
||||||
current = current._parent_request
|
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
|
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
|
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.
|
will be stored into the FixtureDef object itself.
|
||||||
|
|
||||||
:param FixtureDef fixturedef:
|
|
||||||
"""
|
"""
|
||||||
# prepare a subrequest object before calling fixture function
|
# prepare a subrequest object before calling fixture function
|
||||||
# (latter managed by fixturedef)
|
# (latter managed by fixturedef)
|
||||||
|
@ -538,9 +543,8 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||||
if has_params:
|
if has_params:
|
||||||
frame = inspect.stack()[3]
|
frame = inspect.stack()[3]
|
||||||
frameinfo = inspect.getframeinfo(frame[0])
|
frameinfo = inspect.getframeinfo(frame[0])
|
||||||
source_path = frameinfo.filename
|
source_path = py.path.local(frameinfo.filename)
|
||||||
source_lineno = frameinfo.lineno
|
source_lineno = frameinfo.lineno
|
||||||
source_path = py.path.local(source_path)
|
|
||||||
if source_path.relto(funcitem.config.rootdir):
|
if source_path.relto(funcitem.config.rootdir):
|
||||||
source_path = source_path.relto(funcitem.config.rootdir)
|
source_path = source_path.relto(funcitem.config.rootdir)
|
||||||
msg = (
|
msg = (
|
||||||
|
|
|
@ -45,10 +45,10 @@ def pytest_addoption(parser, pluginmanager):
|
||||||
files situated at the tests root directory due to how pytest
|
files situated at the tests root directory due to how pytest
|
||||||
:ref:`discovers plugins during startup <pluginorder>`.
|
:ref:`discovers plugins during startup <pluginorder>`.
|
||||||
|
|
||||||
:arg _pytest.config.Parser parser: To add command line options, call
|
:arg _pytest.config.argparsing.Parser parser: To add command line options, call
|
||||||
:py:func:`parser.addoption(...) <_pytest.config.Parser.addoption>`.
|
:py:func:`parser.addoption(...) <_pytest.config.argparsing.Parser.addoption>`.
|
||||||
To add ini-file values call :py:func:`parser.addini(...)
|
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,
|
:arg _pytest.config.PytestPluginManager pluginmanager: pytest plugin manager,
|
||||||
which can be used to install :py:func:`hookspec`'s or :py:func:`hookimpl`'s
|
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 _pytest.config.Config early_config: pytest config object
|
||||||
:param list[str] args: list of arguments passed on the command line
|
: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)
|
@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.
|
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.
|
in a future release.
|
||||||
|
|
||||||
The item being executed if ``when`` is ``"runtest"``, otherwise ``None``.
|
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.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,10 @@ from datetime import datetime
|
||||||
import py
|
import py
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from _pytest import deprecated
|
||||||
from _pytest import nodes
|
from _pytest import nodes
|
||||||
from _pytest.config import filename_arg
|
from _pytest.config import filename_arg
|
||||||
|
from _pytest.warnings import _issue_warning_captured
|
||||||
|
|
||||||
|
|
||||||
class Junit(py.xml.Namespace):
|
class Junit(py.xml.Namespace):
|
||||||
|
@ -421,9 +423,7 @@ def pytest_addoption(parser):
|
||||||
default="total",
|
default="total",
|
||||||
) # choices=['total', 'call'])
|
) # choices=['total', 'call'])
|
||||||
parser.addini(
|
parser.addini(
|
||||||
"junit_family",
|
"junit_family", "Emit XML for schema: one of legacy|xunit1|xunit2", default=None
|
||||||
"Emit XML for schema: one of legacy|xunit1|xunit2",
|
|
||||||
default="xunit1",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -431,13 +431,17 @@ def pytest_configure(config):
|
||||||
xmlpath = config.option.xmlpath
|
xmlpath = config.option.xmlpath
|
||||||
# prevent opening xmllog on slave nodes (xdist)
|
# prevent opening xmllog on slave nodes (xdist)
|
||||||
if xmlpath and not hasattr(config, "slaveinput"):
|
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(
|
config._xml = LogXML(
|
||||||
xmlpath,
|
xmlpath,
|
||||||
config.option.junitprefix,
|
config.option.junitprefix,
|
||||||
config.getini("junit_suite_name"),
|
config.getini("junit_suite_name"),
|
||||||
config.getini("junit_logging"),
|
config.getini("junit_logging"),
|
||||||
config.getini("junit_duration_report"),
|
config.getini("junit_duration_report"),
|
||||||
config.getini("junit_family"),
|
junit_family,
|
||||||
config.getini("junit_log_passing_tests"),
|
config.getini("junit_log_passing_tests"),
|
||||||
)
|
)
|
||||||
config.pluginmanager.register(config._xml)
|
config.pluginmanager.register(config._xml)
|
||||||
|
|
|
@ -15,6 +15,7 @@ from _pytest import nodes
|
||||||
from _pytest.config import directory_arg
|
from _pytest.config import directory_arg
|
||||||
from _pytest.config import hookimpl
|
from _pytest.config import hookimpl
|
||||||
from _pytest.config import UsageError
|
from _pytest.config import UsageError
|
||||||
|
from _pytest.fixtures import FixtureManager
|
||||||
from _pytest.outcomes import exit
|
from _pytest.outcomes import exit
|
||||||
from _pytest.runner import collect_one_node
|
from _pytest.runner import collect_one_node
|
||||||
from _pytest.runner import SetupState
|
from _pytest.runner import SetupState
|
||||||
|
@ -184,7 +185,7 @@ def pytest_addoption(parser):
|
||||||
|
|
||||||
def wrap_session(config, doit):
|
def wrap_session(config, doit):
|
||||||
"""Skeleton command line program"""
|
"""Skeleton command line program"""
|
||||||
session = Session(config)
|
session = Session.from_config(config)
|
||||||
session.exitstatus = ExitCode.OK
|
session.exitstatus = ExitCode.OK
|
||||||
initstate = 0
|
initstate = 0
|
||||||
try:
|
try:
|
||||||
|
@ -372,6 +373,7 @@ class Session(nodes.FSCollector):
|
||||||
Interrupted = Interrupted
|
Interrupted = Interrupted
|
||||||
Failed = Failed
|
Failed = Failed
|
||||||
_setupstate = None # type: SetupState
|
_setupstate = None # type: SetupState
|
||||||
|
_fixturemanager = None # type: FixtureManager
|
||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
nodes.FSCollector.__init__(
|
nodes.FSCollector.__init__(
|
||||||
|
@ -395,6 +397,10 @@ class Session(nodes.FSCollector):
|
||||||
|
|
||||||
self.config.pluginmanager.register(self, name="session")
|
self.config.pluginmanager.register(self, name="session")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_config(cls, config):
|
||||||
|
return cls._create(config)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<%s %s exitstatus=%r testsfailed=%d testscollected=%d>" % (
|
return "<%s %s exitstatus=%r testsfailed=%d testscollected=%d>" % (
|
||||||
self.__class__.__name__,
|
self.__class__.__name__,
|
||||||
|
|
|
@ -2,6 +2,8 @@ import inspect
|
||||||
import warnings
|
import warnings
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from collections.abc import MutableMapping
|
from collections.abc import MutableMapping
|
||||||
|
from typing import List
|
||||||
|
from typing import Optional
|
||||||
from typing import Set
|
from typing import Set
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
|
@ -144,7 +146,15 @@ class Mark:
|
||||||
#: keyword arguments of the mark decorator
|
#: keyword arguments of the mark decorator
|
||||||
kwargs = attr.ib() # Dict[str, object]
|
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
|
:param other: the mark to combine with
|
||||||
:type other: Mark
|
:type other: Mark
|
||||||
|
@ -153,8 +163,20 @@ class Mark:
|
||||||
combines by appending args and merging the mappings
|
combines by appending args and merging the mappings
|
||||||
"""
|
"""
|
||||||
assert self.name == other.name
|
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(
|
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),
|
"{!r} not found in `markers` configuration option".format(name),
|
||||||
pytrace=False,
|
pytrace=False,
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
warnings.warn(
|
# Raise a specific error for common misspellings of "parametrize".
|
||||||
"Unknown pytest.mark.%s - is this a typo? You can register "
|
if name in ["parameterize", "parametrise", "parameterise"]:
|
||||||
"custom marks to avoid this warning - for details, see "
|
__tracebackhide__ = True
|
||||||
"https://docs.pytest.org/en/latest/mark.html" % name,
|
fail("Unknown '{}' mark, did you mean 'parametrize'?".format(name))
|
||||||
PytestUnknownMarkWarning,
|
|
||||||
)
|
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, (), {}))
|
return MarkDecorator(Mark(name, (), {}))
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,10 @@ import _pytest._code
|
||||||
from _pytest._code.code import ExceptionChainRepr
|
from _pytest._code.code import ExceptionChainRepr
|
||||||
from _pytest._code.code import ExceptionInfo
|
from _pytest._code.code import ExceptionInfo
|
||||||
from _pytest._code.code import ReprExceptionInfo
|
from _pytest._code.code import ReprExceptionInfo
|
||||||
|
from _pytest.compat import cached_property
|
||||||
from _pytest.compat import getfslineno
|
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 FixtureDef
|
||||||
from _pytest.fixtures import FixtureLookupError
|
from _pytest.fixtures import FixtureLookupError
|
||||||
from _pytest.fixtures import FixtureLookupErrorRepr
|
from _pytest.fixtures import FixtureLookupErrorRepr
|
||||||
|
@ -71,18 +74,27 @@ def ischildnode(baseid, nodeid):
|
||||||
return node_parts[: len(base_parts)] == base_parts
|
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.
|
""" base class for Collector and Item the test collection tree.
|
||||||
Collector subclasses have children, Items are terminal nodes."""
|
Collector subclasses have children, Items are terminal nodes."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
name,
|
name,
|
||||||
parent=None,
|
parent: Optional["Node"] = None,
|
||||||
config=None,
|
config: Optional[Config] = None,
|
||||||
session: Optional["Session"] = None,
|
session: Optional["Session"] = None,
|
||||||
fspath=None,
|
fspath: Optional[py.path.local] = None,
|
||||||
nodeid=None,
|
nodeid: Optional[str] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
#: a unique name within the scope of the parent node
|
#: a unique name within the scope of the parent node
|
||||||
self.name = name
|
self.name = name
|
||||||
|
@ -91,14 +103,20 @@ class Node:
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
|
|
||||||
#: the pytest config object
|
#: 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
|
#: the session this node is part of
|
||||||
if session is None:
|
if session:
|
||||||
assert parent.session is not None
|
|
||||||
self.session = parent.session
|
|
||||||
else:
|
|
||||||
self.session = 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)
|
#: filesystem path where this node was collected from (can be None)
|
||||||
self.fspath = fspath or getattr(parent, "fspath", None)
|
self.fspath = fspath or getattr(parent, "fspath", None)
|
||||||
|
@ -119,10 +137,16 @@ class Node:
|
||||||
assert "::()" not in nodeid
|
assert "::()" not in nodeid
|
||||||
self._nodeid = nodeid
|
self._nodeid = nodeid
|
||||||
else:
|
else:
|
||||||
|
if not self.parent:
|
||||||
|
raise TypeError("nodeid or parent must be provided")
|
||||||
self._nodeid = self.parent.nodeid
|
self._nodeid = self.parent.nodeid
|
||||||
if self.name != "()":
|
if self.name != "()":
|
||||||
self._nodeid += "::" + self.name
|
self._nodeid += "::" + self.name
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_parent(cls, parent, *, name):
|
||||||
|
return cls._create(parent=parent, name=name)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ihook(self):
|
def ihook(self):
|
||||||
""" fspath sensitive hook proxy used to call pytest hooks"""
|
""" fspath sensitive hook proxy used to call pytest hooks"""
|
||||||
|
@ -182,7 +206,7 @@ class Node:
|
||||||
""" return list of all parent collectors up to self,
|
""" return list of all parent collectors up to self,
|
||||||
starting from root of collection tree. """
|
starting from root of collection tree. """
|
||||||
chain = []
|
chain = []
|
||||||
item = self
|
item = self # type: Optional[Node]
|
||||||
while item is not None:
|
while item is not None:
|
||||||
chain.append(item)
|
chain.append(item)
|
||||||
item = item.parent
|
item = item.parent
|
||||||
|
@ -263,7 +287,7 @@ class Node:
|
||||||
def getparent(self, cls):
|
def getparent(self, cls):
|
||||||
""" get the next parent node (including ourself)
|
""" get the next parent node (including ourself)
|
||||||
which is an instance of the given class"""
|
which is an instance of the given class"""
|
||||||
current = self
|
current = self # type: Optional[Node]
|
||||||
while current and not isinstance(current, cls):
|
while current and not isinstance(current, cls):
|
||||||
current = current.parent
|
current = current.parent
|
||||||
return current
|
return current
|
||||||
|
@ -355,12 +379,14 @@ class Collector(Node):
|
||||||
|
|
||||||
def repr_failure(self, excinfo):
|
def repr_failure(self, excinfo):
|
||||||
""" represent a collection failure. """
|
""" represent a collection failure. """
|
||||||
if excinfo.errisinstance(self.CollectError):
|
if excinfo.errisinstance(self.CollectError) and not self.config.getoption(
|
||||||
|
"fulltrace", False
|
||||||
|
):
|
||||||
exc = excinfo.value
|
exc = excinfo.value
|
||||||
return str(exc.args[0])
|
return str(exc.args[0])
|
||||||
|
|
||||||
# Respect explicit tbstyle option, but default to "short"
|
# 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")
|
tbstyle = self.config.getoption("tbstyle", "auto")
|
||||||
if tbstyle == "auto":
|
if tbstyle == "auto":
|
||||||
tbstyle = "short"
|
tbstyle = "short"
|
||||||
|
@ -406,6 +432,10 @@ class FSCollector(Collector):
|
||||||
|
|
||||||
super().__init__(name, parent, config, session, nodeid=nodeid, fspath=fspath)
|
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):
|
class File(FSCollector):
|
||||||
""" base class for collecting tests from a file. """
|
""" base class for collecting tests from a file. """
|
||||||
|
@ -448,17 +478,9 @@ class Item(Node):
|
||||||
def reportinfo(self) -> Tuple[str, Optional[int], str]:
|
def reportinfo(self) -> Tuple[str, Optional[int], str]:
|
||||||
return self.fspath, None, ""
|
return self.fspath, None, ""
|
||||||
|
|
||||||
@property
|
@cached_property
|
||||||
def location(self) -> Tuple[str, Optional[int], str]:
|
def location(self) -> Tuple[str, Optional[int], str]:
|
||||||
try:
|
location = self.reportinfo()
|
||||||
return self._location
|
fspath = self.session._node_location_to_relpath(location[0])
|
||||||
except AttributeError:
|
assert type(location[2]) is str
|
||||||
location = self.reportinfo()
|
return (fspath, location[1], location[2])
|
||||||
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
|
|
||||||
|
|
|
@ -312,7 +312,7 @@ class HookRecorder:
|
||||||
return self.getfailures("pytest_collectreport")
|
return self.getfailures("pytest_collectreport")
|
||||||
|
|
||||||
def listoutcomes(
|
def listoutcomes(
|
||||||
self
|
self,
|
||||||
) -> Tuple[List[TestReport], List[TestReport], List[TestReport]]:
|
) -> Tuple[List[TestReport], List[TestReport], List[TestReport]]:
|
||||||
passed = []
|
passed = []
|
||||||
skipped = []
|
skipped = []
|
||||||
|
@ -332,10 +332,17 @@ class HookRecorder:
|
||||||
return [len(x) for x in self.listoutcomes()]
|
return [len(x) for x in self.listoutcomes()]
|
||||||
|
|
||||||
def assertoutcome(self, passed: int = 0, skipped: int = 0, failed: int = 0) -> None:
|
def assertoutcome(self, passed: int = 0, skipped: int = 0, failed: int = 0) -> None:
|
||||||
realpassed, realskipped, realfailed = self.listoutcomes()
|
__tracebackhide__ = True
|
||||||
assert passed == len(realpassed)
|
|
||||||
assert skipped == len(realskipped)
|
outcomes = self.listoutcomes()
|
||||||
assert failed == len(realfailed)
|
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:
|
def clear(self) -> None:
|
||||||
self.calls[:] = []
|
self.calls[:] = []
|
||||||
|
@ -441,8 +448,9 @@ class RunResult:
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Assert that the specified outcomes appear with the respective
|
"""Assert that the specified outcomes appear with the respective
|
||||||
numbers (0 means it didn't occur) in the text output from a test run.
|
numbers (0 means it didn't occur) in the text output from a test run.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
__tracebackhide__ = True
|
||||||
|
|
||||||
d = self.parseoutcomes()
|
d = self.parseoutcomes()
|
||||||
obtained = {
|
obtained = {
|
||||||
"passed": d.get("passed", 0),
|
"passed": d.get("passed", 0),
|
||||||
|
@ -536,10 +544,12 @@ class Testdir:
|
||||||
mp.delenv("TOX_ENV_DIR", raising=False)
|
mp.delenv("TOX_ENV_DIR", raising=False)
|
||||||
# Discard outer pytest options.
|
# Discard outer pytest options.
|
||||||
mp.delenv("PYTEST_ADDOPTS", raising=False)
|
mp.delenv("PYTEST_ADDOPTS", raising=False)
|
||||||
|
# Ensure no user config is used.
|
||||||
# Environment (updates) for inner runs.
|
|
||||||
tmphome = str(self.tmpdir)
|
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):
|
def __repr__(self):
|
||||||
return "<Testdir {!r}>".format(self.tmpdir)
|
return "<Testdir {!r}>".format(self.tmpdir)
|
||||||
|
@ -735,7 +745,7 @@ class Testdir:
|
||||||
:param arg: a :py:class:`py.path.local` instance of the file
|
: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)
|
assert "::" not in str(arg)
|
||||||
p = py.path.local(arg)
|
p = py.path.local(arg)
|
||||||
config.hook.pytest_sessionstart(session=session)
|
config.hook.pytest_sessionstart(session=session)
|
||||||
|
@ -753,7 +763,7 @@ class Testdir:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
config = self.parseconfigure(path)
|
config = self.parseconfigure(path)
|
||||||
session = Session(config)
|
session = Session.from_config(config)
|
||||||
x = session.fspath.bestrelpath(path)
|
x = session.fspath.bestrelpath(path)
|
||||||
config.hook.pytest_sessionstart(session=session)
|
config.hook.pytest_sessionstart(session=session)
|
||||||
res = session.perform_collect([x], genitems=False)[0]
|
res = session.perform_collect([x], genitems=False)[0]
|
||||||
|
@ -845,12 +855,6 @@ class Testdir:
|
||||||
plugins = list(plugins)
|
plugins = list(plugins)
|
||||||
finalizers = []
|
finalizers = []
|
||||||
try:
|
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
|
# Any sys.module or sys.path changes done while running pytest
|
||||||
# inline should be reverted after the test run completes to avoid
|
# inline should be reverted after the test run completes to avoid
|
||||||
# clashing with later inline tests run within the same pytest test,
|
# clashing with later inline tests run within the same pytest test,
|
||||||
|
@ -1083,7 +1087,6 @@ class Testdir:
|
||||||
env["PYTHONPATH"] = os.pathsep.join(
|
env["PYTHONPATH"] = os.pathsep.join(
|
||||||
filter(None, [os.getcwd(), env.get("PYTHONPATH", "")])
|
filter(None, [os.getcwd(), env.get("PYTHONPATH", "")])
|
||||||
)
|
)
|
||||||
env.update(self._env_run_update)
|
|
||||||
kw["env"] = env
|
kw["env"] = env
|
||||||
|
|
||||||
if stdin is Testdir.CLOSE_STDIN:
|
if stdin is Testdir.CLOSE_STDIN:
|
||||||
|
@ -1253,11 +1256,7 @@ class Testdir:
|
||||||
pytest.skip("pexpect.spawn not available")
|
pytest.skip("pexpect.spawn not available")
|
||||||
logfile = self.tmpdir.join("spawn.out").open("wb")
|
logfile = self.tmpdir.join("spawn.out").open("wb")
|
||||||
|
|
||||||
# Do not load user config.
|
child = pexpect.spawn(cmd, logfile=logfile)
|
||||||
env = os.environ.copy()
|
|
||||||
env.update(self._env_run_update)
|
|
||||||
|
|
||||||
child = pexpect.spawn(cmd, logfile=logfile, env=env)
|
|
||||||
self.request.addfinalizer(logfile.close)
|
self.request.addfinalizer(logfile.close)
|
||||||
child.timeout = expect_timeout
|
child.timeout = expect_timeout
|
||||||
return child
|
return child
|
||||||
|
@ -1430,8 +1429,10 @@ class LineMatcher:
|
||||||
self._log("{:>{width}}".format("and:", width=wnick), repr(nextline))
|
self._log("{:>{width}}".format("and:", width=wnick), repr(nextline))
|
||||||
extralines.append(nextline)
|
extralines.append(nextline)
|
||||||
else:
|
else:
|
||||||
self._log("remains unmatched: {!r}".format(line))
|
msg = "remains unmatched: {!r}".format(line)
|
||||||
pytest.fail(self._log_text.lstrip())
|
self._log(msg)
|
||||||
|
self._fail(msg)
|
||||||
|
self._log_output = []
|
||||||
|
|
||||||
def no_fnmatch_line(self, pat):
|
def no_fnmatch_line(self, pat):
|
||||||
"""Ensure captured lines do not match the given pattern, using ``fnmatch.fnmatch``.
|
"""Ensure captured lines do not match the given pattern, using ``fnmatch.fnmatch``.
|
||||||
|
@ -1457,18 +1458,21 @@ class LineMatcher:
|
||||||
__tracebackhide__ = True
|
__tracebackhide__ = True
|
||||||
nomatch_printed = False
|
nomatch_printed = False
|
||||||
wnick = len(match_nickname) + 1
|
wnick = len(match_nickname) + 1
|
||||||
try:
|
for line in self.lines:
|
||||||
for line in self.lines:
|
if match_func(line, pat):
|
||||||
if match_func(line, pat):
|
msg = "{}: {!r}".format(match_nickname, pat)
|
||||||
self._log("%s:" % match_nickname, repr(pat))
|
self._log(msg)
|
||||||
self._log("{:>{width}}".format("with:", width=wnick), repr(line))
|
self._log("{:>{width}}".format("with:", width=wnick), repr(line))
|
||||||
pytest.fail(self._log_text.lstrip())
|
self._fail(msg)
|
||||||
else:
|
else:
|
||||||
if not nomatch_printed:
|
if not nomatch_printed:
|
||||||
self._log(
|
self._log("{:>{width}}".format("nomatch:", width=wnick), repr(pat))
|
||||||
"{:>{width}}".format("nomatch:", width=wnick), repr(pat)
|
nomatch_printed = True
|
||||||
)
|
self._log("{:>{width}}".format("and:", width=wnick), repr(line))
|
||||||
nomatch_printed = True
|
self._log_output = []
|
||||||
self._log("{:>{width}}".format("and:", width=wnick), repr(line))
|
|
||||||
finally:
|
def _fail(self, msg):
|
||||||
self._log_output = []
|
__tracebackhide__ = True
|
||||||
|
log_text = self._log_text
|
||||||
|
self._log_output = []
|
||||||
|
pytest.fail(log_text)
|
||||||
|
|
|
@ -9,6 +9,8 @@ from collections import Counter
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
|
from typing import List
|
||||||
|
from typing import Optional
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
|
||||||
import py
|
import py
|
||||||
|
@ -31,9 +33,11 @@ from _pytest.compat import safe_getattr
|
||||||
from _pytest.compat import safe_isclass
|
from _pytest.compat import safe_isclass
|
||||||
from _pytest.compat import STRING_TYPES
|
from _pytest.compat import STRING_TYPES
|
||||||
from _pytest.config import hookimpl
|
from _pytest.config import hookimpl
|
||||||
|
from _pytest.deprecated import FUNCARGNAMES
|
||||||
from _pytest.main import FSHookProxy
|
from _pytest.main import FSHookProxy
|
||||||
from _pytest.mark import MARK_GEN
|
from _pytest.mark import MARK_GEN
|
||||||
from _pytest.mark.structures import get_unpacked_marks
|
from _pytest.mark.structures import get_unpacked_marks
|
||||||
|
from _pytest.mark.structures import Mark
|
||||||
from _pytest.mark.structures import normalize_mark_list
|
from _pytest.mark.structures import normalize_mark_list
|
||||||
from _pytest.outcomes import fail
|
from _pytest.outcomes import fail
|
||||||
from _pytest.outcomes import skip
|
from _pytest.outcomes import skip
|
||||||
|
@ -119,15 +123,8 @@ def pytest_cmdline_main(config):
|
||||||
|
|
||||||
|
|
||||||
def pytest_generate_tests(metafunc):
|
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"):
|
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):
|
def pytest_configure(config):
|
||||||
|
@ -193,8 +190,8 @@ def path_matches_patterns(path, patterns):
|
||||||
|
|
||||||
def pytest_pycollect_makemodule(path, parent):
|
def pytest_pycollect_makemodule(path, parent):
|
||||||
if path.basename == "__init__.py":
|
if path.basename == "__init__.py":
|
||||||
return Package(path, parent)
|
return Package.from_parent(parent, fspath=path)
|
||||||
return Module(path, parent)
|
return Module.from_parent(parent, fspath=path)
|
||||||
|
|
||||||
|
|
||||||
@hookimpl(hookwrapper=True)
|
@hookimpl(hookwrapper=True)
|
||||||
|
@ -206,7 +203,7 @@ def pytest_pycollect_makeitem(collector, name, obj):
|
||||||
# nothing was collected elsewhere, let's do it here
|
# nothing was collected elsewhere, let's do it here
|
||||||
if safe_isclass(obj):
|
if safe_isclass(obj):
|
||||||
if collector.istestclass(obj, name):
|
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):
|
elif collector.istestfunction(obj, name):
|
||||||
# mock seems to store unbound methods (issue473), normalize it
|
# mock seems to store unbound methods (issue473), normalize it
|
||||||
obj = getattr(obj, "__func__", obj)
|
obj = getattr(obj, "__func__", obj)
|
||||||
|
@ -225,7 +222,7 @@ def pytest_pycollect_makeitem(collector, name, obj):
|
||||||
)
|
)
|
||||||
elif getattr(obj, "__test__", True):
|
elif getattr(obj, "__test__", True):
|
||||||
if is_generator(obj):
|
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(
|
reason = "yield tests were removed in pytest 4.0 - {name} will be ignored".format(
|
||||||
name=name
|
name=name
|
||||||
)
|
)
|
||||||
|
@ -236,10 +233,6 @@ def pytest_pycollect_makeitem(collector, name, obj):
|
||||||
outcome.force_result(res)
|
outcome.force_result(res)
|
||||||
|
|
||||||
|
|
||||||
def pytest_make_parametrize_id(config, val, argname=None):
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class PyobjContext:
|
class PyobjContext:
|
||||||
module = pyobj_property("Module")
|
module = pyobj_property("Module")
|
||||||
cls = pyobj_property("Class")
|
cls = pyobj_property("Class")
|
||||||
|
@ -286,8 +279,7 @@ class PyobjMixin(PyobjContext):
|
||||||
break
|
break
|
||||||
parts.append(name)
|
parts.append(name)
|
||||||
parts.reverse()
|
parts.reverse()
|
||||||
s = ".".join(parts)
|
return ".".join(parts)
|
||||||
return s.replace(".[", "[")
|
|
||||||
|
|
||||||
def reportinfo(self) -> Tuple[str, int, str]:
|
def reportinfo(self) -> Tuple[str, int, str]:
|
||||||
# XXX caching?
|
# XXX caching?
|
||||||
|
@ -389,7 +381,7 @@ class PyCollector(PyobjMixin, nodes.Collector):
|
||||||
cls = clscol and clscol.obj or None
|
cls = clscol and clscol.obj or None
|
||||||
fm = self.session._fixturemanager
|
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)
|
fixtureinfo = fm.getfixtureinfo(definition, funcobj, cls)
|
||||||
|
|
||||||
metafunc = Metafunc(
|
metafunc = Metafunc(
|
||||||
|
@ -404,7 +396,7 @@ class PyCollector(PyobjMixin, nodes.Collector):
|
||||||
self.ihook.pytest_generate_tests.call_extra(methods, dict(metafunc=metafunc))
|
self.ihook.pytest_generate_tests.call_extra(methods, dict(metafunc=metafunc))
|
||||||
|
|
||||||
if not metafunc._calls:
|
if not metafunc._calls:
|
||||||
yield Function(name, parent=self, fixtureinfo=fixtureinfo)
|
yield Function.from_parent(self, name=name, fixtureinfo=fixtureinfo)
|
||||||
else:
|
else:
|
||||||
# add funcargs() as fixturedefs to fixtureinfo.arg2fixturedefs
|
# add funcargs() as fixturedefs to fixtureinfo.arg2fixturedefs
|
||||||
fixtures.add_funcarg_pseudo_fixture_def(self, metafunc, fm)
|
fixtures.add_funcarg_pseudo_fixture_def(self, metafunc, fm)
|
||||||
|
@ -416,9 +408,9 @@ class PyCollector(PyobjMixin, nodes.Collector):
|
||||||
|
|
||||||
for callspec in metafunc._calls:
|
for callspec in metafunc._calls:
|
||||||
subname = "{}[{}]".format(name, callspec.id)
|
subname = "{}[{}]".format(name, callspec.id)
|
||||||
yield Function(
|
yield Function.from_parent(
|
||||||
|
self,
|
||||||
name=subname,
|
name=subname,
|
||||||
parent=self,
|
|
||||||
callspec=callspec,
|
callspec=callspec,
|
||||||
callobj=funcobj,
|
callobj=funcobj,
|
||||||
fixtureinfo=fixtureinfo,
|
fixtureinfo=fixtureinfo,
|
||||||
|
@ -634,7 +626,7 @@ class Package(Module):
|
||||||
if init_module.check(file=1) and path_matches_patterns(
|
if init_module.check(file=1) and path_matches_patterns(
|
||||||
init_module, self.config.getini("python_files")
|
init_module, self.config.getini("python_files")
|
||||||
):
|
):
|
||||||
yield Module(init_module, self)
|
yield Module.from_parent(self, fspath=init_module)
|
||||||
pkg_prefixes = set()
|
pkg_prefixes = set()
|
||||||
for path in this_path.visit(rec=self._recurse, bf=True, sort=True):
|
for path in this_path.visit(rec=self._recurse, bf=True, sort=True):
|
||||||
# We will visit our own __init__.py file, in which case we skip it.
|
# 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):
|
class Class(PyCollector):
|
||||||
""" Collector for test methods. """
|
""" Collector for test methods. """
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_parent(cls, parent, *, name, obj=None):
|
||||||
|
return cls._create(name=name, parent=parent)
|
||||||
|
|
||||||
def collect(self):
|
def collect(self):
|
||||||
if not safe_getattr(self.obj, "__test__", True):
|
if not safe_getattr(self.obj, "__test__", True):
|
||||||
return []
|
return []
|
||||||
|
@ -710,7 +706,7 @@ class Class(PyCollector):
|
||||||
self._inject_setup_class_fixture()
|
self._inject_setup_class_fixture()
|
||||||
self._inject_setup_method_fixture()
|
self._inject_setup_method_fixture()
|
||||||
|
|
||||||
return [Instance(name="()", parent=self)]
|
return [Instance.from_parent(self, name="()")]
|
||||||
|
|
||||||
def _inject_setup_class_fixture(self):
|
def _inject_setup_class_fixture(self):
|
||||||
"""Injects a hidden autouse, class scoped fixture into the collected class object
|
"""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))
|
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.
|
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
|
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.
|
test function is defined.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, definition, fixtureinfo, config, cls=None, module=None):
|
def __init__(
|
||||||
assert (
|
self,
|
||||||
isinstance(definition, FunctionDefinition)
|
definition: "FunctionDefinition",
|
||||||
or type(definition).__name__ == "DefinitionMock"
|
fixtureinfo,
|
||||||
)
|
config,
|
||||||
|
cls=None,
|
||||||
|
module=None,
|
||||||
|
) -> None:
|
||||||
self.definition = definition
|
self.definition = definition
|
||||||
|
|
||||||
#: access to the :class:`_pytest.config.Config` object for the test session
|
#: 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``.
|
#: class object where the test function is defined in or ``None``.
|
||||||
self.cls = cls
|
self.cls = cls
|
||||||
|
|
||||||
self._calls = []
|
self._calls = [] # type: List[CallSpec2]
|
||||||
self._ids = set()
|
|
||||||
self._arg2fixturedefs = fixtureinfo.name2fixturedefs
|
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
|
""" Add new invocations to the underlying test function using the list
|
||||||
of argvalues for the given argnames. Parametrization is performed
|
of argvalues for the given argnames. Parametrization is performed
|
||||||
during the collection phase. If you need to setup expensive resources
|
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
|
function so that it can perform more expensive setups during the
|
||||||
setup phase of a test rather than at collection time.
|
setup phase of a test rather than at collection time.
|
||||||
|
|
||||||
:arg ids: list of string ids, or a callable.
|
:arg ids: sequence of (or generator for) ids for ``argvalues``,
|
||||||
If strings, each is corresponding to the argvalues so that they are
|
or a callable to return part of the id for each argvalue.
|
||||||
part of the test id. If None is given as id of specific test, the
|
|
||||||
automatically generated id for that argument will be used.
|
With sequences (and generators like ``itertools.count()``) the
|
||||||
If callable, it should take one argument (a single argvalue) and return
|
returned ids should be of type ``string``, ``int``, ``float``,
|
||||||
a string or return None. If None, the automatically generated id for that
|
``bool``, or ``None``.
|
||||||
argument will be used.
|
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
|
If no ids are provided they will be generated automatically from
|
||||||
the argvalues.
|
the argvalues.
|
||||||
|
|
||||||
|
@ -966,6 +988,12 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
|
||||||
)
|
)
|
||||||
del argvalues
|
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:
|
if scope is None:
|
||||||
scope = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect)
|
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)
|
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)
|
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(
|
scopenum = scope2index(
|
||||||
scope, descr="parametrize() call in {}".format(self.function.__name__)
|
scope, descr="parametrize() call in {}".format(self.function.__name__)
|
||||||
)
|
)
|
||||||
|
@ -1009,27 +1047,48 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
|
||||||
:rtype: List[str]
|
:rtype: List[str]
|
||||||
:return: the list of ids for each argname given
|
:return: the list of ids for each argname given
|
||||||
"""
|
"""
|
||||||
from _pytest._io.saferepr import saferepr
|
|
||||||
|
|
||||||
idfn = None
|
idfn = None
|
||||||
if callable(ids):
|
if callable(ids):
|
||||||
idfn = ids
|
idfn = ids
|
||||||
ids = None
|
ids = None
|
||||||
if ids:
|
if ids:
|
||||||
func_name = self.function.__name__
|
func_name = self.function.__name__
|
||||||
if len(ids) != len(parameters):
|
ids = self._validate_ids(ids, parameters, func_name)
|
||||||
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 = idmaker(argnames, parameters, idfn, ids, self.config, item=item)
|
ids = idmaker(argnames, parameters, idfn, ids, self.config, item=item)
|
||||||
return ids
|
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):
|
def _resolve_arg_value_types(self, argnames, indirect):
|
||||||
"""Resolves if each parametrized argument must be considered a parameter to a fixture or a "funcarg"
|
"""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.
|
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:
|
if generated_id is not None:
|
||||||
val = generated_id
|
val = generated_id
|
||||||
except Exception as e:
|
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 {}"
|
||||||
msg = "{}: error raised while trying to determine id of parameter '{}' at position {}\n"
|
|
||||||
msg = msg.format(item.nodeid, argname, idx)
|
msg = msg.format(item.nodeid, argname, idx)
|
||||||
raise ValueError(msg) from e
|
raise ValueError(msg) from e
|
||||||
elif config:
|
elif config:
|
||||||
|
@ -1333,7 +1391,7 @@ def write_docstring(tw, doc, indent=" "):
|
||||||
tw.write(indent + line + "\n")
|
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
|
""" a Function Item is responsible for setting up and executing a
|
||||||
Python test function.
|
Python test function.
|
||||||
"""
|
"""
|
||||||
|
@ -1399,6 +1457,10 @@ class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr):
|
||||||
#: .. versionadded:: 3.0
|
#: .. versionadded:: 3.0
|
||||||
self.originalname = originalname
|
self.originalname = originalname
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_parent(cls, parent, **kw):
|
||||||
|
return cls._create(parent=parent, **kw)
|
||||||
|
|
||||||
def _initrequest(self):
|
def _initrequest(self):
|
||||||
self.funcargs = {}
|
self.funcargs = {}
|
||||||
self._request = fixtures.FixtureRequest(self)
|
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"
|
"(compatonly) for code expecting pytest-2.2 style request objects"
|
||||||
return self
|
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):
|
def runtest(self):
|
||||||
""" execute the underlying test function. """
|
""" execute the underlying test function. """
|
||||||
self.ihook.pytest_pyfunc_call(pyfuncitem=self)
|
self.ihook.pytest_pyfunc_call(pyfuncitem=self)
|
||||||
|
|
|
@ -552,7 +552,7 @@ def raises(
|
||||||
|
|
||||||
|
|
||||||
@overload # noqa: F811
|
@overload # noqa: F811
|
||||||
def raises(
|
def raises( # noqa: F811
|
||||||
expected_exception: Union["Type[_E]", Tuple["Type[_E]", ...]],
|
expected_exception: Union["Type[_E]", Tuple["Type[_E]", ...]],
|
||||||
func: Callable,
|
func: Callable,
|
||||||
*args: Any,
|
*args: Any,
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue