Merge remote-tracking branch 'upstream/features' into blueyed/pdb-doctest-bdbquit
This commit is contained in:
commit
f4734213e5
|
@ -16,3 +16,11 @@ source = src/
|
|||
*/lib/python*/site-packages/
|
||||
*/pypy*/site-packages/
|
||||
*\Lib\site-packages\
|
||||
|
||||
[report]
|
||||
skip_covered = True
|
||||
show_missing = True
|
||||
exclude_lines =
|
||||
\#\s*pragma: no cover
|
||||
^\s*raise NotImplementedError\b
|
||||
^\s*return NotImplemented\b
|
||||
|
|
|
@ -6,7 +6,7 @@ Here is a quick checklist that should be present in PRs.
|
|||
-->
|
||||
|
||||
- [ ] Target the `master` branch for bug fixes, documentation updates and trivial changes.
|
||||
- [ ] Target the `features` branch for new features and removals/deprecations.
|
||||
- [ ] 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.
|
||||
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
exclude: doc/en/example/py2py3/test_py2.py
|
||||
repos:
|
||||
- repo: https://github.com/python/black
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 19.3b0
|
||||
hooks:
|
||||
- id: black
|
||||
args: [--safe, --quiet]
|
||||
language_version: python3
|
||||
- repo: https://github.com/asottile/blacken-docs
|
||||
rev: v1.0.0
|
||||
hooks:
|
||||
- id: blacken-docs
|
||||
additional_dependencies: [black==19.3b0]
|
||||
language_version: python3
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v2.2.3
|
||||
hooks:
|
||||
|
@ -28,7 +26,7 @@ repos:
|
|||
hooks:
|
||||
- id: flake8
|
||||
language_version: python3
|
||||
additional_dependencies: [flake8-typing-imports]
|
||||
additional_dependencies: [flake8-typing-imports==1.3.0]
|
||||
- repo: https://github.com/asottile/reorder_python_imports
|
||||
rev: v1.4.0
|
||||
hooks:
|
||||
|
@ -44,15 +42,10 @@ repos:
|
|||
hooks:
|
||||
- id: rst-backticks
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v0.711
|
||||
rev: v0.720
|
||||
hooks:
|
||||
- id: mypy
|
||||
name: mypy (src)
|
||||
files: ^src/
|
||||
args: []
|
||||
- id: mypy
|
||||
name: mypy (testing)
|
||||
files: ^testing/
|
||||
files: ^(src/|testing/)
|
||||
args: []
|
||||
- repo: local
|
||||
hooks:
|
||||
|
@ -66,7 +59,7 @@ repos:
|
|||
name: changelog filenames
|
||||
language: fail
|
||||
entry: 'changelog files must be named ####.(feature|bugfix|doc|deprecation|removal|vendor|trivial).rst'
|
||||
exclude: changelog/(\d+\.(feature|bugfix|doc|deprecation|removal|vendor|trivial).rst|README.rst|_template.rst)
|
||||
exclude: changelog/(\d+\.(feature|improvement|bugfix|doc|deprecation|removal|vendor|trivial).rst|README.rst|_template.rst)
|
||||
files: ^changelog/
|
||||
- id: py-deprecated
|
||||
name: py library is deprecated
|
||||
|
|
25
.travis.yml
25
.travis.yml
|
@ -13,6 +13,10 @@ env:
|
|||
global:
|
||||
- PYTEST_ADDOPTS=-vv
|
||||
|
||||
# setuptools-scm needs all tags in order to obtain a proper version
|
||||
git:
|
||||
depth: false
|
||||
|
||||
install:
|
||||
- python -m pip install --upgrade --pre tox
|
||||
|
||||
|
@ -31,7 +35,9 @@ jobs:
|
|||
- test $(python -c 'import sys; print("%d%d" % sys.version_info[0:2])') = 37
|
||||
|
||||
# Full run of latest supported version, without xdist.
|
||||
- env: TOXENV=py37
|
||||
# Coverage for:
|
||||
# - test_sys_breakpoint_interception (via pexpect).
|
||||
- env: TOXENV=py37-pexpect PYTEST_COVERAGE=1
|
||||
python: '3.7'
|
||||
|
||||
# Coverage tracking is slow with pypy, skip it.
|
||||
|
@ -45,13 +51,11 @@ jobs:
|
|||
# - pytester's LsofFdLeakChecker
|
||||
# - TestArgComplete (linux only)
|
||||
# - numpy
|
||||
# - old attrs
|
||||
# Empty PYTEST_ADDOPTS to run this non-verbose.
|
||||
- env: TOXENV=py37-lsof-numpy-twisted-xdist PYTEST_COVERAGE=1 PYTEST_ADDOPTS=
|
||||
- env: TOXENV=py37-lsof-oldattrs-numpy-twisted-xdist PYTEST_COVERAGE=1 PYTEST_ADDOPTS=
|
||||
|
||||
# Specialized factors for py37.
|
||||
# Coverage for:
|
||||
# - test_sys_breakpoint_interception (via pexpect).
|
||||
- env: TOXENV=py37-pexpect PYTEST_COVERAGE=1
|
||||
- env: TOXENV=py37-pluggymaster-xdist
|
||||
- env: TOXENV=py37-freeze
|
||||
|
||||
|
@ -68,8 +72,17 @@ jobs:
|
|||
|
||||
- stage: deploy
|
||||
python: '3.6'
|
||||
install: pip install -U setuptools setuptools_scm
|
||||
install: pip install -U setuptools setuptools_scm tox
|
||||
script: skip
|
||||
# token to upload github release notes: GH_RELEASE_NOTES_TOKEN
|
||||
env:
|
||||
- secure: "OjOeL7/0JUDkV00SsTs732e8vQjHynpbG9FKTNtZZJ+1Zn4Cib+hAlwmlBnvVukML0X60YpcfjnC4quDOIGLPsh5zeXnvJmYtAIIUNQXjWz8NhcGYrhyzuP1rqV22U68RTCdmOq3lMYU/W2acwHP7T49PwJtOiUM5kF120UAQ0Zi5EmkqkIvH8oM5mO9Dlver+/U7Htpz9rhKrHBXQNCMZI6yj2aUyukqB2PN2fjAlDbCF//+FmvYw9NjT4GeFOSkTCf4ER9yfqs7yglRfwiLtOCZ2qKQhWZNsSJDB89rxIRXWavJUjJKeY2EW2/NkomYJDpqJLIF4JeFRw/HhA47CYPeo6BJqyyNV+0CovL1frpWfi9UQw2cMbgFUkUIUk3F6DD59PHNIOX2R/HX56dQsw7WKl3QuHlCOkICXYg8F7Ta684IoKjeTX03/6QNOkURfDBwfGszY0FpbxrjCSWKom6RyZdyidnESaxv9RzjcIRZVh1rp8KMrwS1OrwRSdG0zjlsPr49hWMenN/8fKgcHTV4/r1Tj6mip0dorSRCrgUNIeRBKgmui6FS8642ab5JNKOxMteVPVR2sFuhjOQ0Jy+PmvceYY9ZMWc3+/B/KVh0dZ3hwvLGZep/vxDS2PwCA5/xw31714vT5LxidKo8yECjBynMU/wUTTS695D3NY="
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
# required by publish_gh_release_notes
|
||||
- pandoc
|
||||
after_deploy: tox -e publish_gh_release_notes
|
||||
deploy:
|
||||
provider: pypi
|
||||
user: nicoddemus
|
||||
|
|
8
AUTHORS
8
AUTHORS
|
@ -23,6 +23,7 @@ Andras Tim
|
|||
Andrea Cimatoribus
|
||||
Andreas Zeidler
|
||||
Andrey Paramonov
|
||||
Andrzej Klajnert
|
||||
Andrzej Ostrowski
|
||||
Andy Freeland
|
||||
Anthon van der Neut
|
||||
|
@ -55,6 +56,7 @@ Charnjit SiNGH (CCSJ)
|
|||
Chris Lamb
|
||||
Christian Boelsen
|
||||
Christian Fetzer
|
||||
Christian Neumüller
|
||||
Christian Theunert
|
||||
Christian Tismer
|
||||
Christopher Gilling
|
||||
|
@ -96,6 +98,7 @@ Feng Ma
|
|||
Florian Bruhin
|
||||
Floris Bruynooghe
|
||||
Gabriel Reis
|
||||
Gene Wood
|
||||
George Kussumoto
|
||||
Georgy Dyuldin
|
||||
Graham Horler
|
||||
|
@ -173,6 +176,7 @@ mbyt
|
|||
Michael Aquilina
|
||||
Michael Birtwell
|
||||
Michael Droettboom
|
||||
Michael Goerz
|
||||
Michael Seifert
|
||||
Michal Wajszczuk
|
||||
Mihai Capotă
|
||||
|
@ -209,6 +213,7 @@ Raphael Castaneda
|
|||
Raphael Pierzina
|
||||
Raquel Alegre
|
||||
Ravi Chandra
|
||||
Robert Holt
|
||||
Roberto Polli
|
||||
Roland Puntaier
|
||||
Romain Dorgueil
|
||||
|
@ -239,6 +244,7 @@ Tareq Alayan
|
|||
Ted Xiao
|
||||
Thomas Grainger
|
||||
Thomas Hisch
|
||||
Tim Hoffmann
|
||||
Tim Strazny
|
||||
Tom Dalton
|
||||
Tom Viner
|
||||
|
@ -258,7 +264,9 @@ Wil Cooley
|
|||
William Lee
|
||||
Wim Glenn
|
||||
Wouter van Ackooy
|
||||
Xixi Zhao
|
||||
Xuan Luong
|
||||
Xuecong Liao
|
||||
Yoav Caspi
|
||||
Zac Hatfield-Dodds
|
||||
Zoltán Máté
|
||||
|
|
355
CHANGELOG.rst
355
CHANGELOG.rst
|
@ -1,6 +1,6 @@
|
|||
=================
|
||||
Changelog history
|
||||
=================
|
||||
=========
|
||||
Changelog
|
||||
=========
|
||||
|
||||
Versions follow `Semantic Versioning <https://semver.org/>`_ (``<major>.<minor>.<patch>``).
|
||||
|
||||
|
@ -18,6 +18,287 @@ with advance notice in the **Deprecations** section of releases.
|
|||
|
||||
.. towncrier release notes start
|
||||
|
||||
pytest 5.2.1 (2019-10-06)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#5902 <https://github.com/pytest-dev/pytest/issues/5902>`_: Fix warnings about deprecated ``cmp`` attribute in ``attrs>=19.2``.
|
||||
|
||||
|
||||
pytest 4.6.6 (2019-10-11)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#5523 <https://github.com/pytest-dev/pytest/issues/5523>`_: Fixed using multiple short options together in the command-line (for example ``-vs``) in Python 3.8+.
|
||||
|
||||
|
||||
- `#5537 <https://github.com/pytest-dev/pytest/issues/5537>`_: Replace ``importlib_metadata`` backport with ``importlib.metadata`` from the
|
||||
standard library on Python 3.8+.
|
||||
|
||||
|
||||
- `#5806 <https://github.com/pytest-dev/pytest/issues/5806>`_: Fix "lexer" being used when uploading to bpaste.net from ``--pastebin`` to "text".
|
||||
|
||||
|
||||
- `#5902 <https://github.com/pytest-dev/pytest/issues/5902>`_: Fix warnings about deprecated ``cmp`` attribute in ``attrs>=19.2``.
|
||||
|
||||
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- `#5801 <https://github.com/pytest-dev/pytest/issues/5801>`_: Fixes python version checks (detected by ``flake8-2020``) in case python4 becomes a thing.
|
||||
|
||||
|
||||
|
||||
pytest 5.2.0 (2019-09-28)
|
||||
=========================
|
||||
|
||||
Deprecations
|
||||
------------
|
||||
|
||||
- `#1682 <https://github.com/pytest-dev/pytest/issues/1682>`_: Passing arguments to pytest.fixture() as positional arguments is deprecated - pass them
|
||||
as a keyword argument instead.
|
||||
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- `#1682 <https://github.com/pytest-dev/pytest/issues/1682>`_: The ``scope`` parameter of ``@pytest.fixture`` can now be a callable that receives
|
||||
the fixture name and the ``config`` object as keyword-only parameters.
|
||||
See `the docs <https://docs.pytest.org/en/latest/fixture.html#dynamic-scope>`__ for more information.
|
||||
|
||||
|
||||
- `#5764 <https://github.com/pytest-dev/pytest/issues/5764>`_: New behavior of the ``--pastebin`` option: failures to connect to the pastebin server are reported, without failing the pytest run
|
||||
|
||||
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#5806 <https://github.com/pytest-dev/pytest/issues/5806>`_: Fix "lexer" being used when uploading to bpaste.net from ``--pastebin`` to "text".
|
||||
|
||||
|
||||
- `#5884 <https://github.com/pytest-dev/pytest/issues/5884>`_: Fix ``--setup-only`` and ``--setup-show`` for custom pytest items.
|
||||
|
||||
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- `#5056 <https://github.com/pytest-dev/pytest/issues/5056>`_: The HelpFormatter uses ``py.io.get_terminal_width`` for better width detection.
|
||||
|
||||
|
||||
pytest 5.1.3 (2019-09-18)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#5807 <https://github.com/pytest-dev/pytest/issues/5807>`_: Fix pypy3.6 (nightly) on windows.
|
||||
|
||||
|
||||
- `#5811 <https://github.com/pytest-dev/pytest/issues/5811>`_: Handle ``--fulltrace`` correctly with ``pytest.raises``.
|
||||
|
||||
|
||||
- `#5819 <https://github.com/pytest-dev/pytest/issues/5819>`_: Windows: Fix regression with conftest whose qualified name contains uppercase
|
||||
characters (introduced by #5792).
|
||||
|
||||
|
||||
pytest 5.1.2 (2019-08-30)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#2270 <https://github.com/pytest-dev/pytest/issues/2270>`_: Fixed ``self`` reference in function-scoped fixtures defined plugin classes: previously ``self``
|
||||
would be a reference to a *test* class, not the *plugin* class.
|
||||
|
||||
|
||||
- `#570 <https://github.com/pytest-dev/pytest/issues/570>`_: Fixed long standing issue where fixture scope was not respected when indirect fixtures were used during
|
||||
parametrization.
|
||||
|
||||
|
||||
- `#5782 <https://github.com/pytest-dev/pytest/issues/5782>`_: Fix decoding error when printing an error response from ``--pastebin``.
|
||||
|
||||
|
||||
- `#5786 <https://github.com/pytest-dev/pytest/issues/5786>`_: Chained exceptions in test and collection reports are now correctly serialized, allowing plugins like
|
||||
``pytest-xdist`` to display them properly.
|
||||
|
||||
|
||||
- `#5792 <https://github.com/pytest-dev/pytest/issues/5792>`_: Windows: Fix error that occurs in certain circumstances when loading
|
||||
``conftest.py`` from a working directory that has casing other than the one stored
|
||||
in the filesystem (e.g., ``c:\test`` instead of ``C:\test``).
|
||||
|
||||
|
||||
pytest 5.1.1 (2019-08-20)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#5751 <https://github.com/pytest-dev/pytest/issues/5751>`_: Fixed ``TypeError`` when importing pytest on Python 3.5.0 and 3.5.1.
|
||||
|
||||
|
||||
pytest 5.1.0 (2019-08-15)
|
||||
=========================
|
||||
|
||||
Removals
|
||||
--------
|
||||
|
||||
- `#5180 <https://github.com/pytest-dev/pytest/issues/5180>`_: As per our policy, the following features have been deprecated in the 4.X series and are now
|
||||
removed:
|
||||
|
||||
* ``Request.getfuncargvalue``: use ``Request.getfixturevalue`` instead.
|
||||
|
||||
* ``pytest.raises`` and ``pytest.warns`` no longer support strings as the second argument.
|
||||
|
||||
* ``message`` parameter of ``pytest.raises``.
|
||||
|
||||
* ``pytest.raises``, ``pytest.warns`` and ``ParameterSet.param`` now use native keyword-only
|
||||
syntax. This might change the exception message from previous versions, but they still raise
|
||||
``TypeError`` on unknown keyword arguments as before.
|
||||
|
||||
* ``pytest.config`` global variable.
|
||||
|
||||
* ``tmpdir_factory.ensuretemp`` method.
|
||||
|
||||
* ``pytest_logwarning`` hook.
|
||||
|
||||
* ``RemovedInPytest4Warning`` warning type.
|
||||
|
||||
* ``request`` is now a reserved name for fixtures.
|
||||
|
||||
|
||||
For more information consult
|
||||
`Deprecations and Removals <https://docs.pytest.org/en/latest/deprecations.html>`__ in the docs.
|
||||
|
||||
|
||||
- `#5565 <https://github.com/pytest-dev/pytest/issues/5565>`_: Removed unused support code for `unittest2 <https://pypi.org/project/unittest2/>`__.
|
||||
|
||||
The ``unittest2`` backport module is no longer
|
||||
necessary since Python 3.3+, and the small amount of code in pytest to support it also doesn't seem
|
||||
to be used: after removed, all tests still pass unchanged.
|
||||
|
||||
Although our policy is to introduce a deprecation period before removing any features or support
|
||||
for third party libraries, because this code is apparently not used
|
||||
at all (even if ``unittest2`` is used by a test suite executed by pytest), it was decided to
|
||||
remove it in this release.
|
||||
|
||||
If you experience a regression because of this, please
|
||||
`file an issue <https://github.com/pytest-dev/pytest/issues/new>`__.
|
||||
|
||||
|
||||
- `#5615 <https://github.com/pytest-dev/pytest/issues/5615>`_: ``pytest.fail``, ``pytest.xfail`` and ``pytest.skip`` no longer support bytes for the message argument.
|
||||
|
||||
This was supported for Python 2 where it was tempting to use ``"message"``
|
||||
instead of ``u"message"``.
|
||||
|
||||
Python 3 code is unlikely to pass ``bytes`` to these functions. If you do,
|
||||
please decode it to an ``str`` beforehand.
|
||||
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- `#5564 <https://github.com/pytest-dev/pytest/issues/5564>`_: New ``Config.invocation_args`` attribute containing the unchanged arguments passed to ``pytest.main()``.
|
||||
|
||||
|
||||
- `#5576 <https://github.com/pytest-dev/pytest/issues/5576>`_: New `NUMBER <https://docs.pytest.org/en/latest/doctest.html#using-doctest-options>`__
|
||||
option for doctests to ignore irrelevant differences in floating-point numbers.
|
||||
Inspired by Sébastien Boisgérault's `numtest <https://github.com/boisgera/numtest>`__
|
||||
extension for doctest.
|
||||
|
||||
|
||||
|
||||
Improvements
|
||||
------------
|
||||
|
||||
- `#5471 <https://github.com/pytest-dev/pytest/issues/5471>`_: JUnit XML now includes a timestamp and hostname in the testsuite tag.
|
||||
|
||||
|
||||
- `#5707 <https://github.com/pytest-dev/pytest/issues/5707>`_: Time taken to run the test suite now includes a human-readable representation when it takes over
|
||||
60 seconds, for example::
|
||||
|
||||
===== 2 failed in 102.70s (0:01:42) =====
|
||||
|
||||
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#4344 <https://github.com/pytest-dev/pytest/issues/4344>`_: Fix RuntimeError/StopIteration when trying to collect package with "__init__.py" only.
|
||||
|
||||
|
||||
- `#5115 <https://github.com/pytest-dev/pytest/issues/5115>`_: Warnings issued during ``pytest_configure`` are explicitly not treated as errors, even if configured as such, because it otherwise completely breaks pytest.
|
||||
|
||||
|
||||
- `#5477 <https://github.com/pytest-dev/pytest/issues/5477>`_: The XML file produced by ``--junitxml`` now correctly contain a ``<testsuites>`` root element.
|
||||
|
||||
|
||||
- `#5524 <https://github.com/pytest-dev/pytest/issues/5524>`_: Fix issue where ``tmp_path`` and ``tmpdir`` would not remove directories containing files marked as read-only,
|
||||
which could lead to pytest crashing when executed a second time with the ``--basetemp`` option.
|
||||
|
||||
|
||||
- `#5537 <https://github.com/pytest-dev/pytest/issues/5537>`_: Replace ``importlib_metadata`` backport with ``importlib.metadata`` from the
|
||||
standard library on Python 3.8+.
|
||||
|
||||
|
||||
- `#5578 <https://github.com/pytest-dev/pytest/issues/5578>`_: Improve type checking for some exception-raising functions (``pytest.xfail``, ``pytest.skip``, etc)
|
||||
so they provide better error messages when users meant to use marks (for example ``@pytest.xfail``
|
||||
instead of ``@pytest.mark.xfail``).
|
||||
|
||||
|
||||
- `#5606 <https://github.com/pytest-dev/pytest/issues/5606>`_: Fixed internal error when test functions were patched with objects that cannot be compared
|
||||
for truth values against others, like ``numpy`` arrays.
|
||||
|
||||
|
||||
- `#5634 <https://github.com/pytest-dev/pytest/issues/5634>`_: ``pytest.exit`` is now correctly handled in ``unittest`` cases.
|
||||
This makes ``unittest`` cases handle ``quit`` from pytest's pdb correctly.
|
||||
|
||||
|
||||
- `#5650 <https://github.com/pytest-dev/pytest/issues/5650>`_: Improved output when parsing an ini configuration file fails.
|
||||
|
||||
|
||||
- `#5701 <https://github.com/pytest-dev/pytest/issues/5701>`_: Fix collection of ``staticmethod`` objects defined with ``functools.partial``.
|
||||
|
||||
|
||||
- `#5734 <https://github.com/pytest-dev/pytest/issues/5734>`_: Skip async generator test functions, and update the warning message to refer to ``async def`` functions.
|
||||
|
||||
|
||||
|
||||
Improved Documentation
|
||||
----------------------
|
||||
|
||||
- `#5669 <https://github.com/pytest-dev/pytest/issues/5669>`_: Add docstring for ``Testdir.copy_example``.
|
||||
|
||||
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- `#5095 <https://github.com/pytest-dev/pytest/issues/5095>`_: XML files of the ``xunit2`` family are now validated against the schema by pytest's own test suite
|
||||
to avoid future regressions.
|
||||
|
||||
|
||||
- `#5516 <https://github.com/pytest-dev/pytest/issues/5516>`_: Cache node splitting function which can improve collection performance in very large test suites.
|
||||
|
||||
|
||||
- `#5603 <https://github.com/pytest-dev/pytest/issues/5603>`_: Simplified internal ``SafeRepr`` class and removed some dead code.
|
||||
|
||||
|
||||
- `#5664 <https://github.com/pytest-dev/pytest/issues/5664>`_: When invoking pytest's own testsuite with ``PYTHONDONTWRITEBYTECODE=1``,
|
||||
the ``test_xfail_handling`` test no longer fails.
|
||||
|
||||
|
||||
- `#5684 <https://github.com/pytest-dev/pytest/issues/5684>`_: Replace manual handling of ``OSError.errno`` in the codebase by new ``OSError`` subclasses (``PermissionError``, ``FileNotFoundError``, etc.).
|
||||
|
||||
|
||||
pytest 5.0.1 (2019-07-04)
|
||||
=========================
|
||||
|
||||
|
@ -90,6 +371,24 @@ Removals
|
|||
- `#5412 <https://github.com/pytest-dev/pytest/issues/5412>`_: ``ExceptionInfo`` objects (returned by ``pytest.raises``) now have the same ``str`` representation as ``repr``, which
|
||||
avoids some confusion when users use ``print(e)`` to inspect the object.
|
||||
|
||||
This means code like:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
with pytest.raises(SomeException) as e:
|
||||
...
|
||||
assert "some message" in str(e)
|
||||
|
||||
|
||||
Needs to be changed to:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
with pytest.raises(SomeException) as e:
|
||||
...
|
||||
assert "some message" in str(e.value)
|
||||
|
||||
|
||||
|
||||
|
||||
Deprecations
|
||||
|
@ -225,6 +524,27 @@ Improved Documentation
|
|||
- `#5416 <https://github.com/pytest-dev/pytest/issues/5416>`_: Fix PytestUnknownMarkWarning in run/skip example.
|
||||
|
||||
|
||||
pytest 4.6.5 (2019-08-05)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#4344 <https://github.com/pytest-dev/pytest/issues/4344>`_: Fix RuntimeError/StopIteration when trying to collect package with "__init__.py" only.
|
||||
|
||||
|
||||
- `#5478 <https://github.com/pytest-dev/pytest/issues/5478>`_: Fix encode error when using unicode strings in exceptions with ``pytest.raises``.
|
||||
|
||||
|
||||
- `#5524 <https://github.com/pytest-dev/pytest/issues/5524>`_: Fix issue where ``tmp_path`` and ``tmpdir`` would not remove directories containing files marked as read-only,
|
||||
which could lead to pytest crashing when executed a second time with the ``--basetemp`` option.
|
||||
|
||||
|
||||
- `#5547 <https://github.com/pytest-dev/pytest/issues/5547>`_: ``--step-wise`` now handles ``xfail(strict=True)`` markers properly.
|
||||
|
||||
|
||||
- `#5650 <https://github.com/pytest-dev/pytest/issues/5650>`_: Improved output when parsing an ini configuration file fails.
|
||||
|
||||
pytest 4.6.4 (2019-06-28)
|
||||
=========================
|
||||
|
||||
|
@ -2173,10 +2493,10 @@ Features
|
|||
design. This introduces new ``Node.iter_markers(name)`` and
|
||||
``Node.get_closest_marker(name)`` APIs. Users are **strongly encouraged** to
|
||||
read the `reasons for the revamp in the docs
|
||||
<https://docs.pytest.org/en/latest/mark.html#marker-revamp-and-iteration>`_,
|
||||
<https://docs.pytest.org/en/latest/historical-notes.html#marker-revamp-and-iteration>`_,
|
||||
or jump over to details about `updating existing code to use the new APIs
|
||||
<https://docs.pytest.org/en/latest/mark.html#updating-code>`_. (`#3317
|
||||
<https://github.com/pytest-dev/pytest/issues/3317>`_)
|
||||
<https://docs.pytest.org/en/latest/historical-notes.html#updating-code>`_.
|
||||
(`#3317 <https://github.com/pytest-dev/pytest/issues/3317>`_)
|
||||
|
||||
- Now when ``@pytest.fixture`` is applied more than once to the same function a
|
||||
``ValueError`` is raised. This buggy behavior would cause surprising problems
|
||||
|
@ -2582,10 +2902,10 @@ Features
|
|||
<https://github.com/pytest-dev/pytest/issues/3038>`_)
|
||||
|
||||
- New `pytest_runtest_logfinish
|
||||
<https://docs.pytest.org/en/latest/writing_plugins.html#_pytest.hookspec.pytest_runtest_logfinish>`_
|
||||
<https://docs.pytest.org/en/latest/reference.html#_pytest.hookspec.pytest_runtest_logfinish>`_
|
||||
hook which is called when a test item has finished executing, analogous to
|
||||
`pytest_runtest_logstart
|
||||
<https://docs.pytest.org/en/latest/writing_plugins.html#_pytest.hookspec.pytest_runtest_start>`_.
|
||||
<https://docs.pytest.org/en/latest/reference.html#_pytest.hookspec.pytest_runtest_logstart>`_.
|
||||
(`#3101 <https://github.com/pytest-dev/pytest/issues/3101>`_)
|
||||
|
||||
- Improve performance when collecting tests using many fixtures. (`#3107
|
||||
|
@ -3575,7 +3895,7 @@ Bug Fixes
|
|||
Thanks `@sirex`_ for the report and `@nicoddemus`_ for the PR.
|
||||
|
||||
* Replace ``raise StopIteration`` usages in the code by simple ``returns`` to finish generators, in accordance to `PEP-479`_ (`#2160`_).
|
||||
Thanks `@tgoodlet`_ for the report and `@nicoddemus`_ for the PR.
|
||||
Thanks to `@nicoddemus`_ for the PR.
|
||||
|
||||
* Fix internal errors when an unprintable ``AssertionError`` is raised inside a test.
|
||||
Thanks `@omerhadari`_ for the PR.
|
||||
|
@ -3706,7 +4026,7 @@ Bug Fixes
|
|||
|
||||
.. _@syre: https://github.com/syre
|
||||
.. _@adler-j: https://github.com/adler-j
|
||||
.. _@d-b-w: https://bitbucket.org/d-b-w/
|
||||
.. _@d-b-w: https://github.com/d-b-w
|
||||
.. _@DuncanBetts: https://github.com/DuncanBetts
|
||||
.. _@dupuy: https://bitbucket.org/dupuy/
|
||||
.. _@kerrick-lyft: https://github.com/kerrick-lyft
|
||||
|
@ -3766,7 +4086,7 @@ Bug Fixes
|
|||
|
||||
.. _@adborden: https://github.com/adborden
|
||||
.. _@cwitty: https://github.com/cwitty
|
||||
.. _@d_b_w: https://github.com/d_b_w
|
||||
.. _@d_b_w: https://github.com/d-b-w
|
||||
.. _@gdyuldin: https://github.com/gdyuldin
|
||||
.. _@matclab: https://github.com/matclab
|
||||
.. _@MSeifert04: https://github.com/MSeifert04
|
||||
|
@ -3801,7 +4121,7 @@ Bug Fixes
|
|||
Thanks `@axil`_ for the PR.
|
||||
|
||||
* Explain a bad scope value passed to ``@fixture`` declarations or
|
||||
a ``MetaFunc.parametrize()`` call. Thanks `@tgoodlet`_ for the PR.
|
||||
a ``MetaFunc.parametrize()`` call.
|
||||
|
||||
* This version includes ``pluggy-0.4.0``, which correctly handles
|
||||
``VersionConflict`` errors in plugins (`#704`_).
|
||||
|
@ -3811,7 +4131,6 @@ Bug Fixes
|
|||
.. _@philpep: https://github.com/philpep
|
||||
.. _@raquel-ucl: https://github.com/raquel-ucl
|
||||
.. _@axil: https://github.com/axil
|
||||
.. _@tgoodlet: https://github.com/tgoodlet
|
||||
.. _@vlad-dragos: https://github.com/vlad-dragos
|
||||
|
||||
.. _#1853: https://github.com/pytest-dev/pytest/issues/1853
|
||||
|
@ -4157,7 +4476,7 @@ time or change existing behaviors in order to make them less surprising/more use
|
|||
* Updated docstrings with a more uniform style.
|
||||
|
||||
* Add stderr write for ``pytest.exit(msg)`` during startup. Previously the message was never shown.
|
||||
Thanks `@BeyondEvil`_ for reporting `#1210`_. Thanks to `@JonathonSonesen`_ and
|
||||
Thanks `@BeyondEvil`_ for reporting `#1210`_. Thanks to `@jgsonesen`_ and
|
||||
`@tomviner`_ for the PR.
|
||||
|
||||
* No longer display the incorrect test deselection reason (`#1372`_).
|
||||
|
@ -4205,7 +4524,7 @@ time or change existing behaviors in order to make them less surprising/more use
|
|||
Thanks to `@Stranger6667`_ for the PR.
|
||||
|
||||
* Fixed the total tests tally in junit xml output (`#1798`_).
|
||||
Thanks to `@cryporchild`_ for the PR.
|
||||
Thanks to `@cboelsen`_ for the PR.
|
||||
|
||||
* Fixed off-by-one error with lines from ``request.node.warn``.
|
||||
Thanks to `@blueyed`_ for the PR.
|
||||
|
@ -4278,7 +4597,7 @@ time or change existing behaviors in order to make them less surprising/more use
|
|||
.. _@BeyondEvil: https://github.com/BeyondEvil
|
||||
.. _@blueyed: https://github.com/blueyed
|
||||
.. _@ceridwen: https://github.com/ceridwen
|
||||
.. _@cryporchild: https://github.com/cryporchild
|
||||
.. _@cboelsen: https://github.com/cboelsen
|
||||
.. _@csaftoiu: https://github.com/csaftoiu
|
||||
.. _@d6e: https://github.com/d6e
|
||||
.. _@davehunt: https://github.com/davehunt
|
||||
|
@ -4289,7 +4608,7 @@ time or change existing behaviors in order to make them less surprising/more use
|
|||
.. _@gprasad84: https://github.com/gprasad84
|
||||
.. _@graingert: https://github.com/graingert
|
||||
.. _@hartym: https://github.com/hartym
|
||||
.. _@JonathonSonesen: https://github.com/JonathonSonesen
|
||||
.. _@jgsonesen: https://github.com/jgsonesen
|
||||
.. _@kalekundert: https://github.com/kalekundert
|
||||
.. _@kvas-it: https://github.com/kvas-it
|
||||
.. _@marscher: https://github.com/marscher
|
||||
|
@ -4426,7 +4745,7 @@ time or change existing behaviors in order to make them less surprising/more use
|
|||
|
||||
**Changes**
|
||||
|
||||
* **Important**: `py.code <https://pylib.readthedocs.io/en/latest/code.html>`_ has been
|
||||
* **Important**: `py.code <https://pylib.readthedocs.io/en/stable/code.html>`_ has been
|
||||
merged into the ``pytest`` repository as ``pytest._code``. This decision
|
||||
was made because ``py.code`` had very few uses outside ``pytest`` and the
|
||||
fact that it was in a different repository made it difficult to fix bugs on
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at coc@pytest.org. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
The coc@pytest.org address is routed to the following people who can also be
|
||||
contacted individually:
|
||||
|
||||
- Brianna Laugher ([@pfctdayelise](https://github.com/pfctdayelise)): brianna@laugher.id.au
|
||||
- Bruno Oliveira ([@nicoddemus](https://github.com/nicoddemus)): nicoddemus@gmail.com
|
||||
- Florian Bruhin ([@the-compiler](https://github.com/the-compiler)): pytest@the-compiler.org
|
||||
- Ronny Pfannschmidt ([@RonnyPfannschmidt](https://github.com/RonnyPfannschmidt)): ich@ronnypfannschmidt.de
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
|
@ -5,8 +5,9 @@ Contribution getting started
|
|||
Contributions are highly welcomed and appreciated. Every little help counts,
|
||||
so do not hesitate!
|
||||
|
||||
.. contents:: Contribution links
|
||||
.. contents::
|
||||
:depth: 2
|
||||
:backlinks: none
|
||||
|
||||
|
||||
.. _submitfeedback:
|
||||
|
@ -166,7 +167,7 @@ Short version
|
|||
#. Enable and install `pre-commit <https://pre-commit.com>`_ to ensure style-guides and code checks are followed.
|
||||
#. Target ``master`` for bugfixes and doc changes.
|
||||
#. Target ``features`` for new features or functionality changes.
|
||||
#. Follow **PEP-8** for naming and `black <https://github.com/python/black>`_ for formatting.
|
||||
#. Follow **PEP-8** for naming and `black <https://github.com/psf/black>`_ for formatting.
|
||||
#. Tests are run using ``tox``::
|
||||
|
||||
tox -e linting,py37
|
||||
|
|
|
@ -9,7 +9,7 @@ What is it
|
|||
==========
|
||||
|
||||
Open Collective is an online funding platform for open and transparent communities.
|
||||
It provide tools to raise money and share your finances in full transparency.
|
||||
It provides tools to raise money and share your finances in full transparency.
|
||||
|
||||
It is the platform of choice for individuals and companies that want to make one-time or
|
||||
monthly donations directly to the project.
|
||||
|
@ -19,7 +19,7 @@ Funds
|
|||
|
||||
The OpenCollective funds donated to pytest will be used to fund overall maintenance,
|
||||
local sprints, merchandising (stickers to distribute in conferences for example), and future
|
||||
gatherings of pytest developers (Sprints).
|
||||
gatherings of pytest developers (sprints).
|
||||
|
||||
`Core contributors`_ which are contributing on a continuous basis are free to submit invoices
|
||||
to bill maintenance hours using the platform. How much each contributor should request is still an
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
:target: https://dev.azure.com/pytest-dev/pytest
|
||||
|
||||
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
||||
:target: https://github.com/python/black
|
||||
:target: https://github.com/psf/black
|
||||
|
||||
.. image:: https://www.codetriage.com/pytest-dev/pytest/badges/users.svg
|
||||
:target: https://www.codetriage.com/pytest-dev/pytest
|
||||
|
@ -111,13 +111,13 @@ Consult the `Changelog <https://docs.pytest.org/en/latest/changelog.html>`__ pag
|
|||
Support pytest
|
||||
--------------
|
||||
|
||||
You can support pytest by obtaining a `Tideflift subscription`_.
|
||||
You can support pytest by obtaining a `Tidelift subscription`_.
|
||||
|
||||
Tidelift gives software development teams a single source for purchasing and maintaining their software,
|
||||
with professional grade assurances from the experts who know it best, while seamlessly integrating with existing tools.
|
||||
|
||||
|
||||
.. _`Tideflift subscription`: https://tidelift.com/subscription/pkg/pypi-pytest?utm_source=pypi-pytest&utm_medium=referral&utm_campaign=readme
|
||||
.. _`Tidelift subscription`: https://tidelift.com/subscription/pkg/pypi-pytest?utm_source=pypi-pytest&utm_medium=referral&utm_campaign=readme
|
||||
|
||||
|
||||
Security
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
As per our policy, the following features have been deprecated in the 4.X series and are now
|
||||
removed:
|
||||
|
||||
* ``Request.getfuncargvalue``: use ``Request.getfixturevalue`` instead.
|
||||
|
||||
* ``pytest.raises`` and ``pytest.warns`` no longer support strings as the second argument.
|
||||
|
||||
* ``message`` parameter of ``pytest.raises``.
|
||||
|
||||
* ``pytest.raises``, ``pytest.warns`` and ``ParameterSet.param`` now use native keyword-only
|
||||
syntax. This might change the exception message from previous versions, but they still raise
|
||||
``TypeError`` on unknown keyword arguments as before.
|
||||
|
||||
* ``pytest.config`` global variable.
|
||||
|
||||
* ``tmpdir_factory.ensuretemp`` method.
|
||||
|
||||
* ``pytest_logwarning`` hook.
|
||||
|
||||
* ``RemovedInPytest4Warning`` warning type.
|
||||
|
||||
* ``request`` is now a reserved name for fixtures.
|
||||
|
||||
|
||||
For more information consult
|
||||
`Deprecations and Removals <https://docs.pytest.org/en/latest/deprecations.html>`__ in the docs.
|
|
@ -1 +0,0 @@
|
|||
The XML file produced by ``--junitxml`` now correctly contain a ``<testsuites>`` root element.
|
|
@ -1 +0,0 @@
|
|||
Fixed using multiple short options together in the command-line (for example ``-vs``) in Python 3.8+.
|
|
@ -1,2 +0,0 @@
|
|||
Replace ``importlib_metadata`` backport with ``importlib.metadata`` from the
|
||||
standard library on Python 3.8+.
|
|
@ -1 +0,0 @@
|
|||
New ``Config.invocation_args`` attribute containing the unchanged arguments passed to ``pytest.main()``.
|
|
@ -1,13 +0,0 @@
|
|||
Removed unused support code for `unittest2 <https://pypi.org/project/unittest2/>`__.
|
||||
|
||||
The ``unittest2`` backport module is no longer
|
||||
necessary since Python 3.3+, and the small amount of code in pytest to support it also doesn't seem
|
||||
to be used: after removed, all tests still pass unchanged.
|
||||
|
||||
Although our policy is to introduce a deprecation period before removing any features or support
|
||||
for third party libraries, because this code is apparently not used
|
||||
at all (even if ``unittest2`` is used by a test suite executed by pytest), it was decided to
|
||||
remove it in this release.
|
||||
|
||||
If you experience a regression because of this, please
|
||||
`file an issue <https://github.com/pytest-dev/pytest/issues/new>`__.
|
|
@ -1,4 +0,0 @@
|
|||
New `NUMBER <https://docs.pytest.org/en/latest/doctest.html#using-doctest-options>`__
|
||||
option for doctests to ignore irrelevant differences in floating-point numbers.
|
||||
Inspired by Sébastien Boisgérault's `numtest <https://github.com/boisgera/numtest>`__
|
||||
extension for doctest.
|
|
@ -1 +0,0 @@
|
|||
Simplified internal ``SafeRepr`` class and removed some dead code.
|
|
@ -0,0 +1 @@
|
|||
Fix crash with ``KeyboardInterrupt`` during ``--setup-show``.
|
|
@ -0,0 +1,19 @@
|
|||
``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.
|
|
@ -0,0 +1,34 @@
|
|||
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 @@
|
|||
Fixed issue when parametrizing fixtures with numpy arrays (and possibly other sequence-like types).
|
|
@ -0,0 +1,2 @@
|
|||
``Config.InvocationParams.args`` is now always a ``tuple`` to better convey that it should be
|
||||
immutable and avoid accidental modifications.
|
|
@ -12,6 +12,7 @@ Each file should be named like ``<ISSUE>.<TYPE>.rst``, where
|
|||
``<ISSUE>`` is an issue number, and ``<TYPE>`` is one of:
|
||||
|
||||
* ``feature``: new user facing features, like new command-line options and new behavior.
|
||||
* ``improvement``: improvement of existing functionality, usually without requiring user intervention (for example, new fields being written in ``--junitxml``, improved colors in terminal, etc).
|
||||
* ``bugfix``: fixes a reported bug.
|
||||
* ``doc``: documentation improvement, like rewording an entire session or adding missing docs.
|
||||
* ``deprecation``: feature deprecation.
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
coverage:
|
||||
status:
|
||||
project: true
|
||||
patch: true
|
||||
changes: true
|
||||
|
||||
comment: off
|
|
@ -0,0 +1 @@
|
|||
``repr`` of ``ExceptionInfo`` objects has been improved to honor the ``__repr__`` method of the underlying exception.
|
|
@ -16,7 +16,7 @@ REGENDOC_ARGS := \
|
|||
--normalize "/[ \t]+\n/\n/" \
|
||||
--normalize "~\$$REGENDOC_TMPDIR~/home/sweet/project~" \
|
||||
--normalize "~/path/to/example~/home/sweet/project~" \
|
||||
--normalize "/in \d+.\d+ seconds/in 0.12 seconds/" \
|
||||
--normalize "/in \d.\d\ds/in 0.12s/" \
|
||||
--normalize "@/tmp/pytest-of-.*/pytest-\d+@PYTEST_TMPDIR@" \
|
||||
--normalize "@pytest-(\d+)\\.[^ ,]+@pytest-\1.x.y@" \
|
||||
--normalize "@(This is pytest version )(\d+)\\.[^ ,]+@\1\2.x.y@" \
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<li><a href="{{ pathto('index') }}">Home</a></li>
|
||||
<li><a href="{{ pathto('getting-started') }}">Install</a></li>
|
||||
<li><a href="{{ pathto('contents') }}">Contents</a></li>
|
||||
<li><a href="{{ pathto('reference') }}">Reference</a></li>
|
||||
<li><a href="{{ pathto('reference') }}">API Reference</a></li>
|
||||
<li><a href="{{ pathto('example/index') }}">Examples</a></li>
|
||||
<li><a href="{{ pathto('customize') }}">Customize</a></li>
|
||||
<li><a href="{{ pathto('changelog') }}">Changelog</a></li>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
{%- block footer %}
|
||||
<div class="footer">
|
||||
© Copyright {{ copyright }}.
|
||||
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a>.
|
||||
Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> {{ sphinx_version }}.
|
||||
</div>
|
||||
{% if pagename == 'index' %}
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
{#
|
||||
basic/searchbox.html with heading removed.
|
||||
#}
|
||||
{%- if pagename != "search" and builder != "singlehtml" %}
|
||||
<div id="searchbox" style="display: none" role="search">
|
||||
<div class="searchformwrapper">
|
||||
<form class="search" action="{{ pathto('search') }}" method="get">
|
||||
<input type="text" name="q" aria-labelledby="searchlabel"
|
||||
placeholder="Search"/>
|
||||
<input type="submit" value="{{ _('Go') }}" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">$('#searchbox').show(0);</script>
|
||||
{%- endif %}
|
|
@ -8,11 +8,12 @@
|
|||
|
||||
{% set page_width = '1020px' %}
|
||||
{% set sidebar_width = '220px' %}
|
||||
/* orange of logo is #d67c29 but we use black for links for now */
|
||||
{% set link_color = '#000' %}
|
||||
{% set link_hover_color = '#000' %}
|
||||
/* muted version of green logo color #C9D22A */
|
||||
{% set link_color = '#606413' %}
|
||||
/* blue logo color */
|
||||
{% set link_hover_color = '#009de0' %}
|
||||
{% set base_font = 'sans-serif' %}
|
||||
{% set header_font = 'serif' %}
|
||||
{% set header_font = 'sans-serif' %}
|
||||
|
||||
@import url("basic.css");
|
||||
|
||||
|
@ -20,7 +21,7 @@
|
|||
|
||||
body {
|
||||
font-family: {{ base_font }};
|
||||
font-size: 17px;
|
||||
font-size: 16px;
|
||||
background-color: white;
|
||||
color: #000;
|
||||
margin: 0;
|
||||
|
@ -78,13 +79,13 @@ div.related {
|
|||
}
|
||||
|
||||
div.sphinxsidebar a {
|
||||
color: #444;
|
||||
text-decoration: none;
|
||||
border-bottom: 1px dotted #999;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
div.sphinxsidebar a:hover {
|
||||
border-bottom: 1px solid #999;
|
||||
color: {{ link_hover_color }};
|
||||
border-bottom: 1px solid {{ link_hover_color }};
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
|
@ -106,14 +107,14 @@ div.sphinxsidebar h3,
|
|||
div.sphinxsidebar h4 {
|
||||
font-family: {{ header_font }};
|
||||
color: #444;
|
||||
font-size: 24px;
|
||||
font-size: 21px;
|
||||
font-weight: normal;
|
||||
margin: 0 0 5px 0;
|
||||
margin: 16px 0 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h4 {
|
||||
font-size: 20px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3 a {
|
||||
|
@ -205,10 +206,22 @@ div.body p, div.body dd, div.body li {
|
|||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
ul.simple li {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
div.topic ul.simple li {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
div.topic li > p:first-child {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
div.admonition {
|
||||
background: #fafafa;
|
||||
margin: 20px -30px;
|
||||
padding: 10px 30px;
|
||||
padding: 10px 20px;
|
||||
border-top: 1px solid #ccc;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
@ -217,11 +230,6 @@ div.admonition tt.xref, div.admonition a tt {
|
|||
border-bottom: 1px solid #fafafa;
|
||||
}
|
||||
|
||||
dd div.admonition {
|
||||
margin-left: -60px;
|
||||
padding-left: 60px;
|
||||
}
|
||||
|
||||
div.admonition p.admonition-title {
|
||||
font-family: {{ header_font }};
|
||||
font-weight: normal;
|
||||
|
@ -231,7 +239,7 @@ div.admonition p.admonition-title {
|
|||
line-height: 1;
|
||||
}
|
||||
|
||||
div.admonition p.last {
|
||||
div.admonition :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
|
@ -243,7 +251,7 @@ dt:target, .highlight {
|
|||
background: #FAF3E8;
|
||||
}
|
||||
|
||||
div.note {
|
||||
div.note, div.warning {
|
||||
background-color: #eee;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
@ -257,6 +265,11 @@ div.topic {
|
|||
background-color: #eee;
|
||||
}
|
||||
|
||||
div.topic a {
|
||||
text-decoration: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
p.admonition-title {
|
||||
display: inline;
|
||||
}
|
||||
|
@ -358,21 +371,10 @@ ul, ol {
|
|||
|
||||
pre {
|
||||
background: #eee;
|
||||
padding: 7px 30px;
|
||||
margin: 15px -30px;
|
||||
padding: 7px 12px;
|
||||
line-height: 1.3em;
|
||||
}
|
||||
|
||||
dl pre, blockquote pre, li pre {
|
||||
margin-left: -60px;
|
||||
padding-left: 60px;
|
||||
}
|
||||
|
||||
dl dl pre {
|
||||
margin-left: -90px;
|
||||
padding-left: 90px;
|
||||
}
|
||||
|
||||
tt {
|
||||
background-color: #ecf0f3;
|
||||
color: #222;
|
||||
|
@ -393,6 +395,20 @@ a.reference:hover {
|
|||
border-bottom: 1px solid {{ link_hover_color }};
|
||||
}
|
||||
|
||||
li.toctree-l1 a.reference,
|
||||
li.toctree-l2 a.reference,
|
||||
li.toctree-l3 a.reference,
|
||||
li.toctree-l4 a.reference {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
li.toctree-l1 a.reference:hover,
|
||||
li.toctree-l2 a.reference:hover,
|
||||
li.toctree-l3 a.reference:hover,
|
||||
li.toctree-l4 a.reference:hover {
|
||||
border-bottom: 1px solid {{ link_hover_color }};
|
||||
}
|
||||
|
||||
a.footnote-reference {
|
||||
text-decoration: none;
|
||||
font-size: 0.7em;
|
||||
|
@ -408,6 +424,56 @@ a:hover tt {
|
|||
background: #EEE;
|
||||
}
|
||||
|
||||
#reference div.section h2 {
|
||||
/* separate code elements in the reference section */
|
||||
border-top: 2px solid #ccc;
|
||||
padding-top: 0.5em;
|
||||
}
|
||||
|
||||
#reference div.section h3 {
|
||||
/* separate code elements in the reference section */
|
||||
border-top: 1px solid #ccc;
|
||||
padding-top: 0.5em;
|
||||
}
|
||||
|
||||
dl.class, dl.function {
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
dl.class > dd {
|
||||
border-left: 3px solid #ccc;
|
||||
margin-left: 0px;
|
||||
padding-left: 30px;
|
||||
}
|
||||
|
||||
dl.field-list {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
dl.field-list dd {
|
||||
padding-left: 4em;
|
||||
border-left: 3px solid #ccc;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
dl.field-list dd > ul {
|
||||
list-style: none;
|
||||
padding-left: 0px;
|
||||
}
|
||||
|
||||
dl.field-list dd > ul > li li :first-child {
|
||||
text-indent: 0;
|
||||
}
|
||||
|
||||
dl.field-list dd > ul > li :first-child {
|
||||
text-indent: -2em;
|
||||
padding-left: 0px;
|
||||
}
|
||||
|
||||
dl.field-list dd > p:first-child {
|
||||
text-indent: -2em;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 870px) {
|
||||
|
||||
|
|
|
@ -24,11 +24,9 @@ The ideal pytest helper
|
|||
- feels confident in using pytest (e.g. has explored command line options, knows how to write parametrized tests, has an idea about conftest contents)
|
||||
- does not need to be an expert in every aspect!
|
||||
|
||||
`Pytest helpers, sign up here`_! (preferably in February, hard deadline 22 March)
|
||||
Pytest helpers, sign up here! (preferably in February, hard deadline 22 March)
|
||||
|
||||
|
||||
.. _`Pytest helpers, sign up here`: http://goo.gl/forms/nxqAhqWt1P
|
||||
|
||||
|
||||
The ideal partner project
|
||||
-----------------------------------------
|
||||
|
@ -40,11 +38,9 @@ The ideal partner project
|
|||
- has the support of the core development team, in trying out pytest adoption
|
||||
- has no tests... or 100% test coverage... or somewhere in between!
|
||||
|
||||
`Partner projects, sign up here`_! (by 22 March)
|
||||
Partner projects, sign up here! (by 22 March)
|
||||
|
||||
|
||||
.. _`Partner projects, sign up here`: http://goo.gl/forms/ZGyqlHiwk3
|
||||
|
||||
|
||||
What does it mean to "adopt pytest"?
|
||||
-----------------------------------------
|
||||
|
@ -68,11 +64,11 @@ Progressive success might look like:
|
|||
It may be after the month is up, the partner project decides that pytest is not right for it. That's okay - hopefully the pytest team will also learn something about its weaknesses or deficiencies.
|
||||
|
||||
.. _`nose and unittest`: faq.html#how-does-pytest-relate-to-nose-and-unittest
|
||||
.. _assert: asserts.html
|
||||
.. _assert: assert.html
|
||||
.. _pycmd: https://bitbucket.org/hpk42/pycmd/overview
|
||||
.. _`setUp/tearDown methods`: xunit_setup.html
|
||||
.. _fixtures: fixture.html
|
||||
.. _markers: markers.html
|
||||
.. _markers: mark.html
|
||||
.. _distributed: xdist.html
|
||||
|
||||
|
||||
|
|
|
@ -6,8 +6,15 @@ Release announcements
|
|||
:maxdepth: 2
|
||||
|
||||
|
||||
release-5.2.1
|
||||
release-5.2.0
|
||||
release-5.1.3
|
||||
release-5.1.2
|
||||
release-5.1.1
|
||||
release-5.1.0
|
||||
release-5.0.1
|
||||
release-5.0.0
|
||||
release-4.6.5
|
||||
release-4.6.4
|
||||
release-4.6.3
|
||||
release-4.6.2
|
||||
|
|
|
@ -12,7 +12,7 @@ courtesy of Benjamin Peterson. You can now safely use ``assert``
|
|||
statements in test modules without having to worry about side effects
|
||||
or python optimization ("-OO") options. This is achieved by rewriting
|
||||
assert statements in test modules upon import, using a PEP302 hook.
|
||||
See http://pytest.org/assert.html#advanced-assertion-introspection for
|
||||
See https://docs.pytest.org/en/latest/assert.html for
|
||||
detailed information. The work has been partly sponsored by my company,
|
||||
merlinux GmbH.
|
||||
|
||||
|
|
|
@ -75,7 +75,7 @@ The py.test Development Team
|
|||
|
||||
**Changes**
|
||||
|
||||
* **Important**: `py.code <https://pylib.readthedocs.io/en/latest/code.html>`_ has been
|
||||
* **Important**: `py.code <https://pylib.readthedocs.io/en/stable/code.html>`_ has been
|
||||
merged into the ``pytest`` repository as ``pytest._code``. This decision
|
||||
was made because ``py.code`` had very few uses outside ``pytest`` and the
|
||||
fact that it was in a different repository made it difficult to fix bugs on
|
||||
|
@ -88,7 +88,7 @@ The py.test Development Team
|
|||
**experimental**, so you definitely should not import it explicitly!
|
||||
|
||||
Please note that the original ``py.code`` is still available in
|
||||
`pylib <https://pylib.readthedocs.io>`_.
|
||||
`pylib <https://pylib.readthedocs.io/en/stable/>`_.
|
||||
|
||||
* ``pytest_enter_pdb`` now optionally receives the pytest config object.
|
||||
Thanks `@nicoddemus`_ for the PR.
|
||||
|
|
|
@ -66,8 +66,8 @@ The py.test Development Team
|
|||
|
||||
.. _#510: https://github.com/pytest-dev/pytest/issues/510
|
||||
.. _#1506: https://github.com/pytest-dev/pytest/pull/1506
|
||||
.. _#1496: https://github.com/pytest-dev/pytest/issue/1496
|
||||
.. _#1524: https://github.com/pytest-dev/pytest/issue/1524
|
||||
.. _#1496: https://github.com/pytest-dev/pytest/issues/1496
|
||||
.. _#1524: https://github.com/pytest-dev/pytest/pull/1524
|
||||
|
||||
.. _@astraw38: https://github.com/astraw38
|
||||
.. _@hackebrot: https://github.com/hackebrot
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
pytest-4.6.5
|
||||
=======================================
|
||||
|
||||
pytest 4.6.5 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
|
||||
* Thomas Grainger
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
|
@ -0,0 +1,56 @@
|
|||
pytest-5.1.0
|
||||
=======================================
|
||||
|
||||
The pytest team is proud to announce the 5.1.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:
|
||||
|
||||
* Albert Tugushev
|
||||
* Alexey Zankevich
|
||||
* Anthony Sottile
|
||||
* Bruno Oliveira
|
||||
* Daniel Hahler
|
||||
* David Röthlisberger
|
||||
* Florian Bruhin
|
||||
* Ilya Stepin
|
||||
* Jon Dufresne
|
||||
* Kaiqi
|
||||
* Max R
|
||||
* Miro Hrončok
|
||||
* Oliver Bestwalter
|
||||
* Ran Benita
|
||||
* Ronny Pfannschmidt
|
||||
* Samuel Searles-Bryant
|
||||
* Semen Zhydenko
|
||||
* Steffen Schroeder
|
||||
* Thomas Grainger
|
||||
* Tim Hoffmann
|
||||
* William Woodall
|
||||
* Wojtek Erbetowski
|
||||
* Xixi Zhao
|
||||
* Yash Todi
|
||||
* boris
|
||||
* dmitry.dygalo
|
||||
* helloocc
|
||||
* martbln
|
||||
* mei-li
|
||||
|
||||
|
||||
Happy testing,
|
||||
The Pytest Development Team
|
|
@ -0,0 +1,24 @@
|
|||
pytest-5.1.1
|
||||
=======================================
|
||||
|
||||
pytest 5.1.1 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
|
||||
* Florian Bruhin
|
||||
* Hugo van Kemenade
|
||||
* Ran Benita
|
||||
* Ronny Pfannschmidt
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
|
@ -0,0 +1,23 @@
|
|||
pytest-5.1.2
|
||||
=======================================
|
||||
|
||||
pytest 5.1.2 has just been released to PyPI.
|
||||
|
||||
This is a bug-fix release, being a drop-in replacement. To upgrade::
|
||||
|
||||
pip install --upgrade pytest
|
||||
|
||||
The full changelog is available at https://docs.pytest.org/en/latest/changelog.html.
|
||||
|
||||
Thanks to all who contributed to this release, among them:
|
||||
|
||||
* Andrzej Klajnert
|
||||
* Anthony Sottile
|
||||
* Bruno Oliveira
|
||||
* Christian Neumüller
|
||||
* Robert Holt
|
||||
* linchiwei123
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
|
@ -0,0 +1,23 @@
|
|||
pytest-5.1.3
|
||||
=======================================
|
||||
|
||||
pytest 5.1.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
|
||||
* Bruno Oliveira
|
||||
* Christian Neumüller
|
||||
* Daniel Hahler
|
||||
* Gene Wood
|
||||
* Hugo
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
|
@ -0,0 +1,35 @@
|
|||
pytest-5.2.0
|
||||
=======================================
|
||||
|
||||
The pytest team is proud to announce the 5.2.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:
|
||||
|
||||
* Andrzej Klajnert
|
||||
* Anthony Sottile
|
||||
* Bruno Oliveira
|
||||
* Daniel Hahler
|
||||
* James Cooke
|
||||
* Michael Goerz
|
||||
* Ran Benita
|
||||
* Tomáš Chvátal
|
||||
|
||||
|
||||
Happy testing,
|
||||
The Pytest Development Team
|
|
@ -0,0 +1,23 @@
|
|||
pytest-5.2.1
|
||||
=======================================
|
||||
|
||||
pytest 5.2.1 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
|
||||
* Florian Bruhin
|
||||
* Hynek Schlawack
|
||||
* Kevin J. Foley
|
||||
* tadashigaki
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
|
@ -47,7 +47,7 @@ you will see the return value of the function call:
|
|||
E + where 3 = f()
|
||||
|
||||
test_assert1.py:6: AssertionError
|
||||
========================= 1 failed in 0.12 seconds =========================
|
||||
============================ 1 failed in 0.12s =============================
|
||||
|
||||
``pytest`` has support for showing the values of the most common subexpressions
|
||||
including calls, attributes, comparisons, and binary and unary
|
||||
|
@ -208,7 +208,7 @@ if you run this module:
|
|||
E Use -v to get the full diff
|
||||
|
||||
test_assert2.py:6: AssertionError
|
||||
========================= 1 failed in 0.12 seconds =========================
|
||||
============================ 1 failed in 0.12s =============================
|
||||
|
||||
Special comparisons are done for a number of cases:
|
||||
|
||||
|
@ -238,14 +238,17 @@ file which provides an alternative explanation for ``Foo`` objects:
|
|||
|
||||
def pytest_assertrepr_compare(op, left, right):
|
||||
if isinstance(left, Foo) and isinstance(right, Foo) and op == "==":
|
||||
return ["Comparing Foo instances:", " vals: %s != %s" % (left.val, right.val)]
|
||||
return [
|
||||
"Comparing Foo instances:",
|
||||
" vals: {} != {}".format(left.val, right.val),
|
||||
]
|
||||
|
||||
now, given this test module:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_foocompare.py
|
||||
class Foo(object):
|
||||
class Foo:
|
||||
def __init__(self, val):
|
||||
self.val = val
|
||||
|
||||
|
@ -276,7 +279,7 @@ the conftest file:
|
|||
E vals: 1 != 2
|
||||
|
||||
test_foocompare.py:12: AssertionError
|
||||
1 failed in 0.12 seconds
|
||||
1 failed in 0.12s
|
||||
|
||||
.. _assert-details:
|
||||
.. _`assert introspection`:
|
||||
|
|
|
@ -160,9 +160,12 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
|||
in python < 3.6 this is a pathlib2.Path
|
||||
|
||||
|
||||
no tests ran in 0.12 seconds
|
||||
no tests ran in 0.12s
|
||||
|
||||
You can also interactively ask for help, e.g. by typing on the Python interactive prompt something like::
|
||||
You can also interactively ask for help, e.g. by typing on the Python interactive prompt something like:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import pytest
|
||||
|
||||
help(pytest)
|
||||
|
|
|
@ -33,15 +33,18 @@ Other plugins may access the `config.cache`_ object to set/get
|
|||
Rerunning only failures or failures first
|
||||
-----------------------------------------------
|
||||
|
||||
First, let's create 50 test invocation of which only 2 fail::
|
||||
First, let's create 50 test invocation of which only 2 fail:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_50.py
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.parametrize("i", range(50))
|
||||
def test_num(i):
|
||||
if i in (17, 25):
|
||||
pytest.fail("bad luck")
|
||||
pytest.fail("bad luck")
|
||||
|
||||
If you run this for the first time you will see two failures:
|
||||
|
||||
|
@ -57,10 +60,10 @@ If you run this for the first time you will see two failures:
|
|||
@pytest.mark.parametrize("i", range(50))
|
||||
def test_num(i):
|
||||
if i in (17, 25):
|
||||
> pytest.fail("bad luck")
|
||||
E Failed: bad luck
|
||||
> pytest.fail("bad luck")
|
||||
E Failed: bad luck
|
||||
|
||||
test_50.py:6: Failed
|
||||
test_50.py:7: Failed
|
||||
_______________________________ test_num[25] _______________________________
|
||||
|
||||
i = 25
|
||||
|
@ -68,11 +71,11 @@ If you run this for the first time you will see two failures:
|
|||
@pytest.mark.parametrize("i", range(50))
|
||||
def test_num(i):
|
||||
if i in (17, 25):
|
||||
> pytest.fail("bad luck")
|
||||
E Failed: bad luck
|
||||
> pytest.fail("bad luck")
|
||||
E Failed: bad luck
|
||||
|
||||
test_50.py:6: Failed
|
||||
2 failed, 48 passed in 0.12 seconds
|
||||
test_50.py:7: Failed
|
||||
2 failed, 48 passed in 0.12s
|
||||
|
||||
If you then run it with ``--lf``:
|
||||
|
||||
|
@ -96,10 +99,10 @@ If you then run it with ``--lf``:
|
|||
@pytest.mark.parametrize("i", range(50))
|
||||
def test_num(i):
|
||||
if i in (17, 25):
|
||||
> pytest.fail("bad luck")
|
||||
E Failed: bad luck
|
||||
> pytest.fail("bad luck")
|
||||
E Failed: bad luck
|
||||
|
||||
test_50.py:6: Failed
|
||||
test_50.py:7: Failed
|
||||
_______________________________ test_num[25] _______________________________
|
||||
|
||||
i = 25
|
||||
|
@ -107,11 +110,11 @@ If you then run it with ``--lf``:
|
|||
@pytest.mark.parametrize("i", range(50))
|
||||
def test_num(i):
|
||||
if i in (17, 25):
|
||||
> pytest.fail("bad luck")
|
||||
E Failed: bad luck
|
||||
> pytest.fail("bad luck")
|
||||
E Failed: bad luck
|
||||
|
||||
test_50.py:6: Failed
|
||||
================= 2 failed, 48 deselected in 0.12 seconds ==================
|
||||
test_50.py:7: Failed
|
||||
===================== 2 failed, 48 deselected in 0.12s =====================
|
||||
|
||||
You have run only the two failing tests from the last run, while the 48 passing
|
||||
tests have not been run ("deselected").
|
||||
|
@ -140,10 +143,10 @@ of ``FF`` and dots):
|
|||
@pytest.mark.parametrize("i", range(50))
|
||||
def test_num(i):
|
||||
if i in (17, 25):
|
||||
> pytest.fail("bad luck")
|
||||
E Failed: bad luck
|
||||
> pytest.fail("bad luck")
|
||||
E Failed: bad luck
|
||||
|
||||
test_50.py:6: Failed
|
||||
test_50.py:7: Failed
|
||||
_______________________________ test_num[25] _______________________________
|
||||
|
||||
i = 25
|
||||
|
@ -151,11 +154,11 @@ of ``FF`` and dots):
|
|||
@pytest.mark.parametrize("i", range(50))
|
||||
def test_num(i):
|
||||
if i in (17, 25):
|
||||
> pytest.fail("bad luck")
|
||||
E Failed: bad luck
|
||||
> pytest.fail("bad luck")
|
||||
E Failed: bad luck
|
||||
|
||||
test_50.py:6: Failed
|
||||
=================== 2 failed, 48 passed in 0.12 seconds ====================
|
||||
test_50.py:7: Failed
|
||||
======================= 2 failed, 48 passed in 0.12s =======================
|
||||
|
||||
.. _`config.cache`:
|
||||
|
||||
|
@ -183,15 +186,19 @@ The new config.cache object
|
|||
Plugins or conftest.py support code can get a cached value using the
|
||||
pytest ``config`` object. Here is a basic example plugin which
|
||||
implements a :ref:`fixture` which re-uses previously created state
|
||||
across pytest invocations::
|
||||
across pytest invocations:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_caching.py
|
||||
import pytest
|
||||
import time
|
||||
|
||||
|
||||
def expensive_computation():
|
||||
print("running expensive computation...")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mydata(request):
|
||||
val = request.config.cache.get("example/value", None)
|
||||
|
@ -201,6 +208,7 @@ across pytest invocations::
|
|||
request.config.cache.set("example/value", val)
|
||||
return val
|
||||
|
||||
|
||||
def test_function(mydata):
|
||||
assert mydata == 23
|
||||
|
||||
|
@ -219,10 +227,10 @@ If you run this command for the first time, you can see the print statement:
|
|||
> assert mydata == 23
|
||||
E assert 42 == 23
|
||||
|
||||
test_caching.py:17: AssertionError
|
||||
test_caching.py:20: AssertionError
|
||||
-------------------------- Captured stdout setup ---------------------------
|
||||
running expensive computation...
|
||||
1 failed in 0.12 seconds
|
||||
1 failed in 0.12s
|
||||
|
||||
If you run it a second time, the value will be retrieved from
|
||||
the cache and nothing will be printed:
|
||||
|
@ -240,8 +248,8 @@ the cache and nothing will be printed:
|
|||
> assert mydata == 23
|
||||
E assert 42 == 23
|
||||
|
||||
test_caching.py:17: AssertionError
|
||||
1 failed in 0.12 seconds
|
||||
test_caching.py:20: AssertionError
|
||||
1 failed in 0.12s
|
||||
|
||||
See the :ref:`cache-api` for more details.
|
||||
|
||||
|
@ -275,7 +283,7 @@ You can always peek at the content of the cache using the
|
|||
example/value contains:
|
||||
42
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
========================== no tests ran in 0.12s ===========================
|
||||
|
||||
``--cache-show`` takes an optional argument to specify a glob pattern for
|
||||
filtering:
|
||||
|
@ -292,7 +300,7 @@ filtering:
|
|||
example/value contains:
|
||||
42
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
========================== no tests ran in 0.12s ===========================
|
||||
|
||||
Clearing Cache content
|
||||
----------------------
|
||||
|
|
|
@ -49,16 +49,21 @@ Using print statements for debugging
|
|||
---------------------------------------------------
|
||||
|
||||
One primary benefit of the default capturing of stdout/stderr output
|
||||
is that you can use print statements for debugging::
|
||||
is that you can use print statements for debugging:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_module.py
|
||||
|
||||
|
||||
def setup_function(function):
|
||||
print("setting up %s" % function)
|
||||
print("setting up", function)
|
||||
|
||||
|
||||
def test_func1():
|
||||
assert True
|
||||
|
||||
|
||||
def test_func2():
|
||||
assert False
|
||||
|
||||
|
@ -83,10 +88,10 @@ of the failing function and hide the other one:
|
|||
> assert False
|
||||
E assert False
|
||||
|
||||
test_module.py:9: AssertionError
|
||||
test_module.py:12: AssertionError
|
||||
-------------------------- Captured stdout setup ---------------------------
|
||||
setting up <function test_func2 at 0xdeadbeef>
|
||||
==================== 1 failed, 1 passed in 0.12 seconds ====================
|
||||
======================= 1 failed, 1 passed in 0.12s ========================
|
||||
|
||||
Accessing captured output from a test function
|
||||
---------------------------------------------------
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
#
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
# The short X.Y version.
|
||||
import datetime
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
@ -63,8 +62,7 @@ master_doc = "contents"
|
|||
|
||||
# General information about the project.
|
||||
project = "pytest"
|
||||
year = datetime.datetime.utcnow().year
|
||||
copyright = "2015–2019 , holger krekel and pytest-dev team"
|
||||
copyright = "2015–2019, holger krekel and pytest-dev team"
|
||||
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
|
@ -167,18 +165,18 @@ html_favicon = "img/pytest1favi.ico"
|
|||
|
||||
html_sidebars = {
|
||||
"index": [
|
||||
"slim_searchbox.html",
|
||||
"sidebarintro.html",
|
||||
"globaltoc.html",
|
||||
"links.html",
|
||||
"sourcelink.html",
|
||||
"searchbox.html",
|
||||
],
|
||||
"**": [
|
||||
"slim_searchbox.html",
|
||||
"globaltoc.html",
|
||||
"relations.html",
|
||||
"links.html",
|
||||
"sourcelink.html",
|
||||
"searchbox.html",
|
||||
],
|
||||
}
|
||||
|
||||
|
|
|
@ -107,8 +107,8 @@ check for ini-files as follows:
|
|||
|
||||
# first look for pytest.ini files
|
||||
path/pytest.ini
|
||||
path/setup.cfg # must also contain [tool:pytest] section to match
|
||||
path/tox.ini # must also contain [pytest] section to match
|
||||
path/setup.cfg # must also contain [tool:pytest] section to match
|
||||
pytest.ini
|
||||
... # all the way down to the root
|
||||
|
||||
|
@ -134,10 +134,13 @@ progress output, you can write it into a configuration file:
|
|||
.. code-block:: ini
|
||||
|
||||
# content of pytest.ini or tox.ini
|
||||
# setup.cfg files should use [tool:pytest] section instead
|
||||
[pytest]
|
||||
addopts = -ra -q
|
||||
|
||||
# content of setup.cfg
|
||||
[tool:pytest]
|
||||
addopts = -ra -q
|
||||
|
||||
Alternatively, you can set a ``PYTEST_ADDOPTS`` environment variable to add command
|
||||
line options while the environment is in use:
|
||||
|
||||
|
|
|
@ -459,7 +459,9 @@ Internal classes accessed through ``Node``
|
|||
.. versionremoved:: 4.0
|
||||
|
||||
Access of ``Module``, ``Function``, ``Class``, ``Instance``, ``File`` and ``Item`` through ``Node`` instances now issue
|
||||
this warning::
|
||||
this warning:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
usage of Function.Module is deprecated, please use pytest.Module instead
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ then you can just invoke ``pytest`` directly:
|
|||
|
||||
test_example.txt . [100%]
|
||||
|
||||
========================= 1 passed in 0.12 seconds =========================
|
||||
============================ 1 passed in 0.12s =============================
|
||||
|
||||
By default, pytest will collect ``test*.txt`` files looking for doctest directives, but you
|
||||
can pass additional globs using the ``--doctest-glob`` option (multi-allowed).
|
||||
|
@ -66,7 +66,7 @@ and functions, including from test modules:
|
|||
mymodule.py . [ 50%]
|
||||
test_example.txt . [100%]
|
||||
|
||||
========================= 2 passed in 0.12 seconds =========================
|
||||
============================ 2 passed in 0.12s =============================
|
||||
|
||||
You can make these changes permanent in your project by
|
||||
putting them into a pytest.ini file like this:
|
||||
|
@ -156,6 +156,8 @@ pytest also introduces new options:
|
|||
a string! This means that it may not be appropriate to enable globally in
|
||||
``doctest_optionflags`` in your configuration file.
|
||||
|
||||
.. versionadded:: 5.1
|
||||
|
||||
|
||||
Continue on failure
|
||||
-------------------
|
||||
|
@ -218,15 +220,21 @@ namespace in which your doctests run. It is intended to be used within
|
|||
your own fixtures to provide the tests that use them with context.
|
||||
|
||||
``doctest_namespace`` is a standard ``dict`` object into which you
|
||||
place the objects you want to appear in the doctest namespace::
|
||||
place the objects you want to appear in the doctest namespace:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of conftest.py
|
||||
import numpy
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def add_np(doctest_namespace):
|
||||
doctest_namespace['np'] = numpy
|
||||
doctest_namespace["np"] = numpy
|
||||
|
||||
which can then be used in your doctests directly::
|
||||
which can then be used in your doctests directly:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of numpy.py
|
||||
def arange():
|
||||
|
@ -246,7 +254,9 @@ Skipping tests dynamically
|
|||
|
||||
.. versionadded:: 4.4
|
||||
|
||||
You can use ``pytest.skip`` to dynamically skip doctests. For example::
|
||||
You can use ``pytest.skip`` to dynamically skip doctests. For example:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
>>> import sys, pytest
|
||||
>>> if sys.platform.startswith('win'):
|
||||
|
|
|
@ -177,7 +177,7 @@ class TestRaises:
|
|||
|
||||
def test_reinterpret_fails_with_print_for_the_fun_of_it(self):
|
||||
items = [1, 2, 3]
|
||||
print("items is %r" % items)
|
||||
print("items is {!r}".format(items))
|
||||
a, b = items.pop()
|
||||
|
||||
def test_some_error(self):
|
||||
|
|
|
@ -18,7 +18,7 @@ example: specifying and selecting acceptance tests
|
|||
return AcceptFixture(request)
|
||||
|
||||
|
||||
class AcceptFixture(object):
|
||||
class AcceptFixture:
|
||||
def __init__(self, request):
|
||||
if not request.config.getoption("acceptance"):
|
||||
pytest.skip("specify -A to run acceptance tests")
|
||||
|
@ -65,7 +65,7 @@ extend the `accept example`_ by putting this in our test module:
|
|||
return arg
|
||||
|
||||
|
||||
class TestSpecialAcceptance(object):
|
||||
class TestSpecialAcceptance:
|
||||
def test_sometest(self, accept):
|
||||
assert accept.tmpdir.join("special").check()
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture("session")
|
||||
@pytest.fixture(scope="session")
|
||||
def setup(request):
|
||||
setup = CostlySetup()
|
||||
yield setup
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
import pytest
|
||||
|
||||
# fixtures documentation order example
|
||||
order = []
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def s1():
|
||||
order.append("s1")
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def m1():
|
||||
order.append("m1")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def f1(f3):
|
||||
order.append("f1")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def f3():
|
||||
order.append("f3")
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def a1():
|
||||
order.append("a1")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def f2():
|
||||
order.append("f2")
|
||||
|
||||
|
||||
def test_order(f1, m1, f2, s1):
|
||||
assert order == ["s1", "m1", "a1", "f3", "f1", "f2"]
|
|
@ -33,7 +33,7 @@ You can "mark" a test function with custom metadata like this:
|
|||
pass
|
||||
|
||||
|
||||
class TestClass(object):
|
||||
class TestClass:
|
||||
def test_method(self):
|
||||
pass
|
||||
|
||||
|
@ -52,7 +52,7 @@ You can then restrict a test run to only run tests marked with ``webtest``:
|
|||
|
||||
test_server.py::test_send_http PASSED [100%]
|
||||
|
||||
================== 1 passed, 3 deselected in 0.12 seconds ==================
|
||||
===================== 1 passed, 3 deselected in 0.12s ======================
|
||||
|
||||
Or the inverse, running all tests except the webtest ones:
|
||||
|
||||
|
@ -69,7 +69,7 @@ Or the inverse, running all tests except the webtest ones:
|
|||
test_server.py::test_another PASSED [ 66%]
|
||||
test_server.py::TestClass::test_method PASSED [100%]
|
||||
|
||||
================== 3 passed, 1 deselected in 0.12 seconds ==================
|
||||
===================== 3 passed, 1 deselected in 0.12s ======================
|
||||
|
||||
Selecting tests based on their node ID
|
||||
--------------------------------------
|
||||
|
@ -89,7 +89,7 @@ tests based on their module, class, method, or function name:
|
|||
|
||||
test_server.py::TestClass::test_method PASSED [100%]
|
||||
|
||||
========================= 1 passed in 0.12 seconds =========================
|
||||
============================ 1 passed in 0.12s =============================
|
||||
|
||||
You can also select on the class:
|
||||
|
||||
|
@ -104,7 +104,7 @@ You can also select on the class:
|
|||
|
||||
test_server.py::TestClass::test_method PASSED [100%]
|
||||
|
||||
========================= 1 passed in 0.12 seconds =========================
|
||||
============================ 1 passed in 0.12s =============================
|
||||
|
||||
Or select multiple nodes:
|
||||
|
||||
|
@ -120,7 +120,7 @@ Or select multiple nodes:
|
|||
test_server.py::TestClass::test_method PASSED [ 50%]
|
||||
test_server.py::test_send_http PASSED [100%]
|
||||
|
||||
========================= 2 passed in 0.12 seconds =========================
|
||||
============================ 2 passed in 0.12s =============================
|
||||
|
||||
.. _node-id:
|
||||
|
||||
|
@ -159,7 +159,7 @@ select tests based on their names:
|
|||
|
||||
test_server.py::test_send_http PASSED [100%]
|
||||
|
||||
================== 1 passed, 3 deselected in 0.12 seconds ==================
|
||||
===================== 1 passed, 3 deselected in 0.12s ======================
|
||||
|
||||
And you can also run all tests except the ones that match the keyword:
|
||||
|
||||
|
@ -176,7 +176,7 @@ And you can also run all tests except the ones that match the keyword:
|
|||
test_server.py::test_another PASSED [ 66%]
|
||||
test_server.py::TestClass::test_method PASSED [100%]
|
||||
|
||||
================== 3 passed, 1 deselected in 0.12 seconds ==================
|
||||
===================== 3 passed, 1 deselected in 0.12s ======================
|
||||
|
||||
Or to select "http" and "quick" tests:
|
||||
|
||||
|
@ -192,7 +192,7 @@ Or to select "http" and "quick" tests:
|
|||
test_server.py::test_send_http PASSED [ 50%]
|
||||
test_server.py::test_something_quick PASSED [100%]
|
||||
|
||||
================== 2 passed, 2 deselected in 0.12 seconds ==================
|
||||
===================== 2 passed, 2 deselected in 0.12s ======================
|
||||
|
||||
.. note::
|
||||
|
||||
|
@ -278,7 +278,7 @@ its test methods:
|
|||
|
||||
|
||||
@pytest.mark.webtest
|
||||
class TestClass(object):
|
||||
class TestClass:
|
||||
def test_startup(self):
|
||||
pass
|
||||
|
||||
|
@ -295,7 +295,7 @@ Due to legacy reasons, it is possible to set the ``pytestmark`` attribute on a T
|
|||
import pytest
|
||||
|
||||
|
||||
class TestClass(object):
|
||||
class TestClass:
|
||||
pytestmark = pytest.mark.webtest
|
||||
|
||||
or if you need to use multiple markers you can use a list:
|
||||
|
@ -305,7 +305,7 @@ or if you need to use multiple markers you can use a list:
|
|||
import pytest
|
||||
|
||||
|
||||
class TestClass(object):
|
||||
class TestClass:
|
||||
pytestmark = [pytest.mark.webtest, pytest.mark.slowtest]
|
||||
|
||||
You can also set a module level marker::
|
||||
|
@ -336,7 +336,7 @@ apply a marker to an individual test instance:
|
|||
|
||||
@pytest.mark.foo
|
||||
@pytest.mark.parametrize(
|
||||
("n", "expected"), [(1, 2), pytest.param((1, 3), marks=pytest.mark.bar), (2, 3)]
|
||||
("n", "expected"), [(1, 2), pytest.param(1, 3, marks=pytest.mark.bar), (2, 3)]
|
||||
)
|
||||
def test_increment(n, expected):
|
||||
assert n + 1 == expected
|
||||
|
@ -384,7 +384,7 @@ specifies via named environments:
|
|||
envnames = [mark.args[0] for mark in item.iter_markers(name="env")]
|
||||
if envnames:
|
||||
if item.config.getoption("-E") not in envnames:
|
||||
pytest.skip("test requires env in %r" % envnames)
|
||||
pytest.skip("test requires env in {!r}".format(envnames))
|
||||
|
||||
A test file using this local plugin:
|
||||
|
||||
|
@ -413,7 +413,7 @@ the test needs:
|
|||
|
||||
test_someenv.py s [100%]
|
||||
|
||||
======================== 1 skipped in 0.12 seconds =========================
|
||||
============================ 1 skipped in 0.12s ============================
|
||||
|
||||
and here is one that specifies exactly the environment needed:
|
||||
|
||||
|
@ -428,7 +428,7 @@ and here is one that specifies exactly the environment needed:
|
|||
|
||||
test_someenv.py . [100%]
|
||||
|
||||
========================= 1 passed in 0.12 seconds =========================
|
||||
============================ 1 passed in 0.12s =============================
|
||||
|
||||
The ``--markers`` option always gives you a list of available markers:
|
||||
|
||||
|
@ -499,7 +499,7 @@ The output is as follows:
|
|||
$ pytest -q -s
|
||||
Mark(name='my_marker', args=(<function hello_world at 0xdeadbeef>,), kwargs={})
|
||||
.
|
||||
1 passed in 0.12 seconds
|
||||
1 passed in 0.12s
|
||||
|
||||
We can see that the custom marker has its argument set extended with the function ``hello_world``. This is the key difference between creating a custom marker as a callable, which invokes ``__call__`` behind the scenes, and using ``with_args``.
|
||||
|
||||
|
@ -523,7 +523,7 @@ code you can read over all such settings. Example:
|
|||
|
||||
|
||||
@pytest.mark.glob("class", x=2)
|
||||
class TestClass(object):
|
||||
class TestClass:
|
||||
@pytest.mark.glob("function", x=3)
|
||||
def test_something(self):
|
||||
pass
|
||||
|
@ -539,7 +539,7 @@ test function. From a conftest file we can read it like this:
|
|||
|
||||
def pytest_runtest_setup(item):
|
||||
for mark in item.iter_markers(name="glob"):
|
||||
print("glob args=%s kwargs=%s" % (mark.args, mark.kwargs))
|
||||
print("glob args={} kwargs={}".format(mark.args, mark.kwargs))
|
||||
sys.stdout.flush()
|
||||
|
||||
Let's run this without capturing output and see what we get:
|
||||
|
@ -551,7 +551,7 @@ Let's run this without capturing output and see what we get:
|
|||
glob args=('class',) kwargs={'x': 2}
|
||||
glob args=('module',) kwargs={'x': 1}
|
||||
.
|
||||
1 passed in 0.12 seconds
|
||||
1 passed in 0.12s
|
||||
|
||||
marking platform specific tests with pytest
|
||||
--------------------------------------------------------------
|
||||
|
@ -578,7 +578,7 @@ for your particular platform, you could use the following plugin:
|
|||
supported_platforms = ALL.intersection(mark.name for mark in item.iter_markers())
|
||||
plat = sys.platform
|
||||
if supported_platforms and plat not in supported_platforms:
|
||||
pytest.skip("cannot run on platform %s" % (plat))
|
||||
pytest.skip("cannot run on platform {}".format(plat))
|
||||
|
||||
then tests will be skipped if they were specified for a different platform.
|
||||
Let's do a little test file to show how this looks like:
|
||||
|
@ -623,7 +623,7 @@ then you will see two tests skipped and two executed tests as expected:
|
|||
|
||||
========================= short test summary info ==========================
|
||||
SKIPPED [2] $REGENDOC_TMPDIR/conftest.py:13: cannot run on platform linux
|
||||
=================== 2 passed, 2 skipped in 0.12 seconds ====================
|
||||
======================= 2 passed, 2 skipped in 0.12s =======================
|
||||
|
||||
Note that if you specify a platform via the marker-command line option like this:
|
||||
|
||||
|
@ -638,7 +638,7 @@ Note that if you specify a platform via the marker-command line option like this
|
|||
|
||||
test_plat.py . [100%]
|
||||
|
||||
================== 1 passed, 3 deselected in 0.12 seconds ==================
|
||||
===================== 1 passed, 3 deselected in 0.12s ======================
|
||||
|
||||
then the unmarked-tests will not be run. It is thus a way to restrict the run to the specific tests.
|
||||
|
||||
|
@ -711,7 +711,7 @@ We can now use the ``-m option`` to select one set:
|
|||
test_module.py:8: in test_interface_complex
|
||||
assert 0
|
||||
E assert 0
|
||||
================== 2 failed, 2 deselected in 0.12 seconds ==================
|
||||
===================== 2 failed, 2 deselected in 0.12s ======================
|
||||
|
||||
or to select both "event" and "interface" tests:
|
||||
|
||||
|
@ -739,4 +739,4 @@ or to select both "event" and "interface" tests:
|
|||
test_module.py:12: in test_event_simple
|
||||
assert 0
|
||||
E assert 0
|
||||
================== 3 failed, 1 deselected in 0.12 seconds ==================
|
||||
===================== 3 failed, 1 deselected in 0.12s ======================
|
||||
|
|
|
@ -69,4 +69,4 @@ class Python:
|
|||
@pytest.mark.parametrize("obj", [42, {}, {1: 3}])
|
||||
def test_basic_objects(python1, python2, obj):
|
||||
python1.dumps(obj)
|
||||
python2.load_and_is_true("obj == %s" % obj)
|
||||
python2.load_and_is_true("obj == {}".format(obj))
|
||||
|
|
|
@ -41,7 +41,7 @@ now execute the test specification:
|
|||
usecase execution failed
|
||||
spec failed: 'some': 'other'
|
||||
no further details known at this point.
|
||||
==================== 1 failed, 1 passed in 0.12 seconds ====================
|
||||
======================= 1 failed, 1 passed in 0.12s ========================
|
||||
|
||||
.. regendoc:wipe
|
||||
|
||||
|
@ -77,7 +77,7 @@ consulted when reporting in ``verbose`` mode:
|
|||
usecase execution failed
|
||||
spec failed: 'some': 'other'
|
||||
no further details known at this point.
|
||||
==================== 1 failed, 1 passed in 0.12 seconds ====================
|
||||
======================= 1 failed, 1 passed in 0.12s ========================
|
||||
|
||||
.. regendoc:wipe
|
||||
|
||||
|
@ -97,4 +97,4 @@ interesting to just look at the collection tree:
|
|||
<YamlItem hello>
|
||||
<YamlItem ok>
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
========================== no tests ran in 0.12s ===========================
|
||||
|
|
|
@ -33,13 +33,13 @@ class YamlItem(pytest.Item):
|
|||
return "\n".join(
|
||||
[
|
||||
"usecase execution failed",
|
||||
" spec failed: %r: %r" % excinfo.value.args[1:3],
|
||||
" spec failed: {1!r}: {2!r}".format(*excinfo.value.args),
|
||||
" no further details known at this point.",
|
||||
]
|
||||
)
|
||||
|
||||
def reportinfo(self):
|
||||
return self.fspath, 0, "usecase: %s" % self.name
|
||||
return self.fspath, 0, "usecase: {}".format(self.name)
|
||||
|
||||
|
||||
class YamlException(Exception):
|
||||
|
|
|
@ -19,24 +19,30 @@ Generating parameters combinations, depending on command line
|
|||
|
||||
Let's say we want to execute a test with different computation
|
||||
parameters and the parameter range shall be determined by a command
|
||||
line argument. Let's first write a simple (do-nothing) computation test::
|
||||
line argument. Let's first write a simple (do-nothing) computation test:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_compute.py
|
||||
|
||||
|
||||
def test_compute(param1):
|
||||
assert param1 < 4
|
||||
|
||||
Now we add a test configuration like this::
|
||||
Now we add a test configuration like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of conftest.py
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption("--all", action="store_true",
|
||||
help="run all combinations")
|
||||
parser.addoption("--all", action="store_true", help="run all combinations")
|
||||
|
||||
|
||||
def pytest_generate_tests(metafunc):
|
||||
if 'param1' in metafunc.fixturenames:
|
||||
if metafunc.config.getoption('all'):
|
||||
if "param1" in metafunc.fixturenames:
|
||||
if metafunc.config.getoption("all"):
|
||||
end = 5
|
||||
else:
|
||||
end = 2
|
||||
|
@ -48,7 +54,7 @@ This means that we only run 2 tests if we do not pass ``--all``:
|
|||
|
||||
$ pytest -q test_compute.py
|
||||
.. [100%]
|
||||
2 passed in 0.12 seconds
|
||||
2 passed in 0.12s
|
||||
|
||||
We run only two computations, so we see two dots.
|
||||
let's run the full monty:
|
||||
|
@ -66,8 +72,8 @@ let's run the full monty:
|
|||
> assert param1 < 4
|
||||
E assert 4 < 4
|
||||
|
||||
test_compute.py:3: AssertionError
|
||||
1 failed, 4 passed in 0.12 seconds
|
||||
test_compute.py:4: AssertionError
|
||||
1 failed, 4 passed in 0.12s
|
||||
|
||||
As expected when running the full range of ``param1`` values
|
||||
we'll get an error on the last one.
|
||||
|
@ -83,7 +89,9 @@ Running pytest with ``--collect-only`` will show the generated IDs.
|
|||
|
||||
Numbers, strings, booleans and None will have their usual string representation
|
||||
used in the test ID. For other objects, pytest will make a string based on
|
||||
the argument name::
|
||||
the argument name:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_time.py
|
||||
|
||||
|
@ -112,7 +120,7 @@ the argument name::
|
|||
def idfn(val):
|
||||
if isinstance(val, (datetime,)):
|
||||
# note this wouldn't show any hours/minutes/seconds
|
||||
return val.strftime('%Y%m%d')
|
||||
return val.strftime("%Y%m%d")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("a,b,expected", testdata, ids=idfn)
|
||||
|
@ -120,12 +128,18 @@ the argument name::
|
|||
diff = a - b
|
||||
assert diff == expected
|
||||
|
||||
@pytest.mark.parametrize("a,b,expected", [
|
||||
pytest.param(datetime(2001, 12, 12), datetime(2001, 12, 11),
|
||||
timedelta(1), id='forward'),
|
||||
pytest.param(datetime(2001, 12, 11), datetime(2001, 12, 12),
|
||||
timedelta(-1), id='backward'),
|
||||
])
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"a,b,expected",
|
||||
[
|
||||
pytest.param(
|
||||
datetime(2001, 12, 12), datetime(2001, 12, 11), timedelta(1), id="forward"
|
||||
),
|
||||
pytest.param(
|
||||
datetime(2001, 12, 11), datetime(2001, 12, 12), timedelta(-1), id="backward"
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_timedistance_v3(a, b, expected):
|
||||
diff = a - b
|
||||
assert diff == expected
|
||||
|
@ -158,7 +172,7 @@ objects, they are still using the default pytest representation:
|
|||
<Function test_timedistance_v3[forward]>
|
||||
<Function test_timedistance_v3[backward]>
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
========================== no tests ran in 0.12s ===========================
|
||||
|
||||
In ``test_timedistance_v3``, we used ``pytest.param`` to specify the test IDs
|
||||
together with the actual data, instead of listing them separately.
|
||||
|
@ -171,10 +185,13 @@ A quick port of "testscenarios"
|
|||
Here is a quick port to run tests configured with `test scenarios`_,
|
||||
an add-on from Robert Collins for the standard unittest framework. We
|
||||
only have to work a bit to construct the correct arguments for pytest's
|
||||
:py:func:`Metafunc.parametrize`::
|
||||
:py:func:`Metafunc.parametrize`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_scenarios.py
|
||||
|
||||
|
||||
def pytest_generate_tests(metafunc):
|
||||
idlist = []
|
||||
argvalues = []
|
||||
|
@ -182,13 +199,15 @@ only have to work a bit to construct the correct arguments for pytest's
|
|||
idlist.append(scenario[0])
|
||||
items = scenario[1].items()
|
||||
argnames = [x[0] for x in items]
|
||||
argvalues.append(([x[1] for x in items]))
|
||||
argvalues.append([x[1] for x in items])
|
||||
metafunc.parametrize(argnames, argvalues, ids=idlist, scope="class")
|
||||
|
||||
scenario1 = ('basic', {'attribute': 'value'})
|
||||
scenario2 = ('advanced', {'attribute': 'value2'})
|
||||
|
||||
class TestSampleWithScenarios(object):
|
||||
scenario1 = ("basic", {"attribute": "value"})
|
||||
scenario2 = ("advanced", {"attribute": "value2"})
|
||||
|
||||
|
||||
class TestSampleWithScenarios:
|
||||
scenarios = [scenario1, scenario2]
|
||||
|
||||
def test_demo1(self, attribute):
|
||||
|
@ -210,7 +229,7 @@ this is a fully self-contained example which you can run with:
|
|||
|
||||
test_scenarios.py .... [100%]
|
||||
|
||||
========================= 4 passed in 0.12 seconds =========================
|
||||
============================ 4 passed in 0.12s =============================
|
||||
|
||||
If you just collect tests you'll also nicely see 'advanced' and 'basic' as variants for the test function:
|
||||
|
||||
|
@ -229,7 +248,7 @@ If you just collect tests you'll also nicely see 'advanced' and 'basic' as varia
|
|||
<Function test_demo1[advanced]>
|
||||
<Function test_demo2[advanced]>
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
========================== no tests ran in 0.12s ===========================
|
||||
|
||||
Note that we told ``metafunc.parametrize()`` that your scenario values
|
||||
should be considered class-scoped. With pytest-2.3 this leads to a
|
||||
|
@ -243,12 +262,16 @@ Deferring the setup of parametrized resources
|
|||
The parametrization of test functions happens at collection
|
||||
time. It is a good idea to setup expensive resources like DB
|
||||
connections or subprocess only when the actual test is run.
|
||||
Here is a simple example how you can achieve that, first
|
||||
the actual test requiring a ``db`` object::
|
||||
Here is a simple example how you can achieve that. This test
|
||||
requires a ``db`` object fixture:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_backends.py
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def test_db_initialized(db):
|
||||
# a dummy test
|
||||
if db.__class__.__name__ == "DB2":
|
||||
|
@ -256,20 +279,27 @@ the actual test requiring a ``db`` object::
|
|||
|
||||
We can now add a test configuration that generates two invocations of
|
||||
the ``test_db_initialized`` function and also implements a factory that
|
||||
creates a database object for the actual test invocations::
|
||||
creates a database object for the actual test invocations:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of conftest.py
|
||||
import pytest
|
||||
|
||||
def pytest_generate_tests(metafunc):
|
||||
if 'db' in metafunc.fixturenames:
|
||||
metafunc.parametrize("db", ['d1', 'd2'], indirect=True)
|
||||
|
||||
class DB1(object):
|
||||
def pytest_generate_tests(metafunc):
|
||||
if "db" in metafunc.fixturenames:
|
||||
metafunc.parametrize("db", ["d1", "d2"], indirect=True)
|
||||
|
||||
|
||||
class DB1:
|
||||
"one database object"
|
||||
class DB2(object):
|
||||
|
||||
|
||||
class DB2:
|
||||
"alternative database object"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def db(request):
|
||||
if request.param == "d1":
|
||||
|
@ -293,7 +323,7 @@ Let's first see how it looks like at collection time:
|
|||
<Function test_db_initialized[d1]>
|
||||
<Function test_db_initialized[d2]>
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
========================== no tests ran in 0.12s ===========================
|
||||
|
||||
And then when we run the test:
|
||||
|
||||
|
@ -312,8 +342,8 @@ And then when we run the test:
|
|||
> pytest.fail("deliberately failing for demo purposes")
|
||||
E Failed: deliberately failing for demo purposes
|
||||
|
||||
test_backends.py:6: Failed
|
||||
1 failed, 1 passed in 0.12 seconds
|
||||
test_backends.py:8: Failed
|
||||
1 failed, 1 passed in 0.12s
|
||||
|
||||
The first invocation with ``db == "DB1"`` passed while the second with ``db == "DB2"`` failed. Our ``db`` fixture function has instantiated each of the DB values during the setup phase while the ``pytest_generate_tests`` generated two according calls to the ``test_db_initialized`` during the collection phase.
|
||||
|
||||
|
@ -327,23 +357,29 @@ parameter on particular arguments. It can be done by passing list or tuple of
|
|||
arguments' names to ``indirect``. In the example below there is a function ``test_indirect`` which uses
|
||||
two fixtures: ``x`` and ``y``. Here we give to indirect the list, which contains the name of the
|
||||
fixture ``x``. The indirect parameter will be applied to this argument only, and the value ``a``
|
||||
will be passed to respective fixture function::
|
||||
will be passed to respective fixture function:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_indirect_list.py
|
||||
|
||||
import pytest
|
||||
@pytest.fixture(scope='function')
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def x(request):
|
||||
return request.param * 3
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def y(request):
|
||||
return request.param * 2
|
||||
|
||||
@pytest.mark.parametrize('x, y', [('a', 'b')], indirect=['x'])
|
||||
def test_indirect(x,y):
|
||||
assert x == 'aaa'
|
||||
assert y == 'b'
|
||||
|
||||
@pytest.mark.parametrize("x, y", [("a", "b")], indirect=["x"])
|
||||
def test_indirect(x, y):
|
||||
assert x == "aaa"
|
||||
assert y == "b"
|
||||
|
||||
The result of this test will be successful:
|
||||
|
||||
|
@ -358,7 +394,7 @@ The result of this test will be successful:
|
|||
<Module test_indirect_list.py>
|
||||
<Function test_indirect[a-b]>
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
========================== no tests ran in 0.12s ===========================
|
||||
|
||||
.. regendoc:wipe
|
||||
|
||||
|
@ -370,23 +406,28 @@ Parametrizing test methods through per-class configuration
|
|||
|
||||
Here is an example ``pytest_generate_tests`` function implementing a
|
||||
parametrization scheme similar to Michael Foord's `unittest
|
||||
parametrizer`_ but in a lot less code::
|
||||
parametrizer`_ but in a lot less code:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of ./test_parametrize.py
|
||||
import pytest
|
||||
|
||||
|
||||
def pytest_generate_tests(metafunc):
|
||||
# called once per each test function
|
||||
funcarglist = metafunc.cls.params[metafunc.function.__name__]
|
||||
argnames = sorted(funcarglist[0])
|
||||
metafunc.parametrize(argnames, [[funcargs[name] for name in argnames]
|
||||
for funcargs in funcarglist])
|
||||
metafunc.parametrize(
|
||||
argnames, [[funcargs[name] for name in argnames] for funcargs in funcarglist]
|
||||
)
|
||||
|
||||
class TestClass(object):
|
||||
|
||||
class TestClass:
|
||||
# a map specifying multiple argument sets for a test method
|
||||
params = {
|
||||
'test_equals': [dict(a=1, b=2), dict(a=3, b=3), ],
|
||||
'test_zerodivision': [dict(a=1, b=0), ],
|
||||
"test_equals": [dict(a=1, b=2), dict(a=3, b=3)],
|
||||
"test_zerodivision": [dict(a=1, b=0)],
|
||||
}
|
||||
|
||||
def test_equals(self, a, b):
|
||||
|
@ -412,8 +453,8 @@ argument sets to use for each test function. Let's run it:
|
|||
> assert a == b
|
||||
E assert 1 == 2
|
||||
|
||||
test_parametrize.py:18: AssertionError
|
||||
1 failed, 2 passed in 0.12 seconds
|
||||
test_parametrize.py:21: AssertionError
|
||||
1 failed, 2 passed in 0.12s
|
||||
|
||||
Indirect parametrization with multiple fixtures
|
||||
--------------------------------------------------------------
|
||||
|
@ -434,11 +475,11 @@ Running it results in some skips if we don't have all the python interpreters in
|
|||
.. code-block:: pytest
|
||||
|
||||
. $ pytest -rs -q multipython.py
|
||||
ssssssssssss...ssssssssssss [100%]
|
||||
ssssssssssssssssssssssss... [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.7' not found
|
||||
3 passed, 24 skipped in 0.12 seconds
|
||||
SKIPPED [12] $REGENDOC_TMPDIR/CWD/multipython.py:30: 'python3.6' not found
|
||||
3 passed, 24 skipped in 0.12s
|
||||
|
||||
Indirect parametrization of optional implementations/imports
|
||||
--------------------------------------------------------------------
|
||||
|
@ -447,36 +488,47 @@ If you want to compare the outcomes of several implementations of a given
|
|||
API, you can write test functions that receive the already imported implementations
|
||||
and get skipped in case the implementation is not importable/available. Let's
|
||||
say we have a "base" implementation and the other (possibly optimized ones)
|
||||
need to provide similar results::
|
||||
need to provide similar results:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of conftest.py
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def basemod(request):
|
||||
return pytest.importorskip("base")
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", params=["opt1", "opt2"])
|
||||
def optmod(request):
|
||||
return pytest.importorskip(request.param)
|
||||
|
||||
And then a base implementation of a simple function::
|
||||
And then a base implementation of a simple function:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of base.py
|
||||
def func1():
|
||||
return 1
|
||||
|
||||
And an optimized version::
|
||||
And an optimized version:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of opt1.py
|
||||
def func1():
|
||||
return 1.0001
|
||||
|
||||
And finally a little test module::
|
||||
And finally a little test module:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_module.py
|
||||
|
||||
|
||||
def test_func1(basemod, optmod):
|
||||
assert round(basemod.func1(), 3) == round(optmod.func1(), 3)
|
||||
|
||||
|
@ -495,8 +547,8 @@ If you run this with reporting for skips enabled:
|
|||
test_module.py .s [100%]
|
||||
|
||||
========================= short test summary info ==========================
|
||||
SKIPPED [1] $REGENDOC_TMPDIR/conftest.py:11: could not import 'opt2': No module named 'opt2'
|
||||
=================== 1 passed, 1 skipped in 0.12 seconds ====================
|
||||
SKIPPED [1] $REGENDOC_TMPDIR/conftest.py:13: 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
|
||||
of our ``test_func1`` was skipped. A few notes:
|
||||
|
@ -552,13 +604,13 @@ Then run ``pytest`` with verbose mode and with only the ``basic`` marker:
|
|||
platform linux -- Python 3.x.y, pytest-5.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collecting ... collected 17 items / 14 deselected / 3 selected
|
||||
collecting ... collected 18 items / 15 deselected / 3 selected
|
||||
|
||||
test_pytest_param_example.py::test_eval[1+7-8] PASSED [ 33%]
|
||||
test_pytest_param_example.py::test_eval[basic_2+4] PASSED [ 66%]
|
||||
test_pytest_param_example.py::test_eval[basic_6*9] XFAIL [100%]
|
||||
|
||||
============ 2 passed, 14 deselected, 1 xfailed in 0.12 seconds ============
|
||||
=============== 2 passed, 15 deselected, 1 xfailed in 0.12s ================
|
||||
|
||||
As the result:
|
||||
|
||||
|
@ -579,22 +631,28 @@ Use :func:`pytest.raises` with the
|
|||
in which some tests raise exceptions and others do not.
|
||||
|
||||
It is helpful to define a no-op context manager ``does_not_raise`` to serve
|
||||
as a complement to ``raises``. For example::
|
||||
as a complement to ``raises``. For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from contextlib import contextmanager
|
||||
import pytest
|
||||
|
||||
|
||||
@contextmanager
|
||||
def does_not_raise():
|
||||
yield
|
||||
|
||||
|
||||
@pytest.mark.parametrize('example_input,expectation', [
|
||||
(3, does_not_raise()),
|
||||
(2, does_not_raise()),
|
||||
(1, does_not_raise()),
|
||||
(0, pytest.raises(ZeroDivisionError)),
|
||||
])
|
||||
@pytest.mark.parametrize(
|
||||
"example_input,expectation",
|
||||
[
|
||||
(3, does_not_raise()),
|
||||
(2, does_not_raise()),
|
||||
(1, does_not_raise()),
|
||||
(0, pytest.raises(ZeroDivisionError)),
|
||||
],
|
||||
)
|
||||
def test_division(example_input, expectation):
|
||||
"""Test how much I know division."""
|
||||
with expectation:
|
||||
|
@ -604,14 +662,20 @@ In the example above, the first three test cases should run unexceptionally,
|
|||
while the fourth should raise ``ZeroDivisionError``.
|
||||
|
||||
If you're only supporting Python 3.7+, you can simply use ``nullcontext``
|
||||
to define ``does_not_raise``::
|
||||
to define ``does_not_raise``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from contextlib import nullcontext as does_not_raise
|
||||
|
||||
Or, if you're supporting Python 3.3+ you can use::
|
||||
Or, if you're supporting Python 3.3+ you can use:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from contextlib import ExitStack as does_not_raise
|
||||
|
||||
Or, if desired, you can ``pip install contextlib2`` and use::
|
||||
Or, if desired, you can ``pip install contextlib2`` and use:
|
||||
|
||||
from contextlib2 import ExitStack as does_not_raise
|
||||
.. code-block:: python
|
||||
|
||||
from contextlib2 import nullcontext as does_not_raise
|
||||
|
|
|
@ -31,7 +31,7 @@ you will see that ``pytest`` only collects test-modules, which do not match the
|
|||
.. code-block:: pytest
|
||||
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
platform linux -- Python 3.x.y, pytest-5.x.y, py-1.x.y, pluggy-0.x.y
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 5 items
|
||||
|
||||
|
@ -131,12 +131,15 @@ Here is an example:
|
|||
|
||||
This would make ``pytest`` look for tests in files that match the ``check_*
|
||||
.py`` glob-pattern, ``Check`` prefixes in classes, and functions and methods
|
||||
that match ``*_check``. For example, if we have::
|
||||
that match ``*_check``. For example, if we have:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of check_myapp.py
|
||||
class CheckMyApp(object):
|
||||
class CheckMyApp:
|
||||
def simple_check(self):
|
||||
pass
|
||||
|
||||
def complex_check(self):
|
||||
pass
|
||||
|
||||
|
@ -155,7 +158,7 @@ The test collection would look like this:
|
|||
<Function simple_check>
|
||||
<Function complex_check>
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
========================== no tests ran in 0.12s ===========================
|
||||
|
||||
You can check for multiple glob patterns by adding a space between the patterns:
|
||||
|
||||
|
@ -218,7 +221,7 @@ You can always peek at the collection tree without running tests like this:
|
|||
<Function test_method>
|
||||
<Function test_anothermethod>
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
========================== no tests ran in 0.12s ===========================
|
||||
|
||||
.. _customizing-test-collection:
|
||||
|
||||
|
@ -238,7 +241,9 @@ You can easily instruct ``pytest`` to discover tests from every Python file:
|
|||
However, many projects will have a ``setup.py`` which they don't want to be
|
||||
imported. Moreover, there may files only importable by a specific python
|
||||
version. For such cases you can dynamically define files to be ignored by
|
||||
listing them in a ``conftest.py`` file::
|
||||
listing them in a ``conftest.py`` file:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of conftest.py
|
||||
import sys
|
||||
|
@ -247,7 +252,9 @@ listing them in a ``conftest.py`` file::
|
|||
if sys.version_info[0] > 2:
|
||||
collect_ignore.append("pkg/module_py2.py")
|
||||
|
||||
and then if you have a module file like this::
|
||||
and then if you have a module file like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of pkg/module_py2.py
|
||||
def test_only_on_python2():
|
||||
|
@ -256,10 +263,12 @@ and then if you have a module file like this::
|
|||
except Exception, e:
|
||||
pass
|
||||
|
||||
and a ``setup.py`` dummy file like this::
|
||||
and a ``setup.py`` dummy file like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of setup.py
|
||||
0/0 # will raise exception if imported
|
||||
0 / 0 # will raise exception if imported
|
||||
|
||||
If you run with a Python 2 interpreter then you will find the one test and will
|
||||
leave out the ``setup.py`` file:
|
||||
|
@ -288,14 +297,16 @@ file will be left out:
|
|||
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
|
||||
collected 0 items
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
========================== no tests ran in 0.12s ===========================
|
||||
|
||||
It's also possible to ignore files based on Unix shell-style wildcards by adding
|
||||
patterns to ``collect_ignore_glob``.
|
||||
|
||||
The following example ``conftest.py`` ignores the file ``setup.py`` and in
|
||||
addition all files that end with ``*_py2.py`` when executed with a Python 3
|
||||
interpreter::
|
||||
interpreter:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of conftest.py
|
||||
import sys
|
||||
|
|
|
@ -119,7 +119,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
a = "1" * 100 + "a" + "2" * 100
|
||||
b = "1" * 100 + "b" + "2" * 100
|
||||
> assert a == b
|
||||
E AssertionError: assert '111111111111...2222222222222' == '1111111111111...2222222222222'
|
||||
E AssertionError: assert '111111111111...2222222222222' == '111111111111...2222222222222'
|
||||
E Skipping 90 identical leading characters in diff, use -v to show
|
||||
E Skipping 91 identical trailing characters in diff, use -v to show
|
||||
E - 1111111111a222222222
|
||||
|
@ -136,7 +136,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
a = "1\n" * 100 + "a" + "2\n" * 100
|
||||
b = "1\n" * 100 + "b" + "2\n" * 100
|
||||
> assert a == b
|
||||
E AssertionError: assert '1\n1\n1\n1\n...n2\n2\n2\n2\n' == '1\n1\n1\n1\n1...n2\n2\n2\n2\n'
|
||||
E AssertionError: assert '1\n1\n1\n1\n...n2\n2\n2\n2\n' == '1\n1\n1\n1\n...n2\n2\n2\n2\n'
|
||||
E Skipping 190 identical leading characters in diff, use -v to show
|
||||
E Skipping 191 identical trailing characters in diff, use -v to show
|
||||
E 1
|
||||
|
@ -235,7 +235,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
def test_not_in_text_multiline(self):
|
||||
text = "some multiline\ntext\nwhich\nincludes foo\nand a\ntail"
|
||||
> assert "foo" not in text
|
||||
E AssertionError: assert 'foo' not in 'some multiline\ntext\nw...ncludes foo\nand a\ntail'
|
||||
E AssertionError: assert 'foo' not in 'some multil...nand a\ntail'
|
||||
E 'foo' is contained here:
|
||||
E some multiline
|
||||
E text
|
||||
|
@ -267,7 +267,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
def test_not_in_text_single_long(self):
|
||||
text = "head " * 50 + "foo " + "tail " * 20
|
||||
> assert "foo" not in text
|
||||
E AssertionError: assert 'foo' not in 'head head head head hea...ail tail tail tail tail '
|
||||
E AssertionError: assert 'foo' not in 'head head h...l tail tail '
|
||||
E 'foo' is contained here:
|
||||
E head head foo tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail
|
||||
E ? +++
|
||||
|
@ -280,7 +280,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
def test_not_in_text_single_long_term(self):
|
||||
text = "head " * 50 + "f" * 70 + "tail " * 20
|
||||
> assert "f" * 70 not in text
|
||||
E AssertionError: assert 'fffffffffff...ffffffffffff' not in 'head head he...l tail tail '
|
||||
E AssertionError: assert 'fffffffffff...ffffffffffff' not in 'head head h...l tail tail '
|
||||
E 'ffffffffffffffffff...fffffffffffffffffff' is contained here:
|
||||
E head head fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffftail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail
|
||||
E ? ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
@ -301,7 +301,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
left = Foo(1, "b")
|
||||
right = Foo(1, "c")
|
||||
> assert left == right
|
||||
E AssertionError: assert TestSpecialis...oo(a=1, b='b') == TestSpecialise...oo(a=1, b='c')
|
||||
E AssertionError: assert TestSpecialis...oo(a=1, b='b') == TestSpecialis...oo(a=1, b='c')
|
||||
E Omitting 1 identical items, use -vv to show
|
||||
E Differing attributes:
|
||||
E b: 'b' != 'c'
|
||||
|
@ -434,9 +434,9 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
|
||||
def test_reinterpret_fails_with_print_for_the_fun_of_it(self):
|
||||
items = [1, 2, 3]
|
||||
print("items is %r" % items)
|
||||
print("items is {!r}".format(items))
|
||||
> a, b = items.pop()
|
||||
E TypeError: 'int' object is not iterable
|
||||
E TypeError: cannot unpack non-iterable int object
|
||||
|
||||
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: 'int' object is not iterable
|
||||
E TypeError: cannot unpack non-iterable int object
|
||||
|
||||
failure_demo.py:222: TypeError
|
||||
______________________ TestMoreErrors.test_startswith ______________________
|
||||
|
@ -650,4 +650,4 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
E + where 1 = This is JSON\n{\n 'foo': 'bar'\n}.a
|
||||
|
||||
failure_demo.py:282: AssertionError
|
||||
======================== 44 failed in 0.12 seconds =========================
|
||||
============================ 44 failed in 0.12s ============================
|
||||
|
|
|
@ -65,7 +65,7 @@ Let's run this without supplying our new option:
|
|||
test_sample.py:6: AssertionError
|
||||
--------------------------- Captured stdout call ---------------------------
|
||||
first
|
||||
1 failed in 0.12 seconds
|
||||
1 failed in 0.12s
|
||||
|
||||
And now with supplying a command line option:
|
||||
|
||||
|
@ -89,7 +89,7 @@ And now with supplying a command line option:
|
|||
test_sample.py:6: AssertionError
|
||||
--------------------------- Captured stdout call ---------------------------
|
||||
second
|
||||
1 failed in 0.12 seconds
|
||||
1 failed in 0.12s
|
||||
|
||||
You can see that the command line option arrived in our test. This
|
||||
completes the basic pattern. However, one often rather wants to process
|
||||
|
@ -132,7 +132,7 @@ directory with the above conftest.py:
|
|||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 0 items
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
========================== no tests ran in 0.12s ===========================
|
||||
|
||||
.. _`excontrolskip`:
|
||||
|
||||
|
@ -201,7 +201,7 @@ and when running it will see a skipped "slow" test:
|
|||
|
||||
========================= short test summary info ==========================
|
||||
SKIPPED [1] test_module.py:8: need --runslow option to run
|
||||
=================== 1 passed, 1 skipped in 0.12 seconds ====================
|
||||
======================= 1 passed, 1 skipped in 0.12s =======================
|
||||
|
||||
Or run it including the ``slow`` marked test:
|
||||
|
||||
|
@ -216,7 +216,7 @@ Or run it including the ``slow`` marked test:
|
|||
|
||||
test_module.py .. [100%]
|
||||
|
||||
========================= 2 passed in 0.12 seconds =========================
|
||||
============================ 2 passed in 0.12s =============================
|
||||
|
||||
Writing well integrated assertion helpers
|
||||
--------------------------------------------------
|
||||
|
@ -238,7 +238,7 @@ Example:
|
|||
def checkconfig(x):
|
||||
__tracebackhide__ = True
|
||||
if not hasattr(x, "config"):
|
||||
pytest.fail("not configured: %s" % (x,))
|
||||
pytest.fail("not configured: {}".format(x))
|
||||
|
||||
|
||||
def test_something():
|
||||
|
@ -261,7 +261,7 @@ Let's run our little function:
|
|||
E Failed: not configured: 42
|
||||
|
||||
test_checkconfig.py:11: Failed
|
||||
1 failed in 0.12 seconds
|
||||
1 failed in 0.12s
|
||||
|
||||
If you only want to hide certain exceptions, you can set ``__tracebackhide__``
|
||||
to a callable which gets the ``ExceptionInfo`` object. You can for example use
|
||||
|
@ -280,7 +280,7 @@ this to make sure unexpected exception types aren't hidden:
|
|||
def checkconfig(x):
|
||||
__tracebackhide__ = operator.methodcaller("errisinstance", ConfigException)
|
||||
if not hasattr(x, "config"):
|
||||
raise ConfigException("not configured: %s" % (x,))
|
||||
raise ConfigException("not configured: {}".format(x))
|
||||
|
||||
|
||||
def test_something():
|
||||
|
@ -358,7 +358,7 @@ which will add the string to the test header accordingly:
|
|||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 0 items
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
========================== no tests ran in 0.12s ===========================
|
||||
|
||||
.. regendoc:wipe
|
||||
|
||||
|
@ -388,7 +388,7 @@ which will add info only when run with "--v":
|
|||
rootdir: $REGENDOC_TMPDIR
|
||||
collecting ... collected 0 items
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
========================== no tests ran in 0.12s ===========================
|
||||
|
||||
and nothing when run plainly:
|
||||
|
||||
|
@ -401,7 +401,7 @@ and nothing when run plainly:
|
|||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 0 items
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
========================== no tests ran in 0.12s ===========================
|
||||
|
||||
profiling test duration
|
||||
--------------------------
|
||||
|
@ -447,7 +447,7 @@ Now we can profile which test functions execute the slowest:
|
|||
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
|
||||
========================= 3 passed in 0.12 seconds =========================
|
||||
============================ 3 passed in 0.12s =============================
|
||||
|
||||
incremental testing - test steps
|
||||
---------------------------------------------------
|
||||
|
@ -478,7 +478,7 @@ an ``incremental`` marker which is to be used on classes:
|
|||
if "incremental" in item.keywords:
|
||||
previousfailed = getattr(item.parent, "_previousfailed", None)
|
||||
if previousfailed is not None:
|
||||
pytest.xfail("previous test failed (%s)" % previousfailed.name)
|
||||
pytest.xfail("previous test failed ({})".format(previousfailed.name))
|
||||
|
||||
These two hook implementations work together to abort incremental-marked
|
||||
tests in a class. Here is a test module example:
|
||||
|
@ -491,7 +491,7 @@ tests in a class. Here is a test module example:
|
|||
|
||||
|
||||
@pytest.mark.incremental
|
||||
class TestUserHandling(object):
|
||||
class TestUserHandling:
|
||||
def test_login(self):
|
||||
pass
|
||||
|
||||
|
@ -531,7 +531,7 @@ If we run this:
|
|||
========================= short test summary info ==========================
|
||||
XFAIL test_step.py::TestUserHandling::test_deletion
|
||||
reason: previous test failed (test_modification)
|
||||
============== 1 failed, 2 passed, 1 xfailed in 0.12 seconds ===============
|
||||
================== 1 failed, 2 passed, 1 xfailed in 0.12s ==================
|
||||
|
||||
We'll see that ``test_deletion`` was not executed because ``test_modification``
|
||||
failed. It is reported as an "expected failure".
|
||||
|
@ -556,7 +556,7 @@ Here is an example for making a ``db`` fixture available in a directory:
|
|||
import pytest
|
||||
|
||||
|
||||
class DB(object):
|
||||
class DB:
|
||||
pass
|
||||
|
||||
|
||||
|
@ -644,7 +644,7 @@ We can run this:
|
|||
E assert 0
|
||||
|
||||
a/test_db2.py:2: AssertionError
|
||||
========== 3 failed, 2 passed, 1 xfailed, 1 error in 0.12 seconds ==========
|
||||
============= 3 failed, 2 passed, 1 xfailed, 1 error in 0.12s ==============
|
||||
|
||||
The two test modules in the ``a`` directory see the same ``db`` fixture instance
|
||||
while the one test in the sister-directory ``b`` doesn't see it. We could of course
|
||||
|
@ -684,7 +684,7 @@ case we just write some information out to a ``failures`` file:
|
|||
with open("failures", mode) as f:
|
||||
# let's also access a fixture for the fun of it
|
||||
if "tmpdir" in item.fixturenames:
|
||||
extra = " (%s)" % item.funcargs["tmpdir"]
|
||||
extra = " ({})".format(item.funcargs["tmpdir"])
|
||||
else:
|
||||
extra = ""
|
||||
|
||||
|
@ -733,7 +733,7 @@ and run them:
|
|||
E assert 0
|
||||
|
||||
test_module.py:6: AssertionError
|
||||
========================= 2 failed in 0.12 seconds =========================
|
||||
============================ 2 failed in 0.12s =============================
|
||||
|
||||
you will have a "failures" file which contains the failing test ids:
|
||||
|
||||
|
@ -848,7 +848,7 @@ and run it:
|
|||
E assert 0
|
||||
|
||||
test_module.py:19: AssertionError
|
||||
==================== 2 failed, 1 error in 0.12 seconds =====================
|
||||
======================== 2 failed, 1 error in 0.12s ========================
|
||||
|
||||
You'll see that the fixture finalizers could use the precise reporting
|
||||
information.
|
||||
|
|
|
@ -5,30 +5,36 @@ A session-scoped fixture effectively has access to all
|
|||
collected test items. Here is an example of a fixture
|
||||
function which walks all collected tests and looks
|
||||
if their test class defines a ``callme`` method and
|
||||
calls it::
|
||||
calls it:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of conftest.py
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def callattr_ahead_of_alltests(request):
|
||||
print("callattr_ahead_of_alltests called")
|
||||
seen = set([None])
|
||||
seen = {None}
|
||||
session = request.node
|
||||
for item in session.items:
|
||||
cls = item.getparent(pytest.Class)
|
||||
if cls not in seen:
|
||||
if hasattr(cls.obj, "callme"):
|
||||
cls.obj.callme()
|
||||
cls.obj.callme()
|
||||
seen.add(cls)
|
||||
|
||||
test classes may now define a ``callme`` method which
|
||||
will be called ahead of running any tests::
|
||||
will be called ahead of running any tests:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_module.py
|
||||
|
||||
class TestHello(object):
|
||||
|
||||
class TestHello:
|
||||
@classmethod
|
||||
def callme(cls):
|
||||
print("callme called!")
|
||||
|
@ -39,16 +45,20 @@ will be called ahead of running any tests::
|
|||
def test_method2(self):
|
||||
print("test_method1 called")
|
||||
|
||||
class TestOther(object):
|
||||
|
||||
class TestOther:
|
||||
@classmethod
|
||||
def callme(cls):
|
||||
print("callme other called")
|
||||
|
||||
def test_other(self):
|
||||
print("test other")
|
||||
|
||||
|
||||
# works with unittest as well ...
|
||||
import unittest
|
||||
|
||||
|
||||
class SomeTest(unittest.TestCase):
|
||||
@classmethod
|
||||
def callme(self):
|
||||
|
@ -71,4 +81,4 @@ If you run this without output capturing:
|
|||
.test other
|
||||
.test_unit1 method called
|
||||
.
|
||||
4 passed in 0.12 seconds
|
||||
4 passed in 0.12s
|
||||
|
|
|
@ -15,7 +15,9 @@ Running an existing test suite with pytest
|
|||
Say you want to contribute to an existing repository somewhere.
|
||||
After pulling the code into your development space using some
|
||||
flavor of version control and (optionally) setting up a virtualenv
|
||||
you will want to run::
|
||||
you will want to run:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
cd <repository>
|
||||
pip install -e . # Environment dependent alternatives include
|
||||
|
|
|
@ -49,20 +49,25 @@ argument. For each argument name, a fixture function with that name provides
|
|||
the fixture object. Fixture functions are registered by marking them with
|
||||
:py:func:`@pytest.fixture <_pytest.python.fixture>`. Let's look at a simple
|
||||
self-contained test module containing a fixture and a test function
|
||||
using it::
|
||||
using it:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of ./test_smtpsimple.py
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def smtp_connection():
|
||||
import smtplib
|
||||
|
||||
return smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
|
||||
|
||||
|
||||
def test_ehlo(smtp_connection):
|
||||
response, msg = smtp_connection.ehlo()
|
||||
assert response == 250
|
||||
assert 0 # for demo purposes
|
||||
assert 0 # for demo purposes
|
||||
|
||||
Here, the ``test_ehlo`` needs the ``smtp_connection`` fixture value. pytest
|
||||
will discover and call the :py:func:`@pytest.fixture <_pytest.python.fixture>`
|
||||
|
@ -87,11 +92,11 @@ marked ``smtp_connection`` fixture function. Running the test looks like this:
|
|||
def test_ehlo(smtp_connection):
|
||||
response, msg = smtp_connection.ehlo()
|
||||
assert response == 250
|
||||
> assert 0 # for demo purposes
|
||||
> assert 0 # for demo purposes
|
||||
E assert 0
|
||||
|
||||
test_smtpsimple.py:11: AssertionError
|
||||
========================= 1 failed in 0.12 seconds =========================
|
||||
test_smtpsimple.py:14: AssertionError
|
||||
============================ 1 failed in 0.12s =============================
|
||||
|
||||
In the failure traceback we see that the test function was called with a
|
||||
``smtp_connection`` argument, the ``smtplib.SMTP()`` instance created by the fixture
|
||||
|
@ -180,12 +185,15 @@ Possible values for ``scope`` are: ``function``, ``class``, ``module``, ``packag
|
|||
|
||||
The next example puts the fixture function into a separate ``conftest.py`` file
|
||||
so that tests from multiple test modules in the directory can
|
||||
access the fixture function::
|
||||
access the fixture function:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of conftest.py
|
||||
import pytest
|
||||
import smtplib
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def smtp_connection():
|
||||
return smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
|
||||
|
@ -193,16 +201,20 @@ access the fixture function::
|
|||
The name of the fixture again is ``smtp_connection`` and you can access its
|
||||
result by listing the name ``smtp_connection`` as an input parameter in any
|
||||
test or fixture function (in or below the directory where ``conftest.py`` is
|
||||
located)::
|
||||
located):
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_module.py
|
||||
|
||||
|
||||
def test_ehlo(smtp_connection):
|
||||
response, msg = smtp_connection.ehlo()
|
||||
assert response == 250
|
||||
assert b"smtp.gmail.com" in msg
|
||||
assert 0 # for demo purposes
|
||||
|
||||
|
||||
def test_noop(smtp_connection):
|
||||
response, msg = smtp_connection.noop()
|
||||
assert response == 250
|
||||
|
@ -234,7 +246,7 @@ inspect what is going on and can now run the tests:
|
|||
> assert 0 # for demo purposes
|
||||
E assert 0
|
||||
|
||||
test_module.py:6: AssertionError
|
||||
test_module.py:7: AssertionError
|
||||
________________________________ test_noop _________________________________
|
||||
|
||||
smtp_connection = <smtplib.SMTP object at 0xdeadbeef>
|
||||
|
@ -245,8 +257,8 @@ inspect what is going on and can now run the tests:
|
|||
> assert 0 # for demo purposes
|
||||
E assert 0
|
||||
|
||||
test_module.py:11: AssertionError
|
||||
========================= 2 failed in 0.12 seconds =========================
|
||||
test_module.py:13: AssertionError
|
||||
============================ 2 failed in 0.12s =============================
|
||||
|
||||
You see the two ``assert 0`` failing and more importantly you can also see
|
||||
that the same (module-scoped) ``smtp_connection`` object was passed into the
|
||||
|
@ -289,51 +301,59 @@ are finalized when the last test of a *package* finishes.
|
|||
Use this new feature sparingly and please make sure to report any issues you find.
|
||||
|
||||
|
||||
Higher-scoped fixtures are instantiated first
|
||||
---------------------------------------------
|
||||
.. _dynamic scope:
|
||||
|
||||
Dynamic scope
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
.. versionadded:: 5.2
|
||||
|
||||
In some cases, you might want to change the scope of the fixture without changing the code.
|
||||
To do that, pass a callable to ``scope``. The callable must return a string with a valid scope
|
||||
and will be executed only once - during the fixture definition. It will be called with two
|
||||
keyword arguments - ``fixture_name`` as a string and ``config`` with a configuration object.
|
||||
|
||||
This can be especially useful when dealing with fixtures that need time for setup, like spawning
|
||||
a docker container. You can use the command-line argument to control the scope of the spawned
|
||||
containers for different environments. See the example below.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def determine_scope(fixture_name, config):
|
||||
if config.getoption("--keep-containers"):
|
||||
return "session"
|
||||
return "function"
|
||||
|
||||
|
||||
@pytest.fixture(scope=determine_scope)
|
||||
def docker_container():
|
||||
yield spawn_container()
|
||||
|
||||
|
||||
|
||||
Order: Higher-scoped fixtures are instantiated first
|
||||
----------------------------------------------------
|
||||
|
||||
|
||||
|
||||
Within a function request for features, fixture of higher-scopes (such as ``session``) are instantiated first than
|
||||
lower-scoped fixtures (such as ``function`` or ``class``). The relative order of fixtures of same scope follows
|
||||
the declared order in the test function and honours dependencies between fixtures.
|
||||
the declared order in the test function and honours dependencies between fixtures. Autouse fixtures will be
|
||||
instantiated before explicitly used fixtures.
|
||||
|
||||
Consider the code below:
|
||||
|
||||
.. code-block:: python
|
||||
.. literalinclude:: example/fixtures/test_fixtures_order.py
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def s1():
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def m1():
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def f1(tmpdir):
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def f2():
|
||||
pass
|
||||
|
||||
|
||||
def test_foo(f1, m1, f2, s1):
|
||||
...
|
||||
|
||||
|
||||
The fixtures requested by ``test_foo`` will be instantiated in the following order:
|
||||
The fixtures requested by ``test_order`` will be instantiated in the following order:
|
||||
|
||||
1. ``s1``: is the highest-scoped fixture (``session``).
|
||||
2. ``m1``: is the second highest-scoped fixture (``module``).
|
||||
3. ``tmpdir``: is a ``function``-scoped fixture, required by ``f1``: it needs to be instantiated at this point
|
||||
because it is a dependency of ``f1``.
|
||||
4. ``f1``: is the first ``function``-scoped fixture in ``test_foo`` parameter list.
|
||||
5. ``f2``: is the last ``function``-scoped fixture in ``test_foo`` parameter list.
|
||||
3. ``a1``: is a ``function``-scoped ``autouse`` fixture: it will be instantiated before other fixtures
|
||||
within the same scope.
|
||||
4. ``f3``: is a ``function``-scoped fixture, required by ``f1``: it needs to be instantiated at this point
|
||||
5. ``f1``: is the first ``function``-scoped fixture in ``test_order`` parameter list.
|
||||
6. ``f2``: is the last ``function``-scoped fixture in ``test_order`` parameter list.
|
||||
|
||||
|
||||
.. _`finalization`:
|
||||
|
@ -371,7 +391,7 @@ Let's execute it:
|
|||
$ pytest -s -q --tb=no
|
||||
FFteardown smtp
|
||||
|
||||
2 failed in 0.12 seconds
|
||||
2 failed in 0.12s
|
||||
|
||||
We see that the ``smtp_connection`` instance is finalized after the two
|
||||
tests finished execution. Note that if we decorated our fixture
|
||||
|
@ -400,6 +420,34 @@ The ``smtp_connection`` connection will be closed after the test finished
|
|||
execution because the ``smtp_connection`` object automatically closes when
|
||||
the ``with`` statement ends.
|
||||
|
||||
Using the contextlib.ExitStack context manager finalizers will always be called
|
||||
regardless if the fixture *setup* code raises an exception. This is handy to properly
|
||||
close all resources created by a fixture even if one of them fails to be created/acquired:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_yield3.py
|
||||
|
||||
import contextlib
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def connect(port):
|
||||
... # create connection
|
||||
yield
|
||||
... # close connection
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def equipments():
|
||||
with contextlib.ExitStack() as stack:
|
||||
yield [stack.enter_context(connect(port)) for port in ("C1", "C3", "C28")]
|
||||
|
||||
In the example above, if ``"C28"`` fails with an exception, ``"C1"`` and ``"C3"`` will still
|
||||
be properly closed.
|
||||
|
||||
Note that if an exception happens during the *setup* code (before the ``yield`` keyword), the
|
||||
*teardown* code (after the ``yield``) will not be called.
|
||||
|
||||
|
@ -428,27 +476,39 @@ Here's the ``smtp_connection`` fixture changed to use ``addfinalizer`` for clean
|
|||
return smtp_connection # provide the fixture value
|
||||
|
||||
|
||||
Here's the ``equipments`` fixture changed to use ``addfinalizer`` for cleanup:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_yield3.py
|
||||
|
||||
import contextlib
|
||||
import functools
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def connect(port):
|
||||
... # create connection
|
||||
yield
|
||||
... # close connection
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def equipments(request):
|
||||
r = []
|
||||
for port in ("C1", "C3", "C28"):
|
||||
cm = connect(port)
|
||||
equip = cm.__enter__()
|
||||
request.addfinalizer(functools.partial(cm.__exit__, None, None, None))
|
||||
r.append(equip)
|
||||
return r
|
||||
|
||||
|
||||
Both ``yield`` and ``addfinalizer`` methods work similarly by calling their code after the test
|
||||
ends, but ``addfinalizer`` has two key differences over ``yield``:
|
||||
|
||||
1. It is possible to register multiple finalizer functions.
|
||||
|
||||
2. Finalizers will always be called regardless if the fixture *setup* code raises an exception.
|
||||
This is handy to properly close all resources created by a fixture even if one of them
|
||||
fails to be created/acquired::
|
||||
|
||||
@pytest.fixture
|
||||
def equipments(request):
|
||||
r = []
|
||||
for port in ('C1', 'C3', 'C28'):
|
||||
equip = connect(port)
|
||||
request.addfinalizer(equip.disconnect)
|
||||
r.append(equip)
|
||||
return r
|
||||
|
||||
In the example above, if ``"C28"`` fails with an exception, ``"C1"`` and ``"C3"`` will still
|
||||
be properly closed. Of course, if an exception happens before the finalize function is
|
||||
registered then it will not be executed.
|
||||
ends. Of course, if an exception happens before the finalize function is registered then it
|
||||
will not be executed.
|
||||
|
||||
|
||||
.. _`request-context`:
|
||||
|
@ -459,18 +519,21 @@ Fixtures can introspect the requesting test context
|
|||
Fixture functions can accept the :py:class:`request <FixtureRequest>` object
|
||||
to introspect the "requesting" test function, class or module context.
|
||||
Further extending the previous ``smtp_connection`` fixture example, let's
|
||||
read an optional server URL from the test module which uses our fixture::
|
||||
read an optional server URL from the test module which uses our fixture:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of conftest.py
|
||||
import pytest
|
||||
import smtplib
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def smtp_connection(request):
|
||||
server = getattr(request.module, "smtpserver", "smtp.gmail.com")
|
||||
smtp_connection = smtplib.SMTP(server, 587, timeout=5)
|
||||
yield smtp_connection
|
||||
print("finalizing %s (%s)" % (smtp_connection, server))
|
||||
print("finalizing {} ({})".format(smtp_connection, server))
|
||||
smtp_connection.close()
|
||||
|
||||
We use the ``request.module`` attribute to optionally obtain an
|
||||
|
@ -482,15 +545,18 @@ again, nothing much has changed:
|
|||
$ pytest -s -q --tb=no
|
||||
FFfinalizing <smtplib.SMTP object at 0xdeadbeef> (smtp.gmail.com)
|
||||
|
||||
2 failed in 0.12 seconds
|
||||
2 failed in 0.12s
|
||||
|
||||
Let's quickly create another test module that actually sets the
|
||||
server URL in its module namespace::
|
||||
server URL in its module namespace:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_anothersmtp.py
|
||||
|
||||
smtpserver = "mail.python.org" # will be read by smtp fixture
|
||||
|
||||
|
||||
def test_showhelo(smtp_connection):
|
||||
assert 0, smtp_connection.helo()
|
||||
|
||||
|
@ -502,7 +568,7 @@ Running it:
|
|||
F [100%]
|
||||
================================= FAILURES =================================
|
||||
______________________________ test_showhelo _______________________________
|
||||
test_anothersmtp.py:5: in test_showhelo
|
||||
test_anothersmtp.py:6: in test_showhelo
|
||||
assert 0, smtp_connection.helo()
|
||||
E AssertionError: (250, b'mail.python.org')
|
||||
E assert 0
|
||||
|
@ -522,16 +588,14 @@ of a fixture is needed multiple times in a single test. Instead of returning
|
|||
data directly, the fixture instead returns a function which generates the data.
|
||||
This function can then be called multiple times in the test.
|
||||
|
||||
Factories can have have parameters as needed::
|
||||
Factories can have parameters as needed:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.fixture
|
||||
def make_customer_record():
|
||||
|
||||
def _make_customer_record(name):
|
||||
return {
|
||||
"name": name,
|
||||
"orders": []
|
||||
}
|
||||
return {"name": name, "orders": []}
|
||||
|
||||
return _make_customer_record
|
||||
|
||||
|
@ -541,7 +605,9 @@ Factories can have have parameters as needed::
|
|||
customer_2 = make_customer_record("Mike")
|
||||
customer_3 = make_customer_record("Meredith")
|
||||
|
||||
If the data created by the factory requires managing, the fixture can take care of that::
|
||||
If the data created by the factory requires managing, the fixture can take care of that:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.fixture
|
||||
def make_customer_record():
|
||||
|
@ -580,18 +646,20 @@ configured in multiple ways.
|
|||
Extending the previous example, we can flag the fixture to create two
|
||||
``smtp_connection`` fixture instances which will cause all tests using the fixture
|
||||
to run twice. The fixture function gets access to each parameter
|
||||
through the special :py:class:`request <FixtureRequest>` object::
|
||||
through the special :py:class:`request <FixtureRequest>` object:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of conftest.py
|
||||
import pytest
|
||||
import smtplib
|
||||
|
||||
@pytest.fixture(scope="module",
|
||||
params=["smtp.gmail.com", "mail.python.org"])
|
||||
|
||||
@pytest.fixture(scope="module", params=["smtp.gmail.com", "mail.python.org"])
|
||||
def smtp_connection(request):
|
||||
smtp_connection = smtplib.SMTP(request.param, 587, timeout=5)
|
||||
yield smtp_connection
|
||||
print("finalizing %s" % smtp_connection)
|
||||
print("finalizing {}".format(smtp_connection))
|
||||
smtp_connection.close()
|
||||
|
||||
The main change is the declaration of ``params`` with
|
||||
|
@ -616,7 +684,7 @@ So let's just do another run:
|
|||
> assert 0 # for demo purposes
|
||||
E assert 0
|
||||
|
||||
test_module.py:6: AssertionError
|
||||
test_module.py:7: AssertionError
|
||||
________________________ test_noop[smtp.gmail.com] _________________________
|
||||
|
||||
smtp_connection = <smtplib.SMTP object at 0xdeadbeef>
|
||||
|
@ -627,7 +695,7 @@ So let's just do another run:
|
|||
> assert 0 # for demo purposes
|
||||
E assert 0
|
||||
|
||||
test_module.py:11: AssertionError
|
||||
test_module.py:13: AssertionError
|
||||
________________________ test_ehlo[mail.python.org] ________________________
|
||||
|
||||
smtp_connection = <smtplib.SMTP object at 0xdeadbeef>
|
||||
|
@ -638,7 +706,7 @@ So let's just do another run:
|
|||
> assert b"smtp.gmail.com" in msg
|
||||
E AssertionError: assert b'smtp.gmail.com' in b'mail.python.org\nPIPELINING\nSIZE 51200000\nETRN\nSTARTTLS\nAUTH DIGEST-MD5 NTLM CRAM-MD5\nENHANCEDSTATUSCODES\n8BITMIME\nDSN\nSMTPUTF8\nCHUNKING'
|
||||
|
||||
test_module.py:5: AssertionError
|
||||
test_module.py:6: AssertionError
|
||||
-------------------------- Captured stdout setup ---------------------------
|
||||
finalizing <smtplib.SMTP object at 0xdeadbeef>
|
||||
________________________ test_noop[mail.python.org] ________________________
|
||||
|
@ -651,10 +719,10 @@ So let's just do another run:
|
|||
> assert 0 # for demo purposes
|
||||
E assert 0
|
||||
|
||||
test_module.py:11: AssertionError
|
||||
test_module.py:13: AssertionError
|
||||
------------------------- Captured stdout teardown -------------------------
|
||||
finalizing <smtplib.SMTP object at 0xdeadbeef>
|
||||
4 failed in 0.12 seconds
|
||||
4 failed in 0.12s
|
||||
|
||||
We see that our two test functions each ran twice, against the different
|
||||
``smtp_connection`` instances. Note also, that with the ``mail.python.org``
|
||||
|
@ -672,28 +740,35 @@ Numbers, strings, booleans and None will have their usual string
|
|||
representation used in the test ID. For other objects, pytest will
|
||||
make a string based on the argument name. It is possible to customise
|
||||
the string used in a test ID for a certain fixture value by using the
|
||||
``ids`` keyword argument::
|
||||
``ids`` keyword argument:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_ids.py
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(params=[0, 1], ids=["spam", "ham"])
|
||||
def a(request):
|
||||
return request.param
|
||||
|
||||
|
||||
def test_a(a):
|
||||
pass
|
||||
|
||||
|
||||
def idfn(fixture_value):
|
||||
if fixture_value == 0:
|
||||
return "eggs"
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
@pytest.fixture(params=[0, 1], ids=idfn)
|
||||
def b(request):
|
||||
return request.param
|
||||
|
||||
|
||||
def test_b(b):
|
||||
pass
|
||||
|
||||
|
@ -726,7 +801,7 @@ Running the above tests results in the following test IDs being used:
|
|||
<Function test_ehlo[mail.python.org]>
|
||||
<Function test_noop[mail.python.org]>
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
========================== no tests ran in 0.12s ===========================
|
||||
|
||||
.. _`fixture-parametrize-marks`:
|
||||
|
||||
|
@ -736,14 +811,19 @@ Using marks with parametrized fixtures
|
|||
:func:`pytest.param` can be used to apply marks in values sets of parametrized fixtures in the same way
|
||||
that they can be used with :ref:`@pytest.mark.parametrize <@pytest.mark.parametrize>`.
|
||||
|
||||
Example::
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_fixture_marks.py
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(params=[0, 1, pytest.param(2, marks=pytest.mark.skip)])
|
||||
def data_set(request):
|
||||
return request.param
|
||||
|
||||
|
||||
def test_data(data_set):
|
||||
pass
|
||||
|
||||
|
@ -762,7 +842,7 @@ Running this test will *skip* the invocation of ``data_set`` with value ``2``:
|
|||
test_fixture_marks.py::test_data[1] PASSED [ 66%]
|
||||
test_fixture_marks.py::test_data[2] SKIPPED [100%]
|
||||
|
||||
=================== 2 passed, 1 skipped in 0.12 seconds ====================
|
||||
======================= 2 passed, 1 skipped in 0.12s =======================
|
||||
|
||||
.. _`interdependent fixtures`:
|
||||
|
||||
|
@ -774,20 +854,25 @@ can use other fixtures themselves. This contributes to a modular design
|
|||
of your fixtures and allows re-use of framework-specific fixtures across
|
||||
many projects. As a simple example, we can extend the previous example
|
||||
and instantiate an object ``app`` where we stick the already defined
|
||||
``smtp_connection`` resource into it::
|
||||
``smtp_connection`` resource into it:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_appsetup.py
|
||||
|
||||
import pytest
|
||||
|
||||
class App(object):
|
||||
|
||||
class App:
|
||||
def __init__(self, smtp_connection):
|
||||
self.smtp_connection = smtp_connection
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def app(smtp_connection):
|
||||
return App(smtp_connection)
|
||||
|
||||
|
||||
def test_smtp_connection_exists(app):
|
||||
assert app.smtp_connection
|
||||
|
||||
|
@ -806,7 +891,7 @@ Here we declare an ``app`` fixture which receives the previously defined
|
|||
test_appsetup.py::test_smtp_connection_exists[smtp.gmail.com] PASSED [ 50%]
|
||||
test_appsetup.py::test_smtp_connection_exists[mail.python.org] PASSED [100%]
|
||||
|
||||
========================= 2 passed in 0.12 seconds =========================
|
||||
============================ 2 passed in 0.12s =============================
|
||||
|
||||
Due to the parametrization of ``smtp_connection``, the test will run twice with two
|
||||
different ``App`` instances and respective smtp servers. There is no
|
||||
|
@ -836,31 +921,40 @@ this eases testing of applications which create and use global state.
|
|||
|
||||
The following example uses two parametrized fixtures, one of which is
|
||||
scoped on a per-module basis, and all the functions perform ``print`` calls
|
||||
to show the setup/teardown flow::
|
||||
to show the setup/teardown flow:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_module.py
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(scope="module", params=["mod1", "mod2"])
|
||||
def modarg(request):
|
||||
param = request.param
|
||||
print(" SETUP modarg %s" % param)
|
||||
print(" SETUP modarg", param)
|
||||
yield param
|
||||
print(" TEARDOWN modarg %s" % param)
|
||||
print(" TEARDOWN modarg", param)
|
||||
|
||||
@pytest.fixture(scope="function", params=[1,2])
|
||||
|
||||
@pytest.fixture(scope="function", params=[1, 2])
|
||||
def otherarg(request):
|
||||
param = request.param
|
||||
print(" SETUP otherarg %s" % param)
|
||||
print(" SETUP otherarg", param)
|
||||
yield param
|
||||
print(" TEARDOWN otherarg %s" % param)
|
||||
print(" TEARDOWN otherarg", param)
|
||||
|
||||
|
||||
def test_0(otherarg):
|
||||
print(" RUN test0 with otherarg %s" % otherarg)
|
||||
print(" RUN test0 with otherarg", otherarg)
|
||||
|
||||
|
||||
def test_1(modarg):
|
||||
print(" RUN test1 with modarg %s" % modarg)
|
||||
print(" RUN test1 with modarg", modarg)
|
||||
|
||||
|
||||
def test_2(otherarg, modarg):
|
||||
print(" RUN test2 with otherarg %s and modarg %s" % (otherarg, modarg))
|
||||
print(" RUN test2 with otherarg {} and modarg {}".format(otherarg, modarg))
|
||||
|
||||
|
||||
Let's run the tests in verbose mode and with looking at the print-output:
|
||||
|
@ -907,7 +1001,7 @@ Let's run the tests in verbose mode and with looking at the print-output:
|
|||
TEARDOWN modarg mod2
|
||||
|
||||
|
||||
========================= 8 passed in 0.12 seconds =========================
|
||||
============================ 8 passed in 0.12s =============================
|
||||
|
||||
You can see that the parametrized module-scoped ``modarg`` resource caused an
|
||||
ordering of test execution that lead to the fewest possible "active" resources.
|
||||
|
@ -935,7 +1029,9 @@ current working directory but otherwise do not care for the concrete
|
|||
directory. Here is how you can use the standard `tempfile
|
||||
<http://docs.python.org/library/tempfile.html>`_ and pytest fixtures to
|
||||
achieve it. We separate the creation of the fixture into a conftest.py
|
||||
file::
|
||||
file:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of conftest.py
|
||||
|
||||
|
@ -943,19 +1039,23 @@ file::
|
|||
import tempfile
|
||||
import os
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def cleandir():
|
||||
newpath = tempfile.mkdtemp()
|
||||
os.chdir(newpath)
|
||||
|
||||
and declare its use in a test module via a ``usefixtures`` marker::
|
||||
and declare its use in a test module via a ``usefixtures`` marker:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_setenv.py
|
||||
import os
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("cleandir")
|
||||
class TestDirectoryInit(object):
|
||||
class TestDirectoryInit:
|
||||
def test_cwd_starts_empty(self):
|
||||
assert os.listdir(os.getcwd()) == []
|
||||
with open("myfile", "w") as f:
|
||||
|
@ -973,7 +1073,7 @@ to verify our fixture is activated and the tests pass:
|
|||
|
||||
$ pytest -q
|
||||
.. [100%]
|
||||
2 passed in 0.12 seconds
|
||||
2 passed in 0.12s
|
||||
|
||||
You can specify multiple fixtures like this:
|
||||
|
||||
|
@ -1032,25 +1132,32 @@ without declaring a function argument explicitly or a `usefixtures`_ decorator.
|
|||
As a practical example, suppose we have a database fixture which has a
|
||||
begin/rollback/commit architecture and we want to automatically surround
|
||||
each test method by a transaction and a rollback. Here is a dummy
|
||||
self-contained implementation of this idea::
|
||||
self-contained implementation of this idea:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_db_transact.py
|
||||
|
||||
import pytest
|
||||
|
||||
class DB(object):
|
||||
|
||||
class DB:
|
||||
def __init__(self):
|
||||
self.intransaction = []
|
||||
|
||||
def begin(self, name):
|
||||
self.intransaction.append(name)
|
||||
|
||||
def rollback(self):
|
||||
self.intransaction.pop()
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def db():
|
||||
return DB()
|
||||
|
||||
class TestClass(object):
|
||||
|
||||
class TestClass:
|
||||
@pytest.fixture(autouse=True)
|
||||
def transact(self, request, db):
|
||||
db.begin(request.function.__name__)
|
||||
|
@ -1074,7 +1181,7 @@ If we run it, we get two passing tests:
|
|||
|
||||
$ pytest -q
|
||||
.. [100%]
|
||||
2 passed in 0.12 seconds
|
||||
2 passed in 0.12s
|
||||
|
||||
Here is how autouse fixtures work in other scopes:
|
||||
|
||||
|
@ -1098,7 +1205,9 @@ Here is how autouse fixtures work in other scopes:
|
|||
Note that the above ``transact`` fixture may very well be a fixture that
|
||||
you want to make available in your project without having it generally
|
||||
active. The canonical way to do that is to put the transact definition
|
||||
into a conftest.py file **without** using ``autouse``::
|
||||
into a conftest.py file **without** using ``autouse``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of conftest.py
|
||||
@pytest.fixture
|
||||
|
@ -1107,10 +1216,12 @@ into a conftest.py file **without** using ``autouse``::
|
|||
yield
|
||||
db.rollback()
|
||||
|
||||
and then e.g. have a TestClass using it by declaring the need::
|
||||
and then e.g. have a TestClass using it by declaring the need:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.usefixtures("transact")
|
||||
class TestClass(object):
|
||||
class TestClass:
|
||||
def test_method1(self):
|
||||
...
|
||||
|
||||
|
|
|
@ -122,4 +122,4 @@ Resources
|
|||
* Google:
|
||||
|
||||
* `Flaky Tests at Google and How We Mitigate Them <https://testing.googleblog.com/2016/05/flaky-tests-at-google-and-how-we.html>`_ by John Micco, 2016
|
||||
* `Where do Google's flaky tests come from? <https://docs.google.com/document/d/1mZ0-Kc97DI_F3tf_GBW_NB_aqka-P1jVOsFfufxqUUM/edit#heading=h.ec0r4fypsleh>`_ by Jeff Listfield, 2017
|
||||
* `Where do Google's flaky tests come from? <https://testing.googleblog.com/2017/04/where-do-our-flaky-tests-come-from.html>`_ by Jeff Listfield, 2017
|
||||
|
|
|
@ -21,19 +21,23 @@ funcarg for a test function is required. If a factory wants to
|
|||
re-use a resource across different scopes, it often used
|
||||
the ``request.cached_setup()`` helper to manage caching of
|
||||
resources. Here is a basic example how we could implement
|
||||
a per-session Database object::
|
||||
a per-session Database object:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of conftest.py
|
||||
class Database(object):
|
||||
class Database:
|
||||
def __init__(self):
|
||||
print("database instance created")
|
||||
|
||||
def destroy(self):
|
||||
print("database instance destroyed")
|
||||
|
||||
|
||||
def pytest_funcarg__db(request):
|
||||
return request.cached_setup(setup=DataBase,
|
||||
teardown=lambda db: db.destroy,
|
||||
scope="session")
|
||||
return request.cached_setup(
|
||||
setup=DataBase, teardown=lambda db: db.destroy, scope="session"
|
||||
)
|
||||
|
||||
There are several limitations and difficulties with this approach:
|
||||
|
||||
|
@ -68,7 +72,9 @@ Direct scoping of fixture/funcarg factories
|
|||
|
||||
Instead of calling cached_setup() with a cache scope, you can use the
|
||||
:ref:`@pytest.fixture <pytest.fixture>` decorator and directly state
|
||||
the scope::
|
||||
the scope:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def db(request):
|
||||
|
@ -90,11 +96,13 @@ Previously, funcarg factories could not directly cause parametrization.
|
|||
You needed to specify a ``@parametrize`` decorator on your test function
|
||||
or implement a ``pytest_generate_tests`` hook to perform
|
||||
parametrization, i.e. calling a test multiple times with different value
|
||||
sets. pytest-2.3 introduces a decorator for use on the factory itself::
|
||||
sets. pytest-2.3 introduces a decorator for use on the factory itself:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.fixture(params=["mysql", "pg"])
|
||||
def db(request):
|
||||
... # use request.param
|
||||
... # use request.param
|
||||
|
||||
Here the factory will be invoked twice (with the respective "mysql"
|
||||
and "pg" values set as ``request.param`` attributes) and all of
|
||||
|
@ -107,7 +115,9 @@ allow to re-use already written factories because effectively
|
|||
parametrized via
|
||||
:py:func:`~_pytest.python.Metafunc.parametrize(indirect=True)` calls.
|
||||
|
||||
Of course it's perfectly fine to combine parametrization and scoping::
|
||||
Of course it's perfectly fine to combine parametrization and scoping:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.fixture(scope="session", params=["mysql", "pg"])
|
||||
def db(request):
|
||||
|
@ -128,7 +138,9 @@ No ``pytest_funcarg__`` prefix when using @fixture decorator
|
|||
|
||||
When using the ``@fixture`` decorator the name of the function
|
||||
denotes the name under which the resource can be accessed as a function
|
||||
argument::
|
||||
argument:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.fixture()
|
||||
def db(request):
|
||||
|
@ -137,7 +149,9 @@ argument::
|
|||
The name under which the funcarg resource can be requested is ``db``.
|
||||
|
||||
You can still use the "old" non-decorator way of specifying funcarg factories
|
||||
aka::
|
||||
aka:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def pytest_funcarg__db(request):
|
||||
...
|
||||
|
|
|
@ -28,19 +28,22 @@ Install ``pytest``
|
|||
.. code-block:: bash
|
||||
|
||||
$ pytest --version
|
||||
This is pytest version 5.x.y, imported from $PYTHON_PREFIX/lib/python3.6/site-packages/pytest.py
|
||||
This is pytest version 5.x.y, imported from $PYTHON_PREFIX/lib/python3.7/site-packages/pytest.py
|
||||
|
||||
.. _`simpletest`:
|
||||
|
||||
Create your first test
|
||||
----------------------------------------------------------
|
||||
|
||||
Create a simple test function with just four lines of code::
|
||||
Create a simple test function with just four lines of code:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_sample.py
|
||||
def func(x):
|
||||
return x + 1
|
||||
|
||||
|
||||
def test_answer():
|
||||
assert func(3) == 5
|
||||
|
||||
|
@ -65,8 +68,8 @@ That’s it. You can now execute the test function:
|
|||
E assert 4 == 5
|
||||
E + where 4 = func(3)
|
||||
|
||||
test_sample.py:5: AssertionError
|
||||
========================= 1 failed in 0.12 seconds =========================
|
||||
test_sample.py:6: AssertionError
|
||||
============================ 1 failed in 0.12s =============================
|
||||
|
||||
This test returns a failure report because ``func(3)`` does not return ``5``.
|
||||
|
||||
|
@ -83,13 +86,18 @@ Run multiple tests
|
|||
Assert that a certain exception is raised
|
||||
--------------------------------------------------------------
|
||||
|
||||
Use the :ref:`raises <assertraises>` helper to assert that some code raises an exception::
|
||||
Use the :ref:`raises <assertraises>` helper to assert that some code raises an exception:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_sysexit.py
|
||||
import pytest
|
||||
|
||||
|
||||
def f():
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
def test_mytest():
|
||||
with pytest.raises(SystemExit):
|
||||
f()
|
||||
|
@ -100,22 +108,24 @@ Execute the test function with “quiet” reporting mode:
|
|||
|
||||
$ pytest -q test_sysexit.py
|
||||
. [100%]
|
||||
1 passed in 0.12 seconds
|
||||
1 passed in 0.12s
|
||||
|
||||
Group multiple tests in a class
|
||||
--------------------------------------------------------------
|
||||
|
||||
Once you develop multiple tests, you may want to group them into a class. pytest makes it easy to create a class containing more than one test::
|
||||
Once you develop multiple tests, you may want to group them into a class. pytest makes it easy to create a class containing more than one test:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_class.py
|
||||
class TestClass(object):
|
||||
class TestClass:
|
||||
def test_one(self):
|
||||
x = "this"
|
||||
assert 'h' in x
|
||||
assert "h" in x
|
||||
|
||||
def test_two(self):
|
||||
x = "hello"
|
||||
assert hasattr(x, 'check')
|
||||
assert hasattr(x, "check")
|
||||
|
||||
``pytest`` discovers all tests following its :ref:`Conventions for Python test discovery <test discovery>`, so it finds both ``test_`` prefixed functions. There is no need to subclass anything. We can simply run the module by passing its filename:
|
||||
|
||||
|
@ -130,19 +140,21 @@ Once you develop multiple tests, you may want to group them into a class. pytest
|
|||
|
||||
def test_two(self):
|
||||
x = "hello"
|
||||
> assert hasattr(x, 'check')
|
||||
> assert hasattr(x, "check")
|
||||
E AssertionError: assert False
|
||||
E + where False = hasattr('hello', 'check')
|
||||
|
||||
test_class.py:8: AssertionError
|
||||
1 failed, 1 passed in 0.12 seconds
|
||||
1 failed, 1 passed in 0.12s
|
||||
|
||||
The first test passed and the second failed. You can easily see the intermediate values in the assertion to help you understand the reason for the failure.
|
||||
|
||||
Request a unique temporary directory for functional tests
|
||||
--------------------------------------------------------------
|
||||
|
||||
``pytest`` provides `Builtin fixtures/function arguments <https://docs.pytest.org/en/latest/builtin.html#builtinfixtures>`_ to request arbitrary resources, like a unique temporary directory::
|
||||
``pytest`` provides `Builtin fixtures/function arguments <https://docs.pytest.org/en/latest/builtin.html>`_ to request arbitrary resources, like a unique temporary directory:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_tmpdir.py
|
||||
def test_needsfiles(tmpdir):
|
||||
|
@ -168,7 +180,7 @@ List the name ``tmpdir`` in the test function signature and ``pytest`` will look
|
|||
test_tmpdir.py:3: AssertionError
|
||||
--------------------------- Captured stdout call ---------------------------
|
||||
PYTEST_TMPDIR/test_needsfiles0
|
||||
1 failed in 0.12 seconds
|
||||
1 failed in 0.12s
|
||||
|
||||
More info on tmpdir handling is available at :ref:`Temporary directories and files <tmpdir handling>`.
|
||||
|
||||
|
|
|
@ -12,13 +12,17 @@ pip_ for installing your application and any dependencies,
|
|||
as well as the ``pytest`` package itself.
|
||||
This ensures your code and dependencies are isolated from your system Python installation.
|
||||
|
||||
Next, place a ``setup.py`` file in the root of your package with the following minimum content::
|
||||
Next, place a ``setup.py`` file in the root of your package with the following minimum content:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
setup(name="PACKAGENAME", packages=find_packages())
|
||||
|
||||
Where ``PACKAGENAME`` is the name of your package. You can then install your package in "editable" mode by running from the same directory::
|
||||
Where ``PACKAGENAME`` is the name of your package. You can then install your package in "editable" mode by running from the same directory:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pip install -e .
|
||||
|
||||
|
@ -60,7 +64,9 @@ Tests outside application code
|
|||
|
||||
Putting tests into an extra directory outside your actual application code
|
||||
might be useful if you have many functional tests or for other reasons want
|
||||
to keep tests separate from actual application code (often a good idea)::
|
||||
to keep tests separate from actual application code (often a good idea):
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
setup.py
|
||||
mypkg/
|
||||
|
@ -82,7 +88,7 @@ This has the following benefits:
|
|||
|
||||
.. note::
|
||||
|
||||
See :ref:`pythonpath` for more information about the difference between calling ``pytest`` and
|
||||
See :ref:`pytest vs python -m pytest` for more information about the difference between calling ``pytest`` and
|
||||
``python -m pytest``.
|
||||
|
||||
Note that using this scheme your test files must have **unique names**, because
|
||||
|
@ -92,7 +98,9 @@ be imported as ``test_app`` and ``test_view`` top-level modules by adding ``test
|
|||
``sys.path``.
|
||||
|
||||
If you need to have test modules with the same name, you might add ``__init__.py`` files to your
|
||||
``tests`` folder and subfolders, changing them to packages::
|
||||
``tests`` folder and subfolders, changing them to packages:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
setup.py
|
||||
mypkg/
|
||||
|
@ -114,7 +122,9 @@ This is problematic if you are using a tool like `tox`_ to test your package in
|
|||
because you want to test the *installed* version of your package, not the local code from the repository.
|
||||
|
||||
In this situation, it is **strongly** suggested to use a ``src`` layout where application root package resides in a
|
||||
sub-directory of your root::
|
||||
sub-directory of your root:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
setup.py
|
||||
src/
|
||||
|
@ -140,7 +150,9 @@ Tests as part of application code
|
|||
|
||||
Inlining test directories into your application package
|
||||
is useful if you have direct relation between tests and application modules and
|
||||
want to distribute them along with your application::
|
||||
want to distribute them along with your application:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
setup.py
|
||||
mypkg/
|
||||
|
@ -153,7 +165,9 @@ want to distribute them along with your application::
|
|||
test_view.py
|
||||
...
|
||||
|
||||
In this scheme, it is easy to run your tests using the ``--pyargs`` option::
|
||||
In this scheme, it is easy to run your tests using the ``--pyargs`` option:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pytest --pyargs mypkg
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ To execute it:
|
|||
E + where 4 = inc(3)
|
||||
|
||||
test_sample.py:6: AssertionError
|
||||
========================= 1 failed in 0.12 seconds =========================
|
||||
============================ 1 failed in 0.12s =============================
|
||||
|
||||
Due to ``pytest``'s detailed assertion introspection, only plain ``assert`` statements are used.
|
||||
See :ref:`Getting Started <getstarted>` for more examples.
|
||||
|
@ -61,7 +61,7 @@ Features
|
|||
|
||||
- Can run :ref:`unittest <unittest>` (including trial) and :ref:`nose <noseintegration>` test suites out of the box;
|
||||
|
||||
- Python Python 3.5+ and PyPy 3;
|
||||
- Python 3.5+ and PyPy 3;
|
||||
|
||||
- Rich plugin architecture, with over 315+ `external plugins <http://plugincompat.herokuapp.com>`_ and thriving community;
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ Distributed under the terms of the `MIT`_ license, pytest is free and open sourc
|
|||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2004-2017 Holger Krekel and others
|
||||
Copyright (c) 2004-2019 Holger Krekel and others
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
.. _`distribute docs`:
|
||||
.. _`distribute`: https://pypi.org/project/distribute/
|
||||
.. _`pip`: https://pypi.org/project/pip/
|
||||
.. _`venv`: https://docs.python.org/3/library/venv.html/
|
||||
.. _`venv`: https://docs.python.org/3/library/venv.html
|
||||
.. _`virtualenv`: https://pypi.org/project/virtualenv/
|
||||
.. _hudson: http://hudson-ci.org/
|
||||
.. _jenkins: http://jenkins-ci.org/
|
||||
|
|
|
@ -70,7 +70,9 @@ caplog fixture
|
|||
^^^^^^^^^^^^^^
|
||||
|
||||
Inside tests it is possible to change the log level for the captured log
|
||||
messages. This is supported by the ``caplog`` fixture::
|
||||
messages. This is supported by the ``caplog`` fixture:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_foo(caplog):
|
||||
caplog.set_level(logging.INFO)
|
||||
|
@ -78,59 +80,69 @@ messages. This is supported by the ``caplog`` fixture::
|
|||
|
||||
By default the level is set on the root logger,
|
||||
however as a convenience it is also possible to set the log level of any
|
||||
logger::
|
||||
logger:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_foo(caplog):
|
||||
caplog.set_level(logging.CRITICAL, logger='root.baz')
|
||||
caplog.set_level(logging.CRITICAL, logger="root.baz")
|
||||
pass
|
||||
|
||||
The log levels set are restored automatically at the end of the test.
|
||||
|
||||
It is also possible to use a context manager to temporarily change the log
|
||||
level inside a ``with`` block::
|
||||
level inside a ``with`` block:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_bar(caplog):
|
||||
with caplog.at_level(logging.INFO):
|
||||
pass
|
||||
|
||||
Again, by default the level of the root logger is affected but the level of any
|
||||
logger can be changed instead with::
|
||||
logger can be changed instead with:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_bar(caplog):
|
||||
with caplog.at_level(logging.CRITICAL, logger='root.baz'):
|
||||
with caplog.at_level(logging.CRITICAL, logger="root.baz"):
|
||||
pass
|
||||
|
||||
Lastly all the logs sent to the logger during the test run are made available on
|
||||
the fixture in the form of both the ``logging.LogRecord`` instances and the final log text.
|
||||
This is useful for when you want to assert on the contents of a message::
|
||||
This is useful for when you want to assert on the contents of a message:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_baz(caplog):
|
||||
func_under_test()
|
||||
for record in caplog.records:
|
||||
assert record.levelname != 'CRITICAL'
|
||||
assert 'wally' not in caplog.text
|
||||
assert record.levelname != "CRITICAL"
|
||||
assert "wally" not in caplog.text
|
||||
|
||||
For all the available attributes of the log records see the
|
||||
``logging.LogRecord`` class.
|
||||
|
||||
You can also resort to ``record_tuples`` if all you want to do is to ensure,
|
||||
that certain messages have been logged under a given logger name with a given
|
||||
severity and message::
|
||||
severity and message:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_foo(caplog):
|
||||
logging.getLogger().info('boo %s', 'arg')
|
||||
logging.getLogger().info("boo %s", "arg")
|
||||
|
||||
assert caplog.record_tuples == [
|
||||
('root', logging.INFO, 'boo arg'),
|
||||
]
|
||||
assert caplog.record_tuples == [("root", logging.INFO, "boo arg")]
|
||||
|
||||
You can call ``caplog.clear()`` to reset the captured log records in a test::
|
||||
You can call ``caplog.clear()`` to reset the captured log records in a test:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_something_with_clearing_records(caplog):
|
||||
some_method_that_creates_log_records()
|
||||
caplog.clear()
|
||||
your_test_method()
|
||||
assert ['Foo'] == [rec.message for rec in caplog.records]
|
||||
assert ["Foo"] == [rec.message for rec in caplog.records]
|
||||
|
||||
|
||||
The ``caplog.records`` attribute contains records from the current stage only, so
|
||||
|
@ -149,7 +161,7 @@ the records for the ``setup`` and ``call`` stages during teardown like so:
|
|||
yield window
|
||||
for when in ("setup", "call"):
|
||||
messages = [
|
||||
x.message for x in caplog.get_records(when) if x.level == logging.WARNING
|
||||
x.message for x in caplog.get_records(when) if x.levelno == logging.WARNING
|
||||
]
|
||||
if messages:
|
||||
pytest.fail(
|
||||
|
|
|
@ -40,7 +40,7 @@ You can register custom marks in your ``pytest.ini`` file like this:
|
|||
|
||||
Note that everything after the ``:`` is an optional description.
|
||||
|
||||
Alternatively, you can register new markers programatically in a
|
||||
Alternatively, you can register new markers programmatically in a
|
||||
:ref:`pytest_configure <initialization-hooks>` hook:
|
||||
|
||||
.. code-block:: python
|
||||
|
|
|
@ -46,10 +46,13 @@ environment variable is missing, or to set multiple values to a known variable.
|
|||
:py:meth:`monkeypatch.setenv` and :py:meth:`monkeypatch.delenv` can be used for
|
||||
these patches.
|
||||
|
||||
4. Use :py:meth:`monkeypatch.syspath_prepend` to modify the system ``$PATH`` safely, and
|
||||
4. Use ``monkeypatch.setenv("PATH", value, prepend=os.pathsep)`` to modify ``$PATH``, and
|
||||
:py:meth:`monkeypatch.chdir` to change the context of the current working directory
|
||||
during a test.
|
||||
|
||||
5. Use :py:meth:`monkeypatch.syspath_prepend` to modify ``sys.path`` which will also
|
||||
call :py:meth:`pkg_resources.fixup_namespace_packages` and :py:meth:`importlib.invalidate_caches`.
|
||||
|
||||
See the `monkeypatch blog post`_ for some introduction material
|
||||
and a discussion of its motivation.
|
||||
|
||||
|
@ -269,7 +272,7 @@ to do this using the ``setenv`` and ``delenv`` method. Our example code to test:
|
|||
username = os.getenv("USER")
|
||||
|
||||
if username is None:
|
||||
raise EnvironmentError("USER environment is not set.")
|
||||
raise OSError("USER environment is not set.")
|
||||
|
||||
return username.lower()
|
||||
|
||||
|
@ -293,7 +296,7 @@ both paths can be safely tested without impacting the running environment:
|
|||
"""Remove the USER env var and assert EnvironmentError is raised."""
|
||||
monkeypatch.delenv("USER", raising=False)
|
||||
|
||||
with pytest.raises(EnvironmentError):
|
||||
with pytest.raises(OSError):
|
||||
_ = get_os_user_lower()
|
||||
|
||||
This behavior can be moved into ``fixture`` structures and shared across tests:
|
||||
|
@ -320,7 +323,7 @@ This behavior can be moved into ``fixture`` structures and shared across tests:
|
|||
|
||||
|
||||
def test_raise_exception(mock_env_missing):
|
||||
with pytest.raises(EnvironmentError):
|
||||
with pytest.raises(OSError):
|
||||
_ = get_os_user_lower()
|
||||
|
||||
|
||||
|
|
|
@ -75,7 +75,7 @@ them in turn:
|
|||
E + where 54 = eval('6*9')
|
||||
|
||||
test_expectation.py:6: AssertionError
|
||||
==================== 1 failed, 2 passed in 0.12 seconds ====================
|
||||
======================= 1 failed, 2 passed in 0.12s ========================
|
||||
|
||||
.. note::
|
||||
|
||||
|
@ -128,7 +128,7 @@ Let's run this:
|
|||
|
||||
test_expectation.py ..x [100%]
|
||||
|
||||
=================== 2 passed, 1 xfailed in 0.12 seconds ====================
|
||||
======================= 2 passed, 1 xfailed in 0.12s =======================
|
||||
|
||||
The one parameter set which caused a failure previously now
|
||||
shows up as an "xfailed (expected to fail)" test.
|
||||
|
@ -205,7 +205,7 @@ If we now pass two stringinput values, our test will run twice:
|
|||
|
||||
$ pytest -q --stringinput="hello" --stringinput="world" test_strings.py
|
||||
.. [100%]
|
||||
2 passed in 0.12 seconds
|
||||
2 passed in 0.12s
|
||||
|
||||
Let's also run with a stringinput that will lead to a failing test:
|
||||
|
||||
|
@ -225,7 +225,7 @@ Let's also run with a stringinput that will lead to a failing test:
|
|||
E + where <built-in method isalpha of str object at 0xdeadbeef> = '!'.isalpha
|
||||
|
||||
test_strings.py:4: AssertionError
|
||||
1 failed in 0.12 seconds
|
||||
1 failed in 0.12s
|
||||
|
||||
As expected our test function fails.
|
||||
|
||||
|
@ -239,7 +239,7 @@ list:
|
|||
s [100%]
|
||||
========================= short test summary info ==========================
|
||||
SKIPPED [1] test_strings.py: got empty parameter set ['stringinput'], function test_valid_string at $REGENDOC_TMPDIR/test_strings.py:2
|
||||
1 skipped in 0.12 seconds
|
||||
1 skipped in 0.12s
|
||||
|
||||
Note that when calling ``metafunc.parametrize`` multiple times with different parameter sets, all parameter names across
|
||||
those sets cannot be duplicated, otherwise an error will be raised.
|
||||
|
|
|
@ -8,7 +8,9 @@ Installing and Using plugins
|
|||
This section talks about installing and using third party plugins.
|
||||
For writing your own plugins, please refer to :ref:`writing-plugins`.
|
||||
|
||||
Installing a third party plugin can be easily done with ``pip``::
|
||||
Installing a third party plugin can be easily done with ``pip``:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pip install pytest-NAME
|
||||
pip uninstall pytest-NAME
|
||||
|
@ -95,7 +97,9 @@ Finding out which plugins are active
|
|||
------------------------------------
|
||||
|
||||
If you want to find out which plugins are active in your
|
||||
environment you can type::
|
||||
environment you can type:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pytest --trace-config
|
||||
|
||||
|
@ -108,7 +112,9 @@ and their names. It will also print local plugins aka
|
|||
Deactivating / unregistering a plugin by name
|
||||
---------------------------------------------
|
||||
|
||||
You can prevent plugins from loading or unregister them::
|
||||
You can prevent plugins from loading or unregister them:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pytest -p no:NAME
|
||||
|
||||
|
|
|
@ -28,7 +28,6 @@ Here are some examples of projects using ``pytest`` (please send notes via :ref:
|
|||
* `sentry <https://getsentry.com/welcome/>`_, realtime app-maintenance and exception tracking
|
||||
* `Astropy <http://www.astropy.org/>`_ and `affiliated packages <http://www.astropy.org/affiliated/index.html>`_
|
||||
* `tox <http://testrun.org/tox>`_, virtualenv/Hudson integration tool
|
||||
* `PIDA <http://pida.co.uk>`_ framework for integrated development
|
||||
* `PyPM <http://code.activestate.com/pypm/>`_ ActiveState's package manager
|
||||
* `Fom <http://packages.python.org/Fom/>`_ a fluid object mapper for FluidDB
|
||||
* `applib <https://github.com/ActiveState/applib>`_ cross-platform utilities
|
||||
|
@ -37,10 +36,10 @@ Here are some examples of projects using ``pytest`` (please send notes via :ref:
|
|||
* `mwlib <https://pypi.org/project/mwlib/>`_ mediawiki parser and utility library
|
||||
* `The Translate Toolkit <http://translate.sourceforge.net/wiki/toolkit/index>`_ for localization and conversion
|
||||
* `execnet <http://codespeak.net/execnet>`_ rapid multi-Python deployment
|
||||
* `pylib <https://py.readthedocs.io>`_ cross-platform path, IO, dynamic code library
|
||||
* `Pacha <http://pacha.cafepais.com/>`_ configuration management in five minutes
|
||||
* `pylib <https://pylib.readthedocs.io/en/stable/>`_ cross-platform path, IO, dynamic code library
|
||||
* `bbfreeze <https://pypi.org/project/bbfreeze/>`_ create standalone executables from Python scripts
|
||||
* `pdb++ <http://bitbucket.org/antocuni/pdb>`_ a fancier version of PDB
|
||||
* `pdb++ <https://github.com/pdbpp/pdbpp>`_ a fancier version of PDB
|
||||
* `pudb <https://github.com/inducer/pudb>`_ full-screen console debugger for python
|
||||
* `py-s3fuse <http://code.google.com/p/py-s3fuse/>`_ Amazon S3 FUSE based filesystem
|
||||
* `waskr <http://code.google.com/p/waskr/>`_ WSGI Stats Middleware
|
||||
* `guachi <http://code.google.com/p/guachi/>`_ global persistent configs for Python modules
|
||||
|
@ -77,7 +76,7 @@ Some organisations using pytest
|
|||
* `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 <http://www.cellzome.com/>`_
|
||||
* cellzome
|
||||
* `Open End, Gothenborg <http://www.openend.se>`_
|
||||
* `Laboratory of Bioinformatics, Warsaw <http://genesilico.pl/>`_
|
||||
* `merlinux, Germany <http://merlinux.eu>`_
|
||||
|
|
|
@ -7,8 +7,8 @@ Python 3.4's last release is scheduled for
|
|||
`March 2019 <https://www.python.org/dev/peps/pep-0429/#release-schedule>`__. pytest is one of
|
||||
the participating projects of the https://python3statement.org.
|
||||
|
||||
The **pytest 4.6** series will be the last to support Python 2.7 and 3.4, and is scheduled
|
||||
to be released by **mid-2019**. **pytest 5.0** and onwards will support only Python 3.5+.
|
||||
The **pytest 4.6** series is the last to support Python 2.7 and 3.4, and was released in
|
||||
**June 2019**. **pytest 5.0** and onwards will support only Python 3.5+.
|
||||
|
||||
Thanks to the `python_requires`_ ``setuptools`` option,
|
||||
Python 2.7 and Python 3.4 users using a modern ``pip`` version
|
||||
|
@ -24,3 +24,8 @@ branch will continue to exist so the community itself can contribute patches. Th
|
|||
be happy to accept those patches and make new ``4.6`` releases **until mid-2020**.
|
||||
|
||||
.. _`python_requires`: https://packaging.python.org/guides/distributing-packages-using-setuptools/#python-requires
|
||||
|
||||
Technical Aspects
|
||||
-----------------
|
||||
|
||||
The technical aspects of the Python 2.7 and 3.4 support plan (such as when releases will occurr, how to backport fixes, etc) is described in issue `#5275 <https://github.com/pytest-dev/pytest/issues/5275>`__.
|
||||
|
|
|
@ -22,7 +22,9 @@ Consider this file and directory layout::
|
|||
|- test_foo.py
|
||||
|
||||
|
||||
When executing::
|
||||
When executing:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pytest root/
|
||||
|
||||
|
@ -54,7 +56,9 @@ Consider this file and directory layout::
|
|||
|- test_foo.py
|
||||
|
||||
|
||||
When executing::
|
||||
When executing:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pytest root/
|
||||
|
||||
|
@ -68,6 +72,8 @@ imported in the global import namespace.
|
|||
|
||||
This is also discussed in details in :ref:`test discovery`.
|
||||
|
||||
.. _`pytest vs python -m pytest`:
|
||||
|
||||
Invoking ``pytest`` versus ``python -m pytest``
|
||||
-----------------------------------------------
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
Reference
|
||||
=========
|
||||
API Reference
|
||||
=============
|
||||
|
||||
This page contains the full reference to pytest's API.
|
||||
|
||||
|
@ -27,6 +27,8 @@ pytest.skip
|
|||
|
||||
.. autofunction:: _pytest.outcomes.skip(msg, [allow_module_level=False])
|
||||
|
||||
.. _`pytest.importorskip ref`:
|
||||
|
||||
pytest.importorskip
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -57,7 +59,7 @@ pytest.raises
|
|||
|
||||
**Tutorial**: :ref:`assertraises`.
|
||||
|
||||
.. autofunction:: pytest.raises(expected_exception: Exception, [match], [message])
|
||||
.. autofunction:: pytest.raises(expected_exception: Exception, [match])
|
||||
:with: excinfo
|
||||
|
||||
pytest.deprecated_call
|
||||
|
@ -469,9 +471,11 @@ testdir
|
|||
This fixture provides a :class:`Testdir` instance useful for black-box testing of test files, making it ideal to
|
||||
test plugins.
|
||||
|
||||
To use it, include in your top-most ``conftest.py`` file::
|
||||
To use it, include in your top-most ``conftest.py`` file:
|
||||
|
||||
pytest_plugins = 'pytester'
|
||||
.. code-block:: python
|
||||
|
||||
pytest_plugins = "pytester"
|
||||
|
||||
|
||||
|
||||
|
@ -999,7 +1003,9 @@ passed multiple times. The expected format is ``name=value``. For example::
|
|||
[pytest]
|
||||
addopts = --maxfail=2 -rf # exit after 2 failures, report fail info
|
||||
|
||||
issuing ``pytest test_hello.py`` actually means::
|
||||
issuing ``pytest test_hello.py`` actually means:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pytest --maxfail=2 -rf test_hello.py
|
||||
|
||||
|
|
|
@ -145,7 +145,7 @@ You can use the ``skipif`` marker (as any other marker) on classes:
|
|||
.. code-block:: python
|
||||
|
||||
@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
|
||||
class TestPosixCalls(object):
|
||||
class TestPosixCalls:
|
||||
def test_function(self):
|
||||
"will not be setup or run under 'win32' platform"
|
||||
|
||||
|
@ -179,14 +179,17 @@ information.
|
|||
Skipping on a missing import dependency
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can use the following helper at module level
|
||||
or within a test or test setup function::
|
||||
You can skip tests on a missing import by using :ref:`pytest.importorskip ref`
|
||||
at module level, within a test, or test setup function.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
docutils = pytest.importorskip("docutils")
|
||||
|
||||
If ``docutils`` cannot be imported here, this will lead to a
|
||||
skip outcome of the test. You can also skip based on the
|
||||
version number of a library::
|
||||
If ``docutils`` cannot be imported here, this will lead to a skip outcome of
|
||||
the test. You can also skip based on the version number of a library:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
docutils = pytest.importorskip("docutils", minversion="0.3")
|
||||
|
||||
|
@ -223,7 +226,9 @@ XFail: mark test functions as expected to fail
|
|||
----------------------------------------------
|
||||
|
||||
You can use the ``xfail`` marker to indicate that you
|
||||
expect a test to fail::
|
||||
expect a test to fail:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.xfail
|
||||
def test_function():
|
||||
|
@ -366,7 +371,7 @@ Running it with the report-on-xfail option gives this output:
|
|||
XFAIL xfail_demo.py::test_hello6
|
||||
reason: reason
|
||||
XFAIL xfail_demo.py::test_hello7
|
||||
======================== 7 xfailed in 0.12 seconds =========================
|
||||
============================ 7 xfailed in 0.12s ============================
|
||||
|
||||
.. _`skip/xfail with parametrize`:
|
||||
|
||||
|
|
|
@ -2,12 +2,6 @@
|
|||
Talks and Tutorials
|
||||
==========================
|
||||
|
||||
.. sidebar:: Next Open Trainings
|
||||
|
||||
- `Training at Europython 2019 <https://ep2019.europython.eu/talks/94WEnsY-introduction-to-pytest/>`_, 8th July 2019, Basel, Switzerland.
|
||||
|
||||
- `Training at Workshoptage 2019 <https://workshoptage.ch/workshops/2019/test-driven-development-fuer-python-mit-pytest/>`_ (German), 10th September 2019, Rapperswil, Switzerland.
|
||||
|
||||
.. _`funcargs`: funcargs.html
|
||||
|
||||
Books
|
||||
|
|
|
@ -64,7 +64,7 @@ Running this would result in a passed test except for the last
|
|||
E assert 0
|
||||
|
||||
test_tmp_path.py:13: AssertionError
|
||||
========================= 1 failed in 0.12 seconds =========================
|
||||
============================ 1 failed in 0.12s =============================
|
||||
|
||||
.. _`tmp_path_factory example`:
|
||||
|
||||
|
@ -90,10 +90,14 @@ provide a temporary directory unique to the test invocation,
|
|||
created in the `base temporary directory`_.
|
||||
|
||||
``tmpdir`` is a `py.path.local`_ object which offers ``os.path`` methods
|
||||
and more. Here is an example test usage::
|
||||
and more. Here is an example test usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_tmpdir.py
|
||||
import os
|
||||
|
||||
|
||||
def test_create_file(tmpdir):
|
||||
p = tmpdir.mkdir("sub").join("hello.txt")
|
||||
p.write("content")
|
||||
|
@ -128,8 +132,8 @@ Running this would result in a passed test except for the last
|
|||
> assert 0
|
||||
E assert 0
|
||||
|
||||
test_tmpdir.py:7: AssertionError
|
||||
========================= 1 failed in 0.12 seconds =========================
|
||||
test_tmpdir.py:9: AssertionError
|
||||
============================ 1 failed in 0.12s =============================
|
||||
|
||||
.. _`tmpdir factory example`:
|
||||
|
||||
|
|
|
@ -10,7 +10,9 @@ It's meant for leveraging existing ``unittest``-based test suites
|
|||
to use pytest as a test runner and also allow to incrementally adapt
|
||||
the test suite to take full advantage of pytest's features.
|
||||
|
||||
To run an existing ``unittest``-style test suite using ``pytest``, type::
|
||||
To run an existing ``unittest``-style test suite using ``pytest``, type:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pytest tests
|
||||
|
||||
|
@ -78,7 +80,9 @@ Running your unittest with ``pytest`` allows you to use its
|
|||
tests. Assuming you have at least skimmed the pytest fixture features,
|
||||
let's jump-start into an example that integrates a pytest ``db_class``
|
||||
fixture, setting up a class-cached database object, and then reference
|
||||
it from a unittest-style test::
|
||||
it from a unittest-style test:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of conftest.py
|
||||
|
||||
|
@ -87,10 +91,12 @@ it from a unittest-style test::
|
|||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def db_class(request):
|
||||
class DummyDB(object):
|
||||
class DummyDB:
|
||||
pass
|
||||
|
||||
# set a class attribute on the invoking test context
|
||||
request.cls.db = DummyDB()
|
||||
|
||||
|
@ -103,21 +109,24 @@ as the ``cls`` attribute, denoting the class from which the fixture
|
|||
is used. This architecture de-couples fixture writing from actual test
|
||||
code and allows re-use of the fixture by a minimal reference, the fixture
|
||||
name. So let's write an actual ``unittest.TestCase`` class using our
|
||||
fixture definition::
|
||||
fixture definition:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_unittest_db.py
|
||||
|
||||
import unittest
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("db_class")
|
||||
class MyTest(unittest.TestCase):
|
||||
def test_method1(self):
|
||||
assert hasattr(self, "db")
|
||||
assert 0, self.db # fail for demo purposes
|
||||
assert 0, self.db # fail for demo purposes
|
||||
|
||||
def test_method2(self):
|
||||
assert 0, self.db # fail for demo purposes
|
||||
assert 0, self.db # fail for demo purposes
|
||||
|
||||
The ``@pytest.mark.usefixtures("db_class")`` class-decorator makes sure that
|
||||
the pytest fixture function ``db_class`` is called once per class.
|
||||
|
@ -142,22 +151,22 @@ the ``self.db`` values in the traceback:
|
|||
|
||||
def test_method1(self):
|
||||
assert hasattr(self, "db")
|
||||
> assert 0, self.db # fail for demo purposes
|
||||
> assert 0, self.db # fail for demo purposes
|
||||
E AssertionError: <conftest.db_class.<locals>.DummyDB object at 0xdeadbeef>
|
||||
E assert 0
|
||||
|
||||
test_unittest_db.py:9: AssertionError
|
||||
test_unittest_db.py:10: AssertionError
|
||||
___________________________ MyTest.test_method2 ____________________________
|
||||
|
||||
self = <test_unittest_db.MyTest testMethod=test_method2>
|
||||
|
||||
def test_method2(self):
|
||||
> assert 0, self.db # fail for demo purposes
|
||||
> assert 0, self.db # fail for demo purposes
|
||||
E AssertionError: <conftest.db_class.<locals>.DummyDB object at 0xdeadbeef>
|
||||
E assert 0
|
||||
|
||||
test_unittest_db.py:12: AssertionError
|
||||
========================= 2 failed in 0.12 seconds =========================
|
||||
test_unittest_db.py:13: AssertionError
|
||||
============================ 2 failed in 0.12s =============================
|
||||
|
||||
This default pytest traceback shows that the two test methods
|
||||
share the same ``self.db`` instance which was our intention
|
||||
|
@ -179,17 +188,19 @@ Let's look at an ``initdir`` fixture which makes all test methods of a
|
|||
``TestCase`` class execute in a temporary directory with a
|
||||
pre-initialized ``samplefile.ini``. Our ``initdir`` fixture itself uses
|
||||
the pytest builtin :ref:`tmpdir <tmpdir>` fixture to delegate the
|
||||
creation of a per-test temporary directory::
|
||||
creation of a per-test temporary directory:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_unittest_cleandir.py
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
class MyTest(unittest.TestCase):
|
||||
|
||||
class MyTest(unittest.TestCase):
|
||||
@pytest.fixture(autouse=True)
|
||||
def initdir(self, tmpdir):
|
||||
tmpdir.chdir() # change to pytest-provided temporary directory
|
||||
tmpdir.chdir() # change to pytest-provided temporary directory
|
||||
tmpdir.join("samplefile.ini").write("# testdata")
|
||||
|
||||
def test_method(self):
|
||||
|
@ -208,7 +219,7 @@ Running this test module ...:
|
|||
|
||||
$ pytest -q test_unittest_cleandir.py
|
||||
. [100%]
|
||||
1 passed in 0.12 seconds
|
||||
1 passed in 0.12s
|
||||
|
||||
... gives us one passed test because the ``initdir`` fixture function
|
||||
was executed ahead of the ``test_method``.
|
||||
|
|
|
@ -33,7 +33,19 @@ Running ``pytest`` can result in six different exit codes:
|
|||
:Exit code 4: pytest command line usage error
|
||||
:Exit code 5: No tests were collected
|
||||
|
||||
They are represented by the :class:`_pytest.main.ExitCode` enum.
|
||||
They are represented by the :class:`_pytest.main.ExitCode` enum. The exit codes being a part of the public API can be imported and accessed directly using:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from pytest import ExitCode
|
||||
|
||||
.. note::
|
||||
|
||||
If you would like to customize the exit code in some scenarios, specially when
|
||||
no tests are collected, consider using the
|
||||
`pytest-custom_exit_code <https://github.com/yashtodi94/pytest-custom_exit_code>`__
|
||||
plugin.
|
||||
|
||||
|
||||
Getting help on version, option names, environment variables
|
||||
--------------------------------------------------------------
|
||||
|
@ -235,7 +247,7 @@ Example:
|
|||
XPASS test_example.py::test_xpass always xfail
|
||||
ERROR test_example.py::test_error - assert 0
|
||||
FAILED test_example.py::test_fail - assert 0
|
||||
= 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12 seconds =
|
||||
== 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12s ===
|
||||
|
||||
The ``-r`` options accepts a number of characters after it, with ``a`` used
|
||||
above meaning "all except passes".
|
||||
|
@ -285,7 +297,7 @@ More than one character can be used, so for example to only see failed and skipp
|
|||
========================= short test summary info ==========================
|
||||
FAILED test_example.py::test_fail - assert 0
|
||||
SKIPPED [1] $REGENDOC_TMPDIR/test_example.py:23: skipping this test
|
||||
= 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12 seconds =
|
||||
== 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
|
||||
captured output:
|
||||
|
@ -324,7 +336,7 @@ captured output:
|
|||
ok
|
||||
========================= short test summary info ==========================
|
||||
PASSED test_example.py::test_ok
|
||||
= 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12 seconds =
|
||||
== 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12s ===
|
||||
|
||||
.. _pdb-option:
|
||||
|
||||
|
@ -640,7 +652,7 @@ to all tests.
|
|||
record_testsuite_property("STORAGE_TYPE", "CEPH")
|
||||
|
||||
|
||||
class TestMe(object):
|
||||
class TestMe:
|
||||
def test_foo(self):
|
||||
assert True
|
||||
|
||||
|
@ -706,6 +718,11 @@ for example ``-x`` if you only want to send one particular failure.
|
|||
|
||||
Currently only pasting to the http://bpaste.net service is implemented.
|
||||
|
||||
.. versionchanged:: 5.2
|
||||
|
||||
If creating the URL fails for any reason, a warning is generated instead of failing the
|
||||
entire test suite.
|
||||
|
||||
Early loading plugins
|
||||
---------------------
|
||||
|
||||
|
@ -742,24 +759,33 @@ Calling pytest from Python code
|
|||
|
||||
|
||||
|
||||
You can invoke ``pytest`` from Python code directly::
|
||||
You can invoke ``pytest`` from Python code directly:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
pytest.main()
|
||||
|
||||
this acts as if you would call "pytest" from the command line.
|
||||
It will not raise ``SystemExit`` but return the exitcode instead.
|
||||
You can pass in options and arguments::
|
||||
You can pass in options and arguments:
|
||||
|
||||
pytest.main(['-x', 'mytestdir'])
|
||||
.. code-block:: python
|
||||
|
||||
You can specify additional plugins to ``pytest.main``::
|
||||
pytest.main(["-x", "mytestdir"])
|
||||
|
||||
You can specify additional plugins to ``pytest.main``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of myinvoke.py
|
||||
import pytest
|
||||
class MyPlugin(object):
|
||||
|
||||
|
||||
class MyPlugin:
|
||||
def pytest_sessionfinish(self):
|
||||
print("*** test run reporting finishing")
|
||||
|
||||
|
||||
pytest.main(["-qq"], plugins=[MyPlugin()])
|
||||
|
||||
Running it will show that ``MyPlugin`` was added and its
|
||||
|
|
|
@ -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.12 seconds ===================
|
||||
====================== 1 passed, 1 warnings in 0.12s =======================
|
||||
|
||||
The ``-W`` flag can be passed to control which warnings will be displayed or even turn
|
||||
them into errors:
|
||||
|
@ -64,7 +64,7 @@ them into errors:
|
|||
E UserWarning: api v1, should use functions from v2
|
||||
|
||||
test_show_warnings.py:5: UserWarning
|
||||
1 failed in 0.12 seconds
|
||||
1 failed in 0.12s
|
||||
|
||||
The same option can be set in the ``pytest.ini`` file using the ``filterwarnings`` ini option.
|
||||
For example, the configuration below will ignore all user warnings, but will transform
|
||||
|
@ -127,7 +127,7 @@ decorator or to all tests in a module by setting the ``pytestmark`` variable:
|
|||
*Credits go to Florian Schulze for the reference implementation in the* `pytest-warnings`_
|
||||
*plugin.*
|
||||
|
||||
.. _`-W option`: https://docs.python.org/3/using/cmdline.html?highlight=#cmdoption-W
|
||||
.. _`-W option`: https://docs.python.org/3/using/cmdline.html#cmdoption-w
|
||||
.. _warnings.simplefilter: https://docs.python.org/3/library/warnings.html#warnings.simplefilter
|
||||
.. _`pytest-warnings`: https://github.com/fschulze/pytest-warnings
|
||||
|
||||
|
@ -180,6 +180,7 @@ This will ignore all warnings of type ``DeprecationWarning`` where the start of
|
|||
the regular expression ``".*U.*mode is deprecated"``.
|
||||
|
||||
.. note::
|
||||
|
||||
If warnings are configured at the interpreter level, using
|
||||
the `PYTHONWARNINGS <https://docs.python.org/3/using/cmdline.html#envvar-PYTHONWARNINGS>`_ environment variable or the
|
||||
``-W`` command-line option, pytest will not configure any filters by default.
|
||||
|
@ -277,7 +278,9 @@ argument ``match`` to assert that the exception matches a text or regex::
|
|||
...
|
||||
Failed: DID NOT WARN. No warnings of type ...UserWarning... was emitted...
|
||||
|
||||
You can also call ``pytest.warns`` on a function or code string::
|
||||
You can also call ``pytest.warns`` on a function or code string:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
pytest.warns(expected_warning, func, *args, **kwargs)
|
||||
pytest.warns(expected_warning, "func(*args, **kwargs)")
|
||||
|
@ -404,14 +407,14 @@ 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.12 seconds
|
||||
1 warnings in 0.12s
|
||||
|
||||
These warnings might be filtered using the same builtin mechanisms used to filter other types of warnings.
|
||||
|
||||
Please read our :ref:`backwards-compatibility` to learn how we proceed about deprecating and eventually removing
|
||||
features.
|
||||
|
||||
The following warning types ares used by pytest and are part of the public API:
|
||||
The following warning types are used by pytest and are part of the public API:
|
||||
|
||||
.. autoclass:: pytest.PytestWarning
|
||||
|
||||
|
|
|
@ -164,7 +164,7 @@ If a package is installed this way, ``pytest`` will load
|
|||
.. note::
|
||||
|
||||
Make sure to include ``Framework :: Pytest`` in your list of
|
||||
`PyPI classifiers <https://python-packaging-user-guide.readthedocs.io/distributing/#classifiers>`_
|
||||
`PyPI classifiers <https://pypi.org/classifiers/>`_
|
||||
to make it easy for users to find your plugin.
|
||||
|
||||
|
||||
|
@ -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.12 seconds ===================
|
||||
====================== 2 passed, 1 warnings 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
|
||||
|
@ -693,7 +693,7 @@ declaring the hook functions directly in your plugin module, for example:
|
|||
# contents of myplugin.py
|
||||
|
||||
|
||||
class DeferPlugin(object):
|
||||
class DeferPlugin:
|
||||
"""Simple plugin to defer pytest-xdist hook functions."""
|
||||
|
||||
def pytest_testnodedown(self, node, error):
|
||||
|
|
|
@ -27,11 +27,14 @@ Module level setup/teardown
|
|||
|
||||
If you have multiple test functions and test classes in a single
|
||||
module you can optionally implement the following fixture methods
|
||||
which will usually be called once for all the functions::
|
||||
which will usually be called once for all the functions:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def setup_module(module):
|
||||
""" setup any state specific to the execution of the given module."""
|
||||
|
||||
|
||||
def teardown_module(module):
|
||||
""" teardown any state that was previously setup with a setup_module
|
||||
method.
|
||||
|
@ -43,7 +46,9 @@ Class level setup/teardown
|
|||
----------------------------------
|
||||
|
||||
Similarly, the following methods are called at class level before
|
||||
and after all test methods of the class are called::
|
||||
and after all test methods of the class are called:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
|
@ -51,6 +56,7 @@ and after all test methods of the class are called::
|
|||
usually contains tests).
|
||||
"""
|
||||
|
||||
|
||||
@classmethod
|
||||
def teardown_class(cls):
|
||||
""" teardown any state that was previously setup with a call to
|
||||
|
@ -60,13 +66,16 @@ and after all test methods of the class are called::
|
|||
Method and function level setup/teardown
|
||||
-----------------------------------------------
|
||||
|
||||
Similarly, the following methods are called around each method invocation::
|
||||
Similarly, the following methods are called around each method invocation:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def setup_method(self, method):
|
||||
""" setup any state tied to the execution of the given method in a
|
||||
class. setup_method is invoked for every test method of a class.
|
||||
"""
|
||||
|
||||
|
||||
def teardown_method(self, method):
|
||||
""" teardown any state that was previously setup with a setup_method
|
||||
call.
|
||||
|
@ -75,13 +84,16 @@ Similarly, the following methods are called around each method invocation::
|
|||
As of pytest-3.0, the ``method`` parameter is optional.
|
||||
|
||||
If you would rather define test functions directly at module level
|
||||
you can also use the following functions to implement fixtures::
|
||||
you can also use the following functions to implement fixtures:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def setup_function(function):
|
||||
""" setup any state tied to the execution of the given function.
|
||||
Invoked for every test function in the module.
|
||||
"""
|
||||
|
||||
|
||||
def teardown_function(function):
|
||||
""" teardown any state that was previously setup with a setup_function
|
||||
call.
|
||||
|
|
|
@ -30,6 +30,11 @@ template = "changelog/_template.rst"
|
|||
name = "Features"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "improvement"
|
||||
name = "Improvements"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "bugfix"
|
||||
name = "Bug Fixes"
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
"""
|
||||
Script used to publish GitHub release notes extracted from CHANGELOG.rst.
|
||||
|
||||
This script is meant to be executed after a successful deployment in Travis.
|
||||
|
||||
Uses the following environment variables:
|
||||
|
||||
* GIT_TAG: the name of the tag of the current commit.
|
||||
* GH_RELEASE_NOTES_TOKEN: a personal access token with 'repo' permissions. It should be encrypted using:
|
||||
|
||||
$travis encrypt GH_RELEASE_NOTES_TOKEN=<token> -r pytest-dev/pytest
|
||||
|
||||
And the contents pasted in the ``deploy.env.secure`` section in the ``travis.yml`` file.
|
||||
|
||||
The script also requires ``pandoc`` to be previously installed in the system.
|
||||
|
||||
Requires Python3.6+.
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import github3
|
||||
import pypandoc
|
||||
|
||||
|
||||
def publish_github_release(slug, token, tag_name, body):
|
||||
github = github3.login(token=token)
|
||||
owner, repo = slug.split("/")
|
||||
repo = github.repository(owner, repo)
|
||||
return repo.create_release(tag_name=tag_name, body=body)
|
||||
|
||||
|
||||
def parse_changelog(tag_name):
|
||||
p = Path(__file__).parent.parent / "CHANGELOG.rst"
|
||||
changelog_lines = p.read_text(encoding="UTF-8").splitlines()
|
||||
|
||||
title_regex = re.compile(r"pytest (\d\.\d+\.\d+) \(\d{4}-\d{2}-\d{2}\)")
|
||||
consuming_version = False
|
||||
version_lines = []
|
||||
for line in changelog_lines:
|
||||
m = title_regex.match(line)
|
||||
if m:
|
||||
# found the version we want: start to consume lines until we find the next version title
|
||||
if m.group(1) == tag_name:
|
||||
consuming_version = True
|
||||
# found a new version title while parsing the version we want: break out
|
||||
elif consuming_version:
|
||||
break
|
||||
if consuming_version:
|
||||
version_lines.append(line)
|
||||
|
||||
return "\n".join(version_lines)
|
||||
|
||||
|
||||
def convert_rst_to_md(text):
|
||||
return pypandoc.convert_text(text, "md", format="rst")
|
||||
|
||||
|
||||
def main(argv):
|
||||
if len(argv) > 1:
|
||||
tag_name = argv[1]
|
||||
else:
|
||||
tag_name = os.environ.get("TRAVIS_TAG")
|
||||
if not tag_name:
|
||||
print("tag_name not given and $TRAVIS_TAG not set", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
token = os.environ.get("GH_RELEASE_NOTES_TOKEN")
|
||||
if not token:
|
||||
print("GH_RELEASE_NOTES_TOKEN not set", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
slug = os.environ.get("TRAVIS_REPO_SLUG")
|
||||
if not slug:
|
||||
print("TRAVIS_REPO_SLUG not set", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
rst_body = parse_changelog(tag_name)
|
||||
md_body = convert_rst_to_md(rst_body)
|
||||
if not publish_github_release(slug, token, tag_name, md_body):
|
||||
print("Could not publish release notes:", file=sys.stderr)
|
||||
print(md_body, file=sys.stderr)
|
||||
return 5
|
||||
|
||||
print()
|
||||
print(f"Release notes for {tag_name} published successfully:")
|
||||
print(f"https://github.com/{slug}/releases/tag/{tag_name}")
|
||||
print()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv))
|
|
@ -13,4 +13,6 @@ fi
|
|||
python -m coverage combine
|
||||
python -m coverage xml
|
||||
python -m coverage report -m
|
||||
bash <(curl -s https://codecov.io/bash) -Z -X gcov -X coveragepy -X search -X xcode -X gcovout -X fix -f coverage.xml
|
||||
# Set --connect-timeout to work around https://github.com/curl/curl/issues/4461
|
||||
curl -S -L --connect-timeout 5 --retry 6 -s https://codecov.io/bash -o codecov-upload.sh
|
||||
bash codecov-upload.sh -Z -X fix -f coverage.xml
|
||||
|
|
7
setup.py
7
setup.py
|
@ -5,7 +5,7 @@ from setuptools import setup
|
|||
INSTALL_REQUIRES = [
|
||||
"py>=1.5.0",
|
||||
"packaging",
|
||||
"attrs>=17.4.0",
|
||||
"attrs>=17.4.0", # should match oldattrs tox env.
|
||||
"more-itertools>=4.0.0",
|
||||
"atomicwrites>=1.0",
|
||||
'pathlib2>=2.2.0;python_version<"3.6"',
|
||||
|
@ -21,7 +21,6 @@ def main():
|
|||
use_scm_version={"write_to": "src/_pytest/_version.py"},
|
||||
setup_requires=["setuptools-scm", "setuptools>=40.0"],
|
||||
package_dir={"": "src"},
|
||||
# fmt: off
|
||||
extras_require={
|
||||
"testing": [
|
||||
"argcomplete",
|
||||
|
@ -29,9 +28,9 @@ def main():
|
|||
"mock",
|
||||
"nose",
|
||||
"requests",
|
||||
],
|
||||
"xmlschema",
|
||||
]
|
||||
},
|
||||
# fmt: on
|
||||
install_requires=INSTALL_REQUIRES,
|
||||
)
|
||||
|
||||
|
|
|
@ -5,10 +5,15 @@ import traceback
|
|||
from inspect import CO_VARARGS
|
||||
from inspect import CO_VARKEYWORDS
|
||||
from traceback import format_exception_only
|
||||
from types import CodeType
|
||||
from types import TracebackType
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import Generic
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Pattern
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
|
@ -29,7 +34,7 @@ if False: # TYPE_CHECKING
|
|||
class Code:
|
||||
""" wrapper around Python code objects """
|
||||
|
||||
def __init__(self, rawcode):
|
||||
def __init__(self, rawcode) -> None:
|
||||
if not hasattr(rawcode, "co_filename"):
|
||||
rawcode = getrawcode(rawcode)
|
||||
try:
|
||||
|
@ -38,7 +43,7 @@ class Code:
|
|||
self.name = rawcode.co_name
|
||||
except AttributeError:
|
||||
raise TypeError("not a code object: {!r}".format(rawcode))
|
||||
self.raw = rawcode
|
||||
self.raw = rawcode # type: CodeType
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.raw == other.raw
|
||||
|
@ -351,7 +356,7 @@ class Traceback(list):
|
|||
""" return the index of the frame/TracebackEntry where recursion
|
||||
originates if appropriate, None if no recursion occurred
|
||||
"""
|
||||
cache = {}
|
||||
cache = {} # type: Dict[Tuple[Any, int, int], List[Dict[str, Any]]]
|
||||
for i, entry in enumerate(self):
|
||||
# id for the code.raw is needed to work around
|
||||
# the strange metaprogramming in the decorator lib from pypi
|
||||
|
@ -443,7 +448,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)
|
||||
return cls.from_exc_info(exc_info, exprinfo)
|
||||
|
||||
@classmethod
|
||||
def for_later(cls) -> "ExceptionInfo[_E]":
|
||||
|
@ -502,7 +507,9 @@ class ExceptionInfo(Generic[_E]):
|
|||
def __repr__(self) -> str:
|
||||
if self._excinfo is None:
|
||||
return "<ExceptionInfo for raises contextmanager>"
|
||||
return "<ExceptionInfo %s tblen=%d>" % (self.typename, len(self.traceback))
|
||||
return "<{} {} tblen={}>".format(
|
||||
self.__class__.__name__, saferepr(self._excinfo[1]), len(self.traceback)
|
||||
)
|
||||
|
||||
def exconly(self, tryshort: bool = False) -> str:
|
||||
""" return the exception as a string
|
||||
|
@ -520,7 +527,9 @@ class ExceptionInfo(Generic[_E]):
|
|||
text = text[len(self._striptext) :]
|
||||
return text
|
||||
|
||||
def errisinstance(self, exc: "Type[BaseException]") -> bool:
|
||||
def errisinstance(
|
||||
self, exc: Union["Type[BaseException]", Tuple["Type[BaseException]", ...]]
|
||||
) -> bool:
|
||||
""" return True if the exception is an instance of exc """
|
||||
return isinstance(self.value, exc)
|
||||
|
||||
|
@ -589,7 +598,7 @@ class ExceptionInfo(Generic[_E]):
|
|||
)
|
||||
return fmt.repr_excinfo(self)
|
||||
|
||||
def match(self, regexp: Union[str, Pattern]) -> bool:
|
||||
def match(self, regexp: "Union[str, Pattern]") -> bool:
|
||||
"""
|
||||
Check whether the regular expression 'regexp' is found in the string
|
||||
representation of the exception using ``re.search``. If it matches
|
||||
|
@ -648,7 +657,7 @@ class FormattedExcinfo:
|
|||
args.append((argname, saferepr(argvalue)))
|
||||
return ReprFuncArgs(args)
|
||||
|
||||
def get_source(self, source, line_index=-1, excinfo=None, short=False):
|
||||
def get_source(self, source, line_index=-1, excinfo=None, short=False) -> List[str]:
|
||||
""" return formatted and marked up source lines. """
|
||||
import _pytest._code
|
||||
|
||||
|
@ -720,7 +729,7 @@ class FormattedExcinfo:
|
|||
else:
|
||||
line_index = entry.lineno - entry.getfirstlinesource()
|
||||
|
||||
lines = []
|
||||
lines = [] # type: List[str]
|
||||
style = entry._repr_style
|
||||
if style is None:
|
||||
style = self.style
|
||||
|
@ -797,7 +806,7 @@ class FormattedExcinfo:
|
|||
exc_msg=str(e),
|
||||
max_frames=max_frames,
|
||||
total=len(traceback),
|
||||
)
|
||||
) # type: Optional[str]
|
||||
traceback = traceback[:max_frames] + traceback[-max_frames:]
|
||||
else:
|
||||
if recursionindex is not None:
|
||||
|
@ -810,10 +819,12 @@ class FormattedExcinfo:
|
|||
|
||||
def repr_excinfo(self, excinfo):
|
||||
|
||||
repr_chain = []
|
||||
repr_chain = (
|
||||
[]
|
||||
) # type: List[Tuple[ReprTraceback, Optional[ReprFileLocation], Optional[str]]]
|
||||
e = excinfo.value
|
||||
descr = None
|
||||
seen = set()
|
||||
seen = set() # type: Set[int]
|
||||
while e is not None and id(e) not in seen:
|
||||
seen.add(id(e))
|
||||
if excinfo:
|
||||
|
@ -866,8 +877,8 @@ class TerminalRepr:
|
|||
|
||||
|
||||
class ExceptionRepr(TerminalRepr):
|
||||
def __init__(self):
|
||||
self.sections = []
|
||||
def __init__(self) -> None:
|
||||
self.sections = [] # type: List[Tuple[str, str, str]]
|
||||
|
||||
def addsection(self, name, content, sep="-"):
|
||||
self.sections.append((name, content, sep))
|
||||
|
|
|
@ -7,6 +7,7 @@ import tokenize
|
|||
import warnings
|
||||
from ast import PyCF_ONLY_AST as _AST_FLAG
|
||||
from bisect import bisect_right
|
||||
from typing import List
|
||||
|
||||
import py
|
||||
|
||||
|
@ -19,11 +20,11 @@ class Source:
|
|||
_compilecounter = 0
|
||||
|
||||
def __init__(self, *parts, **kwargs):
|
||||
self.lines = lines = []
|
||||
self.lines = lines = [] # type: List[str]
|
||||
de = kwargs.get("deindent", True)
|
||||
for part in parts:
|
||||
if not part:
|
||||
partlines = []
|
||||
partlines = [] # type: List[str]
|
||||
elif isinstance(part, Source):
|
||||
partlines = part.lines
|
||||
elif isinstance(part, (tuple, list)):
|
||||
|
@ -157,8 +158,7 @@ class Source:
|
|||
source = "\n".join(self.lines) + "\n"
|
||||
try:
|
||||
co = compile(source, filename, mode, flag)
|
||||
except SyntaxError:
|
||||
ex = sys.exc_info()[1]
|
||||
except SyntaxError as ex:
|
||||
# re-represent syntax errors from parsing python strings
|
||||
msglines = self.lines[: ex.lineno]
|
||||
if ex.offset:
|
||||
|
@ -173,7 +173,8 @@ class Source:
|
|||
if flag & _AST_FLAG:
|
||||
return co
|
||||
lines = [(x + "\n") for x in self.lines]
|
||||
linecache.cache[filename] = (1, None, lines, filename)
|
||||
# Type ignored because linecache.cache is private.
|
||||
linecache.cache[filename] = (1, None, lines, filename) # type: ignore
|
||||
return co
|
||||
|
||||
|
||||
|
@ -282,7 +283,7 @@ def get_statement_startend2(lineno, node):
|
|||
return start, end
|
||||
|
||||
|
||||
def getstatementrange_ast(lineno, source, assertion=False, astnode=None):
|
||||
def getstatementrange_ast(lineno, source: Source, assertion=False, astnode=None):
|
||||
if astnode is None:
|
||||
content = str(source)
|
||||
# See #4260:
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
support for presenting detailed information in failing assertions.
|
||||
"""
|
||||
import sys
|
||||
from typing import Optional
|
||||
|
||||
from _pytest.assertion import rewrite
|
||||
from _pytest.assertion import truncate
|
||||
|
@ -52,7 +53,9 @@ def register_assert_rewrite(*names):
|
|||
importhook = hook
|
||||
break
|
||||
else:
|
||||
importhook = DummyRewriteHook()
|
||||
# TODO(typing): Add a protocol for mark_rewrite() and use it
|
||||
# for importhook and for PytestPluginManager.rewrite_hook.
|
||||
importhook = DummyRewriteHook() # type: ignore
|
||||
importhook.mark_rewrite(*names)
|
||||
|
||||
|
||||
|
@ -69,7 +72,7 @@ class AssertionState:
|
|||
def __init__(self, config, mode):
|
||||
self.mode = mode
|
||||
self.trace = config.trace.root.get("assertion")
|
||||
self.hook = None
|
||||
self.hook = None # type: Optional[rewrite.AssertionRewritingHook]
|
||||
|
||||
|
||||
def install_importhook(config):
|
||||
|
@ -108,6 +111,7 @@ def pytest_runtest_setup(item):
|
|||
"""
|
||||
|
||||
def callbinrepr(op, left, right):
|
||||
# type: (str, object, object) -> Optional[str]
|
||||
"""Call the pytest_assertrepr_compare hook and prepare the result
|
||||
|
||||
This uses the first result from the hook and then ensures the
|
||||
|
@ -133,12 +137,13 @@ def pytest_runtest_setup(item):
|
|||
if item.config.getvalue("assertmode") == "rewrite":
|
||||
res = res.replace("%", "%%")
|
||||
return res
|
||||
return None
|
||||
|
||||
util._reprcompare = callbinrepr
|
||||
|
||||
if item.ihook.pytest_assertion_pass.get_hookimpls():
|
||||
|
||||
def call_assertion_pass_hook(lineno, expl, orig):
|
||||
def call_assertion_pass_hook(lineno, orig, expl):
|
||||
item.ihook.pytest_assertion_pass(
|
||||
item=item, lineno=lineno, orig=orig, expl=expl
|
||||
)
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import ast
|
||||
import errno
|
||||
import functools
|
||||
import importlib.abc
|
||||
import importlib.machinery
|
||||
import importlib.util
|
||||
import io
|
||||
|
@ -16,6 +17,7 @@ from typing import Dict
|
|||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
|
||||
import atomicwrites
|
||||
|
||||
|
@ -34,7 +36,7 @@ PYC_EXT = ".py" + (__debug__ and "c" or "o")
|
|||
PYC_TAIL = "." + PYTEST_TAG + PYC_EXT
|
||||
|
||||
|
||||
class AssertionRewritingHook:
|
||||
class AssertionRewritingHook(importlib.abc.MetaPathFinder):
|
||||
"""PEP302/PEP451 import hook which rewrites asserts."""
|
||||
|
||||
def __init__(self, config):
|
||||
|
@ -44,13 +46,13 @@ class AssertionRewritingHook:
|
|||
except ValueError:
|
||||
self.fnpats = ["test_*.py", "*_test.py"]
|
||||
self.session = None
|
||||
self._rewritten_names = set()
|
||||
self._must_rewrite = set()
|
||||
self._rewritten_names = set() # type: Set[str]
|
||||
self._must_rewrite = set() # type: Set[str]
|
||||
# flag to guard against trying to rewrite a pyc file while we are already writing another pyc file,
|
||||
# which might result in infinite recursion (#3506)
|
||||
self._writing_pyc = False
|
||||
self._basenames_to_check_rewrite = {"conftest"}
|
||||
self._marked_for_rewrite_cache = {}
|
||||
self._marked_for_rewrite_cache = {} # type: Dict[str, bool]
|
||||
self._session_paths_checked = False
|
||||
|
||||
def set_session(self, session):
|
||||
|
@ -116,24 +118,11 @@ class AssertionRewritingHook:
|
|||
write = not sys.dont_write_bytecode
|
||||
cache_dir = os.path.join(os.path.dirname(fn), "__pycache__")
|
||||
if write:
|
||||
try:
|
||||
os.mkdir(cache_dir)
|
||||
except OSError:
|
||||
e = sys.exc_info()[1].errno
|
||||
if e == errno.EEXIST:
|
||||
# Either the __pycache__ directory already exists (the
|
||||
# common case) or it's blocked by a non-dir node. In the
|
||||
# latter case, we'll ignore it in _write_pyc.
|
||||
pass
|
||||
elif e in {errno.ENOENT, errno.ENOTDIR}:
|
||||
# One of the path components was not a directory, likely
|
||||
# because we're in a zip file.
|
||||
write = False
|
||||
elif e in {errno.EACCES, errno.EROFS, errno.EPERM}:
|
||||
state.trace("read only directory: %r" % os.path.dirname(fn))
|
||||
write = False
|
||||
else:
|
||||
raise
|
||||
ok = try_mkdir(cache_dir)
|
||||
if not ok:
|
||||
write = False
|
||||
state.trace("read only directory: {}".format(os.path.dirname(fn)))
|
||||
|
||||
cache_name = os.path.basename(fn)[:-3] + PYC_TAIL
|
||||
pyc = os.path.join(cache_dir, cache_name)
|
||||
# Notice that even if we're in a read-only directory, I'm going
|
||||
|
@ -212,7 +201,7 @@ class AssertionRewritingHook:
|
|||
|
||||
return self._is_marked_for_rewrite(name, state)
|
||||
|
||||
def _is_marked_for_rewrite(self, name, state):
|
||||
def _is_marked_for_rewrite(self, name: str, state):
|
||||
try:
|
||||
return self._marked_for_rewrite_cache[name]
|
||||
except KeyError:
|
||||
|
@ -227,7 +216,7 @@ class AssertionRewritingHook:
|
|||
self._marked_for_rewrite_cache[name] = False
|
||||
return False
|
||||
|
||||
def mark_rewrite(self, *names):
|
||||
def mark_rewrite(self, *names: str) -> None:
|
||||
"""Mark import names as needing to be rewritten.
|
||||
|
||||
The named module or package as well as any nested modules will
|
||||
|
@ -394,6 +383,7 @@ def _format_boolop(explanations, is_or):
|
|||
|
||||
|
||||
def _call_reprcompare(ops, results, expls, each_obj):
|
||||
# type: (Tuple[str, ...], Tuple[bool, ...], Tuple[str, ...], Tuple[object, ...]) -> str
|
||||
for i, res, expl in zip(range(len(ops)), results, expls):
|
||||
try:
|
||||
done = not res
|
||||
|
@ -409,11 +399,13 @@ def _call_reprcompare(ops, results, expls, each_obj):
|
|||
|
||||
|
||||
def _call_assertion_pass(lineno, orig, expl):
|
||||
# type: (int, str, str) -> None
|
||||
if util._assertion_pass is not None:
|
||||
util._assertion_pass(lineno=lineno, orig=orig, expl=expl)
|
||||
util._assertion_pass(lineno, orig, expl)
|
||||
|
||||
|
||||
def _check_if_assertion_pass_impl():
|
||||
# type: () -> bool
|
||||
"""Checks if any plugins implement the pytest_assertion_pass hook
|
||||
in order not to generate explanation unecessarily (might be expensive)"""
|
||||
return True if util._assertion_pass else False
|
||||
|
@ -587,7 +579,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
def _assert_expr_to_lineno(self):
|
||||
return _get_assertion_exprs(self.source)
|
||||
|
||||
def run(self, mod):
|
||||
def run(self, mod: ast.Module) -> None:
|
||||
"""Find all assert statements in *mod* and rewrite them."""
|
||||
if not mod.body:
|
||||
# Nothing to do.
|
||||
|
@ -629,12 +621,12 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
]
|
||||
mod.body[pos:pos] = imports
|
||||
# Collect asserts.
|
||||
nodes = [mod]
|
||||
nodes = [mod] # type: List[ast.AST]
|
||||
while nodes:
|
||||
node = nodes.pop()
|
||||
for name, field in ast.iter_fields(node):
|
||||
if isinstance(field, list):
|
||||
new = []
|
||||
new = [] # type: List
|
||||
for i, child in enumerate(field):
|
||||
if isinstance(child, ast.Assert):
|
||||
# Transform assert.
|
||||
|
@ -708,7 +700,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
.explanation_param().
|
||||
|
||||
"""
|
||||
self.explanation_specifiers = {}
|
||||
self.explanation_specifiers = {} # type: Dict[str, ast.expr]
|
||||
self.stack.append(self.explanation_specifiers)
|
||||
|
||||
def pop_format_context(self, expl_expr):
|
||||
|
@ -751,7 +743,8 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
from _pytest.warning_types import PytestAssertRewriteWarning
|
||||
import warnings
|
||||
|
||||
warnings.warn_explicit(
|
||||
# Ignore type: typeshed bug https://github.com/python/typeshed/pull/3121
|
||||
warnings.warn_explicit( # type: ignore
|
||||
PytestAssertRewriteWarning(
|
||||
"assertion is always true, perhaps remove parentheses?"
|
||||
),
|
||||
|
@ -760,15 +753,15 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
lineno=assert_.lineno,
|
||||
)
|
||||
|
||||
self.statements = []
|
||||
self.variables = []
|
||||
self.statements = [] # type: List[ast.stmt]
|
||||
self.variables = [] # type: List[str]
|
||||
self.variable_counter = itertools.count()
|
||||
|
||||
if self.enable_assertion_pass_hook:
|
||||
self.format_variables = []
|
||||
self.format_variables = [] # type: List[str]
|
||||
|
||||
self.stack = []
|
||||
self.expl_stmts = []
|
||||
self.stack = [] # type: List[Dict[str, ast.expr]]
|
||||
self.expl_stmts = [] # type: List[ast.stmt]
|
||||
self.push_format_context()
|
||||
# Rewrite assert into a bunch of statements.
|
||||
top_condition, explanation = self.visit(assert_.test)
|
||||
|
@ -867,10 +860,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
internally already.
|
||||
See issue #3191 for more details.
|
||||
"""
|
||||
|
||||
# Using parse because it is different between py2 and py3.
|
||||
AST_NONE = ast.parse("None").body[0].value
|
||||
val_is_none = ast.Compare(node, [ast.Is()], [AST_NONE])
|
||||
val_is_none = ast.Compare(node, [ast.Is()], [ast.NameConstant(None)])
|
||||
send_warning = ast.parse(
|
||||
"""\
|
||||
from _pytest.warning_types import PytestAssertRewriteWarning
|
||||
|
@ -909,7 +899,7 @@ warn_explicit(
|
|||
# Process each operand, short-circuiting if needed.
|
||||
for i, v in enumerate(boolop.values):
|
||||
if i:
|
||||
fail_inner = []
|
||||
fail_inner = [] # type: List[ast.stmt]
|
||||
# cond is set in a prior loop iteration below
|
||||
self.expl_stmts.append(ast.If(cond, fail_inner, [])) # noqa
|
||||
self.expl_stmts = fail_inner
|
||||
|
@ -920,10 +910,10 @@ warn_explicit(
|
|||
call = ast.Call(app, [expl_format], [])
|
||||
self.expl_stmts.append(ast.Expr(call))
|
||||
if i < levels:
|
||||
cond = res
|
||||
cond = res # type: ast.expr
|
||||
if is_or:
|
||||
cond = ast.UnaryOp(ast.Not(), cond)
|
||||
inner = []
|
||||
inner = [] # type: List[ast.stmt]
|
||||
self.statements.append(ast.If(cond, inner, []))
|
||||
self.statements = body = inner
|
||||
self.statements = save
|
||||
|
@ -989,7 +979,7 @@ warn_explicit(
|
|||
expl = pat % (res_expl, res_expl, value_expl, attr.attr)
|
||||
return res, expl
|
||||
|
||||
def visit_Compare(self, comp):
|
||||
def visit_Compare(self, comp: ast.Compare):
|
||||
self.push_format_context()
|
||||
left_res, left_expl = self.visit(comp.left)
|
||||
if isinstance(comp.left, (ast.Compare, ast.BoolOp)):
|
||||
|
@ -1022,7 +1012,30 @@ warn_explicit(
|
|||
ast.Tuple(results, ast.Load()),
|
||||
)
|
||||
if len(comp.ops) > 1:
|
||||
res = ast.BoolOp(ast.And(), load_names)
|
||||
res = ast.BoolOp(ast.And(), load_names) # type: ast.expr
|
||||
else:
|
||||
res = load_names[0]
|
||||
return res, self.explanation_param(self.pop_format_context(expl_call))
|
||||
|
||||
|
||||
def try_mkdir(cache_dir):
|
||||
"""Attempts to create the given directory, returns True if successful"""
|
||||
try:
|
||||
os.mkdir(cache_dir)
|
||||
except FileExistsError:
|
||||
# Either the __pycache__ directory already exists (the
|
||||
# common case) or it's blocked by a non-dir node. In the
|
||||
# latter case, we'll ignore it in _write_pyc.
|
||||
return True
|
||||
except (FileNotFoundError, NotADirectoryError):
|
||||
# One of the path components was not a directory, likely
|
||||
# because we're in a zip file.
|
||||
return False
|
||||
except PermissionError:
|
||||
return False
|
||||
except OSError as e:
|
||||
# as of now, EROFS doesn't have an equivalent OSError-subclass
|
||||
if e.errno == errno.EROFS:
|
||||
return False
|
||||
raise
|
||||
return True
|
||||
|
|
|
@ -1,20 +1,24 @@
|
|||
"""Utilities for assertion debugging"""
|
||||
import pprint
|
||||
from collections.abc import Sequence
|
||||
from typing import Callable
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
|
||||
import _pytest._code
|
||||
from _pytest import outcomes
|
||||
from _pytest._io.saferepr import saferepr
|
||||
from _pytest.compat import ATTRS_EQ_FIELD
|
||||
|
||||
# The _reprcompare attribute on the util module is used by the new assertion
|
||||
# interpretation code and assertion rewriter to detect this plugin was
|
||||
# loaded and in turn call the hooks defined here as part of the
|
||||
# DebugInterpreter.
|
||||
_reprcompare = None
|
||||
_reprcompare = None # type: Optional[Callable[[str, object, object], Optional[str]]]
|
||||
|
||||
# Works similarly as _reprcompare attribute. Is populated with the hook call
|
||||
# when pytest_runtest_setup is called.
|
||||
_assertion_pass = None
|
||||
_assertion_pass = None # type: Optional[Callable[[int, str, str], None]]
|
||||
|
||||
|
||||
def format_explanation(explanation):
|
||||
|
@ -119,9 +123,9 @@ def isiterable(obj):
|
|||
|
||||
def assertrepr_compare(config, op, left, right):
|
||||
"""Return specialised explanations for some operators/operands"""
|
||||
width = 80 - 15 - len(op) - 2 # 15 chars indentation, 1 space around op
|
||||
left_repr = saferepr(left, maxsize=int(width // 2))
|
||||
right_repr = saferepr(right, maxsize=width - len(left_repr))
|
||||
maxsize = (80 - 15 - len(op) - 2) // 2 # 15 chars indentation, 1 space around op
|
||||
left_repr = saferepr(left, maxsize=maxsize)
|
||||
right_repr = saferepr(right, maxsize=maxsize)
|
||||
|
||||
summary = "{} {} {}".format(left_repr, op, right_repr)
|
||||
|
||||
|
@ -177,7 +181,7 @@ def _diff_text(left, right, verbose=0):
|
|||
"""
|
||||
from difflib import ndiff
|
||||
|
||||
explanation = []
|
||||
explanation = [] # type: List[str]
|
||||
|
||||
def escape_for_readable_diff(binary_text):
|
||||
"""
|
||||
|
@ -235,13 +239,25 @@ def _compare_eq_verbose(left, right):
|
|||
left_lines = repr(left).splitlines(keepends)
|
||||
right_lines = repr(right).splitlines(keepends)
|
||||
|
||||
explanation = []
|
||||
explanation = [] # type: List[str]
|
||||
explanation += ["-" + line for line in left_lines]
|
||||
explanation += ["+" + line for line in right_lines]
|
||||
|
||||
return explanation
|
||||
|
||||
|
||||
def _surrounding_parens_on_own_lines(lines): # type: (List) -> None
|
||||
"""Move opening/closing parenthesis/bracket to own lines."""
|
||||
opening = lines[0][:1]
|
||||
if opening in ["(", "[", "{"]:
|
||||
lines[0] = " " + lines[0][1:]
|
||||
lines[:] = [opening] + lines
|
||||
closing = lines[-1][-1:]
|
||||
if closing in [")", "]", "}"]:
|
||||
lines[-1] = lines[-1][:-1] + ","
|
||||
lines[:] = lines + [closing]
|
||||
|
||||
|
||||
def _compare_eq_iterable(left, right, verbose=0):
|
||||
if not verbose:
|
||||
return ["Use -v to get the full diff"]
|
||||
|
@ -250,16 +266,34 @@ def _compare_eq_iterable(left, right, verbose=0):
|
|||
|
||||
left_formatting = pprint.pformat(left).splitlines()
|
||||
right_formatting = pprint.pformat(right).splitlines()
|
||||
|
||||
# Re-format for different output lengths.
|
||||
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)
|
||||
right_formatting = pprint.pformat(right, width=max_width).splitlines()
|
||||
lines_right = len(right_formatting)
|
||||
else:
|
||||
max_width = min(len(x) for x in right_formatting)
|
||||
left_formatting = pprint.pformat(left, width=max_width).splitlines()
|
||||
lines_left = len(left_formatting)
|
||||
|
||||
if lines_left > 1 or lines_right > 1:
|
||||
_surrounding_parens_on_own_lines(left_formatting)
|
||||
_surrounding_parens_on_own_lines(right_formatting)
|
||||
|
||||
explanation = ["Full diff:"]
|
||||
explanation.extend(
|
||||
line.strip() for line in difflib.ndiff(left_formatting, right_formatting)
|
||||
line.rstrip() for line in difflib.ndiff(left_formatting, right_formatting)
|
||||
)
|
||||
return explanation
|
||||
|
||||
|
||||
def _compare_eq_sequence(left, right, verbose=0):
|
||||
comparing_bytes = isinstance(left, bytes) and isinstance(right, bytes)
|
||||
explanation = []
|
||||
explanation = [] # type: List[str]
|
||||
len_left = len(left)
|
||||
len_right = len(right)
|
||||
for i in range(min(len_left, len_right)):
|
||||
|
@ -327,7 +361,7 @@ def _compare_eq_set(left, right, verbose=0):
|
|||
|
||||
|
||||
def _compare_eq_dict(left, right, verbose=0):
|
||||
explanation = []
|
||||
explanation = [] # type: List[str]
|
||||
set_left = set(left)
|
||||
set_right = set(right)
|
||||
common = set_left.intersection(set_right)
|
||||
|
@ -372,7 +406,9 @@ def _compare_eq_cls(left, right, verbose, type_fns):
|
|||
fields_to_check = [field for field, info in all_fields.items() if info.compare]
|
||||
elif isattrs(left):
|
||||
all_fields = left.__attrs_attrs__
|
||||
fields_to_check = [field.name for field in all_fields if field.cmp]
|
||||
fields_to_check = [
|
||||
field.name for field in all_fields if getattr(field, ATTRS_EQ_FIELD)
|
||||
]
|
||||
|
||||
same = []
|
||||
diff = []
|
||||
|
|
|
@ -14,7 +14,7 @@ import py
|
|||
import pytest
|
||||
from .pathlib import Path
|
||||
from .pathlib import resolve_from_str
|
||||
from .pathlib import rmtree
|
||||
from .pathlib import rm_rf
|
||||
|
||||
README_CONTENT = """\
|
||||
# pytest cache directory #
|
||||
|
@ -44,7 +44,7 @@ class Cache:
|
|||
def for_config(cls, config):
|
||||
cachedir = cls.cache_dir_from_config(config)
|
||||
if config.getoption("cacheclear") and cachedir.exists():
|
||||
rmtree(cachedir, force=True)
|
||||
rm_rf(cachedir)
|
||||
cachedir.mkdir()
|
||||
return cls(cachedir, config)
|
||||
|
||||
|
@ -135,7 +135,7 @@ class Cache:
|
|||
readme_path.write_text(README_CONTENT)
|
||||
|
||||
gitignore_path = self._cachedir.joinpath(".gitignore")
|
||||
msg = "# Created by pytest automatically.\n*"
|
||||
msg = "# Created by pytest automatically.\n*\n"
|
||||
gitignore_path.write_text(msg, encoding="UTF-8")
|
||||
|
||||
cachedir_tag_path = self._cachedir.joinpath("CACHEDIR.TAG")
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue