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