Compare commits
284 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ac43314ee | ||
|
|
972410f8b6 | ||
|
|
0cf267f187 | ||
|
|
7161f5b372 | ||
|
|
1d466d0aa7 | ||
|
|
ef4dec0bcf | ||
|
|
d76735f9e5 | ||
|
|
d1a48ad68f | ||
|
|
6d259c400e | ||
|
|
d3686361ba | ||
|
|
8605ed2a15 | ||
|
|
8b34d981fc | ||
|
|
2795689435 | ||
|
|
dcf65a9643 | ||
|
|
784e1e3b7e | ||
|
|
b25802eca7 | ||
|
|
7f7f84757d | ||
|
|
143499d041 | ||
|
|
26b41a5914 | ||
|
|
e1a426c067 | ||
|
|
2bd97ebaf7 | ||
|
|
45eeb53c98 | ||
|
|
254b195f50 | ||
|
|
1c13418a8b | ||
|
|
299e6479c1 | ||
|
|
6e81c3df92 | ||
|
|
1bd7d287a7 | ||
|
|
b82e1b87cc | ||
|
|
af40473c9a | ||
|
|
f17b734989 | ||
|
|
f050203f5d | ||
|
|
bc6450773a | ||
|
|
204004c8b8 | ||
|
|
772a4a5cf3 | ||
|
|
d9cad1e759 | ||
|
|
6fb3baf071 | ||
|
|
e943aff995 | ||
|
|
9c5da9c0d1 | ||
|
|
f6ab6d71ad | ||
|
|
67755d67fb | ||
|
|
a3c2ec3c5b | ||
|
|
9742f11d37 | ||
|
|
50937fe622 | ||
|
|
9c700d1fd5 | ||
|
|
19cd4d0af7 | ||
|
|
bf0fe1a1fa | ||
|
|
3df32e2732 | ||
|
|
34bc594beb | ||
|
|
bc00d0f7db | ||
|
|
e87d3d70e2 | ||
|
|
d67d68f6d3 | ||
|
|
008d04398a | ||
|
|
0e651d7297 | ||
|
|
9eac4733c5 | ||
|
|
006dc30476 | ||
|
|
533e610a35 | ||
|
|
8fd5a658eb | ||
|
|
c3b7efc818 | ||
|
|
43e7401c91 | ||
|
|
a9e850f749 | ||
|
|
da2e092163 | ||
|
|
8449294e5d | ||
|
|
1dafe969d1 | ||
|
|
1f66e3b0d0 | ||
|
|
bc157417e1 | ||
|
|
8011ff5bda | ||
|
|
899e74aa14 | ||
|
|
66f743c45a | ||
|
|
13a9d876f7 | ||
|
|
77526f412c | ||
|
|
973301b675 | ||
|
|
757ada2fd2 | ||
|
|
befc8a3f10 | ||
|
|
5935fbaa7a | ||
|
|
e8eaebe595 | ||
|
|
cec2dd6a7c | ||
|
|
266bf2c007 | ||
|
|
46df1d5fcf | ||
|
|
d91527599a | ||
|
|
49d690d137 | ||
|
|
4621638f07 | ||
|
|
8881b201aa | ||
|
|
278b289f37 | ||
|
|
e7ade066b6 | ||
|
|
dee520e310 | ||
|
|
4e931b258d | ||
|
|
4011021823 | ||
|
|
bfda2a0050 | ||
|
|
2812c087ec | ||
|
|
6b5cddc48a | ||
|
|
403f556928 | ||
|
|
d8ef86aadf | ||
|
|
a9fe1e159a | ||
|
|
65c8e8a09e | ||
|
|
46d9243eb0 | ||
|
|
63a01bdb33 | ||
|
|
d53209956b | ||
|
|
951213ee09 | ||
|
|
ae067df941 | ||
|
|
40718efacc | ||
|
|
d406786a8d | ||
|
|
0ac069da13 | ||
|
|
d17ea7a9c0 | ||
|
|
c92021fc4f | ||
|
|
50a5cebba8 | ||
|
|
6c602c2282 | ||
|
|
76c70cbf4c | ||
|
|
3d9e68ecfd | ||
|
|
8b0b7156d9 | ||
|
|
cf6e2ceafd | ||
|
|
69a55d334a | ||
|
|
241b7433cd | ||
|
|
057c97812b | ||
|
|
02188e399d | ||
|
|
aae02863db | ||
|
|
49f36bb028 | ||
|
|
52730f6330 | ||
|
|
538efef1ba | ||
|
|
9311d822c7 | ||
|
|
351529cb50 | ||
|
|
94a2e3dddc | ||
|
|
ee96214a8d | ||
|
|
e1ae469504 | ||
|
|
0d00be4f4f | ||
|
|
23146e7527 | ||
|
|
b18df936ea | ||
|
|
4148663706 | ||
|
|
3e1971eb16 | ||
|
|
bcdb86ee7e | ||
|
|
2d77018d1b | ||
|
|
ceef0af1ae | ||
|
|
e4eec3416a | ||
|
|
645774295f | ||
|
|
f2e0c740d3 | ||
|
|
d856f4e51f | ||
|
|
7b9a414524 | ||
|
|
0c63f99016 | ||
|
|
3bc9cbea63 | ||
|
|
6eff3069da | ||
|
|
58a14b6b99 | ||
|
|
b53bf44139 | ||
|
|
d8758443bd | ||
|
|
51f64c2920 | ||
|
|
cea42ff9e4 | ||
|
|
2df9d05981 | ||
|
|
4142c41ffc | ||
|
|
de44293d59 | ||
|
|
5efe6ab93c | ||
|
|
ce59f42ce1 | ||
|
|
7da7b9610c | ||
|
|
d44e42ec15 | ||
|
|
0ea1889265 | ||
|
|
6352cf2374 | ||
|
|
3127ec737b | ||
|
|
aa0b657e58 | ||
|
|
d0f3f26fff | ||
|
|
08f3b02dfc | ||
|
|
2d690b83bf | ||
|
|
0642da0145 | ||
|
|
afa985c135 | ||
|
|
fd64fa1863 | ||
|
|
56dc01ffe0 | ||
|
|
5df45f5b27 | ||
|
|
05d55b86df | ||
|
|
475119988c | ||
|
|
7a6bcc3639 | ||
|
|
8e125c9759 | ||
|
|
ade773390a | ||
|
|
5c26ba9cb1 | ||
|
|
5a544d4fac | ||
|
|
2e7d6a6202 | ||
|
|
ea7357bc58 | ||
|
|
b3319a6074 | ||
|
|
c9628c52d6 | ||
|
|
dcbdcc729b | ||
|
|
15d608867d | ||
|
|
ea2c6b8a88 | ||
|
|
0e6cf0ff28 | ||
|
|
553951c443 | ||
|
|
15ef168821 | ||
|
|
cc6e5ec345 | ||
|
|
77643122a8 | ||
|
|
832cef953b | ||
|
|
bcdbb6b677 | ||
|
|
543779fc43 | ||
|
|
2ade3d5c89 | ||
|
|
7939e5327c | ||
|
|
f7171034f9 | ||
|
|
c7c120fba6 | ||
|
|
415899d428 | ||
|
|
8dda5613ef | ||
|
|
714f2113bb | ||
|
|
a50b92ea67 | ||
|
|
da81c1e49a | ||
|
|
23ab43233e | ||
|
|
1a119a22d1 | ||
|
|
77c5191ad7 | ||
|
|
7395501d1d | ||
|
|
920bffbfbb | ||
|
|
751c061d9a | ||
|
|
a624b84097 | ||
|
|
c75dd10671 | ||
|
|
b696666f5a | ||
|
|
f4f6cb7532 | ||
|
|
1e3d5a0412 | ||
|
|
98981276a0 | ||
|
|
8c96b65082 | ||
|
|
c926999cfb | ||
|
|
519157cfcf | ||
|
|
5d14362a75 | ||
|
|
15fe8c6e90 | ||
|
|
c1e01c2992 | ||
|
|
5e27ea5528 | ||
|
|
33d4c96aa2 | ||
|
|
b3eb5d1eb7 | ||
|
|
2af0a023c9 | ||
|
|
5f52d5ee17 | ||
|
|
95701566f3 | ||
|
|
57be1d60dd | ||
|
|
62f96eea6b | ||
|
|
fa3cca51e1 | ||
|
|
d441fa66fe | ||
|
|
43aee15ba3 | ||
|
|
5c57d92978 | ||
|
|
7afe17740f | ||
|
|
158432217c | ||
|
|
437ff1c01a | ||
|
|
40072b9511 | ||
|
|
520af9d767 | ||
|
|
bdac9d3dd0 | ||
|
|
37158f5303 | ||
|
|
612c3784e5 | ||
|
|
951e07d71d | ||
|
|
36f774a8fb | ||
|
|
a2b921f890 | ||
|
|
bd70f5c148 | ||
|
|
134b957bf4 | ||
|
|
4d21dc4f2d | ||
|
|
74416525d2 | ||
|
|
44cb51010c | ||
|
|
90597226eb | ||
|
|
7fb5ad82d9 | ||
|
|
f4bcb44025 | ||
|
|
b7ae7a654b | ||
|
|
148e6a30c8 | ||
|
|
47bd1688ed | ||
|
|
6630d96253 | ||
|
|
d32ab6029f | ||
|
|
76c00d1c09 | ||
|
|
489c61a22d | ||
|
|
936f725b81 | ||
|
|
c86d2daf81 | ||
|
|
a70c1ca100 | ||
|
|
4668ee03f6 | ||
|
|
76687030f0 | ||
|
|
23ea04f910 | ||
|
|
c334adc78f | ||
|
|
35c85f0db9 | ||
|
|
0deb7b1696 | ||
|
|
53b8aa065c | ||
|
|
6a2d122a50 | ||
|
|
d97473e551 | ||
|
|
525639eaa0 | ||
|
|
7dceabfcb2 | ||
|
|
e1f97e41e3 | ||
|
|
2d2f6cd4fd | ||
|
|
44c940765b | ||
|
|
db5cc35b44 | ||
|
|
84555c89de | ||
|
|
f7a3e001f7 | ||
|
|
42561db1ae | ||
|
|
9cb71af9e5 | ||
|
|
a868a9ac13 | ||
|
|
c7bbb2a788 | ||
|
|
29112d7e0b | ||
|
|
2cf1de3f2d | ||
|
|
c9e69438b1 | ||
|
|
a0207274f4 | ||
|
|
759d7fde5d | ||
|
|
ede6387caa | ||
|
|
dc8c27037a | ||
|
|
63e7f8e340 | ||
|
|
0ca1f6e0f4 | ||
|
|
b9561e29ff |
@@ -1,6 +1,6 @@
|
||||
[run]
|
||||
include =
|
||||
*/src/*
|
||||
src/*
|
||||
testing/*
|
||||
*/lib/python*/site-packages/_pytest/*
|
||||
*/lib/python*/site-packages/pytest.py
|
||||
|
||||
10
.github/ISSUE_TEMPLATE.md
vendored
10
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,8 +1,10 @@
|
||||
<!--
|
||||
Thanks for submitting an issue!
|
||||
|
||||
Here's a quick checklist in what to include:
|
||||
Here's a quick checklist for what to provide:
|
||||
-->
|
||||
|
||||
- [ ] Include a detailed description of the bug or suggestion
|
||||
- [ ] `pip list` of the virtual environment you are using
|
||||
- [ ] a detailed description of the bug or suggestion
|
||||
- [ ] output of `pip list` from the virtual environment you are using
|
||||
- [ ] pytest and operating system versions
|
||||
- [ ] Minimal example if possible
|
||||
- [ ] minimal example if possible
|
||||
|
||||
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,7 +1,9 @@
|
||||
<!--
|
||||
Thanks for submitting a PR, your contribution is really appreciated!
|
||||
|
||||
Here's a quick checklist that should be present in PRs (you can delete this text from the final description, this is
|
||||
just a guideline):
|
||||
Here's a quick checklist that should be present in PRs.
|
||||
(please delete this text from the final description, this is just a guideline)
|
||||
-->
|
||||
|
||||
- [ ] Create a new changelog file in the `changelog` folder, with a name like `<ISSUE NUMBER>.<TYPE>.rst`. See [changelog/README.rst](https://github.com/pytest-dev/pytest/blob/master/changelog/README.rst) for details.
|
||||
- [ ] Target the `master` branch for bug fixes, documentation updates and trivial changes.
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
exclude: doc/en/example/py2py3/test_py2.py
|
||||
repos:
|
||||
- repo: https://github.com/ambv/black
|
||||
rev: 18.9b0
|
||||
- repo: https://github.com/python/black
|
||||
rev: 19.3b0
|
||||
hooks:
|
||||
- id: black
|
||||
args: [--safe, --quiet]
|
||||
language_version: python3
|
||||
- repo: https://github.com/asottile/blacken-docs
|
||||
rev: v0.3.0
|
||||
rev: v0.5.0
|
||||
hooks:
|
||||
- id: blacken-docs
|
||||
additional_dependencies: [black==18.9b0]
|
||||
additional_dependencies: [black==19.3b0]
|
||||
language_version: python3
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v2.1.0
|
||||
@@ -22,22 +22,22 @@ repos:
|
||||
exclude: _pytest/debugging.py
|
||||
language_version: python3
|
||||
- repo: https://gitlab.com/pycqa/flake8
|
||||
rev: 3.7.0
|
||||
rev: 3.7.7
|
||||
hooks:
|
||||
- id: flake8
|
||||
language_version: python3
|
||||
- repo: https://github.com/asottile/reorder_python_imports
|
||||
rev: v1.3.5
|
||||
rev: v1.4.0
|
||||
hooks:
|
||||
- id: reorder-python-imports
|
||||
args: ['--application-directories=.:src']
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v1.11.1
|
||||
rev: v1.15.0
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--keep-percent-format]
|
||||
- repo: https://github.com/pre-commit/pygrep-hooks
|
||||
rev: v1.2.0
|
||||
rev: v1.3.0
|
||||
hooks:
|
||||
- id: rst-backticks
|
||||
- repo: local
|
||||
|
||||
27
.travis.yml
27
.travis.yml
@@ -6,8 +6,13 @@ stages:
|
||||
if: repo = pytest-dev/pytest AND tag IS NOT present
|
||||
- name: deploy
|
||||
if: repo = pytest-dev/pytest AND tag IS present
|
||||
python:
|
||||
- '3.7'
|
||||
python: '3.7'
|
||||
cache: false
|
||||
|
||||
env:
|
||||
global:
|
||||
- PYTEST_ADDOPTS=-vv
|
||||
|
||||
install:
|
||||
- python -m pip install --upgrade --pre tox
|
||||
|
||||
@@ -44,9 +49,9 @@ jobs:
|
||||
|
||||
# Coverage tracking is slow with pypy, skip it.
|
||||
- env: TOXENV=pypy-xdist
|
||||
python: 'pypy2.7-6.0'
|
||||
python: 'pypy'
|
||||
- env: TOXENV=pypy3-xdist
|
||||
python: 'pypy3.5-6.0'
|
||||
python: 'pypy3'
|
||||
|
||||
- env: TOXENV=py34-xdist
|
||||
python: '3.4'
|
||||
@@ -57,7 +62,8 @@ jobs:
|
||||
# - pytester's LsofFdLeakChecker
|
||||
# - TestArgComplete (linux only)
|
||||
# - numpy
|
||||
- env: TOXENV=py37-lsof-numpy-xdist PYTEST_COVERAGE=1
|
||||
# Empty PYTEST_ADDOPTS to run this non-verbose.
|
||||
- env: TOXENV=py37-lsof-numpy-xdist PYTEST_COVERAGE=1 PYTEST_ADDOPTS=
|
||||
|
||||
# Specialized factors for py27.
|
||||
- env: TOXENV=py27-nobyte-numpy-xdist
|
||||
@@ -68,7 +74,7 @@ jobs:
|
||||
# Specialized factors for py37.
|
||||
# Coverage for:
|
||||
# - test_sys_breakpoint_interception (via pexpect).
|
||||
- env: TOXENV=py37-pexpect,py37-trial PYTEST_COVERAGE=1
|
||||
- env: TOXENV=py37-pexpect,py37-twisted PYTEST_COVERAGE=1
|
||||
- env: TOXENV=py37-pluggymaster-xdist
|
||||
- env: TOXENV=py37-freeze
|
||||
|
||||
@@ -80,12 +86,15 @@ jobs:
|
||||
- stage: baseline
|
||||
# Coverage for:
|
||||
# - _pytest.unittest._handle_skip (via pexpect).
|
||||
env: TOXENV=py27-pexpect,py27-trial PYTEST_COVERAGE=1
|
||||
env: TOXENV=py27-pexpect,py27-twisted PYTEST_COVERAGE=1
|
||||
python: '2.7'
|
||||
# Use py36 here for faster baseline.
|
||||
- env: TOXENV=py36-xdist
|
||||
python: '3.6'
|
||||
- env: TOXENV=linting,docs,doctesting PYTEST_COVERAGE=1
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.cache/pre-commit
|
||||
|
||||
- stage: deploy
|
||||
python: '3.6'
|
||||
@@ -144,7 +153,3 @@ notifications:
|
||||
skip_join: true
|
||||
email:
|
||||
- pytest-commit@python.org
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.cache/pip
|
||||
- $HOME/.cache/pre-commit
|
||||
|
||||
5
AUTHORS
5
AUTHORS
@@ -59,6 +59,7 @@ Christopher Gilling
|
||||
Christopher Dignam
|
||||
CrazyMerlyn
|
||||
Cyrus Maden
|
||||
Damian Skrzypczak
|
||||
Dhiren Serai
|
||||
Daniel Grana
|
||||
Daniel Hahler
|
||||
@@ -105,6 +106,7 @@ Hugo van Kemenade
|
||||
Hui Wang (coldnight)
|
||||
Ian Bicking
|
||||
Ian Lesperance
|
||||
Ilya Konstantinov
|
||||
Ionuț Turturică
|
||||
Iwan Briquemont
|
||||
Jaap Broekhuizen
|
||||
@@ -179,6 +181,7 @@ Nicholas Devenish
|
||||
Nicholas Murphy
|
||||
Niclas Olofsson
|
||||
Nicolas Delaby
|
||||
Nikolay Kondratyev
|
||||
Oleg Pidsadnyi
|
||||
Oleg Sushchenko
|
||||
Oliver Bestwalter
|
||||
@@ -222,6 +225,7 @@ Steffen Allner
|
||||
Stephan Obermann
|
||||
Sven-Hendrik Haase
|
||||
Tadek Teleżyński
|
||||
Takafumi Arakaki
|
||||
Tarcisio Fischer
|
||||
Tareq Alayan
|
||||
Ted Xiao
|
||||
@@ -241,6 +245,7 @@ Vidar T. Fauske
|
||||
Virgil Dupras
|
||||
Vitaly Lashmanov
|
||||
Vlad Dragos
|
||||
Volodymyr Piskun
|
||||
Wil Cooley
|
||||
William Lee
|
||||
Wim Glenn
|
||||
|
||||
209
CHANGELOG.rst
209
CHANGELOG.rst
@@ -18,6 +18,215 @@ with advance notice in the **Deprecations** section of releases.
|
||||
|
||||
.. towncrier release notes start
|
||||
|
||||
pytest 4.4.2 (2019-05-08)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#5089 <https://github.com/pytest-dev/pytest/issues/5089>`_: Fix crash caused by error in ``__repr__`` function with both ``showlocals`` and verbose output enabled.
|
||||
|
||||
|
||||
- `#5139 <https://github.com/pytest-dev/pytest/issues/5139>`_: Eliminate core dependency on 'terminal' plugin.
|
||||
|
||||
|
||||
- `#5229 <https://github.com/pytest-dev/pytest/issues/5229>`_: Require ``pluggy>=0.11.0`` which reverts a dependency to ``importlib-metadata`` added in ``0.10.0``.
|
||||
The ``importlib-metadata`` package cannot be imported when installed as an egg and causes issues when relying on ``setup.py`` to install test dependencies.
|
||||
|
||||
|
||||
|
||||
Improved Documentation
|
||||
----------------------
|
||||
|
||||
- `#5171 <https://github.com/pytest-dev/pytest/issues/5171>`_: Doc: ``pytest_ignore_collect``, ``pytest_collect_directory``, ``pytest_collect_file`` and ``pytest_pycollect_makemodule`` hooks's 'path' parameter documented type is now ``py.path.local``
|
||||
|
||||
|
||||
- `#5188 <https://github.com/pytest-dev/pytest/issues/5188>`_: Improve help for ``--runxfail`` flag.
|
||||
|
||||
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- `#5182 <https://github.com/pytest-dev/pytest/issues/5182>`_: Removed internal and unused ``_pytest.deprecated.MARK_INFO_ATTRIBUTE``.
|
||||
|
||||
|
||||
pytest 4.4.1 (2019-04-15)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#5031 <https://github.com/pytest-dev/pytest/issues/5031>`_: Environment variables are properly restored when using pytester's ``testdir`` fixture.
|
||||
|
||||
|
||||
- `#5039 <https://github.com/pytest-dev/pytest/issues/5039>`_: Fix regression with ``--pdbcls``, which stopped working with local modules in 4.0.0.
|
||||
|
||||
|
||||
- `#5092 <https://github.com/pytest-dev/pytest/issues/5092>`_: Produce a warning when unknown keywords are passed to ``pytest.param(...)``.
|
||||
|
||||
|
||||
- `#5098 <https://github.com/pytest-dev/pytest/issues/5098>`_: Invalidate import caches with ``monkeypatch.syspath_prepend``, which is required with namespace packages being used.
|
||||
|
||||
|
||||
pytest 4.4.0 (2019-03-29)
|
||||
=========================
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- `#2224 <https://github.com/pytest-dev/pytest/issues/2224>`_: ``async`` test functions are skipped and a warning is emitted when a suitable
|
||||
async plugin is not installed (such as ``pytest-asyncio`` or ``pytest-trio``).
|
||||
|
||||
Previously ``async`` functions would not execute at all but still be marked as "passed".
|
||||
|
||||
|
||||
- `#2482 <https://github.com/pytest-dev/pytest/issues/2482>`_: Include new ``disable_test_id_escaping_and_forfeit_all_rights_to_community_support`` option to disable ascii-escaping in parametrized values. This may cause a series of problems and as the name makes clear, use at your own risk.
|
||||
|
||||
|
||||
- `#4718 <https://github.com/pytest-dev/pytest/issues/4718>`_: The ``-p`` option can now be used to early-load plugins also by entry-point name, instead of just
|
||||
by module name.
|
||||
|
||||
This makes it possible to early load external plugins like ``pytest-cov`` in the command-line::
|
||||
|
||||
pytest -p pytest_cov
|
||||
|
||||
|
||||
- `#4855 <https://github.com/pytest-dev/pytest/issues/4855>`_: The ``--pdbcls`` option handles classes via module attributes now (e.g.
|
||||
``pdb:pdb.Pdb`` with `pdb++`_), and its validation was improved.
|
||||
|
||||
.. _pdb++: https://pypi.org/project/pdbpp/
|
||||
|
||||
|
||||
- `#4875 <https://github.com/pytest-dev/pytest/issues/4875>`_: The `testpaths <https://docs.pytest.org/en/latest/reference.html#confval-testpaths>`__ configuration option is now displayed next
|
||||
to the ``rootdir`` and ``inifile`` lines in the pytest header if the option is in effect, i.e., directories or file names were
|
||||
not explicitly passed in the command line.
|
||||
|
||||
Also, ``inifile`` is only displayed if there's a configuration file, instead of an empty ``inifile:`` string.
|
||||
|
||||
|
||||
- `#4911 <https://github.com/pytest-dev/pytest/issues/4911>`_: Doctests can be skipped now dynamically using ``pytest.skip()``.
|
||||
|
||||
|
||||
- `#4920 <https://github.com/pytest-dev/pytest/issues/4920>`_: Internal refactorings have been made in order to make the implementation of the
|
||||
`pytest-subtests <https://github.com/pytest-dev/pytest-subtests>`__ plugin
|
||||
possible, which adds unittest sub-test support and a new ``subtests`` fixture as discussed in
|
||||
`#1367 <https://github.com/pytest-dev/pytest/issues/1367>`__.
|
||||
|
||||
For details on the internal refactorings, please see the details on the related PR.
|
||||
|
||||
|
||||
- `#4931 <https://github.com/pytest-dev/pytest/issues/4931>`_: pytester's ``LineMatcher`` asserts that the passed lines are a sequence.
|
||||
|
||||
|
||||
- `#4936 <https://github.com/pytest-dev/pytest/issues/4936>`_: Handle ``-p plug`` after ``-p no:plug``.
|
||||
|
||||
This can be used to override a blocked plugin (e.g. in "addopts") from the
|
||||
command line etc.
|
||||
|
||||
|
||||
- `#4951 <https://github.com/pytest-dev/pytest/issues/4951>`_: Output capturing is handled correctly when only capturing via fixtures (capsys, capfs) with ``pdb.set_trace()``.
|
||||
|
||||
|
||||
- `#4956 <https://github.com/pytest-dev/pytest/issues/4956>`_: ``pytester`` sets ``$HOME`` and ``$USERPROFILE`` to the temporary directory during test runs.
|
||||
|
||||
This ensures to not load configuration files from the real user's home directory.
|
||||
|
||||
|
||||
- `#4980 <https://github.com/pytest-dev/pytest/issues/4980>`_: Namespace packages are handled better with ``monkeypatch.syspath_prepend`` and ``testdir.syspathinsert`` (via ``pkg_resources.fixup_namespace_packages``).
|
||||
|
||||
|
||||
- `#4993 <https://github.com/pytest-dev/pytest/issues/4993>`_: The stepwise plugin reports status information now.
|
||||
|
||||
|
||||
- `#5008 <https://github.com/pytest-dev/pytest/issues/5008>`_: If a ``setup.cfg`` file contains ``[tool:pytest]`` and also the no longer supported ``[pytest]`` section, pytest will use ``[tool:pytest]`` ignoring ``[pytest]``. Previously it would unconditionally error out.
|
||||
|
||||
This makes it simpler for plugins to support old pytest versions.
|
||||
|
||||
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#1895 <https://github.com/pytest-dev/pytest/issues/1895>`_: Fix bug where fixtures requested dynamically via ``request.getfixturevalue()`` might be teardown
|
||||
before the requesting fixture.
|
||||
|
||||
|
||||
- `#4851 <https://github.com/pytest-dev/pytest/issues/4851>`_: pytester unsets ``PYTEST_ADDOPTS`` now to not use outer options with ``testdir.runpytest()``.
|
||||
|
||||
|
||||
- `#4903 <https://github.com/pytest-dev/pytest/issues/4903>`_: Use the correct modified time for years after 2038 in rewritten ``.pyc`` files.
|
||||
|
||||
|
||||
- `#4928 <https://github.com/pytest-dev/pytest/issues/4928>`_: Fix line offsets with ``ScopeMismatch`` errors.
|
||||
|
||||
|
||||
- `#4957 <https://github.com/pytest-dev/pytest/issues/4957>`_: ``-p no:plugin`` is handled correctly for default (internal) plugins now, e.g. with ``-p no:capture``.
|
||||
|
||||
Previously they were loaded (imported) always, making e.g. the ``capfd`` fixture available.
|
||||
|
||||
|
||||
- `#4968 <https://github.com/pytest-dev/pytest/issues/4968>`_: The pdb ``quit`` command is handled properly when used after the ``debug`` command with `pdb++`_.
|
||||
|
||||
.. _pdb++: https://pypi.org/project/pdbpp/
|
||||
|
||||
|
||||
- `#4975 <https://github.com/pytest-dev/pytest/issues/4975>`_: Fix the interpretation of ``-qq`` option where it was being considered as ``-v`` instead.
|
||||
|
||||
|
||||
- `#4978 <https://github.com/pytest-dev/pytest/issues/4978>`_: ``outcomes.Exit`` is not swallowed in ``assertrepr_compare`` anymore.
|
||||
|
||||
|
||||
- `#4988 <https://github.com/pytest-dev/pytest/issues/4988>`_: Close logging's file handler explicitly when the session finishes.
|
||||
|
||||
|
||||
- `#5003 <https://github.com/pytest-dev/pytest/issues/5003>`_: Fix line offset with mark collection error (off by one).
|
||||
|
||||
|
||||
|
||||
Improved Documentation
|
||||
----------------------
|
||||
|
||||
- `#4974 <https://github.com/pytest-dev/pytest/issues/4974>`_: Update docs for ``pytest_cmdline_parse`` hook to note availability liminations
|
||||
|
||||
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- `#4718 <https://github.com/pytest-dev/pytest/issues/4718>`_: ``pluggy>=0.9`` is now required.
|
||||
|
||||
|
||||
- `#4815 <https://github.com/pytest-dev/pytest/issues/4815>`_: ``funcsigs>=1.0`` is now required for Python 2.7.
|
||||
|
||||
|
||||
- `#4829 <https://github.com/pytest-dev/pytest/issues/4829>`_: Some left-over internal code related to ``yield`` tests has been removed.
|
||||
|
||||
|
||||
- `#4890 <https://github.com/pytest-dev/pytest/issues/4890>`_: Remove internally unused ``anypython`` fixture from the pytester plugin.
|
||||
|
||||
|
||||
- `#4912 <https://github.com/pytest-dev/pytest/issues/4912>`_: Remove deprecated Sphinx directive, ``add_description_unit()``,
|
||||
pin sphinx-removed-in to >= 0.2.0 to support Sphinx 2.0.
|
||||
|
||||
|
||||
- `#4913 <https://github.com/pytest-dev/pytest/issues/4913>`_: Fix pytest tests invocation with custom ``PYTHONPATH``.
|
||||
|
||||
|
||||
- `#4965 <https://github.com/pytest-dev/pytest/issues/4965>`_: New ``pytest_report_to_serializable`` and ``pytest_report_from_serializable`` **experimental** hooks.
|
||||
|
||||
These hooks will be used by ``pytest-xdist``, ``pytest-subtests``, and the replacement for
|
||||
resultlog to serialize and customize reports.
|
||||
|
||||
They are experimental, meaning that their details might change or even be removed
|
||||
completely in future patch releases without warning.
|
||||
|
||||
Feedback is welcome from plugin authors and users alike.
|
||||
|
||||
|
||||
- `#4987 <https://github.com/pytest-dev/pytest/issues/4987>`_: ``Collector.repr_failure`` respects the ``--tb`` option, but only defaults to ``short`` now (with ``auto``).
|
||||
|
||||
|
||||
pytest 4.3.1 (2019-03-11)
|
||||
=========================
|
||||
|
||||
|
||||
@@ -166,7 +166,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/ambv/black>`_ for formatting.
|
||||
#. Follow **PEP-8** for naming and `black <https://github.com/python/black>`_ for formatting.
|
||||
#. Tests are run using ``tox``::
|
||||
|
||||
tox -e linting,py27,py37
|
||||
|
||||
@@ -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/ambv/black
|
||||
:target: https://github.com/python/black
|
||||
|
||||
.. image:: https://www.codetriage.com/pytest-dev/pytest/badges/users.svg
|
||||
:target: https://www.codetriage.com/pytest-dev/pytest
|
||||
|
||||
@@ -3,7 +3,7 @@ trigger:
|
||||
- features
|
||||
|
||||
variables:
|
||||
PYTEST_ADDOPTS: "--junitxml=build/test-results/$(tox.env).xml"
|
||||
PYTEST_ADDOPTS: "--junitxml=build/test-results/$(tox.env).xml -vv"
|
||||
python.needs_vc: False
|
||||
python.exe: "python"
|
||||
COVERAGE_FILE: "$(Build.Repository.LocalPath)/.coverage"
|
||||
@@ -30,9 +30,9 @@ jobs:
|
||||
# - numpy
|
||||
# - pytester's LsofFdLeakChecker (being skipped)
|
||||
PYTEST_COVERAGE: '1'
|
||||
py27-trial:
|
||||
py27-twisted:
|
||||
python.version: '2.7'
|
||||
tox.env: 'py27-trial'
|
||||
tox.env: 'py27-twisted'
|
||||
python.needs_vc: True
|
||||
py27-pluggymaster-xdist:
|
||||
python.version: '2.7'
|
||||
@@ -40,13 +40,13 @@ jobs:
|
||||
# Coverage for:
|
||||
# - except-IOError in _attempt_to_close_capture_file for py2.
|
||||
# Also seen with py27-nobyte (using xdist), and py27-xdist.
|
||||
# But no exception with py27-pexpect,py27-trial,py27-numpy.
|
||||
# But no exception with py27-pexpect,py27-twisted,py27-numpy.
|
||||
PYTEST_COVERAGE: '1'
|
||||
pypy:
|
||||
python.version: 'pypy'
|
||||
python.version: 'pypy2'
|
||||
tox.env: 'pypy'
|
||||
python.exe: 'pypy'
|
||||
# NOTE: pypy3 fails to install pip currently due to an interal error.
|
||||
# NOTE: pypy3 fails to install pip currently due to an internal error.
|
||||
# pypy3:
|
||||
# python.version: 'pypy3'
|
||||
# tox.env: 'pypy3'
|
||||
@@ -76,9 +76,9 @@ jobs:
|
||||
py37-linting/docs/doctesting:
|
||||
python.version: '3.7'
|
||||
tox.env: 'linting,docs,doctesting'
|
||||
py37-trial/numpy:
|
||||
py37-twisted/numpy:
|
||||
python.version: '3.7'
|
||||
tox.env: 'py37-trial,py37-numpy'
|
||||
tox.env: 'py37-twisted,py37-numpy'
|
||||
py37-pluggymaster-xdist:
|
||||
python.version: '3.7'
|
||||
tox.env: 'py37-pluggymaster-xdist'
|
||||
@@ -86,7 +86,6 @@ jobs:
|
||||
|
||||
steps:
|
||||
- task: UsePythonVersion@0
|
||||
condition: not(startsWith(variables['python.exe'], 'pypy'))
|
||||
inputs:
|
||||
versionSpec: '$(python.version)'
|
||||
architecture: 'x64'
|
||||
@@ -95,23 +94,6 @@ jobs:
|
||||
condition: eq(variables['python.needs_vc'], True)
|
||||
displayName: 'Install VC for py27'
|
||||
|
||||
- script: choco install python.pypy
|
||||
condition: eq(variables['python.exe'], 'pypy')
|
||||
displayName: 'Install pypy'
|
||||
|
||||
- script: choco install pypy3
|
||||
condition: eq(variables['python.exe'], 'pypy3')
|
||||
displayName: 'Install pypy3'
|
||||
|
||||
- task: PowerShell@2
|
||||
inputs:
|
||||
targetType: 'inline'
|
||||
script: |
|
||||
Invoke-WebRequest -Uri "https://bootstrap.pypa.io/get-pip.py" -OutFile "get-pip.py"
|
||||
$(python.exe) get-pip.py
|
||||
condition: startsWith(variables['python.exe'], 'pypy')
|
||||
displayName: 'Install pip'
|
||||
|
||||
- script: $(python.exe) -m pip install --upgrade pip && $(python.exe) -m pip install tox
|
||||
displayName: 'Install tox'
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ if __name__ == "__main__":
|
||||
import pytest # NOQA
|
||||
import pstats
|
||||
|
||||
script = sys.argv[1:] if len(sys.argv) > 1 else "empty.py"
|
||||
script = sys.argv[1:] if len(sys.argv) > 1 else ["empty.py"]
|
||||
stats = cProfile.run("pytest.cmdline.main(%r)" % script, "prof")
|
||||
p = pstats.Stats("prof")
|
||||
p.strip_dirs()
|
||||
|
||||
@@ -16,4 +16,4 @@ run = 'fc("/d")'
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(timeit.timeit(run, setup=setup % imports[0], number=count))
|
||||
print((timeit.timeit(run, setup=setup % imports[1], number=count)))
|
||||
print(timeit.timeit(run, setup=setup % imports[1], number=count))
|
||||
|
||||
@@ -1,4 +1,2 @@
|
||||
import six
|
||||
|
||||
for i in range(1000):
|
||||
six.exec_("def test_func_%d(): pass" % i)
|
||||
exec("def test_func_%d(): pass" % i)
|
||||
|
||||
@@ -13,6 +13,9 @@ PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
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 "@/tmp/pytest-of-.*/pytest-\d+@PYTEST_TMPDIR@" \
|
||||
--normalize "@pytest-(\d+)\\.[^ ,]+@pytest-\1.x.y@" \
|
||||
@@ -38,8 +41,9 @@ help:
|
||||
clean:
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
|
||||
regen: REGENDOC_FILES:=*.rst */*.rst
|
||||
regen:
|
||||
PYTHONDONTWRITEBYTECODE=1 PYTEST_ADDOPTS=-pno:hypothesis COLUMNS=76 regendoc --update *.rst */*.rst ${REGENDOC_ARGS}
|
||||
PYTHONDONTWRITEBYTECODE=1 PYTEST_ADDOPTS=-pno:hypothesis COLUMNS=76 regendoc --update ${REGENDOC_FILES} ${REGENDOC_ARGS}
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
|
||||
@@ -6,6 +6,9 @@ Release announcements
|
||||
:maxdepth: 2
|
||||
|
||||
|
||||
release-4.4.2
|
||||
release-4.4.1
|
||||
release-4.4.0
|
||||
release-4.3.1
|
||||
release-4.3.0
|
||||
release-4.2.1
|
||||
|
||||
39
doc/en/announce/release-4.4.0.rst
Normal file
39
doc/en/announce/release-4.4.0.rst
Normal file
@@ -0,0 +1,39 @@
|
||||
pytest-4.4.0
|
||||
=======================================
|
||||
|
||||
The pytest team is proud to announce the 4.4.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:
|
||||
|
||||
* Anthony Sottile
|
||||
* ApaDoctor
|
||||
* Bernhard M. Wiedemann
|
||||
* Brian Skinn
|
||||
* Bruno Oliveira
|
||||
* Daniel Hahler
|
||||
* Gary Tyler
|
||||
* Jeong YunWon
|
||||
* Miro Hrončok
|
||||
* Takafumi Arakaki
|
||||
* henrykironde
|
||||
* smheidrich
|
||||
|
||||
|
||||
Happy testing,
|
||||
The Pytest Development Team
|
||||
20
doc/en/announce/release-4.4.1.rst
Normal file
20
doc/en/announce/release-4.4.1.rst
Normal file
@@ -0,0 +1,20 @@
|
||||
pytest-4.4.1
|
||||
=======================================
|
||||
|
||||
pytest 4.4.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
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
||||
33
doc/en/announce/release-4.4.2.rst
Normal file
33
doc/en/announce/release-4.4.2.rst
Normal file
@@ -0,0 +1,33 @@
|
||||
pytest-4.4.2
|
||||
=======================================
|
||||
|
||||
pytest 4.4.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:
|
||||
|
||||
* Allan Lewis
|
||||
* Anthony Sottile
|
||||
* Bruno Oliveira
|
||||
* DamianSkrzypczak
|
||||
* Daniel Hahler
|
||||
* Don Kirkby
|
||||
* Douglas Thor
|
||||
* Hugo
|
||||
* Ilya Konstantinov
|
||||
* Jon Dufresne
|
||||
* Matt Cooper
|
||||
* Nikolay Kondratyev
|
||||
* Ondřej Súkup
|
||||
* Peter Schutt
|
||||
* Romain Chossart
|
||||
* Sitaktif
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
||||
@@ -12,12 +12,15 @@ Asserting with the ``assert`` statement
|
||||
|
||||
``pytest`` allows you to use the standard python ``assert`` for verifying
|
||||
expectations and values in Python tests. For example, you can write the
|
||||
following::
|
||||
following:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_assert1.py
|
||||
def f():
|
||||
return 3
|
||||
|
||||
|
||||
def test_function():
|
||||
assert f() == 4
|
||||
|
||||
@@ -30,7 +33,7 @@ you will see the return value of the function call:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 1 item
|
||||
|
||||
test_assert1.py F [100%]
|
||||
@@ -43,7 +46,7 @@ you will see the return value of the function call:
|
||||
E assert 3 == 4
|
||||
E + where 3 = f()
|
||||
|
||||
test_assert1.py:5: AssertionError
|
||||
test_assert1.py:6: AssertionError
|
||||
========================= 1 failed in 0.12 seconds =========================
|
||||
|
||||
``pytest`` has support for showing the values of the most common subexpressions
|
||||
@@ -52,7 +55,9 @@ operators. (See :ref:`tbreportdemo`). This allows you to use the
|
||||
idiomatic python constructs without boilerplate code while not losing
|
||||
introspection information.
|
||||
|
||||
However, if you specify a message with the assertion like this::
|
||||
However, if you specify a message with the assertion like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
assert a % 2 == 0, "value was odd, should be even"
|
||||
|
||||
@@ -67,22 +72,29 @@ Assertions about expected exceptions
|
||||
------------------------------------------
|
||||
|
||||
In order to write assertions about raised exceptions, you can use
|
||||
``pytest.raises`` as a context manager like this::
|
||||
``pytest.raises`` as a context manager like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def test_zero_division():
|
||||
with pytest.raises(ZeroDivisionError):
|
||||
1 / 0
|
||||
|
||||
and if you need to have access to the actual exception info you may use::
|
||||
and if you need to have access to the actual exception info you may use:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_recursion_depth():
|
||||
with pytest.raises(RuntimeError) as excinfo:
|
||||
|
||||
def f():
|
||||
f()
|
||||
|
||||
f()
|
||||
assert 'maximum recursion' in str(excinfo.value)
|
||||
assert "maximum recursion" in str(excinfo.value)
|
||||
|
||||
``excinfo`` is a ``ExceptionInfo`` instance, which is a wrapper around
|
||||
the actual exception raised. The main attributes of interest are
|
||||
@@ -90,15 +102,19 @@ the actual exception raised. The main attributes of interest are
|
||||
|
||||
You can pass a ``match`` keyword parameter to the context-manager to test
|
||||
that a regular expression matches on the string representation of an exception
|
||||
(similar to the ``TestCase.assertRaisesRegexp`` method from ``unittest``)::
|
||||
(similar to the ``TestCase.assertRaisesRegexp`` method from ``unittest``):
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def myfunc():
|
||||
raise ValueError("Exception 123 raised")
|
||||
|
||||
|
||||
def test_match():
|
||||
with pytest.raises(ValueError, match=r'.* 123 .*'):
|
||||
with pytest.raises(ValueError, match=r".* 123 .*"):
|
||||
myfunc()
|
||||
|
||||
The regexp parameter of the ``match`` method is matched with the ``re.search``
|
||||
@@ -107,7 +123,9 @@ well.
|
||||
|
||||
There's an alternate form of the ``pytest.raises`` function where you pass
|
||||
a function that will be executed with the given ``*args`` and ``**kwargs`` and
|
||||
assert that the given exception is raised::
|
||||
assert that the given exception is raised:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
pytest.raises(ExpectedException, func, *args, **kwargs)
|
||||
|
||||
@@ -116,7 +134,9 @@ exception* or *wrong exception*.
|
||||
|
||||
Note that it is also possible to specify a "raises" argument to
|
||||
``pytest.mark.xfail``, which checks that the test is failing in a more
|
||||
specific way than just having any exception raised::
|
||||
specific way than just having any exception raised:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.xfail(raises=IndexError)
|
||||
def test_f():
|
||||
@@ -134,7 +154,7 @@ or bugs in dependencies.
|
||||
Assertions about expected warnings
|
||||
-----------------------------------------
|
||||
|
||||
.. versionadded:: 2.8
|
||||
|
||||
|
||||
You can check that code raises a particular warning using
|
||||
:ref:`pytest.warns <warns>`.
|
||||
@@ -145,13 +165,16 @@ You can check that code raises a particular warning using
|
||||
Making use of context-sensitive comparisons
|
||||
-------------------------------------------------
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
|
||||
``pytest`` has rich support for providing context-sensitive information
|
||||
when it encounters comparisons. For example::
|
||||
when it encounters comparisons. For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_assert2.py
|
||||
|
||||
|
||||
def test_set_comparison():
|
||||
set1 = set("1308")
|
||||
set2 = set("8035")
|
||||
@@ -165,7 +188,7 @@ if you run this module:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 1 item
|
||||
|
||||
test_assert2.py F [100%]
|
||||
@@ -184,7 +207,7 @@ if you run this module:
|
||||
E '5'
|
||||
E Use -v to get the full diff
|
||||
|
||||
test_assert2.py:5: AssertionError
|
||||
test_assert2.py:6: AssertionError
|
||||
========================= 1 failed in 0.12 seconds =========================
|
||||
|
||||
Special comparisons are done for a number of cases:
|
||||
@@ -205,16 +228,21 @@ the ``pytest_assertrepr_compare`` hook.
|
||||
:noindex:
|
||||
|
||||
As an example consider adding the following hook in a :ref:`conftest.py <conftest.py>`
|
||||
file which provides an alternative explanation for ``Foo`` objects::
|
||||
file which provides an alternative explanation for ``Foo`` objects:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of conftest.py
|
||||
from test_foocompare import Foo
|
||||
|
||||
|
||||
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: %s != %s" % (left.val, right.val)]
|
||||
|
||||
now, given this test module::
|
||||
now, given this test module:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_foocompare.py
|
||||
class Foo(object):
|
||||
@@ -224,6 +252,7 @@ now, given this test module::
|
||||
def __eq__(self, other):
|
||||
return self.val == other.val
|
||||
|
||||
|
||||
def test_compare():
|
||||
f1 = Foo(1)
|
||||
f2 = Foo(2)
|
||||
@@ -246,7 +275,7 @@ the conftest file:
|
||||
E assert Comparing Foo instances:
|
||||
E vals: 1 != 2
|
||||
|
||||
test_foocompare.py:11: AssertionError
|
||||
test_foocompare.py:12: AssertionError
|
||||
1 failed in 0.12 seconds
|
||||
|
||||
.. _assert-details:
|
||||
@@ -255,7 +284,7 @@ the conftest file:
|
||||
Assertion introspection details
|
||||
-------------------------------
|
||||
|
||||
.. versionadded:: 2.1
|
||||
|
||||
|
||||
|
||||
Reporting details about a failing assertion is achieved by rewriting assert
|
||||
@@ -306,13 +335,13 @@ If this is the case you have two options:
|
||||
* Disable rewriting for all modules by using ``--assert=plain``.
|
||||
|
||||
|
||||
.. versionadded:: 2.1
|
||||
|
||||
Add assert rewriting as an alternate introspection technique.
|
||||
|
||||
.. versionchanged:: 2.1
|
||||
|
||||
Introduce the ``--assert`` option. Deprecate ``--no-assert`` and
|
||||
``--nomagic``.
|
||||
|
||||
.. versionchanged:: 3.0
|
||||
|
||||
Removes the ``--no-assert`` and ``--nomagic`` options.
|
||||
Removes the ``--assert=reinterp`` option.
|
||||
|
||||
@@ -28,25 +28,29 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||
|
||||
Values can be any object handled by the json stdlib module.
|
||||
capsys
|
||||
Enable capturing of writes to ``sys.stdout`` and ``sys.stderr`` and make
|
||||
captured output available via ``capsys.readouterr()`` method calls
|
||||
which return a ``(out, err)`` namedtuple. ``out`` and ``err`` will be ``text``
|
||||
objects.
|
||||
Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``.
|
||||
|
||||
The captured output is made available via ``capsys.readouterr()`` method
|
||||
calls, which return a ``(out, err)`` namedtuple.
|
||||
``out`` and ``err`` will be ``text`` objects.
|
||||
capsysbinary
|
||||
Enable capturing of writes to ``sys.stdout`` and ``sys.stderr`` and make
|
||||
captured output available via ``capsys.readouterr()`` method calls
|
||||
which return a ``(out, err)`` tuple. ``out`` and ``err`` will be ``bytes``
|
||||
objects.
|
||||
Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``.
|
||||
|
||||
The captured output is made available via ``capsysbinary.readouterr()``
|
||||
method calls, which return a ``(out, err)`` namedtuple.
|
||||
``out`` and ``err`` will be ``bytes`` objects.
|
||||
capfd
|
||||
Enable capturing of writes to file descriptors ``1`` and ``2`` and make
|
||||
captured output available via ``capfd.readouterr()`` method calls
|
||||
which return a ``(out, err)`` tuple. ``out`` and ``err`` will be ``text``
|
||||
objects.
|
||||
Enable text capturing of writes to file descriptors ``1`` and ``2``.
|
||||
|
||||
The captured output is made available via ``capfd.readouterr()`` method
|
||||
calls, which return a ``(out, err)`` namedtuple.
|
||||
``out`` and ``err`` will be ``text`` objects.
|
||||
capfdbinary
|
||||
Enable capturing of write to file descriptors 1 and 2 and make
|
||||
captured output available via ``capfdbinary.readouterr`` method calls
|
||||
which return a ``(out, err)`` tuple. ``out`` and ``err`` will be
|
||||
``bytes`` objects.
|
||||
Enable bytes capturing of writes to file descriptors ``1`` and ``2``.
|
||||
|
||||
The captured output is made available via ``capfd.readouterr()`` method
|
||||
calls, which return a ``(out, err)`` namedtuple.
|
||||
``out`` and ``err`` will be ``byte`` objects.
|
||||
doctest_namespace
|
||||
Fixture that returns a :py:class:`dict` that will be injected into the namespace of doctests.
|
||||
pytestconfig
|
||||
@@ -55,7 +59,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||
Example::
|
||||
|
||||
def test_foo(pytestconfig):
|
||||
if pytestconfig.getoption("verbose"):
|
||||
if pytestconfig.getoption("verbose") > 0:
|
||||
...
|
||||
record_property
|
||||
Add an extra properties the calling test.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
Cache: working with cross-testrun state
|
||||
=======================================
|
||||
|
||||
.. versionadded:: 2.8
|
||||
|
||||
|
||||
Usage
|
||||
---------
|
||||
@@ -82,7 +82,7 @@ If you then run it with ``--lf``:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 50 items / 48 deselected / 2 selected
|
||||
run-last-failure: rerun previous 2 failures
|
||||
|
||||
@@ -126,7 +126,7 @@ of ``FF`` and dots):
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 50 items
|
||||
run-last-failure: rerun previous 2 failures first
|
||||
|
||||
@@ -218,8 +218,6 @@ If you run this command for the first time, you can see the print statement:
|
||||
def test_function(mydata):
|
||||
> assert mydata == 23
|
||||
E assert 42 == 23
|
||||
E -42
|
||||
E +23
|
||||
|
||||
test_caching.py:17: AssertionError
|
||||
-------------------------- Captured stdout setup ---------------------------
|
||||
@@ -241,8 +239,6 @@ the cache and nothing will be printed:
|
||||
def test_function(mydata):
|
||||
> assert mydata == 23
|
||||
E assert 42 == 23
|
||||
E -42
|
||||
E +23
|
||||
|
||||
test_caching.py:17: AssertionError
|
||||
1 failed in 0.12 seconds
|
||||
@@ -262,7 +258,7 @@ You can always peek at the content of the cache using the
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
------------------------------- cache values -------------------------------
|
||||
cache/lastfailed contains:
|
||||
|
||||
@@ -71,7 +71,7 @@ of the failing function and hide the other one:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 2 items
|
||||
|
||||
test_module.py .F [100%]
|
||||
@@ -121,11 +121,11 @@ same interface but allows to also capture output from
|
||||
libraries or subprocesses that directly write to operating
|
||||
system level output streams (FD1 and FD2).
|
||||
|
||||
.. versionadded:: 3.3
|
||||
|
||||
|
||||
The return value from ``readouterr`` changed to a ``namedtuple`` with two attributes, ``out`` and ``err``.
|
||||
|
||||
.. versionadded:: 3.3
|
||||
|
||||
|
||||
If the code under test writes non-textual data, you can capture this using
|
||||
the ``capsysbinary`` fixture which instead returns ``bytes`` from
|
||||
@@ -133,7 +133,7 @@ the ``readouterr`` method. The ``capfsysbinary`` fixture is currently only
|
||||
available in python 3.
|
||||
|
||||
|
||||
.. versionadded:: 3.3
|
||||
|
||||
|
||||
If the code under test writes non-textual data, you can capture this using
|
||||
the ``capfdbinary`` fixture which instead returns ``bytes`` from
|
||||
@@ -141,7 +141,7 @@ the ``readouterr`` method. The ``capfdbinary`` fixture operates on the
|
||||
filedescriptor level.
|
||||
|
||||
|
||||
.. versionadded:: 3.0
|
||||
|
||||
|
||||
To temporarily disable capture within a test, both ``capsys``
|
||||
and ``capfd`` have a ``disabled()`` method that can be used
|
||||
|
||||
@@ -335,7 +335,7 @@ intersphinx_mapping = {"python": ("https://docs.python.org/3", None)}
|
||||
def setup(app):
|
||||
# from sphinx.ext.autodoc import cut_lines
|
||||
# app.connect('autodoc-process-docstring', cut_lines(4, what=['module']))
|
||||
app.add_description_unit(
|
||||
app.add_object_type(
|
||||
"confval",
|
||||
"confval",
|
||||
objname="configuration value",
|
||||
|
||||
@@ -20,7 +20,7 @@ which were registered by installed plugins.
|
||||
Initialization: determining rootdir and inifile
|
||||
-----------------------------------------------
|
||||
|
||||
.. versionadded:: 2.7
|
||||
|
||||
|
||||
pytest determines a ``rootdir`` for each test run which depends on
|
||||
the command line arguments (specified test files, paths) and on
|
||||
@@ -90,7 +90,7 @@ The ``config`` object will subsequently carry these attributes:
|
||||
|
||||
- ``config.inifile``: the determined ini-file, may be ``None``.
|
||||
|
||||
The rootdir is used a reference directory for constructing test
|
||||
The rootdir is used as a reference directory for constructing test
|
||||
addresses ("nodeids") and can be used also by plugins for storing
|
||||
per-testrun information.
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ Below is a complete list of all pytest features which are considered deprecated.
|
||||
:class:`_pytest.warning_types.PytestWarning` or subclasses, which can be filtered using
|
||||
:ref:`standard warning filters <warnings>`.
|
||||
|
||||
.. _`raises message deprecated`:
|
||||
|
||||
``"message"`` parameter of ``pytest.raises``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -60,7 +62,8 @@ If you still have concerns about this deprecation and future removal, please com
|
||||
|
||||
The ``pytest.config`` global object is deprecated. Instead use
|
||||
``request.config`` (via the ``request`` fixture) or if you are a plugin author
|
||||
use the ``pytest_configure(config)`` hook.
|
||||
use the ``pytest_configure(config)`` hook. Note that many hooks can also access
|
||||
the ``config`` object indirectly, through ``session.config`` or ``item.config`` for example.
|
||||
|
||||
.. _raises-warns-exec:
|
||||
|
||||
@@ -104,7 +107,7 @@ Becomes:
|
||||
Result log (``--result-log``)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 3.0
|
||||
|
||||
|
||||
The ``--resultlog`` command line option has been deprecated: it is little used
|
||||
and there are more modern and better alternatives, for example `pytest-tap <https://tappy.readthedocs.io/en/latest/>`_.
|
||||
|
||||
@@ -10,29 +10,63 @@ can change the pattern by issuing:
|
||||
|
||||
pytest --doctest-glob='*.rst'
|
||||
|
||||
on the command line. Since version ``2.9``, ``--doctest-glob``
|
||||
can be given multiple times in the command-line.
|
||||
on the command line. ``--doctest-glob`` can be given multiple times in the command-line.
|
||||
|
||||
.. versionadded:: 3.1
|
||||
If you then have a text file like this:
|
||||
|
||||
You can specify the encoding that will be used for those doctest files
|
||||
using the ``doctest_encoding`` ini option:
|
||||
.. code-block:: text
|
||||
|
||||
.. code-block:: ini
|
||||
# content of test_example.txt
|
||||
|
||||
# content of pytest.ini
|
||||
[pytest]
|
||||
doctest_encoding = latin1
|
||||
hello this is a doctest
|
||||
>>> x = 3
|
||||
>>> x
|
||||
3
|
||||
|
||||
The default encoding is UTF-8.
|
||||
then you can just invoke ``pytest`` directly:
|
||||
|
||||
You can also trigger running of doctests
|
||||
from docstrings in all python modules (including regular
|
||||
python test modules):
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 1 item
|
||||
|
||||
test_example.txt . [100%]
|
||||
|
||||
========================= 1 passed in 0.12 seconds =========================
|
||||
|
||||
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).
|
||||
|
||||
In addition to text files, you can also execute doctests directly from docstrings of your classes
|
||||
and functions, including from test modules:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of mymodule.py
|
||||
def something():
|
||||
""" a doctest in a docstring
|
||||
>>> something()
|
||||
42
|
||||
"""
|
||||
return 42
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pytest --doctest-modules
|
||||
$ pytest --doctest-modules
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 2 items
|
||||
|
||||
mymodule.py . [ 50%]
|
||||
test_example.txt . [100%]
|
||||
|
||||
========================= 2 passed in 0.12 seconds =========================
|
||||
|
||||
You can make these changes permanent in your project by
|
||||
putting them into a pytest.ini file like this:
|
||||
@@ -43,57 +77,37 @@ putting them into a pytest.ini file like this:
|
||||
[pytest]
|
||||
addopts = --doctest-modules
|
||||
|
||||
If you then have a text file like this:
|
||||
.. note::
|
||||
|
||||
.. code-block:: text
|
||||
The builtin pytest doctest supports only ``doctest`` blocks, but if you are looking
|
||||
for more advanced checking over *all* your documentation,
|
||||
including doctests, ``.. codeblock:: python`` Sphinx directive support,
|
||||
and any other examples your documentation may include, you may wish to
|
||||
consider `Sybil <https://sybil.readthedocs.io/en/latest/index.html>`__.
|
||||
It provides pytest integration out of the box.
|
||||
|
||||
# content of example.rst
|
||||
|
||||
hello this is a doctest
|
||||
>>> x = 3
|
||||
>>> x
|
||||
3
|
||||
Encoding
|
||||
--------
|
||||
|
||||
and another like this::
|
||||
The default encoding is **UTF-8**, but you can specify the encoding
|
||||
that will be used for those doctest files using the
|
||||
``doctest_encoding`` ini option:
|
||||
|
||||
# content of mymodule.py
|
||||
def something():
|
||||
""" a doctest in a docstring
|
||||
>>> something()
|
||||
42
|
||||
"""
|
||||
return 42
|
||||
.. code-block:: ini
|
||||
|
||||
then you can just invoke ``pytest`` without command line options:
|
||||
# content of pytest.ini
|
||||
[pytest]
|
||||
doctest_encoding = latin1
|
||||
|
||||
.. code-block:: pytest
|
||||
Using 'doctest' options
|
||||
-----------------------
|
||||
|
||||
$ pytest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
|
||||
collected 1 item
|
||||
The standard ``doctest`` module provides some `options <https://docs.python.org/3/library/doctest.html#option-flags>`__
|
||||
to configure the strictness of doctest tests. In pytest, you can enable those flags using the
|
||||
configuration file.
|
||||
|
||||
mymodule.py . [100%]
|
||||
|
||||
========================= 1 passed in 0.12 seconds =========================
|
||||
|
||||
It is possible to use fixtures using the ``getfixture`` helper:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
# content of example.rst
|
||||
>>> tmp = getfixture('tmpdir')
|
||||
>>> ...
|
||||
>>>
|
||||
|
||||
Also, :ref:`usefixtures` and :ref:`autouse` fixtures are supported
|
||||
when executing text doctest files.
|
||||
|
||||
The standard ``doctest`` module provides some setting flags to configure the
|
||||
strictness of doctest tests. In pytest, you can enable those flags using the
|
||||
configuration file. To make pytest ignore trailing whitespaces and ignore
|
||||
For example, to make pytest ignore trailing whitespaces and ignore
|
||||
lengthy exception stack traces you can just write:
|
||||
|
||||
.. code-block:: ini
|
||||
@@ -110,16 +124,7 @@ Python 3 unchanged:
|
||||
* ``ALLOW_BYTES``: when enabled, the ``b`` prefix is stripped from byte strings
|
||||
in expected doctest output.
|
||||
|
||||
As with any other option flag, these flags can be enabled in ``pytest.ini`` using
|
||||
the ``doctest_optionflags`` ini option:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[pytest]
|
||||
doctest_optionflags = ALLOW_UNICODE ALLOW_BYTES
|
||||
|
||||
|
||||
Alternatively, it can be enabled by an inline comment in the doc test
|
||||
Alternatively, options can be enabled by an inline comment in the doc test
|
||||
itself:
|
||||
|
||||
.. code-block:: rst
|
||||
@@ -128,7 +133,7 @@ itself:
|
||||
>>> get_unicode_greeting() # doctest: +ALLOW_UNICODE
|
||||
'Hello'
|
||||
|
||||
By default, pytest would report only the first failure for a given doctest. If
|
||||
By default, pytest would report only the first failure for a given doctest. If
|
||||
you want to continue the test even when you have failures, do:
|
||||
|
||||
.. code-block:: bash
|
||||
@@ -136,12 +141,50 @@ you want to continue the test even when you have failures, do:
|
||||
pytest --doctest-modules --doctest-continue-on-failure
|
||||
|
||||
|
||||
Output format
|
||||
-------------
|
||||
|
||||
You can change the diff output format on failure for your doctests
|
||||
by using one of standard doctest modules format in options
|
||||
(see :data:`python:doctest.REPORT_UDIFF`, :data:`python:doctest.REPORT_CDIFF`,
|
||||
:data:`python:doctest.REPORT_NDIFF`, :data:`python:doctest.REPORT_ONLY_FIRST_FAILURE`):
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pytest --doctest-modules --doctest-report none
|
||||
pytest --doctest-modules --doctest-report udiff
|
||||
pytest --doctest-modules --doctest-report cdiff
|
||||
pytest --doctest-modules --doctest-report ndiff
|
||||
pytest --doctest-modules --doctest-report only_first_failure
|
||||
|
||||
|
||||
pytest-specific features
|
||||
------------------------
|
||||
|
||||
Some features are provided to make writing doctests easier or with better integration with
|
||||
your existing test suite. Keep in mind however that by using those features you will make
|
||||
your doctests incompatible with the standard ``doctests`` module.
|
||||
|
||||
Using fixtures
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
It is possible to use fixtures using the ``getfixture`` helper:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
# content of example.rst
|
||||
>>> tmp = getfixture('tmpdir')
|
||||
>>> ...
|
||||
>>>
|
||||
|
||||
Also, :ref:`usefixtures` and :ref:`autouse` fixtures are supported
|
||||
when executing text doctest files.
|
||||
|
||||
|
||||
.. _`doctest_namespace`:
|
||||
|
||||
The 'doctest_namespace' fixture
|
||||
-------------------------------
|
||||
|
||||
.. versionadded:: 3.0
|
||||
'doctest_namespace' fixture
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The ``doctest_namespace`` fixture can be used to inject items into the
|
||||
namespace in which your doctests run. It is intended to be used within
|
||||
@@ -171,20 +214,14 @@ Note that like the normal ``conftest.py``, the fixtures are discovered in the di
|
||||
Meaning that if you put your doctest with your source code, the relevant conftest.py needs to be in the same directory tree.
|
||||
Fixtures will not be discovered in a sibling directory tree!
|
||||
|
||||
Output format
|
||||
-------------
|
||||
Skipping tests dynamically
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. versionadded:: 3.0
|
||||
.. versionadded:: 4.4
|
||||
|
||||
You can change the diff output format on failure for your doctests
|
||||
by using one of standard doctest modules format in options
|
||||
(see :data:`python:doctest.REPORT_UDIFF`, :data:`python:doctest.REPORT_CDIFF`,
|
||||
:data:`python:doctest.REPORT_NDIFF`, :data:`python:doctest.REPORT_ONLY_FIRST_FAILURE`):
|
||||
You can use ``pytest.skip`` to dynamically skip doctests. For example::
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pytest --doctest-modules --doctest-report none
|
||||
pytest --doctest-modules --doctest-report udiff
|
||||
pytest --doctest-modules --doctest-report cdiff
|
||||
pytest --doctest-modules --doctest-report ndiff
|
||||
pytest --doctest-modules --doctest-report only_first_failure
|
||||
>>> import sys, pytest
|
||||
>>> if sys.platform.startswith('win'):
|
||||
... pytest.skip('this doctest does not work on Windows')
|
||||
...
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import six
|
||||
|
||||
import _pytest._code
|
||||
import pytest
|
||||
from pytest import raises
|
||||
@@ -199,7 +197,7 @@ def test_dynamic_compile_shows_nicely():
|
||||
name = "abc-123"
|
||||
module = imp.new_module(name)
|
||||
code = _pytest._code.compile(src, name, "exec")
|
||||
six.exec_(code, module.__dict__)
|
||||
exec(code, module.__dict__)
|
||||
sys.modules[name] = module
|
||||
module.foo()
|
||||
|
||||
|
||||
@@ -9,23 +9,33 @@ Here are some example using the :ref:`mark` mechanism.
|
||||
Marking test functions and selecting them for a run
|
||||
----------------------------------------------------
|
||||
|
||||
You can "mark" a test function with custom metadata like this::
|
||||
You can "mark" a test function with custom metadata like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_server.py
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.webtest
|
||||
def test_send_http():
|
||||
pass # perform some webtest test for your app
|
||||
pass # perform some webtest test for your app
|
||||
|
||||
|
||||
def test_something_quick():
|
||||
pass
|
||||
|
||||
|
||||
def test_another():
|
||||
pass
|
||||
|
||||
|
||||
class TestClass(object):
|
||||
def test_method(self):
|
||||
pass
|
||||
|
||||
.. versionadded:: 2.2
|
||||
|
||||
|
||||
You can then restrict a test run to only run tests marked with ``webtest``:
|
||||
|
||||
@@ -35,7 +45,7 @@ You can then restrict a test run to only run tests marked with ``webtest``:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collecting ... collected 4 items / 3 deselected / 1 selected
|
||||
|
||||
test_server.py::test_send_http PASSED [100%]
|
||||
@@ -50,7 +60,7 @@ Or the inverse, running all tests except the webtest ones:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collecting ... collected 4 items / 1 deselected / 3 selected
|
||||
|
||||
test_server.py::test_something_quick PASSED [ 33%]
|
||||
@@ -72,7 +82,7 @@ tests based on their module, class, method, or function name:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collecting ... collected 1 item
|
||||
|
||||
test_server.py::TestClass::test_method PASSED [100%]
|
||||
@@ -87,7 +97,7 @@ You can also select on the class:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collecting ... collected 1 item
|
||||
|
||||
test_server.py::TestClass::test_method PASSED [100%]
|
||||
@@ -102,7 +112,7 @@ Or select multiple nodes:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collecting ... collected 2 items
|
||||
|
||||
test_server.py::TestClass::test_method PASSED [ 50%]
|
||||
@@ -142,7 +152,7 @@ select tests based on their names:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collecting ... collected 4 items / 3 deselected / 1 selected
|
||||
|
||||
test_server.py::test_send_http PASSED [100%]
|
||||
@@ -157,7 +167,7 @@ And you can also run all tests except the ones that match the keyword:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collecting ... collected 4 items / 1 deselected / 3 selected
|
||||
|
||||
test_server.py::test_something_quick PASSED [ 33%]
|
||||
@@ -174,7 +184,7 @@ Or to select "http" and "quick" tests:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collecting ... collected 4 items / 2 deselected / 2 selected
|
||||
|
||||
test_server.py::test_send_http PASSED [ 50%]
|
||||
@@ -200,7 +210,7 @@ Or to select "http" and "quick" tests:
|
||||
Registering markers
|
||||
-------------------------------------
|
||||
|
||||
.. versionadded:: 2.2
|
||||
|
||||
|
||||
.. ini-syntax for custom markers:
|
||||
|
||||
@@ -257,14 +267,19 @@ Marking whole classes or modules
|
||||
----------------------------------------------------
|
||||
|
||||
You may use ``pytest.mark`` decorators with classes to apply markers to all of
|
||||
its test methods::
|
||||
its test methods:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_mark_classlevel.py
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.webtest
|
||||
class TestClass(object):
|
||||
def test_startup(self):
|
||||
pass
|
||||
|
||||
def test_startup_and_more(self):
|
||||
pass
|
||||
|
||||
@@ -272,17 +287,23 @@ This is equivalent to directly applying the decorator to the
|
||||
two test functions.
|
||||
|
||||
To remain backward-compatible with Python 2.4 you can also set a
|
||||
``pytestmark`` attribute on a TestClass like this::
|
||||
``pytestmark`` attribute on a TestClass like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
class TestClass(object):
|
||||
pytestmark = pytest.mark.webtest
|
||||
|
||||
or if you need to use multiple markers you can use a list::
|
||||
or if you need to use multiple markers you can use a list:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
class TestClass(object):
|
||||
pytestmark = [pytest.mark.webtest, pytest.mark.slowtest]
|
||||
|
||||
@@ -305,18 +326,19 @@ Marking individual tests when using parametrize
|
||||
|
||||
When using parametrize, applying a mark will make it apply
|
||||
to each individual test. However it is also possible to
|
||||
apply a marker to an individual test instance::
|
||||
apply a marker to an individual test instance:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.foo
|
||||
@pytest.mark.parametrize(("n", "expected"), [
|
||||
(1, 2),
|
||||
pytest.param((1, 3), marks=pytest.mark.bar),
|
||||
(2, 3),
|
||||
])
|
||||
@pytest.mark.parametrize(
|
||||
("n", "expected"), [(1, 2), pytest.param((1, 3), marks=pytest.mark.bar), (2, 3)]
|
||||
)
|
||||
def test_increment(n, expected):
|
||||
assert n + 1 == expected
|
||||
assert n + 1 == expected
|
||||
|
||||
In this example the mark "foo" will apply to each of the three
|
||||
tests, whereas the "bar" mark is only applied to the second test.
|
||||
@@ -332,31 +354,46 @@ Custom marker and command line option to control test runs
|
||||
Plugins can provide custom markers and implement specific behaviour
|
||||
based on it. This is a self-contained example which adds a command
|
||||
line option and a parametrized test function marker to run tests
|
||||
specifies via named environments::
|
||||
specifies via named environments:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of conftest.py
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption("-E", action="store", metavar="NAME",
|
||||
help="only run tests matching the environment NAME.")
|
||||
parser.addoption(
|
||||
"-E",
|
||||
action="store",
|
||||
metavar="NAME",
|
||||
help="only run tests matching the environment NAME.",
|
||||
)
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
# register an additional marker
|
||||
config.addinivalue_line("markers",
|
||||
"env(name): mark test to run only on named environment")
|
||||
config.addinivalue_line(
|
||||
"markers", "env(name): mark test to run only on named environment"
|
||||
)
|
||||
|
||||
|
||||
def pytest_runtest_setup(item):
|
||||
envnames = [mark.args[0] for mark in item.iter_markers(name='env')]
|
||||
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)
|
||||
|
||||
A test file using this local plugin::
|
||||
A test file using this local plugin:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_someenv.py
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.env("stage1")
|
||||
def test_basic_db_operation():
|
||||
pass
|
||||
@@ -370,7 +407,7 @@ the test needs:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 1 item
|
||||
|
||||
test_someenv.py s [100%]
|
||||
@@ -385,7 +422,7 @@ and here is one that specifies exactly the environment needed:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 1 item
|
||||
|
||||
test_someenv.py . [100%]
|
||||
@@ -423,25 +460,32 @@ Passing a callable to custom markers
|
||||
|
||||
.. regendoc:wipe
|
||||
|
||||
Below is the config file that will be used in the next examples::
|
||||
Below is the config file that will be used in the next examples:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of conftest.py
|
||||
import sys
|
||||
|
||||
|
||||
def pytest_runtest_setup(item):
|
||||
for marker in item.iter_markers(name='my_marker'):
|
||||
for marker in item.iter_markers(name="my_marker"):
|
||||
print(marker)
|
||||
sys.stdout.flush()
|
||||
|
||||
A custom marker can have its argument set, i.e. ``args`` and ``kwargs`` properties, defined by either invoking it as a callable or using ``pytest.mark.MARKER_NAME.with_args``. These two methods achieve the same effect most of the time.
|
||||
|
||||
However, if there is a callable as the single positional argument with no keyword arguments, using the ``pytest.mark.MARKER_NAME(c)`` will not pass ``c`` as a positional argument but decorate ``c`` with the custom marker (see :ref:`MarkDecorator <mark>`). Fortunately, ``pytest.mark.MARKER_NAME.with_args`` comes to the rescue::
|
||||
However, if there is a callable as the single positional argument with no keyword arguments, using the ``pytest.mark.MARKER_NAME(c)`` will not pass ``c`` as a positional argument but decorate ``c`` with the custom marker (see :ref:`MarkDecorator <mark>`). Fortunately, ``pytest.mark.MARKER_NAME.with_args`` comes to the rescue:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_custom_marker.py
|
||||
import pytest
|
||||
|
||||
|
||||
def hello_world(*args, **kwargs):
|
||||
return 'Hello World'
|
||||
return "Hello World"
|
||||
|
||||
|
||||
@pytest.mark.my_marker.with_args(hello_world)
|
||||
def test_with_args():
|
||||
@@ -467,12 +511,16 @@ Reading markers which were set from multiple places
|
||||
.. regendoc:wipe
|
||||
|
||||
If you are heavily using markers in your test suite you may encounter the case where a marker is applied several times to a test function. From plugin
|
||||
code you can read over all such settings. Example::
|
||||
code you can read over all such settings. Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_mark_three_times.py
|
||||
import pytest
|
||||
|
||||
pytestmark = pytest.mark.glob("module", x=1)
|
||||
|
||||
|
||||
@pytest.mark.glob("class", x=2)
|
||||
class TestClass(object):
|
||||
@pytest.mark.glob("function", x=3)
|
||||
@@ -480,13 +528,16 @@ code you can read over all such settings. Example::
|
||||
pass
|
||||
|
||||
Here we have the marker "glob" applied three times to the same
|
||||
test function. From a conftest file we can read it like this::
|
||||
test function. From a conftest file we can read it like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of conftest.py
|
||||
import sys
|
||||
|
||||
|
||||
def pytest_runtest_setup(item):
|
||||
for mark in item.iter_markers(name='glob'):
|
||||
for mark in item.iter_markers(name="glob"):
|
||||
print("glob args=%s kwargs=%s" % (mark.args, mark.kwargs))
|
||||
sys.stdout.flush()
|
||||
|
||||
@@ -510,7 +561,9 @@ Consider you have a test suite which marks tests for particular platforms,
|
||||
namely ``pytest.mark.darwin``, ``pytest.mark.win32`` etc. and you
|
||||
also have tests that run on all platforms and have no specific
|
||||
marker. If you now want to have a way to only run the tests
|
||||
for your particular platform, you could use the following plugin::
|
||||
for your particular platform, you could use the following plugin:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of conftest.py
|
||||
#
|
||||
@@ -519,6 +572,7 @@ for your particular platform, you could use the following plugin::
|
||||
|
||||
ALL = set("darwin linux win32".split())
|
||||
|
||||
|
||||
def pytest_runtest_setup(item):
|
||||
supported_platforms = ALL.intersection(mark.name for mark in item.iter_markers())
|
||||
plat = sys.platform
|
||||
@@ -526,24 +580,30 @@ for your particular platform, you could use the following plugin::
|
||||
pytest.skip("cannot run on platform %s" % (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::
|
||||
Let's do a little test file to show how this looks like:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_plat.py
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.darwin
|
||||
def test_if_apple_is_evil():
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.linux
|
||||
def test_if_linux_works():
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.win32
|
||||
def test_if_win32_crashes():
|
||||
pass
|
||||
|
||||
|
||||
def test_runs_everywhere():
|
||||
pass
|
||||
|
||||
@@ -555,12 +615,12 @@ then you will see two tests skipped and two executed tests as expected:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 4 items
|
||||
|
||||
test_plat.py s.s. [100%]
|
||||
========================= short test summary info ==========================
|
||||
SKIPPED [2] $REGENDOC_TMPDIR/conftest.py:12: cannot run on platform linux
|
||||
SKIPPED [2] $REGENDOC_TMPDIR/conftest.py:13: cannot run on platform linux
|
||||
|
||||
=================== 2 passed, 2 skipped in 0.12 seconds ====================
|
||||
|
||||
@@ -572,7 +632,7 @@ Note that if you specify a platform via the marker-command line option like this
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 4 items / 3 deselected / 1 selected
|
||||
|
||||
test_plat.py . [100%]
|
||||
@@ -589,28 +649,38 @@ Automatically adding markers based on test names
|
||||
If you a test suite where test function names indicate a certain
|
||||
type of test, you can implement a hook that automatically defines
|
||||
markers so that you can use the ``-m`` option with it. Let's look
|
||||
at this test module::
|
||||
at this test module:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_module.py
|
||||
|
||||
|
||||
def test_interface_simple():
|
||||
assert 0
|
||||
|
||||
|
||||
def test_interface_complex():
|
||||
assert 0
|
||||
|
||||
|
||||
def test_event_simple():
|
||||
assert 0
|
||||
|
||||
|
||||
def test_something_else():
|
||||
assert 0
|
||||
|
||||
We want to dynamically define two markers and can do it in a
|
||||
``conftest.py`` plugin::
|
||||
``conftest.py`` plugin:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of conftest.py
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def pytest_collection_modifyitems(items):
|
||||
for item in items:
|
||||
if "interface" in item.nodeid:
|
||||
@@ -626,18 +696,18 @@ We can now use the ``-m option`` to select one set:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 4 items / 2 deselected / 2 selected
|
||||
|
||||
test_module.py FF [100%]
|
||||
|
||||
================================= FAILURES =================================
|
||||
__________________________ test_interface_simple ___________________________
|
||||
test_module.py:3: in test_interface_simple
|
||||
test_module.py:4: in test_interface_simple
|
||||
assert 0
|
||||
E assert 0
|
||||
__________________________ test_interface_complex __________________________
|
||||
test_module.py:6: in test_interface_complex
|
||||
test_module.py:8: in test_interface_complex
|
||||
assert 0
|
||||
E assert 0
|
||||
================== 2 failed, 2 deselected in 0.12 seconds ==================
|
||||
@@ -650,22 +720,22 @@ or to select both "event" and "interface" tests:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 4 items / 1 deselected / 3 selected
|
||||
|
||||
test_module.py FFF [100%]
|
||||
|
||||
================================= FAILURES =================================
|
||||
__________________________ test_interface_simple ___________________________
|
||||
test_module.py:3: in test_interface_simple
|
||||
test_module.py:4: in test_interface_simple
|
||||
assert 0
|
||||
E assert 0
|
||||
__________________________ test_interface_complex __________________________
|
||||
test_module.py:6: in test_interface_complex
|
||||
test_module.py:8: in test_interface_complex
|
||||
assert 0
|
||||
E assert 0
|
||||
____________________________ test_event_simple _____________________________
|
||||
test_module.py:9: in test_event_simple
|
||||
test_module.py:12: in test_event_simple
|
||||
assert 0
|
||||
E assert 0
|
||||
================== 3 failed, 1 deselected in 0.12 seconds ==================
|
||||
|
||||
@@ -31,7 +31,7 @@ now execute the test specification:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR/nonpython
|
||||
collected 2 items
|
||||
|
||||
test_simple.yml F. [100%]
|
||||
@@ -66,7 +66,7 @@ consulted when reporting in ``verbose`` mode:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR/nonpython
|
||||
collecting ... collected 2 items
|
||||
|
||||
test_simple.yml::hello FAILED [ 50%]
|
||||
@@ -90,7 +90,7 @@ interesting to just look at the collection tree:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR/nonpython
|
||||
collected 2 items
|
||||
<Package $REGENDOC_TMPDIR/nonpython>
|
||||
<YamlFile test_simple.yml>
|
||||
|
||||
@@ -146,7 +146,7 @@ objects, they are still using the default pytest representation:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 8 items
|
||||
<Module test_time.py>
|
||||
<Function test_timedistance_v0[a0-b0-expected0]>
|
||||
@@ -205,7 +205,7 @@ this is a fully self-contained example which you can run with:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 4 items
|
||||
|
||||
test_scenarios.py .... [100%]
|
||||
@@ -220,7 +220,7 @@ If you just collect tests you'll also nicely see 'advanced' and 'basic' as varia
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 4 items
|
||||
<Module test_scenarios.py>
|
||||
<Class TestSampleWithScenarios>
|
||||
@@ -287,7 +287,7 @@ Let's first see how it looks like at collection time:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 2 items
|
||||
<Module test_backends.py>
|
||||
<Function test_db_initialized[d1]>
|
||||
@@ -353,7 +353,7 @@ The result of this test will be successful:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 1 item
|
||||
<Module test_indirect_list.py>
|
||||
<Function test_indirect[a-b]>
|
||||
@@ -411,8 +411,6 @@ argument sets to use for each test function. Let's run it:
|
||||
def test_equals(self, a, b):
|
||||
> assert a == b
|
||||
E assert 1 == 2
|
||||
E -1
|
||||
E +2
|
||||
|
||||
test_parametrize.py:18: AssertionError
|
||||
1 failed, 2 passed in 0.12 seconds
|
||||
@@ -490,7 +488,7 @@ If you run this with reporting for skips enabled:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 2 items
|
||||
|
||||
test_module.py .s [100%]
|
||||
@@ -517,21 +515,25 @@ Set marks or test ID for individual parametrized test
|
||||
--------------------------------------------------------------------
|
||||
|
||||
Use ``pytest.param`` to apply marks or set test ID to individual parametrized test.
|
||||
For example::
|
||||
For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_pytest_param_example.py
|
||||
import pytest
|
||||
@pytest.mark.parametrize('test_input,expected', [
|
||||
('3+5', 8),
|
||||
pytest.param('1+7', 8,
|
||||
marks=pytest.mark.basic),
|
||||
pytest.param('2+4', 6,
|
||||
marks=pytest.mark.basic,
|
||||
id='basic_2+4'),
|
||||
pytest.param('6*9', 42,
|
||||
marks=[pytest.mark.basic, pytest.mark.xfail],
|
||||
id='basic_6*9'),
|
||||
])
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"test_input,expected",
|
||||
[
|
||||
("3+5", 8),
|
||||
pytest.param("1+7", 8, marks=pytest.mark.basic),
|
||||
pytest.param("2+4", 6, marks=pytest.mark.basic, id="basic_2+4"),
|
||||
pytest.param(
|
||||
"6*9", 42, marks=[pytest.mark.basic, pytest.mark.xfail], id="basic_6*9"
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_eval(test_input, expected):
|
||||
assert eval(test_input) == expected
|
||||
|
||||
@@ -548,7 +550,7 @@ Then run ``pytest`` with verbose mode and with only the ``basic`` marker:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collecting ... collected 17 items / 14 deselected / 3 selected
|
||||
|
||||
test_pytest_param_example.py::test_eval[1+7-8] PASSED [ 33%]
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
|
||||
.. _`tbreportdemo`:
|
||||
|
||||
Demo of Python failure reports with pytest
|
||||
==================================================
|
||||
==========================================
|
||||
|
||||
Here is a nice run of several tens of failures
|
||||
and how ``pytest`` presents things (unfortunately
|
||||
not showing the nice colors here in the HTML that you
|
||||
get on the terminal - we are working on that):
|
||||
Here is a nice run of several failures and how ``pytest`` presents things:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
@@ -15,7 +11,7 @@ get on the terminal - we are working on that):
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR/assertion, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR/assertion
|
||||
collected 44 items
|
||||
|
||||
failure_demo.py FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF [100%]
|
||||
@@ -30,7 +26,7 @@ get on the terminal - we are working on that):
|
||||
> assert param1 * 2 < param2
|
||||
E assert (3 * 2) < 6
|
||||
|
||||
failure_demo.py:22: AssertionError
|
||||
failure_demo.py:20: AssertionError
|
||||
_________________________ TestFailing.test_simple __________________________
|
||||
|
||||
self = <failure_demo.TestFailing object at 0xdeadbeef>
|
||||
@@ -47,7 +43,7 @@ get on the terminal - we are working on that):
|
||||
E + where 42 = <function TestFailing.test_simple.<locals>.f at 0xdeadbeef>()
|
||||
E + and 43 = <function TestFailing.test_simple.<locals>.g at 0xdeadbeef>()
|
||||
|
||||
failure_demo.py:33: AssertionError
|
||||
failure_demo.py:31: AssertionError
|
||||
____________________ TestFailing.test_simple_multiline _____________________
|
||||
|
||||
self = <failure_demo.TestFailing object at 0xdeadbeef>
|
||||
@@ -55,7 +51,7 @@ get on the terminal - we are working on that):
|
||||
def test_simple_multiline(self):
|
||||
> otherfunc_multi(42, 6 * 9)
|
||||
|
||||
failure_demo.py:36:
|
||||
failure_demo.py:34:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
a = 42, b = 54
|
||||
@@ -64,7 +60,7 @@ get on the terminal - we are working on that):
|
||||
> assert a == b
|
||||
E assert 42 == 54
|
||||
|
||||
failure_demo.py:17: AssertionError
|
||||
failure_demo.py:15: AssertionError
|
||||
___________________________ TestFailing.test_not ___________________________
|
||||
|
||||
self = <failure_demo.TestFailing object at 0xdeadbeef>
|
||||
@@ -77,7 +73,7 @@ get on the terminal - we are working on that):
|
||||
E assert not 42
|
||||
E + where 42 = <function TestFailing.test_not.<locals>.f at 0xdeadbeef>()
|
||||
|
||||
failure_demo.py:42: AssertionError
|
||||
failure_demo.py:40: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_eq_text _________________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -88,7 +84,7 @@ get on the terminal - we are working on that):
|
||||
E - spam
|
||||
E + eggs
|
||||
|
||||
failure_demo.py:47: AssertionError
|
||||
failure_demo.py:45: AssertionError
|
||||
_____________ TestSpecialisedExplanations.test_eq_similar_text _____________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -101,7 +97,7 @@ get on the terminal - we are working on that):
|
||||
E + foo 2 bar
|
||||
E ? ^
|
||||
|
||||
failure_demo.py:50: AssertionError
|
||||
failure_demo.py:48: AssertionError
|
||||
____________ TestSpecialisedExplanations.test_eq_multiline_text ____________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -114,7 +110,7 @@ get on the terminal - we are working on that):
|
||||
E + eggs
|
||||
E bar
|
||||
|
||||
failure_demo.py:53: AssertionError
|
||||
failure_demo.py:51: AssertionError
|
||||
______________ TestSpecialisedExplanations.test_eq_long_text _______________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -131,7 +127,7 @@ get on the terminal - we are working on that):
|
||||
E + 1111111111b222222222
|
||||
E ? ^
|
||||
|
||||
failure_demo.py:58: AssertionError
|
||||
failure_demo.py:56: AssertionError
|
||||
_________ TestSpecialisedExplanations.test_eq_long_text_multiline __________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -151,7 +147,7 @@ get on the terminal - we are working on that):
|
||||
E
|
||||
E ...Full output truncated (7 lines hidden), use '-vv' to show
|
||||
|
||||
failure_demo.py:63: AssertionError
|
||||
failure_demo.py:61: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_eq_list _________________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -162,7 +158,7 @@ get on the terminal - we are working on that):
|
||||
E At index 2 diff: 2 != 3
|
||||
E Use -v to get the full diff
|
||||
|
||||
failure_demo.py:66: AssertionError
|
||||
failure_demo.py:64: AssertionError
|
||||
______________ TestSpecialisedExplanations.test_eq_list_long _______________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -175,7 +171,7 @@ get on the terminal - we are working on that):
|
||||
E At index 100 diff: 1 != 2
|
||||
E Use -v to get the full diff
|
||||
|
||||
failure_demo.py:71: AssertionError
|
||||
failure_demo.py:69: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_eq_dict _________________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -193,7 +189,7 @@ get on the terminal - we are working on that):
|
||||
E
|
||||
E ...Full output truncated (2 lines hidden), use '-vv' to show
|
||||
|
||||
failure_demo.py:74: AssertionError
|
||||
failure_demo.py:72: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_eq_set __________________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -211,7 +207,7 @@ get on the terminal - we are working on that):
|
||||
E
|
||||
E ...Full output truncated (2 lines hidden), use '-vv' to show
|
||||
|
||||
failure_demo.py:77: AssertionError
|
||||
failure_demo.py:75: AssertionError
|
||||
_____________ TestSpecialisedExplanations.test_eq_longer_list ______________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -222,7 +218,7 @@ get on the terminal - we are working on that):
|
||||
E Right contains more items, first extra item: 3
|
||||
E Use -v to get the full diff
|
||||
|
||||
failure_demo.py:80: AssertionError
|
||||
failure_demo.py:78: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_in_list _________________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -231,7 +227,7 @@ get on the terminal - we are working on that):
|
||||
> assert 1 in [0, 2, 3, 4, 5]
|
||||
E assert 1 in [0, 2, 3, 4, 5]
|
||||
|
||||
failure_demo.py:83: AssertionError
|
||||
failure_demo.py:81: AssertionError
|
||||
__________ TestSpecialisedExplanations.test_not_in_text_multiline __________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -250,7 +246,7 @@ get on the terminal - we are working on that):
|
||||
E
|
||||
E ...Full output truncated (2 lines hidden), use '-vv' to show
|
||||
|
||||
failure_demo.py:87: AssertionError
|
||||
failure_demo.py:85: AssertionError
|
||||
___________ TestSpecialisedExplanations.test_not_in_text_single ____________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -263,7 +259,7 @@ get on the terminal - we are working on that):
|
||||
E single foo line
|
||||
E ? +++
|
||||
|
||||
failure_demo.py:91: AssertionError
|
||||
failure_demo.py:89: AssertionError
|
||||
_________ TestSpecialisedExplanations.test_not_in_text_single_long _________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -276,7 +272,7 @@ get on the terminal - we are working on that):
|
||||
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 ? +++
|
||||
|
||||
failure_demo.py:95: AssertionError
|
||||
failure_demo.py:93: AssertionError
|
||||
______ TestSpecialisedExplanations.test_not_in_text_single_long_term _______
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -289,7 +285,7 @@ get on the terminal - we are working on that):
|
||||
E head head fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffftail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail
|
||||
E ? ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
failure_demo.py:99: AssertionError
|
||||
failure_demo.py:97: AssertionError
|
||||
______________ TestSpecialisedExplanations.test_eq_dataclass _______________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -310,7 +306,7 @@ get on the terminal - we are working on that):
|
||||
E Differing attributes:
|
||||
E b: 'b' != 'c'
|
||||
|
||||
failure_demo.py:111: AssertionError
|
||||
failure_demo.py:109: AssertionError
|
||||
________________ TestSpecialisedExplanations.test_eq_attrs _________________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -331,7 +327,7 @@ get on the terminal - we are working on that):
|
||||
E Differing attributes:
|
||||
E b: 'b' != 'c'
|
||||
|
||||
failure_demo.py:123: AssertionError
|
||||
failure_demo.py:121: AssertionError
|
||||
______________________________ test_attribute ______________________________
|
||||
|
||||
def test_attribute():
|
||||
@@ -343,7 +339,7 @@ get on the terminal - we are working on that):
|
||||
E assert 1 == 2
|
||||
E + where 1 = <failure_demo.test_attribute.<locals>.Foo object at 0xdeadbeef>.b
|
||||
|
||||
failure_demo.py:131: AssertionError
|
||||
failure_demo.py:129: AssertionError
|
||||
_________________________ test_attribute_instance __________________________
|
||||
|
||||
def test_attribute_instance():
|
||||
@@ -355,7 +351,7 @@ get on the terminal - we are working on that):
|
||||
E + where 1 = <failure_demo.test_attribute_instance.<locals>.Foo object at 0xdeadbeef>.b
|
||||
E + where <failure_demo.test_attribute_instance.<locals>.Foo object at 0xdeadbeef> = <class 'failure_demo.test_attribute_instance.<locals>.Foo'>()
|
||||
|
||||
failure_demo.py:138: AssertionError
|
||||
failure_demo.py:136: AssertionError
|
||||
__________________________ test_attribute_failure __________________________
|
||||
|
||||
def test_attribute_failure():
|
||||
@@ -368,7 +364,7 @@ get on the terminal - we are working on that):
|
||||
i = Foo()
|
||||
> assert i.b == 2
|
||||
|
||||
failure_demo.py:149:
|
||||
failure_demo.py:147:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
self = <failure_demo.test_attribute_failure.<locals>.Foo object at 0xdeadbeef>
|
||||
@@ -377,7 +373,7 @@ get on the terminal - we are working on that):
|
||||
> raise Exception("Failed to get attrib")
|
||||
E Exception: Failed to get attrib
|
||||
|
||||
failure_demo.py:144: Exception
|
||||
failure_demo.py:142: Exception
|
||||
_________________________ test_attribute_multiple __________________________
|
||||
|
||||
def test_attribute_multiple():
|
||||
@@ -394,7 +390,7 @@ get on the terminal - we are working on that):
|
||||
E + and 2 = <failure_demo.test_attribute_multiple.<locals>.Bar object at 0xdeadbeef>.b
|
||||
E + where <failure_demo.test_attribute_multiple.<locals>.Bar object at 0xdeadbeef> = <class 'failure_demo.test_attribute_multiple.<locals>.Bar'>()
|
||||
|
||||
failure_demo.py:159: AssertionError
|
||||
failure_demo.py:157: AssertionError
|
||||
__________________________ TestRaises.test_raises __________________________
|
||||
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||
@@ -404,7 +400,7 @@ get on the terminal - we are working on that):
|
||||
> raises(TypeError, int, s)
|
||||
E ValueError: invalid literal for int() with base 10: 'qwe'
|
||||
|
||||
failure_demo.py:169: ValueError
|
||||
failure_demo.py:167: ValueError
|
||||
______________________ TestRaises.test_raises_doesnt _______________________
|
||||
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||
@@ -413,7 +409,7 @@ get on the terminal - we are working on that):
|
||||
> raises(IOError, int, "3")
|
||||
E Failed: DID NOT RAISE <class 'OSError'>
|
||||
|
||||
failure_demo.py:172: Failed
|
||||
failure_demo.py:170: Failed
|
||||
__________________________ TestRaises.test_raise ___________________________
|
||||
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||
@@ -422,7 +418,7 @@ get on the terminal - we are working on that):
|
||||
> raise ValueError("demo error")
|
||||
E ValueError: demo error
|
||||
|
||||
failure_demo.py:175: ValueError
|
||||
failure_demo.py:173: ValueError
|
||||
________________________ TestRaises.test_tupleerror ________________________
|
||||
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||
@@ -431,7 +427,7 @@ get on the terminal - we are working on that):
|
||||
> a, b = [1] # NOQA
|
||||
E ValueError: not enough values to unpack (expected 2, got 1)
|
||||
|
||||
failure_demo.py:178: ValueError
|
||||
failure_demo.py:176: ValueError
|
||||
______ TestRaises.test_reinterpret_fails_with_print_for_the_fun_of_it ______
|
||||
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||
@@ -442,7 +438,7 @@ get on the terminal - we are working on that):
|
||||
> a, b = items.pop()
|
||||
E TypeError: 'int' object is not iterable
|
||||
|
||||
failure_demo.py:183: TypeError
|
||||
failure_demo.py:181: TypeError
|
||||
--------------------------- Captured stdout call ---------------------------
|
||||
items is [1, 2, 3]
|
||||
________________________ TestRaises.test_some_error ________________________
|
||||
@@ -453,7 +449,7 @@ get on the terminal - we are working on that):
|
||||
> if namenotexi: # NOQA
|
||||
E NameError: name 'namenotexi' is not defined
|
||||
|
||||
failure_demo.py:186: NameError
|
||||
failure_demo.py:184: NameError
|
||||
____________________ test_dynamic_compile_shows_nicely _____________________
|
||||
|
||||
def test_dynamic_compile_shows_nicely():
|
||||
@@ -464,18 +460,18 @@ get on the terminal - we are working on that):
|
||||
name = "abc-123"
|
||||
module = imp.new_module(name)
|
||||
code = _pytest._code.compile(src, name, "exec")
|
||||
six.exec_(code, module.__dict__)
|
||||
exec(code, module.__dict__)
|
||||
sys.modules[name] = module
|
||||
> module.foo()
|
||||
|
||||
failure_demo.py:204:
|
||||
failure_demo.py:202:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
def foo():
|
||||
> assert 1 == 0
|
||||
E AssertionError
|
||||
|
||||
<0-codegen 'abc-123' $REGENDOC_TMPDIR/assertion/failure_demo.py:201>:2: AssertionError
|
||||
<0-codegen 'abc-123' $REGENDOC_TMPDIR/assertion/failure_demo.py:199>:2: AssertionError
|
||||
____________________ TestMoreErrors.test_complex_error _____________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -489,9 +485,9 @@ get on the terminal - we are working on that):
|
||||
|
||||
> somefunc(f(), g())
|
||||
|
||||
failure_demo.py:215:
|
||||
failure_demo.py:213:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
failure_demo.py:13: in somefunc
|
||||
failure_demo.py:11: in somefunc
|
||||
otherfunc(x, y)
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
@@ -501,7 +497,7 @@ get on the terminal - we are working on that):
|
||||
> assert a == b
|
||||
E assert 44 == 43
|
||||
|
||||
failure_demo.py:9: AssertionError
|
||||
failure_demo.py:7: AssertionError
|
||||
___________________ TestMoreErrors.test_z1_unpack_error ____________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -511,7 +507,7 @@ get on the terminal - we are working on that):
|
||||
> a, b = items
|
||||
E ValueError: not enough values to unpack (expected 2, got 0)
|
||||
|
||||
failure_demo.py:219: ValueError
|
||||
failure_demo.py:217: ValueError
|
||||
____________________ TestMoreErrors.test_z2_type_error _____________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -521,7 +517,7 @@ get on the terminal - we are working on that):
|
||||
> a, b = items
|
||||
E TypeError: 'int' object is not iterable
|
||||
|
||||
failure_demo.py:223: TypeError
|
||||
failure_demo.py:221: TypeError
|
||||
______________________ TestMoreErrors.test_startswith ______________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -534,7 +530,7 @@ get on the terminal - we are working on that):
|
||||
E + where False = <built-in method startswith of str object at 0xdeadbeef>('456')
|
||||
E + where <built-in method startswith of str object at 0xdeadbeef> = '123'.startswith
|
||||
|
||||
failure_demo.py:228: AssertionError
|
||||
failure_demo.py:226: AssertionError
|
||||
__________________ TestMoreErrors.test_startswith_nested ___________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -553,7 +549,7 @@ get on the terminal - we are working on that):
|
||||
E + where '123' = <function TestMoreErrors.test_startswith_nested.<locals>.f at 0xdeadbeef>()
|
||||
E + and '456' = <function TestMoreErrors.test_startswith_nested.<locals>.g at 0xdeadbeef>()
|
||||
|
||||
failure_demo.py:237: AssertionError
|
||||
failure_demo.py:235: AssertionError
|
||||
_____________________ TestMoreErrors.test_global_func ______________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -564,7 +560,7 @@ get on the terminal - we are working on that):
|
||||
E + where False = isinstance(43, float)
|
||||
E + where 43 = globf(42)
|
||||
|
||||
failure_demo.py:240: AssertionError
|
||||
failure_demo.py:238: AssertionError
|
||||
_______________________ TestMoreErrors.test_instance _______________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -575,7 +571,7 @@ get on the terminal - we are working on that):
|
||||
E assert 42 != 42
|
||||
E + where 42 = <failure_demo.TestMoreErrors object at 0xdeadbeef>.x
|
||||
|
||||
failure_demo.py:244: AssertionError
|
||||
failure_demo.py:242: AssertionError
|
||||
_______________________ TestMoreErrors.test_compare ________________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -585,7 +581,7 @@ get on the terminal - we are working on that):
|
||||
E assert 11 < 5
|
||||
E + where 11 = globf(10)
|
||||
|
||||
failure_demo.py:247: AssertionError
|
||||
failure_demo.py:245: AssertionError
|
||||
_____________________ TestMoreErrors.test_try_finally ______________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -596,7 +592,7 @@ get on the terminal - we are working on that):
|
||||
> assert x == 0
|
||||
E assert 1 == 0
|
||||
|
||||
failure_demo.py:252: AssertionError
|
||||
failure_demo.py:250: AssertionError
|
||||
___________________ TestCustomAssertMsg.test_single_line ___________________
|
||||
|
||||
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
|
||||
@@ -611,7 +607,7 @@ get on the terminal - we are working on that):
|
||||
E assert 1 == 2
|
||||
E + where 1 = <class 'failure_demo.TestCustomAssertMsg.test_single_line.<locals>.A'>.a
|
||||
|
||||
failure_demo.py:263: AssertionError
|
||||
failure_demo.py:261: AssertionError
|
||||
____________________ TestCustomAssertMsg.test_multiline ____________________
|
||||
|
||||
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
|
||||
@@ -630,7 +626,7 @@ get on the terminal - we are working on that):
|
||||
E assert 1 == 2
|
||||
E + where 1 = <class 'failure_demo.TestCustomAssertMsg.test_multiline.<locals>.A'>.a
|
||||
|
||||
failure_demo.py:270: AssertionError
|
||||
failure_demo.py:268: AssertionError
|
||||
___________________ TestCustomAssertMsg.test_custom_repr ___________________
|
||||
|
||||
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
|
||||
@@ -652,5 +648,5 @@ get on the terminal - we are working on that):
|
||||
E assert 1 == 2
|
||||
E + where 1 = This is JSON\n{\n 'foo': 'bar'\n}.a
|
||||
|
||||
failure_demo.py:283: AssertionError
|
||||
failure_demo.py:281: AssertionError
|
||||
======================== 44 failed in 0.12 seconds =========================
|
||||
|
||||
@@ -107,7 +107,7 @@ the command line arguments before they get processed:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of conftest.py
|
||||
# setuptools plugin
|
||||
import sys
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ directory with the above conftest.py:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 0 items
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
@@ -190,7 +190,7 @@ and when running it will see a skipped "slow" test:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 2 items
|
||||
|
||||
test_module.py .s [100%]
|
||||
@@ -207,7 +207,7 @@ Or run it including the ``slow`` marked test:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 2 items
|
||||
|
||||
test_module.py .. [100%]
|
||||
@@ -351,7 +351,7 @@ which will add the string to the test header accordingly:
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
project deps: mylib-1.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 0 items
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
@@ -381,7 +381,7 @@ which will add info only when run with "--v":
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
info1: did you know that ...
|
||||
did you?
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collecting ... collected 0 items
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
@@ -394,7 +394,7 @@ and nothing when run plainly:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 0 items
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
@@ -434,7 +434,7 @@ Now we can profile which test functions execute the slowest:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 3 items
|
||||
|
||||
test_some_are_slow.py ... [100%]
|
||||
@@ -509,7 +509,7 @@ If we run this:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 4 items
|
||||
|
||||
test_step.py .Fx. [100%]
|
||||
@@ -593,7 +593,7 @@ We can run this:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 7 items
|
||||
|
||||
test_step.py .Fx. [ 57%]
|
||||
@@ -707,7 +707,7 @@ and run them:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 2 items
|
||||
|
||||
test_module.py FF [100%]
|
||||
@@ -811,7 +811,7 @@ and run it:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 3 items
|
||||
|
||||
test_module.py Esetting up a test failed! test_module.py::test_setup_fails
|
||||
@@ -854,7 +854,7 @@ information.
|
||||
``PYTEST_CURRENT_TEST`` environment variable
|
||||
--------------------------------------------
|
||||
|
||||
.. versionadded:: 3.2
|
||||
|
||||
|
||||
Sometimes a test session might get stuck and there might be no easy way to figure out
|
||||
which test got stuck, for example if pytest was run in quiet mode (``-q``) or you don't have access to the console
|
||||
|
||||
@@ -7,7 +7,7 @@ pytest fixtures: explicit, modular, scalable
|
||||
|
||||
.. currentmodule:: _pytest.python
|
||||
|
||||
.. versionadded:: 2.0/2.3/2.4
|
||||
|
||||
|
||||
.. _`xUnit`: http://en.wikipedia.org/wiki/XUnit
|
||||
.. _`purpose of test fixtures`: http://en.wikipedia.org/wiki/Test_fixture#Software
|
||||
@@ -74,7 +74,7 @@ marked ``smtp_connection`` fixture function. Running the test looks like this:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 1 item
|
||||
|
||||
test_smtpsimple.py F [100%]
|
||||
@@ -217,7 +217,7 @@ inspect what is going on and can now run the tests:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 2 items
|
||||
|
||||
test_module.py FF [100%]
|
||||
@@ -276,7 +276,7 @@ Finally, the ``class`` scope will invoke the fixture once per test *class*.
|
||||
``package`` scope (experimental)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. versionadded:: 3.7
|
||||
|
||||
|
||||
In pytest 3.7 the ``package`` scope has been introduced. Package-scoped fixtures
|
||||
are finalized when the last test of a *package* finishes.
|
||||
@@ -292,7 +292,7 @@ are finalized when the last test of a *package* finishes.
|
||||
Higher-scoped fixtures are instantiated first
|
||||
---------------------------------------------
|
||||
|
||||
.. versionadded:: 3.5
|
||||
|
||||
|
||||
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
|
||||
@@ -710,7 +710,7 @@ Running the above tests results in the following test IDs being used:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 10 items
|
||||
<Module test_anothersmtp.py>
|
||||
<Function test_showhelo[smtp.gmail.com]>
|
||||
@@ -755,7 +755,7 @@ Running this test will *skip* the invocation of ``data_set`` with value ``2``:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collecting ... collected 3 items
|
||||
|
||||
test_fixture_marks.py::test_data[0] PASSED [ 33%]
|
||||
@@ -800,7 +800,7 @@ Here we declare an ``app`` fixture which receives the previously defined
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collecting ... collected 2 items
|
||||
|
||||
test_appsetup.py::test_smtp_connection_exists[smtp.gmail.com] PASSED [ 50%]
|
||||
@@ -871,7 +871,7 @@ Let's run the tests in verbose mode and with looking at the print-output:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collecting ... collected 8 items
|
||||
|
||||
test_module.py::test_0[1] SETUP otherarg 1
|
||||
|
||||
@@ -52,7 +52,7 @@ That’s it. You can now execute the test function:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 1 item
|
||||
|
||||
test_sample.py F [100%]
|
||||
@@ -83,7 +83,7 @@ Run multiple tests
|
||||
Assert that a certain exception is raised
|
||||
--------------------------------------------------------------
|
||||
|
||||
Use the ``raises`` helper to assert that some code raises an exception::
|
||||
Use the :ref:`raises <assertraises>` helper to assert that some code raises an exception::
|
||||
|
||||
# content of test_sysexit.py
|
||||
import pytest
|
||||
|
||||
@@ -7,7 +7,7 @@ kept here as a historical note so users looking at old code can find documentati
|
||||
cache plugin integrated into the core
|
||||
-------------------------------------
|
||||
|
||||
.. versionadded:: 2.8
|
||||
|
||||
|
||||
The functionality of the :ref:`core cache <cache>` plugin was previously distributed
|
||||
as a third party plugin named ``pytest-cache``. The core plugin
|
||||
@@ -18,7 +18,7 @@ can only store/receive data between test runs that is json-serializable.
|
||||
funcargs and ``pytest_funcarg__``
|
||||
---------------------------------
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
|
||||
|
||||
In versions prior to 2.3 there was no ``@pytest.fixture`` marker
|
||||
and you had to use a magic ``pytest_funcarg__NAME`` prefix
|
||||
@@ -30,7 +30,7 @@ functions.
|
||||
``@pytest.yield_fixture`` decorator
|
||||
-----------------------------------
|
||||
|
||||
.. versionchanged:: 2.10
|
||||
|
||||
|
||||
Prior to version 2.10, in order to use a ``yield`` statement to execute teardown code one
|
||||
had to mark a fixture using the ``yield_fixture`` marker. From 2.10 onward, normal
|
||||
@@ -41,7 +41,7 @@ and considered deprecated.
|
||||
``[pytest]`` header in ``setup.cfg``
|
||||
------------------------------------
|
||||
|
||||
.. versionchanged:: 3.0
|
||||
|
||||
|
||||
Prior to 3.0, the supported section name was ``[pytest]``. Due to how
|
||||
this may collide with some distutils commands, the recommended
|
||||
@@ -54,17 +54,19 @@ name is ``[pytest]``.
|
||||
Applying marks to ``@pytest.mark.parametrize`` parameters
|
||||
---------------------------------------------------------
|
||||
|
||||
.. versionchanged:: 3.1
|
||||
|
||||
|
||||
Prior to version 3.1 the supported mechanism for marking values
|
||||
used the syntax::
|
||||
used the syntax:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import pytest
|
||||
@pytest.mark.parametrize("test_input,expected", [
|
||||
("3+5", 8),
|
||||
("2+4", 6),
|
||||
pytest.mark.xfail(("6*9", 42),),
|
||||
])
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"test_input,expected", [("3+5", 8), ("2+4", 6), pytest.mark.xfail(("6*9", 42))]
|
||||
)
|
||||
def test_eval(test_input, expected):
|
||||
assert eval(test_input) == expected
|
||||
|
||||
@@ -78,7 +80,7 @@ The old syntax is planned to be removed in pytest-4.0.
|
||||
``@pytest.mark.parametrize`` argument names as a tuple
|
||||
------------------------------------------------------
|
||||
|
||||
.. versionchanged:: 2.4
|
||||
|
||||
|
||||
In versions prior to 2.4 one needed to specify the argument
|
||||
names as a tuple. This remains valid but the simpler ``"name1,name2,..."``
|
||||
@@ -89,7 +91,7 @@ it's easier to write and produces less line noise.
|
||||
setup: is now an "autouse fixture"
|
||||
----------------------------------
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
|
||||
|
||||
During development prior to the pytest-2.3 release the name
|
||||
``pytest.setup`` was used but before the release it was renamed
|
||||
@@ -102,12 +104,16 @@ namely :ref:`autouse fixtures`
|
||||
Conditions as strings instead of booleans
|
||||
-----------------------------------------
|
||||
|
||||
.. versionchanged:: 2.4
|
||||
|
||||
|
||||
Prior to pytest-2.4 the only way to specify skipif/xfail conditions was
|
||||
to use strings::
|
||||
to use strings:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
@pytest.mark.skipif("sys.version_info >= (3,3)")
|
||||
def test_function():
|
||||
...
|
||||
@@ -139,17 +145,20 @@ dictionary which is constructed as follows:
|
||||
expression is applied.
|
||||
|
||||
The pytest ``config`` object allows you to skip based on a test
|
||||
configuration value which you might have added::
|
||||
configuration value which you might have added:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.skipif("not config.getvalue('db')")
|
||||
def test_function(...):
|
||||
def test_function():
|
||||
...
|
||||
|
||||
The equivalent with "boolean conditions" is::
|
||||
The equivalent with "boolean conditions" is:
|
||||
|
||||
@pytest.mark.skipif(not pytest.config.getvalue("db"),
|
||||
reason="--db was not specified")
|
||||
def test_function(...):
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.skipif(not pytest.config.getvalue("db"), reason="--db was not specified")
|
||||
def test_function():
|
||||
pass
|
||||
|
||||
.. note::
|
||||
@@ -162,14 +171,18 @@ The equivalent with "boolean conditions" is::
|
||||
``pytest.set_trace()``
|
||||
----------------------
|
||||
|
||||
.. versionchanged:: 2.4
|
||||
|
||||
Previous to version 2.4 to set a break point in code one needed to use ``pytest.set_trace()``::
|
||||
|
||||
Previous to version 2.4 to set a break point in code one needed to use ``pytest.set_trace()``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def test_function():
|
||||
...
|
||||
pytest.set_trace() # invoke PDB debugger and tracing
|
||||
pytest.set_trace() # invoke PDB debugger and tracing
|
||||
|
||||
|
||||
This is no longer needed and one can use the native ``import pdb;pdb.set_trace()`` call directly.
|
||||
@@ -179,7 +192,7 @@ For more details see :ref:`breakpoints`.
|
||||
"compat" properties
|
||||
-------------------
|
||||
|
||||
.. deprecated:: 3.9
|
||||
|
||||
|
||||
Access of ``Module``, ``Function``, ``Class``, ``Instance``, ``File`` and ``Item`` through ``Node`` instances have long
|
||||
been documented as deprecated, but started to emit warnings from pytest ``3.9`` and onward.
|
||||
|
||||
@@ -30,7 +30,7 @@ To execute it:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 1 item
|
||||
|
||||
test_sample.py F [100%]
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
Logging
|
||||
-------
|
||||
|
||||
.. versionadded:: 3.3
|
||||
.. versionchanged:: 3.4
|
||||
|
||||
|
||||
|
||||
pytest captures log messages of level ``WARNING`` or above automatically and displays them in their own section
|
||||
for each failed test in the same manner as captured stdout and stderr.
|
||||
|
||||
@@ -59,7 +59,7 @@ should add ``--strict`` to ``addopts``:
|
||||
Marker revamp and iteration
|
||||
---------------------------
|
||||
|
||||
.. versionadded:: 3.6
|
||||
|
||||
|
||||
pytest's marker implementation traditionally worked by simply updating the ``__dict__`` attribute of functions to cumulatively add markers. As a result, markers would unintentionally be passed along class hierarchies in surprising ways. Further, the API for retrieving them was inconsistent, as markers from parameterization would be stored differently than markers applied using the ``@pytest.mark`` decorator and markers added via ``node.add_marker``.
|
||||
|
||||
|
||||
@@ -29,22 +29,22 @@ pytest enables test parametrization at several levels:
|
||||
|
||||
.. regendoc: wipe
|
||||
|
||||
.. versionadded:: 2.2
|
||||
.. versionchanged:: 2.4
|
||||
|
||||
|
||||
Several improvements.
|
||||
|
||||
The builtin :ref:`pytest.mark.parametrize ref` decorator enables
|
||||
parametrization of arguments for a test function. Here is a typical example
|
||||
of a test function that implements checking that a certain input leads
|
||||
to an expected output::
|
||||
to an expected output:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_expectation.py
|
||||
import pytest
|
||||
@pytest.mark.parametrize("test_input,expected", [
|
||||
("3+5", 8),
|
||||
("2+4", 6),
|
||||
("6*9", 42),
|
||||
])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("test_input,expected", [("3+5", 8), ("2+4", 6), ("6*9", 42)])
|
||||
def test_eval(test_input, expected):
|
||||
assert eval(test_input) == expected
|
||||
|
||||
@@ -58,7 +58,7 @@ them in turn:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 3 items
|
||||
|
||||
test_expectation.py ..F [100%]
|
||||
@@ -68,19 +68,30 @@ them in turn:
|
||||
|
||||
test_input = '6*9', expected = 42
|
||||
|
||||
@pytest.mark.parametrize("test_input,expected", [
|
||||
("3+5", 8),
|
||||
("2+4", 6),
|
||||
("6*9", 42),
|
||||
])
|
||||
@pytest.mark.parametrize("test_input,expected", [("3+5", 8), ("2+4", 6), ("6*9", 42)])
|
||||
def test_eval(test_input, expected):
|
||||
> assert eval(test_input) == expected
|
||||
E AssertionError: assert 54 == 42
|
||||
E + where 54 = eval('6*9')
|
||||
|
||||
test_expectation.py:8: AssertionError
|
||||
test_expectation.py:6: AssertionError
|
||||
==================== 1 failed, 2 passed in 0.12 seconds ====================
|
||||
|
||||
.. note::
|
||||
|
||||
pytest by default escapes any non-ascii characters used in unicode strings
|
||||
for the parametrization because it has several downsides.
|
||||
If however you would like to use unicode strings in parametrization and see them in the terminal as is (non-escaped), use this option in your ``pytest.ini``:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[pytest]
|
||||
disable_test_id_escaping_and_forfeit_all_rights_to_community_support = True
|
||||
|
||||
Keep in mind however that this might cause unwanted side effects and
|
||||
even bugs depending on the OS used and plugins currently installed, so use it at your own risk.
|
||||
|
||||
|
||||
As designed in this example, only one pair of input/output values fails
|
||||
the simple test function. And as usual with test function arguments,
|
||||
you can see the ``input`` and ``output`` values in the traceback.
|
||||
@@ -89,16 +100,18 @@ Note that you could also use the parametrize marker on a class or a module
|
||||
(see :ref:`mark`) which would invoke several functions with the argument sets.
|
||||
|
||||
It is also possible to mark individual test instances within parametrize,
|
||||
for example with the builtin ``mark.xfail``::
|
||||
for example with the builtin ``mark.xfail``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_expectation.py
|
||||
import pytest
|
||||
@pytest.mark.parametrize("test_input,expected", [
|
||||
("3+5", 8),
|
||||
("2+4", 6),
|
||||
pytest.param("6*9", 42,
|
||||
marks=pytest.mark.xfail),
|
||||
])
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"test_input,expected",
|
||||
[("3+5", 8), ("2+4", 6), pytest.param("6*9", 42, marks=pytest.mark.xfail)],
|
||||
)
|
||||
def test_eval(test_input, expected):
|
||||
assert eval(test_input) == expected
|
||||
|
||||
@@ -110,7 +123,7 @@ Let's run this:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 3 items
|
||||
|
||||
test_expectation.py ..x [100%]
|
||||
@@ -125,9 +138,13 @@ example, if they're dynamically generated by some function - the behaviour of
|
||||
pytest is defined by the :confval:`empty_parameter_set_mark` option.
|
||||
|
||||
To get all combinations of multiple parametrized arguments you can stack
|
||||
``parametrize`` decorators::
|
||||
``parametrize`` decorators:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.parametrize("x", [0, 1])
|
||||
@pytest.mark.parametrize("y", [2, 3])
|
||||
def test_foo(x, y):
|
||||
@@ -151,26 +168,36 @@ parametrization.
|
||||
|
||||
For example, let's say we want to run a test taking string inputs which
|
||||
we want to set via a new ``pytest`` command line option. Let's first write
|
||||
a simple test accepting a ``stringinput`` fixture function argument::
|
||||
a simple test accepting a ``stringinput`` fixture function argument:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_strings.py
|
||||
|
||||
|
||||
def test_valid_string(stringinput):
|
||||
assert stringinput.isalpha()
|
||||
|
||||
Now we add a ``conftest.py`` file containing the addition of a
|
||||
command line option and the parametrization of our test function::
|
||||
command line option and the parametrization of our test function:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of conftest.py
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption("--stringinput", action="append", default=[],
|
||||
help="list of stringinputs to pass to test functions")
|
||||
parser.addoption(
|
||||
"--stringinput",
|
||||
action="append",
|
||||
default=[],
|
||||
help="list of stringinputs to pass to test functions",
|
||||
)
|
||||
|
||||
|
||||
def pytest_generate_tests(metafunc):
|
||||
if 'stringinput' in metafunc.fixturenames:
|
||||
metafunc.parametrize("stringinput",
|
||||
metafunc.config.getoption('stringinput'))
|
||||
if "stringinput" in metafunc.fixturenames:
|
||||
metafunc.parametrize("stringinput", metafunc.config.getoption("stringinput"))
|
||||
|
||||
If we now pass two stringinput values, our test will run twice:
|
||||
|
||||
@@ -197,7 +224,7 @@ Let's also run with a stringinput that will lead to a failing test:
|
||||
E + where False = <built-in method isalpha of str object at 0xdeadbeef>()
|
||||
E + where <built-in method isalpha of str object at 0xdeadbeef> = '!'.isalpha
|
||||
|
||||
test_strings.py:3: AssertionError
|
||||
test_strings.py:4: AssertionError
|
||||
1 failed in 0.12 seconds
|
||||
|
||||
As expected our test function fails.
|
||||
@@ -211,7 +238,7 @@ list:
|
||||
$ pytest -q -rs test_strings.py
|
||||
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:1
|
||||
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
|
||||
|
||||
Note that when calling ``metafunc.parametrize`` multiple times with different parameter sets, all parameter names across
|
||||
|
||||
@@ -27,7 +27,7 @@ Here is a little annotated list for some popular plugins:
|
||||
for `twisted <http://twistedmatrix.com>`_ apps, starting a reactor and
|
||||
processing deferreds from test functions.
|
||||
|
||||
* `pytest-cov <https://pypi.org/project/pytest-cov/>`_:
|
||||
* `pytest-cov <https://pypi.org/project/pytest-cov/>`__:
|
||||
coverage reporting, compatible with distributed testing
|
||||
|
||||
* `pytest-xdist <https://pypi.org/project/pytest-xdist/>`_:
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
Reference
|
||||
=========
|
||||
|
||||
@@ -49,7 +48,7 @@ pytest.main
|
||||
.. autofunction:: _pytest.config.main
|
||||
|
||||
pytest.param
|
||||
~~~~~~~~~~~~~
|
||||
~~~~~~~~~~~~
|
||||
|
||||
.. autofunction:: pytest.param(*values, [id], [marks])
|
||||
|
||||
@@ -199,16 +198,18 @@ Marks a test function as *expected to fail*.
|
||||
.. py:function:: pytest.mark.xfail(condition=None, *, reason=None, raises=None, run=True, strict=False)
|
||||
|
||||
:type condition: bool or str
|
||||
:param condition: ``True/False`` if the condition should be marked as xfail or a :ref:`condition string <string conditions>`.
|
||||
:param condition:
|
||||
Condition for marking the test function as xfail (``True/False`` or a
|
||||
:ref:`condition string <string conditions>`).
|
||||
:keyword str reason: Reason why the test function is marked as xfail.
|
||||
:keyword Exception raises: Exception subclass expected to be raised by the test function; other exceptions will fail the test.
|
||||
:keyword bool run:
|
||||
If the test function should actually be executed. If ``False``, the function will always xfail and will
|
||||
not be executed (useful a function is segfaulting).
|
||||
not be executed (useful if a function is segfaulting).
|
||||
:keyword bool strict:
|
||||
* If ``False`` (the default) the function will be shown in the terminal output as ``xfailed`` if it fails
|
||||
and as ``xpass`` if it passes. In both cases this will not cause the test suite to fail as a whole. This
|
||||
is particularly useful to mark *flaky* tests (tests that random at fail) to be tackled later.
|
||||
is particularly useful to mark *flaky* tests (tests that fail at random) to be tackled later.
|
||||
* If ``True``, the function will be shown in the terminal output as ``xfailed`` if it fails, but if it
|
||||
unexpectedly passes then it will **fail** the test suite. This is particularly useful to mark functions
|
||||
that are always failing and there should be a clear indication if they unexpectedly start to pass (for example
|
||||
@@ -881,7 +882,7 @@ pytest_mark
|
||||
**Tutorial**: :ref:`scoped-marking`
|
||||
|
||||
Can be declared at the **global** level in *test modules* to apply one or more :ref:`marks <marks ref>` to all
|
||||
test functions and methods. Can be either a single mark or a sequence of marks.
|
||||
test functions and methods. Can be either a single mark or a list of marks.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@@ -894,7 +895,7 @@ test functions and methods. Can be either a single mark or a sequence of marks.
|
||||
|
||||
import pytest
|
||||
|
||||
pytestmark = (pytest.mark.integration, pytest.mark.slow)
|
||||
pytestmark = [pytest.mark.integration, pytest.mark.slow]
|
||||
|
||||
PYTEST_DONT_REWRITE (module docstring)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -982,7 +983,7 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||
|
||||
.. confval:: cache_dir
|
||||
|
||||
.. versionadded:: 3.2
|
||||
|
||||
|
||||
Sets a directory where stores content of cache plugin. Default directory is
|
||||
``.pytest_cache`` which is created in :ref:`rootdir <rootdir>`. Directory may be
|
||||
@@ -1002,7 +1003,7 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||
|
||||
.. confval:: console_output_style
|
||||
|
||||
.. versionadded:: 3.3
|
||||
|
||||
|
||||
Sets the console output style while running tests:
|
||||
|
||||
@@ -1022,7 +1023,7 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||
|
||||
.. confval:: doctest_encoding
|
||||
|
||||
.. versionadded:: 3.1
|
||||
|
||||
|
||||
Default encoding to use to decode text files with docstrings.
|
||||
:doc:`See how pytest handles doctests <doctest>`.
|
||||
@@ -1036,7 +1037,7 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||
|
||||
.. confval:: empty_parameter_set_mark
|
||||
|
||||
.. versionadded:: 3.4
|
||||
|
||||
|
||||
Allows to pick the action for empty parametersets in parameterization
|
||||
|
||||
@@ -1059,7 +1060,7 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||
|
||||
.. confval:: filterwarnings
|
||||
|
||||
.. versionadded:: 3.1
|
||||
|
||||
|
||||
Sets a list of filters and actions that should be taken for matched
|
||||
warnings. By default all warnings emitted during the test session
|
||||
@@ -1093,7 +1094,7 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||
|
||||
.. confval:: junit_suite_name
|
||||
|
||||
.. versionadded:: 3.1
|
||||
|
||||
|
||||
To set the name of the root test suite xml item, you can configure the ``junit_suite_name`` option in your config file:
|
||||
|
||||
@@ -1105,7 +1106,7 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||
|
||||
.. confval:: log_cli_date_format
|
||||
|
||||
.. versionadded:: 3.3
|
||||
|
||||
|
||||
Sets a :py:func:`time.strftime`-compatible string that will be used when formatting dates for live logging.
|
||||
|
||||
@@ -1118,7 +1119,7 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||
|
||||
.. confval:: log_cli_format
|
||||
|
||||
.. versionadded:: 3.3
|
||||
|
||||
|
||||
Sets a :py:mod:`logging`-compatible string used to format live logging messages.
|
||||
|
||||
@@ -1132,7 +1133,7 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||
|
||||
.. confval:: log_cli_level
|
||||
|
||||
.. versionadded:: 3.3
|
||||
|
||||
|
||||
Sets the minimum log message level that should be captured for live logging. The integer value or
|
||||
the names of the levels can be used.
|
||||
@@ -1147,7 +1148,7 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||
|
||||
.. confval:: log_date_format
|
||||
|
||||
.. versionadded:: 3.3
|
||||
|
||||
|
||||
Sets a :py:func:`time.strftime`-compatible string that will be used when formatting dates for logging capture.
|
||||
|
||||
@@ -1161,7 +1162,7 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||
|
||||
.. confval:: log_file
|
||||
|
||||
.. versionadded:: 3.3
|
||||
|
||||
|
||||
Sets a file name relative to the ``pytest.ini`` file where log messages should be written to, in addition
|
||||
to the other logging facilities that are active.
|
||||
@@ -1176,7 +1177,7 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||
|
||||
.. confval:: log_file_date_format
|
||||
|
||||
.. versionadded:: 3.3
|
||||
|
||||
|
||||
Sets a :py:func:`time.strftime`-compatible string that will be used when formatting dates for the logging file.
|
||||
|
||||
@@ -1189,7 +1190,7 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||
|
||||
.. confval:: log_file_format
|
||||
|
||||
.. versionadded:: 3.3
|
||||
|
||||
|
||||
Sets a :py:mod:`logging`-compatible string used to format logging messages redirected to the logging file.
|
||||
|
||||
@@ -1202,7 +1203,7 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||
|
||||
.. confval:: log_file_level
|
||||
|
||||
.. versionadded:: 3.3
|
||||
|
||||
|
||||
Sets the minimum log message level that should be captured for the logging file. The integer value or
|
||||
the names of the levels can be used.
|
||||
@@ -1217,7 +1218,7 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||
|
||||
.. confval:: log_format
|
||||
|
||||
.. versionadded:: 3.3
|
||||
|
||||
|
||||
Sets a :py:mod:`logging`-compatible string used to format captured logging messages.
|
||||
|
||||
@@ -1231,7 +1232,7 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||
|
||||
.. confval:: log_level
|
||||
|
||||
.. versionadded:: 3.3
|
||||
|
||||
|
||||
Sets the minimum log message level that should be captured for logging capture. The integer value or
|
||||
the names of the levels can be used.
|
||||
@@ -1246,7 +1247,7 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||
|
||||
.. confval:: log_print
|
||||
|
||||
.. versionadded:: 3.3
|
||||
|
||||
|
||||
If set to ``False``, will disable displaying captured logging messages for failed tests.
|
||||
|
||||
@@ -1383,7 +1384,7 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||
|
||||
.. confval:: testpaths
|
||||
|
||||
.. versionadded:: 2.8
|
||||
|
||||
|
||||
Sets list of directories that should be searched for tests when
|
||||
no specific directories, files or test ids are given in the command line when
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
pygments-pytest>=1.1.0
|
||||
sphinx>=1.8.2
|
||||
sphinx>=1.8.2,<2.1
|
||||
sphinxcontrib-trio
|
||||
sphinx-removed-in>=0.1.3
|
||||
sphinx-removed-in>=0.2.0
|
||||
|
||||
@@ -39,7 +39,7 @@ More details on the ``-r`` option can be found by running ``pytest -h``.
|
||||
Skipping test functions
|
||||
-----------------------
|
||||
|
||||
.. versionadded:: 2.9
|
||||
|
||||
|
||||
The simplest way to skip a test function is to mark it with the ``skip`` decorator
|
||||
which may be passed an optional ``reason``:
|
||||
@@ -80,36 +80,48 @@ It is also possible to skip the whole module using
|
||||
``skipif``
|
||||
~~~~~~~~~~
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
|
||||
If you wish to skip something conditionally then you can use ``skipif`` instead.
|
||||
Here is an example of marking a test function to be skipped
|
||||
when run on an interpreter earlier than Python3.6::
|
||||
when run on an interpreter earlier than Python3.6:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import sys
|
||||
@pytest.mark.skipif(sys.version_info < (3,6),
|
||||
reason="requires python3.6 or higher")
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher")
|
||||
def test_function():
|
||||
...
|
||||
|
||||
If the condition evaluates to ``True`` during collection, the test function will be skipped,
|
||||
with the specified reason appearing in the summary when using ``-rs``.
|
||||
|
||||
You can share ``skipif`` markers between modules. Consider this test module::
|
||||
You can share ``skipif`` markers between modules. Consider this test module:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_mymodule.py
|
||||
import mymodule
|
||||
minversion = pytest.mark.skipif(mymodule.__versioninfo__ < (1,1),
|
||||
reason="at least mymodule-1.1 required")
|
||||
|
||||
minversion = pytest.mark.skipif(
|
||||
mymodule.__versioninfo__ < (1, 1), reason="at least mymodule-1.1 required"
|
||||
)
|
||||
|
||||
|
||||
@minversion
|
||||
def test_function():
|
||||
...
|
||||
|
||||
You can import the marker and reuse it in another test module::
|
||||
You can import the marker and reuse it in another test module:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# test_myothermodule.py
|
||||
from test_mymodule import minversion
|
||||
|
||||
|
||||
@minversion
|
||||
def test_anotherfunction():
|
||||
...
|
||||
@@ -128,12 +140,12 @@ so they are supported mainly for backward compatibility reasons.
|
||||
Skip all test functions of a class or module
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can use the ``skipif`` marker (as any other marker) on classes::
|
||||
You can use the ``skipif`` marker (as any other marker) on classes:
|
||||
|
||||
@pytest.mark.skipif(sys.platform == 'win32',
|
||||
reason="does not run on windows")
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
|
||||
class TestPosixCalls(object):
|
||||
|
||||
def test_function(self):
|
||||
"will not be setup or run under 'win32' platform"
|
||||
|
||||
@@ -196,7 +208,7 @@ Here's a quick guide on how to skip tests in a module in different situations:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
pytestmark = pytest.mark.skipif(sys.platform == "win32", "tests for linux only")
|
||||
pytestmark = pytest.mark.skipif(sys.platform == "win32", reason="tests for linux only")
|
||||
|
||||
3. Skip all tests in a module if some import is missing:
|
||||
|
||||
@@ -242,7 +254,7 @@ internally by raising a known exception.
|
||||
``strict`` parameter
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. versionadded:: 2.9
|
||||
|
||||
|
||||
Both ``XFAIL`` and ``XPASS`` don't fail the test suite, unless the ``strict`` keyword-only
|
||||
parameter is passed as ``True``:
|
||||
@@ -269,10 +281,11 @@ You can change the default value of the ``strict`` parameter using the
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
As with skipif_ you can also mark your expectation of a failure
|
||||
on a particular platform::
|
||||
on a particular platform:
|
||||
|
||||
@pytest.mark.xfail(sys.version_info >= (3,6),
|
||||
reason="python3.6 api changes")
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.xfail(sys.version_info >= (3, 6), reason="python3.6 api changes")
|
||||
def test_function():
|
||||
...
|
||||
|
||||
@@ -335,7 +348,7 @@ Running it with the report-on-xfail option gives this output:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR/example, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR/example
|
||||
collected 7 items
|
||||
|
||||
xfail_demo.py xxxxxxx [100%]
|
||||
|
||||
@@ -8,7 +8,7 @@ Temporary directories and files
|
||||
The ``tmp_path`` fixture
|
||||
------------------------
|
||||
|
||||
.. versionadded:: 3.9
|
||||
|
||||
|
||||
|
||||
You can use the ``tmp_path`` fixture which will
|
||||
@@ -43,7 +43,7 @@ Running this would result in a passed test except for the last
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 1 item
|
||||
|
||||
test_tmp_path.py F [100%]
|
||||
@@ -71,7 +71,7 @@ Running this would result in a passed test except for the last
|
||||
The ``tmp_path_factory`` fixture
|
||||
--------------------------------
|
||||
|
||||
.. versionadded:: 3.9
|
||||
|
||||
|
||||
|
||||
The ``tmp_path_factory`` is a session-scoped fixture which can be used
|
||||
@@ -110,7 +110,7 @@ Running this would result in a passed test except for the last
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 1 item
|
||||
|
||||
test_tmpdir.py F [100%]
|
||||
@@ -136,7 +136,7 @@ Running this would result in a passed test except for the last
|
||||
The 'tmpdir_factory' fixture
|
||||
----------------------------
|
||||
|
||||
.. versionadded:: 2.8
|
||||
|
||||
|
||||
The ``tmpdir_factory`` is a session-scoped fixture which can be used
|
||||
to create arbitrary temporary directories from any other fixture or test.
|
||||
|
||||
@@ -130,7 +130,7 @@ the ``self.db`` values in the traceback:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 2 items
|
||||
|
||||
test_unittest_db.py FF [100%]
|
||||
|
||||
@@ -10,7 +10,7 @@ Usage and Invocations
|
||||
Calling pytest through ``python -m pytest``
|
||||
-----------------------------------------------------
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
|
||||
You can invoke testing through the Python interpreter from the command line:
|
||||
|
||||
@@ -155,7 +155,7 @@ option you make sure a trace is shown.
|
||||
Detailed summary report
|
||||
-----------------------
|
||||
|
||||
.. versionadded:: 2.9
|
||||
|
||||
|
||||
The ``-r`` flag can be used to display a "short test summary info" at the end of the test session,
|
||||
making it easy in large test suites to get a clear picture of all failures, skips, xfails, etc.
|
||||
@@ -204,7 +204,7 @@ Example:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 6 items
|
||||
|
||||
test_example.py .FEsxX [100%]
|
||||
@@ -233,7 +233,7 @@ Example:
|
||||
XPASS test_example.py::test_xpass always xfail
|
||||
ERROR test_example.py::test_error
|
||||
FAILED test_example.py::test_fail
|
||||
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.12 seconds =
|
||||
|
||||
The ``-r`` options accepts a number of characters after it, with ``a`` used above meaning "all except passes".
|
||||
|
||||
@@ -256,7 +256,7 @@ More than one character can be used, so for example to only see failed and skipp
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 6 items
|
||||
|
||||
test_example.py .FEsxX [100%]
|
||||
@@ -281,7 +281,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
|
||||
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.12 seconds =
|
||||
|
||||
Using ``p`` lists the passing tests, whilst ``P`` adds an extra section "PASSES" with those tests that passed but had
|
||||
captured output:
|
||||
@@ -292,7 +292,7 @@ captured output:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 6 items
|
||||
|
||||
test_example.py .FEsxX [100%]
|
||||
@@ -320,7 +320,7 @@ captured output:
|
||||
_________________________________ test_ok __________________________________
|
||||
--------------------------- Captured stdout call ---------------------------
|
||||
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.12 seconds =
|
||||
|
||||
.. _pdb-option:
|
||||
|
||||
@@ -384,10 +384,8 @@ in your code and pytest automatically disables its output capture for that test:
|
||||
* Output capture in other tests is not affected.
|
||||
* Any prior test output that has already been captured and will be processed as
|
||||
such.
|
||||
* Any later output produced within the same test will not be captured and will
|
||||
instead get sent directly to ``sys.stdout``. Note that this holds true even
|
||||
for test output occurring after you exit the interactive PDB_ tracing session
|
||||
and continue with the regular test run.
|
||||
* Output capture gets resumed when ending the debugger session (via the
|
||||
``continue`` command).
|
||||
|
||||
|
||||
.. _`breakpoint-builtin`:
|
||||
@@ -430,7 +428,7 @@ integration servers, use this invocation:
|
||||
|
||||
to create an XML file at ``path``.
|
||||
|
||||
.. versionadded:: 3.1
|
||||
|
||||
|
||||
To set the name of the root test suite xml item, you can configure the ``junit_suite_name`` option in your config file:
|
||||
|
||||
@@ -458,8 +456,8 @@ instead, configure the ``junit_duration_report`` option like this:
|
||||
record_property
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
.. versionadded:: 2.8
|
||||
.. versionchanged:: 3.5
|
||||
|
||||
|
||||
|
||||
Fixture renamed from ``record_xml_property`` to ``record_property`` as user
|
||||
properties are now available to all reporters.
|
||||
@@ -530,7 +528,7 @@ Will result in:
|
||||
record_xml_attribute
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. versionadded:: 3.4
|
||||
|
||||
|
||||
To add an additional xml attribute to a testcase element, you can use
|
||||
``record_xml_attribute`` fixture. This can also be used to override existing values:
|
||||
@@ -590,7 +588,7 @@ Instead, this will add an attribute ``assertions="REQ-1234"`` inside the generat
|
||||
LogXML: add_global_property
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. versionadded:: 3.0
|
||||
|
||||
|
||||
If you want to add a properties node in the testsuite level, which may contains properties that are relevant
|
||||
to all testcases you can use ``LogXML.add_global_properties``
|
||||
@@ -640,7 +638,7 @@ This will add a property node below the testsuite node to the generated xml:
|
||||
Creating resultlog format files
|
||||
----------------------------------------------------
|
||||
|
||||
.. deprecated:: 3.0
|
||||
|
||||
|
||||
This option is rarely used and is scheduled for removal in 5.0.
|
||||
|
||||
@@ -680,6 +678,22 @@ for example ``-x`` if you only want to send one particular failure.
|
||||
|
||||
Currently only pasting to the http://bpaste.net service is implemented.
|
||||
|
||||
Early loading plugins
|
||||
---------------------
|
||||
|
||||
You can early-load plugins (internal and external) explicitly in the command-line with the ``-p`` option::
|
||||
|
||||
pytest -p mypluginmodule
|
||||
|
||||
The option receives a ``name`` parameter, which can be:
|
||||
|
||||
* A full module dotted name, for example ``myproject.plugins``. This dotted name must be importable.
|
||||
* The entry-point name of a plugin. This is the name passed to ``setuptools`` when the plugin is
|
||||
registered. For example to early-load the `pytest-cov <https://pypi.org/project/pytest-cov/>`__ plugin you can use::
|
||||
|
||||
pytest -p pytest_cov
|
||||
|
||||
|
||||
Disabling plugins
|
||||
-----------------
|
||||
|
||||
@@ -698,7 +712,7 @@ executing doctest tests from text files, invoke pytest like this:
|
||||
Calling pytest from Python code
|
||||
----------------------------------------------------
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
|
||||
You can invoke ``pytest`` from Python code directly::
|
||||
|
||||
|
||||
@@ -3,18 +3,22 @@
|
||||
Warnings Capture
|
||||
================
|
||||
|
||||
.. versionadded:: 3.1
|
||||
|
||||
|
||||
Starting from version ``3.1``, pytest now automatically catches warnings during test execution
|
||||
and displays them at the end of the session::
|
||||
and displays them at the end of the session:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_show_warnings.py
|
||||
import warnings
|
||||
|
||||
|
||||
def api_v1():
|
||||
warnings.warn(UserWarning("api v1, should use functions from v2"))
|
||||
return 1
|
||||
|
||||
|
||||
def test_one():
|
||||
assert api_v1() == 1
|
||||
|
||||
@@ -26,14 +30,14 @@ Running pytest now produces this output:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 1 item
|
||||
|
||||
test_show_warnings.py . [100%]
|
||||
|
||||
============================= warnings summary =============================
|
||||
test_show_warnings.py::test_one
|
||||
$REGENDOC_TMPDIR/test_show_warnings.py:4: UserWarning: api v1, should use functions from v2
|
||||
$REGENDOC_TMPDIR/test_show_warnings.py:5: UserWarning: api v1, should use functions from v2
|
||||
warnings.warn(UserWarning("api v1, should use functions from v2"))
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/latest/warnings.html
|
||||
@@ -52,14 +56,14 @@ them into errors:
|
||||
def test_one():
|
||||
> assert api_v1() == 1
|
||||
|
||||
test_show_warnings.py:8:
|
||||
test_show_warnings.py:10:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
def api_v1():
|
||||
> warnings.warn(UserWarning("api v1, should use functions from v2"))
|
||||
E UserWarning: api v1, should use functions from v2
|
||||
|
||||
test_show_warnings.py:4: UserWarning
|
||||
test_show_warnings.py:5: UserWarning
|
||||
1 failed in 0.12 seconds
|
||||
|
||||
The same option can be set in the ``pytest.ini`` file using the ``filterwarnings`` ini option.
|
||||
@@ -86,7 +90,7 @@ documentation for other examples and advanced usage.
|
||||
``@pytest.mark.filterwarnings``
|
||||
-------------------------------
|
||||
|
||||
.. versionadded:: 3.2
|
||||
|
||||
|
||||
You can use the ``@pytest.mark.filterwarnings`` to add warning filters to specific test items,
|
||||
allowing you to have finer control of which warnings should be captured at test, class or
|
||||
@@ -152,8 +156,8 @@ using an external system.
|
||||
DeprecationWarning and PendingDeprecationWarning
|
||||
------------------------------------------------
|
||||
|
||||
.. versionadded:: 3.8
|
||||
.. versionchanged:: 3.9
|
||||
|
||||
|
||||
|
||||
By default pytest will display ``DeprecationWarning`` and ``PendingDeprecationWarning`` warnings from
|
||||
user code and third-party libraries, as recommended by `PEP-0565 <https://www.python.org/dev/peps/pep-0565>`_.
|
||||
@@ -195,28 +199,36 @@ Ensuring code triggers a deprecation warning
|
||||
|
||||
You can also call a global helper for checking
|
||||
that a certain function call triggers a ``DeprecationWarning`` or
|
||||
``PendingDeprecationWarning``::
|
||||
``PendingDeprecationWarning``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def test_global():
|
||||
pytest.deprecated_call(myfunction, 17)
|
||||
|
||||
By default, ``DeprecationWarning`` and ``PendingDeprecationWarning`` will not be
|
||||
caught when using ``pytest.warns`` or ``recwarn`` because default Python warnings filters hide
|
||||
them. If you wish to record them in your own code, use the
|
||||
command ``warnings.simplefilter('always')``::
|
||||
command ``warnings.simplefilter('always')``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import warnings
|
||||
import pytest
|
||||
|
||||
|
||||
def test_deprecation(recwarn):
|
||||
warnings.simplefilter('always')
|
||||
warnings.simplefilter("always")
|
||||
warnings.warn("deprecated", DeprecationWarning)
|
||||
assert len(recwarn) == 1
|
||||
assert recwarn.pop(DeprecationWarning)
|
||||
|
||||
You can also use it as a contextmanager::
|
||||
You can also use it as a contextmanager:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_global():
|
||||
with pytest.deprecated_call():
|
||||
@@ -235,14 +247,17 @@ You can also use it as a contextmanager::
|
||||
Asserting warnings with the warns function
|
||||
------------------------------------------
|
||||
|
||||
.. versionadded:: 2.8
|
||||
|
||||
|
||||
You can check that code raises a particular warning using ``pytest.warns``,
|
||||
which works in a similar manner to :ref:`raises <assertraises>`::
|
||||
which works in a similar manner to :ref:`raises <assertraises>`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import warnings
|
||||
import pytest
|
||||
|
||||
|
||||
def test_warning():
|
||||
with pytest.warns(UserWarning):
|
||||
warnings.warn("my warning", UserWarning)
|
||||
@@ -269,7 +284,9 @@ You can also call ``pytest.warns`` on a function or code string::
|
||||
|
||||
The function also returns a list of all raised warnings (as
|
||||
``warnings.WarningMessage`` objects), which you can query for
|
||||
additional information::
|
||||
additional information:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
with pytest.warns(RuntimeWarning) as record:
|
||||
warnings.warn("another warning", RuntimeWarning)
|
||||
@@ -297,7 +314,9 @@ You can record raised warnings either using ``pytest.warns`` or with
|
||||
the ``recwarn`` fixture.
|
||||
|
||||
To record with ``pytest.warns`` without asserting anything about the warnings,
|
||||
pass ``None`` as the expected warning type::
|
||||
pass ``None`` as the expected warning type:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
with pytest.warns(None) as record:
|
||||
warnings.warn("user", UserWarning)
|
||||
@@ -307,10 +326,13 @@ pass ``None`` as the expected warning type::
|
||||
assert str(record[0].message) == "user"
|
||||
assert str(record[1].message) == "runtime"
|
||||
|
||||
The ``recwarn`` fixture will record warnings for the whole function::
|
||||
The ``recwarn`` fixture will record warnings for the whole function:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import warnings
|
||||
|
||||
|
||||
def test_hello(recwarn):
|
||||
warnings.warn("hello", UserWarning)
|
||||
assert len(recwarn) == 1
|
||||
@@ -355,7 +377,7 @@ custom error message.
|
||||
Internal pytest warnings
|
||||
------------------------
|
||||
|
||||
.. versionadded:: 3.8
|
||||
|
||||
|
||||
pytest may generate its own warnings in some situations, such as improper usage or deprecated features.
|
||||
|
||||
|
||||
@@ -496,7 +496,7 @@ hookwrapper: executing around other hooks
|
||||
|
||||
.. currentmodule:: _pytest.core
|
||||
|
||||
.. versionadded:: 2.7
|
||||
|
||||
|
||||
pytest plugins can implement hook wrappers which wrap the execution
|
||||
of other hook implementations. A hook wrapper is a generator function
|
||||
@@ -509,10 +509,13 @@ a :py:class:`Result <pluggy._Result>` instance which encapsulates a result or
|
||||
exception info. The yield point itself will thus typically not raise
|
||||
exceptions (unless there are bugs).
|
||||
|
||||
Here is an example definition of a hook wrapper::
|
||||
Here is an example definition of a hook wrapper:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.hookimpl(hookwrapper=True)
|
||||
def pytest_pyfunc_call(pyfuncitem):
|
||||
do_something_before_next_hook_executes()
|
||||
@@ -617,10 +620,13 @@ if you depend on a plugin that is not installed, validation will fail and
|
||||
the error message will not make much sense to your users.
|
||||
|
||||
One approach is to defer the hook implementation to a new plugin instead of
|
||||
declaring the hook functions directly in your plugin module, for example::
|
||||
declaring the hook functions directly in your plugin module, for example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# contents of myplugin.py
|
||||
|
||||
|
||||
class DeferPlugin(object):
|
||||
"""Simple plugin to defer pytest-xdist hook functions."""
|
||||
|
||||
@@ -628,8 +634,9 @@ declaring the hook functions directly in your plugin module, for example::
|
||||
"""standard xdist hook function.
|
||||
"""
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
if config.pluginmanager.hasplugin('xdist'):
|
||||
if config.pluginmanager.hasplugin("xdist"):
|
||||
config.pluginmanager.register(DeferPlugin())
|
||||
|
||||
This has the added benefit of allowing you to conditionally install hooks
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
"yield_fixture" functions
|
||||
---------------------------------------------------------------
|
||||
|
||||
.. deprecated:: 3.0
|
||||
|
||||
.. versionadded:: 2.4
|
||||
|
||||
|
||||
|
||||
.. important::
|
||||
Since pytest-3.0, fixtures using the normal ``fixture`` decorator can use a ``yield``
|
||||
|
||||
4
setup.py
4
setup.py
@@ -10,10 +10,10 @@ INSTALL_REQUIRES = [
|
||||
'more-itertools>=4.0.0,<6.0.0;python_version<="2.7"',
|
||||
'more-itertools>=4.0.0;python_version>"2.7"',
|
||||
"atomicwrites>=1.0",
|
||||
'funcsigs;python_version<"3.0"',
|
||||
'funcsigs>=1.0;python_version<"3.0"',
|
||||
'pathlib2>=2.2.0;python_version<"3.6"',
|
||||
'colorama;sys_platform=="win32"',
|
||||
"pluggy>=0.7",
|
||||
"pluggy>=0.11",
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import inspect
|
||||
import pprint
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
@@ -14,10 +13,10 @@ from weakref import ref
|
||||
import attr
|
||||
import pluggy
|
||||
import py
|
||||
import six
|
||||
from six import text_type
|
||||
|
||||
import _pytest
|
||||
from _pytest._io.saferepr import safeformat
|
||||
from _pytest._io.saferepr import saferepr
|
||||
from _pytest.compat import _PY2
|
||||
from _pytest.compat import _PY3
|
||||
@@ -138,7 +137,7 @@ class Frame(object):
|
||||
"""
|
||||
f_locals = self.f_locals.copy()
|
||||
f_locals.update(vars)
|
||||
six.exec_(code, self.f_globals, f_locals)
|
||||
exec(code, self.f_globals, f_locals)
|
||||
|
||||
def repr(self, object):
|
||||
""" return a 'safe' (non-recursive, one-line) string repr for 'object'
|
||||
@@ -241,25 +240,20 @@ class TracebackEntry(object):
|
||||
|
||||
def ishidden(self):
|
||||
""" return True if the current frame has a var __tracebackhide__
|
||||
resolving to True
|
||||
resolving to True.
|
||||
|
||||
If __tracebackhide__ is a callable, it gets called with the
|
||||
ExceptionInfo instance and can decide whether to hide the traceback.
|
||||
|
||||
mostly for internal use
|
||||
"""
|
||||
try:
|
||||
tbh = self.frame.f_locals["__tracebackhide__"]
|
||||
except KeyError:
|
||||
try:
|
||||
tbh = self.frame.f_globals["__tracebackhide__"]
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
if callable(tbh):
|
||||
f = self.frame
|
||||
tbh = f.f_locals.get(
|
||||
"__tracebackhide__", f.f_globals.get("__tracebackhide__", False)
|
||||
)
|
||||
if tbh and callable(tbh):
|
||||
return tbh(None if self._excinfo is None else self._excinfo())
|
||||
else:
|
||||
return tbh
|
||||
return tbh
|
||||
|
||||
def __str__(self):
|
||||
try:
|
||||
@@ -418,6 +412,7 @@ class ExceptionInfo(object):
|
||||
to the exception message/``__str__()``
|
||||
"""
|
||||
tup = sys.exc_info()
|
||||
assert tup[0] is not None, "no current exception"
|
||||
_striptext = ""
|
||||
if exprinfo is None and isinstance(tup[1], AssertionError):
|
||||
exprinfo = getattr(tup[1], "msg", None)
|
||||
@@ -618,14 +613,11 @@ class FormattedExcinfo(object):
|
||||
source = source.deindent()
|
||||
return source
|
||||
|
||||
def _saferepr(self, obj):
|
||||
return saferepr(obj)
|
||||
|
||||
def repr_args(self, entry):
|
||||
if self.funcargs:
|
||||
args = []
|
||||
for argname, argvalue in entry.frame.getargs(var=True):
|
||||
args.append((argname, self._saferepr(argvalue)))
|
||||
args.append((argname, saferepr(argvalue)))
|
||||
return ReprFuncArgs(args)
|
||||
|
||||
def get_source(self, source, line_index=-1, excinfo=None, short=False):
|
||||
@@ -678,9 +670,9 @@ class FormattedExcinfo(object):
|
||||
# _repr() function, which is only reprlib.Repr in
|
||||
# disguise, so is very configurable.
|
||||
if self.truncate_locals:
|
||||
str_repr = self._saferepr(value)
|
||||
str_repr = saferepr(value)
|
||||
else:
|
||||
str_repr = pprint.pformat(value)
|
||||
str_repr = safeformat(value)
|
||||
# if len(str_repr) < 70 or not isinstance(value,
|
||||
# (list, tuple, dict)):
|
||||
lines.append("%-10s = %s" % (name, str_repr))
|
||||
|
||||
@@ -203,7 +203,9 @@ def compile_(source, filename=None, mode="exec", flags=0, dont_inherit=0):
|
||||
|
||||
def getfslineno(obj):
|
||||
""" Return source location (path, lineno) for the given object.
|
||||
If the source cannot be determined return ("", -1)
|
||||
If the source cannot be determined return ("", -1).
|
||||
|
||||
The line number is 0-based.
|
||||
"""
|
||||
from .code import Code
|
||||
|
||||
|
||||
@@ -1,8 +1,26 @@
|
||||
import sys
|
||||
import pprint
|
||||
|
||||
from six.moves import reprlib
|
||||
|
||||
|
||||
def _call_and_format_exception(call, x, *args):
|
||||
try:
|
||||
# Try the vanilla repr and make sure that the result is a string
|
||||
return call(x, *args)
|
||||
except Exception as exc:
|
||||
exc_name = type(exc).__name__
|
||||
try:
|
||||
exc_info = str(exc)
|
||||
except Exception:
|
||||
exc_info = "unknown"
|
||||
return '<[%s("%s") raised in repr()] %s object at 0x%x>' % (
|
||||
exc_name,
|
||||
exc_info,
|
||||
x.__class__.__name__,
|
||||
id(x),
|
||||
)
|
||||
|
||||
|
||||
class SafeRepr(reprlib.Repr):
|
||||
"""subclass of repr.Repr that limits the resulting size of repr()
|
||||
and includes information on exceptions raised during the call.
|
||||
@@ -33,28 +51,20 @@ class SafeRepr(reprlib.Repr):
|
||||
return self._callhelper(repr, x)
|
||||
|
||||
def _callhelper(self, call, x, *args):
|
||||
try:
|
||||
# Try the vanilla repr and make sure that the result is a string
|
||||
s = call(x, *args)
|
||||
except Exception:
|
||||
cls, e, tb = sys.exc_info()
|
||||
exc_name = getattr(cls, "__name__", "unknown")
|
||||
try:
|
||||
exc_info = str(e)
|
||||
except Exception:
|
||||
exc_info = "unknown"
|
||||
return '<[%s("%s") raised in repr()] %s object at 0x%x>' % (
|
||||
exc_name,
|
||||
exc_info,
|
||||
x.__class__.__name__,
|
||||
id(x),
|
||||
)
|
||||
else:
|
||||
if len(s) > self.maxsize:
|
||||
i = max(0, (self.maxsize - 3) // 2)
|
||||
j = max(0, self.maxsize - 3 - i)
|
||||
s = s[:i] + "..." + s[len(s) - j :]
|
||||
return s
|
||||
s = _call_and_format_exception(call, x, *args)
|
||||
if len(s) > self.maxsize:
|
||||
i = max(0, (self.maxsize - 3) // 2)
|
||||
j = max(0, self.maxsize - 3 - i)
|
||||
s = s[:i] + "..." + s[len(s) - j :]
|
||||
return s
|
||||
|
||||
|
||||
def safeformat(obj):
|
||||
"""return a pretty printed string for the given object.
|
||||
Failing __repr__ functions of user instances will be represented
|
||||
with a short exception info.
|
||||
"""
|
||||
return _call_and_format_exception(pprint.pformat, obj)
|
||||
|
||||
|
||||
def saferepr(obj, maxsize=240):
|
||||
|
||||
@@ -21,6 +21,9 @@ import six
|
||||
|
||||
from _pytest._io.saferepr import saferepr
|
||||
from _pytest.assertion import util
|
||||
from _pytest.assertion.util import ( # noqa: F401
|
||||
format_explanation as _format_explanation,
|
||||
)
|
||||
from _pytest.compat import spec_from_file_location
|
||||
from _pytest.pathlib import fnmatch_ex
|
||||
from _pytest.pathlib import PurePath
|
||||
@@ -293,7 +296,7 @@ class AssertionRewritingHook(object):
|
||||
mod.__loader__ = self
|
||||
# Normally, this attribute is 3.4+
|
||||
mod.__spec__ = spec_from_file_location(name, co.co_filename, loader=self)
|
||||
six.exec_(co, mod.__dict__)
|
||||
exec(co, mod.__dict__)
|
||||
except: # noqa
|
||||
if name in sys.modules:
|
||||
del sys.modules[name]
|
||||
@@ -344,9 +347,11 @@ def _write_pyc(state, co, source_stat, pyc):
|
||||
try:
|
||||
with atomicwrites.atomic_write(pyc, mode="wb", overwrite=True) as fp:
|
||||
fp.write(imp.get_magic())
|
||||
mtime = int(source_stat.mtime)
|
||||
# as of now, bytecode header expects 32-bit numbers for size and mtime (#4903)
|
||||
mtime = int(source_stat.mtime) & 0xFFFFFFFF
|
||||
size = source_stat.size & 0xFFFFFFFF
|
||||
fp.write(struct.pack("<ll", mtime, size))
|
||||
# "<LL" stands for 2 unsigned longs, little-ending
|
||||
fp.write(struct.pack("<LL", mtime, size))
|
||||
fp.write(marshal.dumps(co))
|
||||
except EnvironmentError as e:
|
||||
state.trace("error writing pyc file at %s: errno=%s" % (pyc, e.errno))
|
||||
@@ -441,7 +446,7 @@ def _read_pyc(source, pyc, trace=lambda x: None):
|
||||
if (
|
||||
len(data) != 12
|
||||
or data[:4] != imp.get_magic()
|
||||
or struct.unpack("<ll", data[4:]) != (mtime, size)
|
||||
or struct.unpack("<LL", data[4:]) != (mtime & 0xFFFFFFFF, size & 0xFFFFFFFF)
|
||||
):
|
||||
trace("_read_pyc(%s): invalid or out of date pyc" % source)
|
||||
return None
|
||||
@@ -483,9 +488,6 @@ def _saferepr(obj):
|
||||
return r.replace(u"\n", u"\\n")
|
||||
|
||||
|
||||
from _pytest.assertion.util import format_explanation as _format_explanation # noqa
|
||||
|
||||
|
||||
def _format_assertmsg(obj):
|
||||
"""Format the custom assertion message given.
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import six
|
||||
|
||||
import _pytest._code
|
||||
from ..compat import Sequence
|
||||
from _pytest import outcomes
|
||||
from _pytest._io.saferepr import saferepr
|
||||
|
||||
# The _reprcompare attribute on the util module is used by the new assertion
|
||||
@@ -102,6 +103,38 @@ except NameError:
|
||||
basestring = str
|
||||
|
||||
|
||||
def issequence(x):
|
||||
return isinstance(x, Sequence) and not isinstance(x, basestring)
|
||||
|
||||
|
||||
def istext(x):
|
||||
return isinstance(x, basestring)
|
||||
|
||||
|
||||
def isdict(x):
|
||||
return isinstance(x, dict)
|
||||
|
||||
|
||||
def isset(x):
|
||||
return isinstance(x, (set, frozenset))
|
||||
|
||||
|
||||
def isdatacls(obj):
|
||||
return getattr(obj, "__dataclass_fields__", None) is not None
|
||||
|
||||
|
||||
def isattrs(obj):
|
||||
return getattr(obj, "__attrs_attrs__", None) is not None
|
||||
|
||||
|
||||
def isiterable(obj):
|
||||
try:
|
||||
iter(obj)
|
||||
return not istext(obj)
|
||||
except TypeError:
|
||||
return False
|
||||
|
||||
|
||||
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
|
||||
@@ -110,31 +143,6 @@ def assertrepr_compare(config, op, left, right):
|
||||
|
||||
summary = u"%s %s %s" % (ecu(left_repr), op, ecu(right_repr))
|
||||
|
||||
def issequence(x):
|
||||
return isinstance(x, Sequence) and not isinstance(x, basestring)
|
||||
|
||||
def istext(x):
|
||||
return isinstance(x, basestring)
|
||||
|
||||
def isdict(x):
|
||||
return isinstance(x, dict)
|
||||
|
||||
def isset(x):
|
||||
return isinstance(x, (set, frozenset))
|
||||
|
||||
def isdatacls(obj):
|
||||
return getattr(obj, "__dataclass_fields__", None) is not None
|
||||
|
||||
def isattrs(obj):
|
||||
return getattr(obj, "__attrs_attrs__", None) is not None
|
||||
|
||||
def isiterable(obj):
|
||||
try:
|
||||
iter(obj)
|
||||
return not istext(obj)
|
||||
except TypeError:
|
||||
return False
|
||||
|
||||
verbose = config.getoption("verbose")
|
||||
explanation = None
|
||||
try:
|
||||
@@ -151,7 +159,7 @@ def assertrepr_compare(config, op, left, right):
|
||||
elif type(left) == type(right) and (isdatacls(left) or isattrs(left)):
|
||||
type_fn = (isdatacls, isattrs)
|
||||
explanation = _compare_eq_cls(left, right, verbose, type_fn)
|
||||
elif verbose:
|
||||
elif verbose > 0:
|
||||
explanation = _compare_eq_verbose(left, right)
|
||||
if isiterable(left) and isiterable(right):
|
||||
expl = _compare_eq_iterable(left, right, verbose)
|
||||
@@ -162,6 +170,8 @@ def assertrepr_compare(config, op, left, right):
|
||||
elif op == "not in":
|
||||
if istext(left) and istext(right):
|
||||
explanation = _notin_text(left, right, verbose)
|
||||
except outcomes.Exit:
|
||||
raise
|
||||
except Exception:
|
||||
explanation = [
|
||||
u"(pytest_assertion plugin: representation of details failed. "
|
||||
@@ -175,8 +185,8 @@ def assertrepr_compare(config, op, left, right):
|
||||
return [summary] + explanation
|
||||
|
||||
|
||||
def _diff_text(left, right, verbose=False):
|
||||
"""Return the explanation for the diff between text or bytes
|
||||
def _diff_text(left, right, verbose=0):
|
||||
"""Return the explanation for the diff between text or bytes.
|
||||
|
||||
Unless --verbose is used this will skip leading and trailing
|
||||
characters which are identical to keep the diff minimal.
|
||||
@@ -202,7 +212,7 @@ def _diff_text(left, right, verbose=False):
|
||||
left = escape_for_readable_diff(left)
|
||||
if isinstance(right, bytes):
|
||||
right = escape_for_readable_diff(right)
|
||||
if not verbose:
|
||||
if verbose < 1:
|
||||
i = 0 # just in case left or right has zero length
|
||||
for i in range(min(len(left), len(right))):
|
||||
if left[i] != right[i]:
|
||||
@@ -250,7 +260,7 @@ def _compare_eq_verbose(left, right):
|
||||
return explanation
|
||||
|
||||
|
||||
def _compare_eq_iterable(left, right, verbose=False):
|
||||
def _compare_eq_iterable(left, right, verbose=0):
|
||||
if not verbose:
|
||||
return [u"Use -v to get the full diff"]
|
||||
# dynamic import to speedup pytest
|
||||
@@ -273,7 +283,7 @@ def _compare_eq_iterable(left, right, verbose=False):
|
||||
return explanation
|
||||
|
||||
|
||||
def _compare_eq_sequence(left, right, verbose=False):
|
||||
def _compare_eq_sequence(left, right, verbose=0):
|
||||
explanation = []
|
||||
for i in range(min(len(left), len(right))):
|
||||
if left[i] != right[i]:
|
||||
@@ -292,7 +302,7 @@ def _compare_eq_sequence(left, right, verbose=False):
|
||||
return explanation
|
||||
|
||||
|
||||
def _compare_eq_set(left, right, verbose=False):
|
||||
def _compare_eq_set(left, right, verbose=0):
|
||||
explanation = []
|
||||
diff_left = left - right
|
||||
diff_right = right - left
|
||||
@@ -307,7 +317,7 @@ def _compare_eq_set(left, right, verbose=False):
|
||||
return explanation
|
||||
|
||||
|
||||
def _compare_eq_dict(left, right, verbose=False):
|
||||
def _compare_eq_dict(left, right, verbose=0):
|
||||
explanation = []
|
||||
common = set(left).intersection(set(right))
|
||||
same = {k: left[k] for k in common if left[k] == right[k]}
|
||||
@@ -368,7 +378,7 @@ def _compare_eq_cls(left, right, verbose, type_fns):
|
||||
return explanation
|
||||
|
||||
|
||||
def _notin_text(term, text, verbose=False):
|
||||
def _notin_text(term, text, verbose=0):
|
||||
index = text.find(term)
|
||||
head = text[:index]
|
||||
tail = text[index + len(term) :]
|
||||
|
||||
@@ -121,10 +121,12 @@ class Cache(object):
|
||||
cache_dir_exists_already = True
|
||||
else:
|
||||
cache_dir_exists_already = self._cachedir.exists()
|
||||
path.parent.mkdir(exist_ok=True, parents=True)
|
||||
path.parent.mkdir(exist_ok=True, parents=True)
|
||||
except (IOError, OSError):
|
||||
self.warn("could not create cache path {path}", path=path)
|
||||
return
|
||||
if not cache_dir_exists_already:
|
||||
self._ensure_supporting_files()
|
||||
try:
|
||||
f = path.open("wb" if PY2 else "w")
|
||||
except (IOError, OSError):
|
||||
@@ -132,24 +134,18 @@ class Cache(object):
|
||||
else:
|
||||
with f:
|
||||
json.dump(value, f, indent=2, sort_keys=True)
|
||||
if not cache_dir_exists_already:
|
||||
self._ensure_supporting_files()
|
||||
|
||||
def _ensure_supporting_files(self):
|
||||
"""Create supporting files in the cache dir that are not really part of the cache."""
|
||||
if self._cachedir.is_dir():
|
||||
readme_path = self._cachedir / "README.md"
|
||||
if not readme_path.is_file():
|
||||
readme_path.write_text(README_CONTENT)
|
||||
readme_path = self._cachedir / "README.md"
|
||||
readme_path.write_text(README_CONTENT)
|
||||
|
||||
gitignore_path = self._cachedir.joinpath(".gitignore")
|
||||
if not gitignore_path.is_file():
|
||||
msg = u"# Created by pytest automatically.\n*"
|
||||
gitignore_path.write_text(msg, encoding="UTF-8")
|
||||
gitignore_path = self._cachedir.joinpath(".gitignore")
|
||||
msg = u"# Created by pytest automatically.\n*"
|
||||
gitignore_path.write_text(msg, encoding="UTF-8")
|
||||
|
||||
cachedir_tag_path = self._cachedir.joinpath("CACHEDIR.TAG")
|
||||
if not cachedir_tag_path.is_file():
|
||||
cachedir_tag_path.write_bytes(CACHEDIR_TAG_CONTENT)
|
||||
cachedir_tag_path = self._cachedir.joinpath("CACHEDIR.TAG")
|
||||
cachedir_tag_path.write_bytes(CACHEDIR_TAG_CONTENT)
|
||||
|
||||
|
||||
class LFPlugin(object):
|
||||
@@ -344,7 +340,7 @@ def cache(request):
|
||||
|
||||
def pytest_report_header(config):
|
||||
"""Display cachedir with --cache-show and if non-default."""
|
||||
if config.option.verbose or config.getini("cache_dir") != ".pytest_cache":
|
||||
if config.option.verbose > 0 or config.getini("cache_dir") != ".pytest_cache":
|
||||
cachedir = config.cache._cachedir
|
||||
# TODO: evaluate generating upward relative paths
|
||||
# starting with .., ../.. if sensible
|
||||
|
||||
@@ -91,6 +91,13 @@ class CaptureManager(object):
|
||||
self._global_capturing = None
|
||||
self._current_item = None
|
||||
|
||||
def __repr__(self):
|
||||
return "<CaptureManager _method=%r _global_capturing=%r _current_item=%r>" % (
|
||||
self._method,
|
||||
self._global_capturing,
|
||||
self._current_item,
|
||||
)
|
||||
|
||||
def _getcapture(self, method):
|
||||
if method == "fd":
|
||||
return MultiCapture(out=True, err=True, Capture=FDCapture)
|
||||
@@ -98,8 +105,17 @@ class CaptureManager(object):
|
||||
return MultiCapture(out=True, err=True, Capture=SysCapture)
|
||||
elif method == "no":
|
||||
return MultiCapture(out=False, err=False, in_=False)
|
||||
else:
|
||||
raise ValueError("unknown capturing method: %r" % method)
|
||||
raise ValueError("unknown capturing method: %r" % method) # pragma: no cover
|
||||
|
||||
def is_capturing(self):
|
||||
if self.is_globally_capturing():
|
||||
return "global"
|
||||
capture_fixture = getattr(self._current_item, "_capture_fixture", None)
|
||||
if capture_fixture is not None:
|
||||
return (
|
||||
"fixture %s" % self._current_item._capture_fixture.request.fixturename
|
||||
)
|
||||
return False
|
||||
|
||||
# Global capturing control
|
||||
|
||||
@@ -128,6 +144,15 @@ class CaptureManager(object):
|
||||
if cap is not None:
|
||||
cap.suspend_capturing(in_=in_)
|
||||
|
||||
def suspend(self, in_=False):
|
||||
# Need to undo local capsys-et-al if it exists before disabling global capture.
|
||||
self.suspend_fixture(self._current_item)
|
||||
self.suspend_global_capture(in_)
|
||||
|
||||
def resume(self):
|
||||
self.resume_global_capture()
|
||||
self.resume_fixture(self._current_item)
|
||||
|
||||
def read_global_capture(self):
|
||||
return self._global_capturing.readouterr()
|
||||
|
||||
@@ -161,15 +186,12 @@ class CaptureManager(object):
|
||||
|
||||
@contextlib.contextmanager
|
||||
def global_and_fixture_disabled(self):
|
||||
"""Context manager to temporarily disables global and current fixture capturing."""
|
||||
# Need to undo local capsys-et-al if exists before disabling global capture
|
||||
self.suspend_fixture(self._current_item)
|
||||
self.suspend_global_capture(in_=False)
|
||||
"""Context manager to temporarily disable global and current fixture capturing."""
|
||||
self.suspend()
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
self.resume_global_capture()
|
||||
self.resume_fixture(self._current_item)
|
||||
self.resume()
|
||||
|
||||
@contextlib.contextmanager
|
||||
def item_capture(self, when, item):
|
||||
@@ -247,10 +269,11 @@ def _ensure_only_one_capture_fixture(request, name):
|
||||
|
||||
@pytest.fixture
|
||||
def capsys(request):
|
||||
"""Enable capturing of writes to ``sys.stdout`` and ``sys.stderr`` and make
|
||||
captured output available via ``capsys.readouterr()`` method calls
|
||||
which return a ``(out, err)`` namedtuple. ``out`` and ``err`` will be ``text``
|
||||
objects.
|
||||
"""Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``.
|
||||
|
||||
The captured output is made available via ``capsys.readouterr()`` method
|
||||
calls, which return a ``(out, err)`` namedtuple.
|
||||
``out`` and ``err`` will be ``text`` objects.
|
||||
"""
|
||||
_ensure_only_one_capture_fixture(request, "capsys")
|
||||
with _install_capture_fixture_on_item(request, SysCapture) as fixture:
|
||||
@@ -259,26 +282,28 @@ def capsys(request):
|
||||
|
||||
@pytest.fixture
|
||||
def capsysbinary(request):
|
||||
"""Enable capturing of writes to ``sys.stdout`` and ``sys.stderr`` and make
|
||||
captured output available via ``capsys.readouterr()`` method calls
|
||||
which return a ``(out, err)`` tuple. ``out`` and ``err`` will be ``bytes``
|
||||
objects.
|
||||
"""Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``.
|
||||
|
||||
The captured output is made available via ``capsysbinary.readouterr()``
|
||||
method calls, which return a ``(out, err)`` namedtuple.
|
||||
``out`` and ``err`` will be ``bytes`` objects.
|
||||
"""
|
||||
_ensure_only_one_capture_fixture(request, "capsysbinary")
|
||||
# Currently, the implementation uses the python3 specific `.buffer`
|
||||
# property of CaptureIO.
|
||||
if sys.version_info < (3,):
|
||||
raise request.raiseerror("capsysbinary is only supported on python 3")
|
||||
raise request.raiseerror("capsysbinary is only supported on Python 3")
|
||||
with _install_capture_fixture_on_item(request, SysCaptureBinary) as fixture:
|
||||
yield fixture
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def capfd(request):
|
||||
"""Enable capturing of writes to file descriptors ``1`` and ``2`` and make
|
||||
captured output available via ``capfd.readouterr()`` method calls
|
||||
which return a ``(out, err)`` tuple. ``out`` and ``err`` will be ``text``
|
||||
objects.
|
||||
"""Enable text capturing of writes to file descriptors ``1`` and ``2``.
|
||||
|
||||
The captured output is made available via ``capfd.readouterr()`` method
|
||||
calls, which return a ``(out, err)`` namedtuple.
|
||||
``out`` and ``err`` will be ``text`` objects.
|
||||
"""
|
||||
_ensure_only_one_capture_fixture(request, "capfd")
|
||||
if not hasattr(os, "dup"):
|
||||
@@ -291,10 +316,11 @@ def capfd(request):
|
||||
|
||||
@pytest.fixture
|
||||
def capfdbinary(request):
|
||||
"""Enable capturing of write to file descriptors 1 and 2 and make
|
||||
captured output available via ``capfdbinary.readouterr`` method calls
|
||||
which return a ``(out, err)`` tuple. ``out`` and ``err`` will be
|
||||
``bytes`` objects.
|
||||
"""Enable bytes capturing of writes to file descriptors ``1`` and ``2``.
|
||||
|
||||
The captured output is made available via ``capfd.readouterr()`` method
|
||||
calls, which return a ``(out, err)`` namedtuple.
|
||||
``out`` and ``err`` will be ``byte`` objects.
|
||||
"""
|
||||
_ensure_only_one_capture_fixture(request, "capfdbinary")
|
||||
if not hasattr(os, "dup"):
|
||||
@@ -316,9 +342,9 @@ def _install_capture_fixture_on_item(request, capture_class):
|
||||
"""
|
||||
request.node._capture_fixture = fixture = CaptureFixture(capture_class, request)
|
||||
capmanager = request.config.pluginmanager.getplugin("capturemanager")
|
||||
# need to active this fixture right away in case it is being used by another fixture (setup phase)
|
||||
# if this fixture is being used only by a test function (call phase), then we wouldn't need this
|
||||
# activation, but it doesn't hurt
|
||||
# Need to active this fixture right away in case it is being used by another fixture (setup phase).
|
||||
# If this fixture is being used only by a test function (call phase), then we wouldn't need this
|
||||
# activation, but it doesn't hurt.
|
||||
capmanager.activate_fixture(request.node)
|
||||
yield fixture
|
||||
fixture.close()
|
||||
@@ -357,7 +383,7 @@ class CaptureFixture(object):
|
||||
def readouterr(self):
|
||||
"""Read and return the captured output so far, resetting the internal buffer.
|
||||
|
||||
:return: captured content as a namedtuple with ``out`` and ``err`` string attributes
|
||||
:return: captured content as a namedtuple with ``out`` and ``err`` string attributes
|
||||
"""
|
||||
captured_out, captured_err = self._captured_out, self._captured_err
|
||||
if self._capture is not None:
|
||||
@@ -446,6 +472,9 @@ class MultiCapture(object):
|
||||
if err:
|
||||
self.err = Capture(2)
|
||||
|
||||
def __repr__(self):
|
||||
return "<MultiCapture out=%r err=%r in_=%r>" % (self.out, self.err, self.in_)
|
||||
|
||||
def start_capturing(self):
|
||||
if self.in_:
|
||||
self.in_.start()
|
||||
@@ -539,7 +568,10 @@ class FDCaptureBinary(object):
|
||||
self.tmpfile_fd = tmpfile.fileno()
|
||||
|
||||
def __repr__(self):
|
||||
return "<FDCapture %s oldfd=%s>" % (self.targetfd, self.targetfd_save)
|
||||
return "<FDCapture %s oldfd=%s>" % (
|
||||
self.targetfd,
|
||||
getattr(self, "targetfd_save", None),
|
||||
)
|
||||
|
||||
def start(self):
|
||||
""" Start capturing on targetfd using memorized tmpfile. """
|
||||
@@ -590,7 +622,7 @@ class FDCapture(FDCaptureBinary):
|
||||
EMPTY_BUFFER = str()
|
||||
|
||||
def snap(self):
|
||||
res = FDCaptureBinary.snap(self)
|
||||
res = super(FDCapture, self).snap()
|
||||
enc = getattr(self.tmpfile, "encoding", None)
|
||||
if enc and isinstance(res, bytes):
|
||||
res = six.text_type(res, enc, "replace")
|
||||
@@ -693,13 +725,11 @@ def _colorama_workaround():
|
||||
first import of colorama while I/O capture is active, colorama will
|
||||
fail in various ways.
|
||||
"""
|
||||
|
||||
if not sys.platform.startswith("win32"):
|
||||
return
|
||||
try:
|
||||
import colorama # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
if sys.platform.startswith("win32"):
|
||||
try:
|
||||
import colorama # noqa: F401
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def _readline_workaround():
|
||||
@@ -720,13 +750,11 @@ def _readline_workaround():
|
||||
|
||||
See https://github.com/pytest-dev/pytest/pull/1281
|
||||
"""
|
||||
|
||||
if not sys.platform.startswith("win32"):
|
||||
return
|
||||
try:
|
||||
import readline # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
if sys.platform.startswith("win32"):
|
||||
try:
|
||||
import readline # noqa: F401
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def _py36_windowsconsoleio_workaround(stream):
|
||||
|
||||
@@ -140,6 +140,7 @@ default_plugins = (
|
||||
"stepwise",
|
||||
"warnings",
|
||||
"logging",
|
||||
"reports",
|
||||
)
|
||||
|
||||
|
||||
@@ -147,10 +148,15 @@ builtin_plugins = set(default_plugins)
|
||||
builtin_plugins.add("pytester")
|
||||
|
||||
|
||||
def get_config():
|
||||
def get_config(args=None):
|
||||
# subsequent calls to main will create a fresh instance
|
||||
pluginmanager = PytestPluginManager()
|
||||
config = Config(pluginmanager)
|
||||
|
||||
if args is not None:
|
||||
# Handle any "-p no:plugin" args.
|
||||
pluginmanager.consider_preparse(args)
|
||||
|
||||
for spec in default_plugins:
|
||||
pluginmanager.import_plugin(spec)
|
||||
return config
|
||||
@@ -178,7 +184,7 @@ def _prepareconfig(args=None, plugins=None):
|
||||
msg = "`args` parameter expected to be a list or tuple of strings, got: {!r} (type: {})"
|
||||
raise TypeError(msg.format(args, type(args)))
|
||||
|
||||
config = get_config()
|
||||
config = get_config(args)
|
||||
pluginmanager = config.pluginmanager
|
||||
try:
|
||||
if plugins:
|
||||
@@ -476,7 +482,10 @@ class PytestPluginManager(PluginManager):
|
||||
i += 1
|
||||
if isinstance(opt, six.string_types):
|
||||
if opt == "-p":
|
||||
parg = args[i]
|
||||
try:
|
||||
parg = args[i]
|
||||
except IndexError:
|
||||
return
|
||||
i += 1
|
||||
elif opt.startswith("-p"):
|
||||
parg = opt[2:]
|
||||
@@ -496,7 +505,15 @@ class PytestPluginManager(PluginManager):
|
||||
if not name.startswith("pytest_"):
|
||||
self.set_blocked("pytest_" + name)
|
||||
else:
|
||||
self.import_plugin(arg)
|
||||
name = arg
|
||||
# Unblock the plugin. None indicates that it has been blocked.
|
||||
# There is no interface with pluggy for this.
|
||||
if self._name2plugin.get(name, -1) is None:
|
||||
del self._name2plugin[name]
|
||||
if not name.startswith("pytest_"):
|
||||
if self._name2plugin.get("pytest_" + name, -1) is None:
|
||||
del self._name2plugin["pytest_" + name]
|
||||
self.import_plugin(arg, consider_entry_points=True)
|
||||
|
||||
def consider_conftest(self, conftestmodule):
|
||||
self.register(conftestmodule, name=conftestmodule.__file__)
|
||||
@@ -512,7 +529,11 @@ class PytestPluginManager(PluginManager):
|
||||
for import_spec in plugins:
|
||||
self.import_plugin(import_spec)
|
||||
|
||||
def import_plugin(self, modname):
|
||||
def import_plugin(self, modname, consider_entry_points=False):
|
||||
"""
|
||||
Imports a plugin with ``modname``. If ``consider_entry_points`` is True, entry point
|
||||
names are also considered to find a plugin.
|
||||
"""
|
||||
# most often modname refers to builtin modules, e.g. "pytester",
|
||||
# "terminal" or "capture". Those plugins are registered under their
|
||||
# basename for historic purposes but must be imported with the
|
||||
@@ -523,22 +544,26 @@ class PytestPluginManager(PluginManager):
|
||||
modname = str(modname)
|
||||
if self.is_blocked(modname) or self.get_plugin(modname) is not None:
|
||||
return
|
||||
if modname in builtin_plugins:
|
||||
importspec = "_pytest." + modname
|
||||
else:
|
||||
importspec = modname
|
||||
|
||||
importspec = "_pytest." + modname if modname in builtin_plugins else modname
|
||||
self.rewrite_hook.mark_rewrite(importspec)
|
||||
|
||||
if consider_entry_points:
|
||||
loaded = self.load_setuptools_entrypoints("pytest11", name=modname)
|
||||
if loaded:
|
||||
return
|
||||
|
||||
try:
|
||||
__import__(importspec)
|
||||
except ImportError as e:
|
||||
new_exc_type = ImportError
|
||||
new_exc_message = 'Error importing plugin "%s": %s' % (
|
||||
modname,
|
||||
safe_str(e.args[0]),
|
||||
)
|
||||
new_exc = new_exc_type(new_exc_message)
|
||||
new_exc = ImportError(new_exc_message)
|
||||
tb = sys.exc_info()[2]
|
||||
|
||||
six.reraise(new_exc_type, new_exc, sys.exc_info()[2])
|
||||
six.reraise(ImportError, new_exc, tb)
|
||||
|
||||
except Skipped as e:
|
||||
from _pytest.warnings import _issue_warning_captured
|
||||
@@ -674,7 +699,7 @@ class Config(object):
|
||||
return self
|
||||
|
||||
def notify_exception(self, excinfo, option=None):
|
||||
if option and option.fulltrace:
|
||||
if option and getattr(option, "fulltrace", False):
|
||||
style = "long"
|
||||
else:
|
||||
style = "native"
|
||||
@@ -697,7 +722,7 @@ class Config(object):
|
||||
@classmethod
|
||||
def fromdictargs(cls, option_dict, args):
|
||||
""" constructor useable for subprocesses. """
|
||||
config = get_config()
|
||||
config = get_config(args)
|
||||
config.option.__dict__.update(option_dict)
|
||||
config.parse(args, addopts=False)
|
||||
for x in config.option.plugins:
|
||||
@@ -741,7 +766,7 @@ class Config(object):
|
||||
by the importhook.
|
||||
"""
|
||||
ns, unknown_args = self._parser.parse_known_and_unknown_args(args)
|
||||
mode = ns.assertmode
|
||||
mode = getattr(ns, "assertmode", "plain")
|
||||
if mode == "rewrite":
|
||||
try:
|
||||
hook = _pytest.assertion.install_importhook(self)
|
||||
|
||||
@@ -33,7 +33,12 @@ def getcfg(args, config=None):
|
||||
p = base.join(inibasename)
|
||||
if exists(p):
|
||||
iniconfig = py.iniconfig.IniConfig(p)
|
||||
if "pytest" in iniconfig.sections:
|
||||
if (
|
||||
inibasename == "setup.cfg"
|
||||
and "tool:pytest" in iniconfig.sections
|
||||
):
|
||||
return base, p, iniconfig["tool:pytest"]
|
||||
elif "pytest" in iniconfig.sections:
|
||||
if inibasename == "setup.cfg" and config is not None:
|
||||
|
||||
fail(
|
||||
@@ -41,11 +46,6 @@ def getcfg(args, config=None):
|
||||
pytrace=False,
|
||||
)
|
||||
return base, p, iniconfig["pytest"]
|
||||
if (
|
||||
inibasename == "setup.cfg"
|
||||
and "tool:pytest" in iniconfig.sections
|
||||
):
|
||||
return base, p, iniconfig["tool:pytest"]
|
||||
elif inibasename == "pytest.ini":
|
||||
# allowed to be empty
|
||||
return base, p, {}
|
||||
|
||||
@@ -3,12 +3,25 @@ from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import pdb
|
||||
import sys
|
||||
from doctest import UnexpectedException
|
||||
|
||||
from _pytest import outcomes
|
||||
from _pytest.config import hookimpl
|
||||
from _pytest.config.exceptions import UsageError
|
||||
|
||||
|
||||
def _validate_usepdb_cls(value):
|
||||
"""Validate syntax of --pdbcls option."""
|
||||
try:
|
||||
modname, classname = value.split(":")
|
||||
except ValueError:
|
||||
raise argparse.ArgumentTypeError(
|
||||
"{!r} is not in the format 'modname:classname'".format(value)
|
||||
)
|
||||
return (modname, classname)
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
@@ -23,6 +36,7 @@ def pytest_addoption(parser):
|
||||
"--pdbcls",
|
||||
dest="usepdb_cls",
|
||||
metavar="modulename:classname",
|
||||
type=_validate_usepdb_cls,
|
||||
help="start a custom interactive Python debugger on errors. "
|
||||
"For example: --pdbcls=IPython.terminal.debugger:TerminalPdb",
|
||||
)
|
||||
@@ -34,11 +48,27 @@ def pytest_addoption(parser):
|
||||
)
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
if config.getvalue("usepdb_cls"):
|
||||
modname, classname = config.getvalue("usepdb_cls").split(":")
|
||||
def _import_pdbcls(modname, classname):
|
||||
try:
|
||||
__import__(modname)
|
||||
pdb_cls = getattr(sys.modules[modname], classname)
|
||||
mod = sys.modules[modname]
|
||||
|
||||
# Handle --pdbcls=pdb:pdb.Pdb (useful e.g. with pdbpp).
|
||||
parts = classname.split(".")
|
||||
pdb_cls = getattr(mod, parts[0])
|
||||
for part in parts[1:]:
|
||||
pdb_cls = getattr(pdb_cls, part)
|
||||
|
||||
return pdb_cls
|
||||
except Exception as exc:
|
||||
value = ":".join((modname, classname))
|
||||
raise UsageError("--pdbcls: could not import {!r}: {}".format(value, exc))
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
pdb_cls = config.getvalue("usepdb_cls")
|
||||
if pdb_cls:
|
||||
pdb_cls = _import_pdbcls(*pdb_cls)
|
||||
else:
|
||||
pdb_cls = pdb.Pdb
|
||||
|
||||
@@ -77,6 +107,12 @@ class pytestPDB(object):
|
||||
_saved = []
|
||||
_recursive_debug = 0
|
||||
|
||||
@classmethod
|
||||
def _is_capturing(cls, capman):
|
||||
if capman:
|
||||
return capman.is_capturing()
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def _init_pdb(cls, *args, **kwargs):
|
||||
""" Initialize PDB debugging, dropping any IO capturing. """
|
||||
@@ -85,7 +121,7 @@ class pytestPDB(object):
|
||||
if cls._pluginmanager is not None:
|
||||
capman = cls._pluginmanager.getplugin("capturemanager")
|
||||
if capman:
|
||||
capman.suspend_global_capture(in_=True)
|
||||
capman.suspend(in_=True)
|
||||
tw = _pytest.config.create_terminal_writer(cls._config)
|
||||
tw.line()
|
||||
if cls._recursive_debug == 0:
|
||||
@@ -93,10 +129,19 @@ class pytestPDB(object):
|
||||
header = kwargs.pop("header", None)
|
||||
if header is not None:
|
||||
tw.sep(">", header)
|
||||
elif capman and capman.is_globally_capturing():
|
||||
tw.sep(">", "PDB set_trace (IO-capturing turned off)")
|
||||
else:
|
||||
tw.sep(">", "PDB set_trace")
|
||||
capturing = cls._is_capturing(capman)
|
||||
if capturing:
|
||||
if capturing == "global":
|
||||
tw.sep(">", "PDB set_trace (IO-capturing turned off)")
|
||||
else:
|
||||
tw.sep(
|
||||
">",
|
||||
"PDB set_trace (IO-capturing turned off for %s)"
|
||||
% capturing,
|
||||
)
|
||||
else:
|
||||
tw.sep(">", "PDB set_trace")
|
||||
|
||||
class _PdbWrapper(cls._pdb_cls, object):
|
||||
_pytest_capman = capman
|
||||
@@ -110,15 +155,24 @@ class pytestPDB(object):
|
||||
|
||||
def do_continue(self, arg):
|
||||
ret = super(_PdbWrapper, self).do_continue(arg)
|
||||
if self._pytest_capman:
|
||||
if cls._recursive_debug == 0:
|
||||
tw = _pytest.config.create_terminal_writer(cls._config)
|
||||
tw.line()
|
||||
if cls._recursive_debug == 0:
|
||||
if self._pytest_capman.is_globally_capturing():
|
||||
|
||||
capman = self._pytest_capman
|
||||
capturing = pytestPDB._is_capturing(capman)
|
||||
if capturing:
|
||||
if capturing == "global":
|
||||
tw.sep(">", "PDB continue (IO-capturing resumed)")
|
||||
else:
|
||||
tw.sep(">", "PDB continue")
|
||||
self._pytest_capman.resume_global_capture()
|
||||
tw.sep(
|
||||
">",
|
||||
"PDB continue (IO-capturing resumed for %s)"
|
||||
% capturing,
|
||||
)
|
||||
capman.resume()
|
||||
else:
|
||||
tw.sep(">", "PDB continue")
|
||||
cls._pluginmanager.hook.pytest_leave_pdb(
|
||||
config=cls._config, pdb=self
|
||||
)
|
||||
@@ -128,8 +182,15 @@ class pytestPDB(object):
|
||||
do_c = do_cont = do_continue
|
||||
|
||||
def set_quit(self):
|
||||
"""Raise Exit outcome when quit command is used in pdb.
|
||||
|
||||
This is a bit of a hack - it would be better if BdbQuit
|
||||
could be handled, but this would require to wrap the
|
||||
whole pytest run, and adjust the report etc.
|
||||
"""
|
||||
super(_PdbWrapper, self).set_quit()
|
||||
outcomes.exit("Quitting debugger")
|
||||
if cls._recursive_debug == 0:
|
||||
outcomes.exit("Quitting debugger")
|
||||
|
||||
def setup(self, f, tb):
|
||||
"""Suspend on setup().
|
||||
@@ -185,17 +246,12 @@ def _test_pytest_function(pyfuncitem):
|
||||
_pdb = pytestPDB._init_pdb()
|
||||
testfunction = pyfuncitem.obj
|
||||
pyfuncitem.obj = _pdb.runcall
|
||||
if pyfuncitem._isyieldedfunction():
|
||||
arg_list = list(pyfuncitem._args)
|
||||
arg_list.insert(0, testfunction)
|
||||
pyfuncitem._args = tuple(arg_list)
|
||||
else:
|
||||
if "func" in pyfuncitem._fixtureinfo.argnames:
|
||||
raise ValueError("--trace can't be used with a fixture named func!")
|
||||
pyfuncitem.funcargs["func"] = testfunction
|
||||
new_list = list(pyfuncitem._fixtureinfo.argnames)
|
||||
new_list.append("func")
|
||||
pyfuncitem._fixtureinfo.argnames = tuple(new_list)
|
||||
if "func" in pyfuncitem._fixtureinfo.argnames: # noqa
|
||||
raise ValueError("--trace can't be used with a fixture named func!")
|
||||
pyfuncitem.funcargs["func"] = testfunction
|
||||
new_list = list(pyfuncitem._fixtureinfo.argnames)
|
||||
new_list.append("func")
|
||||
pyfuncitem._fixtureinfo.argnames = tuple(new_list)
|
||||
|
||||
|
||||
def _enter_pdb(node, excinfo, rep):
|
||||
@@ -244,9 +300,9 @@ def _find_last_non_hidden_frame(stack):
|
||||
|
||||
|
||||
def post_mortem(t):
|
||||
class Pdb(pytestPDB._pdb_cls):
|
||||
class Pdb(pytestPDB._pdb_cls, object):
|
||||
def get_stack(self, f, t):
|
||||
stack, i = pdb.Pdb.get_stack(self, f, t)
|
||||
stack, i = super(Pdb, self).get_stack(f, t)
|
||||
if f is None:
|
||||
i = _find_last_non_hidden_frame(stack)
|
||||
return stack, i
|
||||
|
||||
@@ -48,12 +48,6 @@ RESULT_LOG = PytestDeprecationWarning(
|
||||
"See https://docs.pytest.org/en/latest/deprecations.html#result-log-result-log for more information."
|
||||
)
|
||||
|
||||
MARK_INFO_ATTRIBUTE = RemovedInPytest4Warning(
|
||||
"MarkInfo objects are deprecated as they contain merged marks which are hard to deal with correctly.\n"
|
||||
"Please use node.get_closest_marker(name) or node.iter_markers(name).\n"
|
||||
"Docs: https://docs.pytest.org/en/latest/mark.html#updating-code"
|
||||
)
|
||||
|
||||
RAISES_EXEC = PytestDeprecationWarning(
|
||||
"raises(..., 'code(as_a_string)') is deprecated, use the context manager form or use `exec()` directly\n\n"
|
||||
"See https://docs.pytest.org/en/latest/deprecations.html#raises-warns-exec"
|
||||
@@ -93,3 +87,9 @@ PYTEST_WARNS_UNKNOWN_KWARGS = UnformattedWarning(
|
||||
"pytest.warns() got unexpected keyword arguments: {args!r}.\n"
|
||||
"This will be an error in future versions.",
|
||||
)
|
||||
|
||||
PYTEST_PARAM_UNKNOWN_KWARGS = UnformattedWarning(
|
||||
PytestDeprecationWarning,
|
||||
"pytest.param() got unexpected keyword arguments: {args!r}.\n"
|
||||
"This will be an error in future versions.",
|
||||
)
|
||||
|
||||
@@ -15,6 +15,7 @@ from _pytest._code.code import ReprFileLocation
|
||||
from _pytest._code.code import TerminalRepr
|
||||
from _pytest.compat import safe_getattr
|
||||
from _pytest.fixtures import FixtureRequest
|
||||
from _pytest.outcomes import Skipped
|
||||
|
||||
DOCTEST_REPORT_CHOICE_NONE = "none"
|
||||
DOCTEST_REPORT_CHOICE_CDIFF = "cdiff"
|
||||
@@ -153,6 +154,8 @@ def _init_runner_class():
|
||||
raise failure
|
||||
|
||||
def report_unexpected_exception(self, out, test, example, exc_info):
|
||||
if isinstance(exc_info[1], Skipped):
|
||||
raise exc_info[1]
|
||||
failure = doctest.UnexpectedException(test, example, exc_info)
|
||||
if self.continue_on_failure:
|
||||
out.append(failure)
|
||||
|
||||
@@ -585,11 +585,13 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||
# call the fixture function
|
||||
fixturedef.execute(request=subrequest)
|
||||
finally:
|
||||
# if fixture function failed it might have registered finalizers
|
||||
self.session._setupstate.addfinalizer(
|
||||
functools.partial(fixturedef.finish, request=subrequest),
|
||||
subrequest.node,
|
||||
)
|
||||
self._schedule_finalizers(fixturedef, subrequest)
|
||||
|
||||
def _schedule_finalizers(self, fixturedef, subrequest):
|
||||
# if fixture function failed it might have registered finalizers
|
||||
self.session._setupstate.addfinalizer(
|
||||
functools.partial(fixturedef.finish, request=subrequest), subrequest.node
|
||||
)
|
||||
|
||||
def _check_scope(self, argname, invoking_scope, requested_scope):
|
||||
if argname == "request":
|
||||
@@ -612,7 +614,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||
fs, lineno = getfslineno(factory)
|
||||
p = self._pyfuncitem.session.fspath.bestrelpath(fs)
|
||||
args = _format_args(factory)
|
||||
lines.append("%s:%d: def %s%s" % (p, lineno, factory.__name__, args))
|
||||
lines.append("%s:%d: def %s%s" % (p, lineno + 1, factory.__name__, args))
|
||||
return lines
|
||||
|
||||
def _getscopeitem(self, scope):
|
||||
@@ -659,6 +661,16 @@ class SubRequest(FixtureRequest):
|
||||
def addfinalizer(self, finalizer):
|
||||
self._fixturedef.addfinalizer(finalizer)
|
||||
|
||||
def _schedule_finalizers(self, fixturedef, subrequest):
|
||||
# if the executing fixturedef was not explicitly requested in the argument list (via
|
||||
# getfixturevalue inside the fixture call) then ensure this fixture def will be finished
|
||||
# first
|
||||
if fixturedef.argname not in self.funcargnames:
|
||||
fixturedef.addfinalizer(
|
||||
functools.partial(self._fixturedef.finish, request=self)
|
||||
)
|
||||
super(SubRequest, self)._schedule_finalizers(fixturedef, subrequest)
|
||||
|
||||
|
||||
scopes = "session package module class function".split()
|
||||
scopenum_function = scopes.index("function")
|
||||
@@ -841,7 +853,9 @@ class FixtureDef(object):
|
||||
exceptions.append(sys.exc_info())
|
||||
if exceptions:
|
||||
e = exceptions[0]
|
||||
del exceptions # ensure we don't keep all frames alive because of the traceback
|
||||
del (
|
||||
exceptions
|
||||
) # ensure we don't keep all frames alive because of the traceback
|
||||
six.reraise(*e)
|
||||
|
||||
finally:
|
||||
@@ -1053,7 +1067,7 @@ def pytestconfig(request):
|
||||
Example::
|
||||
|
||||
def test_foo(pytestconfig):
|
||||
if pytestconfig.getoption("verbose"):
|
||||
if pytestconfig.getoption("verbose") > 0:
|
||||
...
|
||||
|
||||
"""
|
||||
|
||||
@@ -60,7 +60,7 @@ def pytest_addoption(parser):
|
||||
dest="plugins",
|
||||
default=[],
|
||||
metavar="name",
|
||||
help="early-load given plugin (multi-allowed). "
|
||||
help="early-load given plugin module name or entry point (multi-allowed). "
|
||||
"To avoid loading of plugins, use the `no:` prefix, e.g. "
|
||||
"`no:doctest`.",
|
||||
)
|
||||
|
||||
@@ -99,7 +99,8 @@ def pytest_cmdline_parse(pluginmanager, args):
|
||||
Stops at first non-None result, see :ref:`firstresult`
|
||||
|
||||
.. note::
|
||||
This hook will not be called for ``conftest.py`` files, only for setuptools plugins.
|
||||
This hook will only be called for plugin classes passed to the ``plugins`` arg when using `pytest.main`_ to
|
||||
perform an in-process test run.
|
||||
|
||||
:param _pytest.config.PytestPluginManager pluginmanager: pytest plugin manager
|
||||
:param list[str] args: list of arguments passed on the command line
|
||||
@@ -187,7 +188,7 @@ def pytest_ignore_collect(path, config):
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult`
|
||||
|
||||
:param str path: the path to analyze
|
||||
:param path: a :py:class:`py.path.local` - the path to analyze
|
||||
:param _pytest.config.Config config: pytest config object
|
||||
"""
|
||||
|
||||
@@ -198,7 +199,7 @@ def pytest_collect_directory(path, parent):
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult`
|
||||
|
||||
:param str path: the path to analyze
|
||||
:param path: a :py:class:`py.path.local` - the path to analyze
|
||||
"""
|
||||
|
||||
|
||||
@@ -206,7 +207,7 @@ def pytest_collect_file(path, parent):
|
||||
""" return collection Node or None for the given path. Any new node
|
||||
needs to have the specified ``parent`` as a parent.
|
||||
|
||||
:param str path: the path to collect
|
||||
:param path: a :py:class:`py.path.local` - the path to collect
|
||||
"""
|
||||
|
||||
|
||||
@@ -248,7 +249,10 @@ def pytest_pycollect_makemodule(path, parent):
|
||||
The pytest_collect_file hook needs to be used if you want to
|
||||
create test modules for files that do not match as a test module.
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult` """
|
||||
Stops at first non-None result, see :ref:`firstresult`
|
||||
|
||||
:param path: a :py:class:`py.path.local` - the path of module to collect
|
||||
"""
|
||||
|
||||
|
||||
@hookspec(firstresult=True)
|
||||
@@ -375,6 +379,41 @@ def pytest_runtest_logreport(report):
|
||||
the respective phase of executing a test. """
|
||||
|
||||
|
||||
@hookspec(firstresult=True)
|
||||
def pytest_report_to_serializable(config, report):
|
||||
"""
|
||||
.. warning::
|
||||
This hook is experimental and subject to change between pytest releases, even
|
||||
bug fixes.
|
||||
|
||||
The intent is for this to be used by plugins maintained by the core-devs, such
|
||||
as ``pytest-xdist``, ``pytest-subtests``, and as a replacement for the internal
|
||||
'resultlog' plugin.
|
||||
|
||||
In the future it might become part of the public hook API.
|
||||
|
||||
Serializes the given report object into a data structure suitable for sending
|
||||
over the wire, e.g. converted to JSON.
|
||||
"""
|
||||
|
||||
|
||||
@hookspec(firstresult=True)
|
||||
def pytest_report_from_serializable(config, data):
|
||||
"""
|
||||
.. warning::
|
||||
This hook is experimental and subject to change between pytest releases, even
|
||||
bug fixes.
|
||||
|
||||
The intent is for this to be used by plugins maintained by the core-devs, such
|
||||
as ``pytest-xdist``, ``pytest-subtests``, and as a replacement for the internal
|
||||
'resultlog' plugin.
|
||||
|
||||
In the future it might become part of the public hook API.
|
||||
|
||||
Restores a report object previously serialized with pytest_report_to_serializable().
|
||||
"""
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Fixture related hooks
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@@ -389,7 +389,7 @@ class LoggingPlugin(object):
|
||||
self._config = config
|
||||
|
||||
# enable verbose output automatically if live logging is enabled
|
||||
if self._log_cli_enabled() and not config.getoption("verbose"):
|
||||
if self._log_cli_enabled() and config.getoption("verbose") < 1:
|
||||
config.option.verbose = 1
|
||||
|
||||
self.print_logs = get_option_ini(config, "log_print")
|
||||
@@ -577,8 +577,15 @@ class LoggingPlugin(object):
|
||||
if self.log_cli_handler:
|
||||
self.log_cli_handler.set_when("sessionfinish")
|
||||
if self.log_file_handler is not None:
|
||||
with catching_logs(self.log_file_handler, level=self.log_file_level):
|
||||
yield
|
||||
try:
|
||||
with catching_logs(
|
||||
self.log_file_handler, level=self.log_file_level
|
||||
):
|
||||
yield
|
||||
finally:
|
||||
# Close the FileHandler explicitly.
|
||||
# (logging.shutdown might have lost the weakref?!)
|
||||
self.log_file_handler.close()
|
||||
else:
|
||||
yield
|
||||
|
||||
|
||||
@@ -225,7 +225,7 @@ def wrap_session(config, doit):
|
||||
config.notify_exception(excinfo, config.option)
|
||||
session.exitstatus = EXIT_INTERNALERROR
|
||||
if excinfo.errisinstance(SystemExit):
|
||||
sys.stderr.write("mainloop: caught Spurious SystemExit!\n")
|
||||
sys.stderr.write("mainloop: caught unexpected SystemExit!\n")
|
||||
|
||||
finally:
|
||||
excinfo = None # Explicitly break reference cycle.
|
||||
@@ -441,6 +441,15 @@ class Session(nodes.FSCollector):
|
||||
|
||||
self.config.pluginmanager.register(self, name="session")
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s %s exitstatus=%r testsfailed=%d testscollected=%d>" % (
|
||||
self.__class__.__name__,
|
||||
self.name,
|
||||
getattr(self, "exitstatus", "<UNSET>"),
|
||||
self.testsfailed,
|
||||
self.testscollected,
|
||||
)
|
||||
|
||||
def _node_location_to_relpath(self, node_path):
|
||||
# bestrelpath is a quite slow function
|
||||
return self._bestrelpathcache[node_path]
|
||||
@@ -548,7 +557,7 @@ class Session(nodes.FSCollector):
|
||||
# Start with a Session root, and delve to argpath item (dir or file)
|
||||
# and stack all Packages found on the way.
|
||||
# No point in finding packages when collecting doctests
|
||||
if not self.config.option.doctestmodules:
|
||||
if not self.config.getoption("doctestmodules", False):
|
||||
pm = self.config.pluginmanager
|
||||
for parent in reversed(argpath.parts()):
|
||||
if pm._confcutdir and pm._confcutdir.relto(parent):
|
||||
|
||||
@@ -52,6 +52,7 @@ def pytest_addoption(parser):
|
||||
"other' matches all test functions and classes whose name "
|
||||
"contains 'test_method' or 'test_other', while -k 'not test_method' "
|
||||
"matches those that don't contain 'test_method' in their names. "
|
||||
"-k 'not test_method and not test_other' will eliminate the matches. "
|
||||
"Additionally keywords are matched to classes and functions "
|
||||
"containing extra names in their 'extra_keyword_matches' set, "
|
||||
"as well as functions which have names assigned directly to them.",
|
||||
|
||||
@@ -10,6 +10,7 @@ from ..compat import ascii_escaped
|
||||
from ..compat import getfslineno
|
||||
from ..compat import MappingMixin
|
||||
from ..compat import NOTSET
|
||||
from _pytest.deprecated import PYTEST_PARAM_UNKNOWN_KWARGS
|
||||
from _pytest.outcomes import fail
|
||||
|
||||
EMPTY_PARAMETERSET_OPTION = "empty_parameter_set_mark"
|
||||
@@ -44,7 +45,7 @@ def get_empty_parameterset_mark(config, argnames, func):
|
||||
f_name = func.__name__
|
||||
_, lineno = getfslineno(func)
|
||||
raise Collector.CollectError(
|
||||
"Empty parameter set in '%s' at line %d" % (f_name, lineno)
|
||||
"Empty parameter set in '%s' at line %d" % (f_name, lineno + 1)
|
||||
)
|
||||
else:
|
||||
raise LookupError(requested_mark)
|
||||
@@ -60,20 +61,25 @@ def get_empty_parameterset_mark(config, argnames, func):
|
||||
|
||||
class ParameterSet(namedtuple("ParameterSet", "values, marks, id")):
|
||||
@classmethod
|
||||
def param(cls, *values, **kw):
|
||||
marks = kw.pop("marks", ())
|
||||
def param(cls, *values, **kwargs):
|
||||
marks = kwargs.pop("marks", ())
|
||||
if isinstance(marks, MarkDecorator):
|
||||
marks = (marks,)
|
||||
else:
|
||||
assert isinstance(marks, (tuple, list, set))
|
||||
|
||||
id_ = kw.pop("id", None)
|
||||
id_ = kwargs.pop("id", None)
|
||||
if id_ is not None:
|
||||
if not isinstance(id_, six.string_types):
|
||||
raise TypeError(
|
||||
"Expected id to be a string, got {}: {!r}".format(type(id_), id_)
|
||||
)
|
||||
id_ = ascii_escaped(id_)
|
||||
|
||||
if kwargs:
|
||||
warnings.warn(
|
||||
PYTEST_PARAM_UNKNOWN_KWARGS.format(args=sorted(kwargs)), stacklevel=3
|
||||
)
|
||||
return cls(values, marks, id_)
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -262,10 +262,27 @@ class MonkeyPatch(object):
|
||||
|
||||
def syspath_prepend(self, path):
|
||||
""" Prepend ``path`` to ``sys.path`` list of import locations. """
|
||||
from pkg_resources import fixup_namespace_packages
|
||||
|
||||
if self._savesyspath is None:
|
||||
self._savesyspath = sys.path[:]
|
||||
sys.path.insert(0, str(path))
|
||||
|
||||
# https://github.com/pypa/setuptools/blob/d8b901bc/docs/pkg_resources.txt#L162-L171
|
||||
fixup_namespace_packages(str(path))
|
||||
|
||||
# A call to syspathinsert() usually means that the caller wants to
|
||||
# import some dynamically created files, thus with python3 we
|
||||
# invalidate its import caches.
|
||||
# This is especially important when any namespace package is in used,
|
||||
# since then the mtime based FileFinder cache (that gets created in
|
||||
# this case already) gets not invalidated when writing the new files
|
||||
# quickly afterwards.
|
||||
if sys.version_info >= (3, 3):
|
||||
from importlib import invalidate_caches
|
||||
|
||||
invalidate_caches()
|
||||
|
||||
def chdir(self, path):
|
||||
""" Change the current working directory to the specified path.
|
||||
Path can be a string or a py.path.local object.
|
||||
|
||||
@@ -248,7 +248,7 @@ class Node(object):
|
||||
if excinfo.errisinstance(fm.FixtureLookupError):
|
||||
return excinfo.value.formatrepr()
|
||||
tbfilter = True
|
||||
if self.config.option.fulltrace:
|
||||
if self.config.getoption("fulltrace", False):
|
||||
style = "long"
|
||||
else:
|
||||
tb = _pytest._code.Traceback([excinfo.traceback[-1]])
|
||||
@@ -260,12 +260,12 @@ class Node(object):
|
||||
style = "long"
|
||||
# XXX should excinfo.getrepr record all data and toterminal() process it?
|
||||
if style is None:
|
||||
if self.config.option.tbstyle == "short":
|
||||
if self.config.getoption("tbstyle", "auto") == "short":
|
||||
style = "short"
|
||||
else:
|
||||
style = "long"
|
||||
|
||||
if self.config.option.verbose > 1:
|
||||
if self.config.getoption("verbose", 0) > 1:
|
||||
truncate_locals = False
|
||||
else:
|
||||
truncate_locals = True
|
||||
@@ -279,7 +279,7 @@ class Node(object):
|
||||
return excinfo.getrepr(
|
||||
funcargs=True,
|
||||
abspath=abspath,
|
||||
showlocals=self.config.option.showlocals,
|
||||
showlocals=self.config.getoption("showlocals", False),
|
||||
style=style,
|
||||
tbfilter=tbfilter,
|
||||
truncate_locals=truncate_locals,
|
||||
@@ -325,7 +325,14 @@ class Collector(Node):
|
||||
if excinfo.errisinstance(self.CollectError):
|
||||
exc = excinfo.value
|
||||
return str(exc.args[0])
|
||||
return self._repr_failure_py(excinfo, style="short")
|
||||
|
||||
# Respect explicit tbstyle option, but default to "short"
|
||||
# (None._repr_failure_py defaults to "long" without "fulltrace" option).
|
||||
tbstyle = self.config.getoption("tbstyle")
|
||||
if tbstyle == "auto":
|
||||
tbstyle = "short"
|
||||
|
||||
return self._repr_failure_py(excinfo, style=tbstyle)
|
||||
|
||||
def _prunetraceback(self, excinfo):
|
||||
if hasattr(self, "fspath"):
|
||||
|
||||
@@ -80,7 +80,8 @@ def skip(msg="", **kwargs):
|
||||
Skip an executing test with the given message.
|
||||
|
||||
This function should be called only during testing (setup, call or teardown) or
|
||||
during collection by using the ``allow_module_level`` flag.
|
||||
during collection by using the ``allow_module_level`` flag. This function can
|
||||
be called in doctests as well.
|
||||
|
||||
:kwarg bool allow_module_level: allows this function to be called at
|
||||
module level, skipping the rest of the module. Default to False.
|
||||
@@ -89,12 +90,14 @@ def skip(msg="", **kwargs):
|
||||
It is better to use the :ref:`pytest.mark.skipif ref` marker when possible to declare a test to be
|
||||
skipped under certain conditions like mismatching platforms or
|
||||
dependencies.
|
||||
Similarly, use the ``# doctest: +SKIP`` directive (see `doctest.SKIP
|
||||
<https://docs.python.org/3/library/doctest.html#doctest.SKIP>`_)
|
||||
to skip a doctest statically.
|
||||
"""
|
||||
__tracebackhide__ = True
|
||||
allow_module_level = kwargs.pop("allow_module_level", False)
|
||||
if kwargs:
|
||||
keys = [k for k in kwargs.keys()]
|
||||
raise TypeError("unexpected keyword arguments: {}".format(keys))
|
||||
raise TypeError("unexpected keyword arguments: {}".format(sorted(kwargs)))
|
||||
raise Skipped(msg=msg, allow_module_level=allow_module_level)
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import codecs
|
||||
import distutils.spawn
|
||||
import gc
|
||||
import os
|
||||
import platform
|
||||
@@ -26,9 +25,11 @@ from _pytest.assertion.rewrite import AssertionRewritingHook
|
||||
from _pytest.capture import MultiCapture
|
||||
from _pytest.capture import SysCapture
|
||||
from _pytest.compat import safe_str
|
||||
from _pytest.compat import Sequence
|
||||
from _pytest.main import EXIT_INTERRUPTED
|
||||
from _pytest.main import EXIT_OK
|
||||
from _pytest.main import Session
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
from _pytest.pathlib import Path
|
||||
|
||||
IGNORE_PAM = [ # filenames added when obtaining details about the current user
|
||||
@@ -151,47 +152,6 @@ winpymap = {
|
||||
}
|
||||
|
||||
|
||||
def getexecutable(name, cache={}):
|
||||
try:
|
||||
return cache[name]
|
||||
except KeyError:
|
||||
executable = distutils.spawn.find_executable(name)
|
||||
if executable:
|
||||
import subprocess
|
||||
|
||||
popen = subprocess.Popen(
|
||||
[str(executable), "--version"],
|
||||
universal_newlines=True,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
out, err = popen.communicate()
|
||||
if name == "jython":
|
||||
if not err or "2.5" not in err:
|
||||
executable = None
|
||||
if "2.5.2" in err:
|
||||
executable = None # http://bugs.jython.org/issue1790
|
||||
elif popen.returncode != 0:
|
||||
# handle pyenv's 127
|
||||
executable = None
|
||||
cache[name] = executable
|
||||
return executable
|
||||
|
||||
|
||||
@pytest.fixture(params=["python2.7", "python3.4", "pypy", "pypy3"])
|
||||
def anypython(request):
|
||||
name = request.param
|
||||
executable = getexecutable(name)
|
||||
if executable is None:
|
||||
if sys.platform == "win32":
|
||||
executable = winpymap.get(name, None)
|
||||
if executable:
|
||||
executable = py.path.local(executable)
|
||||
if executable.check():
|
||||
return executable
|
||||
pytest.skip("no suitable %s found" % (name,))
|
||||
return executable
|
||||
|
||||
|
||||
# used at least by pytest-xdist plugin
|
||||
|
||||
|
||||
@@ -375,6 +335,24 @@ def testdir(request, tmpdir_factory):
|
||||
return Testdir(request, tmpdir_factory)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def _sys_snapshot():
|
||||
snappaths = SysPathsSnapshot()
|
||||
snapmods = SysModulesSnapshot()
|
||||
yield
|
||||
snapmods.restore()
|
||||
snappaths.restore()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def _config_for_test():
|
||||
from _pytest.config import get_config
|
||||
|
||||
config = get_config()
|
||||
yield config
|
||||
config._ensure_unconfigure() # cleanup, e.g. capman closing tmpfiles.
|
||||
|
||||
|
||||
rex_outcome = re.compile(r"(\d+) ([\w-]+)")
|
||||
|
||||
|
||||
@@ -507,8 +485,6 @@ class Testdir(object):
|
||||
name = request.function.__name__
|
||||
self.tmpdir = tmpdir_factory.mktemp(name, numbered=True)
|
||||
self.test_tmproot = tmpdir_factory.mktemp("tmp-" + name, numbered=True)
|
||||
os.environ["PYTEST_DEBUG_TEMPROOT"] = str(self.test_tmproot)
|
||||
os.environ.pop("TOX_ENV_DIR", None) # Ensure that it is not used for caching.
|
||||
self.plugins = []
|
||||
self._cwd_snapshot = CwdSnapshot()
|
||||
self._sys_path_snapshot = SysPathsSnapshot()
|
||||
@@ -521,6 +497,13 @@ class Testdir(object):
|
||||
elif method == "subprocess":
|
||||
self._runpytest_method = self.runpytest_subprocess
|
||||
|
||||
mp = self.monkeypatch = MonkeyPatch()
|
||||
mp.setenv("PYTEST_DEBUG_TEMPROOT", str(self.test_tmproot))
|
||||
# Ensure no unexpected caching via tox.
|
||||
mp.delenv("TOX_ENV_DIR", raising=False)
|
||||
# Discard outer pytest options.
|
||||
mp.delenv("PYTEST_ADDOPTS", raising=False)
|
||||
|
||||
def __repr__(self):
|
||||
return "<Testdir %r>" % (self.tmpdir,)
|
||||
|
||||
@@ -538,7 +521,7 @@ class Testdir(object):
|
||||
self._sys_modules_snapshot.restore()
|
||||
self._sys_path_snapshot.restore()
|
||||
self._cwd_snapshot.restore()
|
||||
os.environ.pop("PYTEST_DEBUG_TEMPROOT", None)
|
||||
self.monkeypatch.undo()
|
||||
|
||||
def __take_sys_modules_snapshot(self):
|
||||
# some zope modules used by twisted-related tests keep internal state
|
||||
@@ -632,25 +615,11 @@ class Testdir(object):
|
||||
|
||||
This is undone automatically when this object dies at the end of each
|
||||
test.
|
||||
|
||||
"""
|
||||
if path is None:
|
||||
path = self.tmpdir
|
||||
sys.path.insert(0, str(path))
|
||||
# a call to syspathinsert() usually means that the caller wants to
|
||||
# import some dynamically created files, thus with python3 we
|
||||
# invalidate its import caches
|
||||
self._possibly_invalidate_import_caches()
|
||||
|
||||
def _possibly_invalidate_import_caches(self):
|
||||
# invalidate caches if we can (py33 and above)
|
||||
try:
|
||||
import importlib
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
if hasattr(importlib, "invalidate_caches"):
|
||||
importlib.invalidate_caches()
|
||||
self.monkeypatch.syspath_prepend(str(path))
|
||||
|
||||
def mkdir(self, name):
|
||||
"""Create a new (sub)directory."""
|
||||
@@ -826,6 +795,12 @@ class Testdir(object):
|
||||
"""
|
||||
finalizers = []
|
||||
try:
|
||||
# Do not load user config (during runs only).
|
||||
mp_run = MonkeyPatch()
|
||||
mp_run.setenv("HOME", str(self.tmpdir))
|
||||
mp_run.setenv("USERPROFILE", str(self.tmpdir))
|
||||
finalizers.append(mp_run.undo)
|
||||
|
||||
# When running pytest inline any plugins active in the main test
|
||||
# process are already imported. So this disables the warning which
|
||||
# will trigger to say they can no longer be rewritten, which is
|
||||
@@ -1056,6 +1031,9 @@ class Testdir(object):
|
||||
env["PYTHONPATH"] = os.pathsep.join(
|
||||
filter(None, [os.getcwd(), env.get("PYTHONPATH", "")])
|
||||
)
|
||||
# Do not load user config.
|
||||
env["HOME"] = str(self.tmpdir)
|
||||
env["USERPROFILE"] = env["HOME"]
|
||||
kw["env"] = env
|
||||
|
||||
popen = subprocess.Popen(
|
||||
@@ -1325,7 +1303,7 @@ class LineMatcher(object):
|
||||
raise ValueError("line %r not found in output" % fnline)
|
||||
|
||||
def _log(self, *args):
|
||||
self._log_output.append(" ".join((str(x) for x in args)))
|
||||
self._log_output.append(" ".join(str(x) for x in args))
|
||||
|
||||
@property
|
||||
def _log_text(self):
|
||||
@@ -1366,6 +1344,7 @@ class LineMatcher(object):
|
||||
will be logged to stdout when a match occurs
|
||||
|
||||
"""
|
||||
assert isinstance(lines2, Sequence)
|
||||
lines2 = self._getlines(lines2)
|
||||
lines1 = self.lines[:]
|
||||
nextline = None
|
||||
|
||||
@@ -43,6 +43,7 @@ from _pytest.mark import MARK_GEN
|
||||
from _pytest.mark.structures import get_unpacked_marks
|
||||
from _pytest.mark.structures import normalize_mark_list
|
||||
from _pytest.outcomes import fail
|
||||
from _pytest.outcomes import skip
|
||||
from _pytest.pathlib import parts
|
||||
from _pytest.warning_types import PytestWarning
|
||||
|
||||
@@ -101,6 +102,13 @@ def pytest_addoption(parser):
|
||||
default=["test"],
|
||||
help="prefixes or glob names for Python test function and method discovery",
|
||||
)
|
||||
parser.addini(
|
||||
"disable_test_id_escaping_and_forfeit_all_rights_to_community_support",
|
||||
type="bool",
|
||||
default=False,
|
||||
help="disable string escape non-ascii characters, might cause unwanted "
|
||||
"side effects(use at your own risk)",
|
||||
)
|
||||
|
||||
group.addoption(
|
||||
"--import-mode",
|
||||
@@ -156,14 +164,18 @@ def pytest_configure(config):
|
||||
@hookimpl(trylast=True)
|
||||
def pytest_pyfunc_call(pyfuncitem):
|
||||
testfunction = pyfuncitem.obj
|
||||
if pyfuncitem._isyieldedfunction():
|
||||
testfunction(*pyfuncitem._args)
|
||||
else:
|
||||
funcargs = pyfuncitem.funcargs
|
||||
testargs = {}
|
||||
for arg in pyfuncitem._fixtureinfo.argnames:
|
||||
testargs[arg] = funcargs[arg]
|
||||
testfunction(**testargs)
|
||||
iscoroutinefunction = getattr(inspect, "iscoroutinefunction", None)
|
||||
if iscoroutinefunction is not None and iscoroutinefunction(testfunction):
|
||||
msg = "Coroutine functions are not natively supported and have been skipped.\n"
|
||||
msg += "You need to install a suitable plugin for your async framework, for example:\n"
|
||||
msg += " - pytest-asyncio\n"
|
||||
msg += " - pytest-trio\n"
|
||||
msg += " - pytest-tornasync"
|
||||
warnings.warn(PytestWarning(msg.format(pyfuncitem.nodeid)))
|
||||
skip(msg="coroutine function and no async plugin installed (see warnings)")
|
||||
funcargs = pyfuncitem.funcargs
|
||||
testargs = {arg: funcargs[arg] for arg in pyfuncitem._fixtureinfo.argnames}
|
||||
testfunction(**testargs)
|
||||
return True
|
||||
|
||||
|
||||
@@ -808,7 +820,7 @@ class FunctionMixin(PyobjMixin):
|
||||
self.obj = self._getobj()
|
||||
|
||||
def _prunetraceback(self, excinfo):
|
||||
if hasattr(self, "_obj") and not self.config.option.fulltrace:
|
||||
if hasattr(self, "_obj") and not self.config.getoption("fulltrace", False):
|
||||
code = _pytest._code.Code(get_real_func(self.obj))
|
||||
path, firstlineno = code.path, code.firstlineno
|
||||
traceback = excinfo.traceback
|
||||
@@ -823,14 +835,14 @@ class FunctionMixin(PyobjMixin):
|
||||
excinfo.traceback = ntraceback.filter()
|
||||
# issue364: mark all but first and last frames to
|
||||
# only show a single-line message for each frame
|
||||
if self.config.option.tbstyle == "auto":
|
||||
if self.config.getoption("tbstyle", "auto") == "auto":
|
||||
if len(excinfo.traceback) > 2:
|
||||
for entry in excinfo.traceback[1:-1]:
|
||||
entry.set_repr_style("short")
|
||||
|
||||
def repr_failure(self, excinfo, outerr=None):
|
||||
assert outerr is None, "XXX outerr usage is deprecated"
|
||||
style = self.config.option.tbstyle
|
||||
style = self.config.getoption("tbstyle", "auto")
|
||||
if style == "auto":
|
||||
style = "long"
|
||||
return self._repr_failure_py(excinfo, style=style)
|
||||
@@ -1151,6 +1163,16 @@ def _find_parametrized_scope(argnames, arg2fixturedefs, indirect):
|
||||
return "function"
|
||||
|
||||
|
||||
def _ascii_escaped_by_config(val, config):
|
||||
if config is None:
|
||||
escape_option = False
|
||||
else:
|
||||
escape_option = config.getini(
|
||||
"disable_test_id_escaping_and_forfeit_all_rights_to_community_support"
|
||||
)
|
||||
return val if escape_option else ascii_escaped(val)
|
||||
|
||||
|
||||
def _idval(val, argname, idx, idfn, item, config):
|
||||
if idfn:
|
||||
try:
|
||||
@@ -1172,7 +1194,7 @@ def _idval(val, argname, idx, idfn, item, config):
|
||||
return hook_id
|
||||
|
||||
if isinstance(val, STRING_TYPES):
|
||||
return ascii_escaped(val)
|
||||
return _ascii_escaped_by_config(val, config)
|
||||
elif isinstance(val, (float, int, bool, NoneType)):
|
||||
return str(val)
|
||||
elif isinstance(val, REGEX_TYPE):
|
||||
@@ -1404,7 +1426,7 @@ class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr):
|
||||
|
||||
if fixtureinfo is None:
|
||||
fixtureinfo = self.session._fixturemanager.getfixtureinfo(
|
||||
self, self.obj, self.cls, funcargs=not self._isyieldedfunction()
|
||||
self, self.obj, self.cls, funcargs=True
|
||||
)
|
||||
self._fixtureinfo = fixtureinfo
|
||||
self.fixturenames = fixtureinfo.names_closure
|
||||
@@ -1418,16 +1440,6 @@ class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr):
|
||||
|
||||
def _initrequest(self):
|
||||
self.funcargs = {}
|
||||
if self._isyieldedfunction():
|
||||
assert not hasattr(
|
||||
self, "callspec"
|
||||
), "yielded functions (deprecated) cannot have funcargs"
|
||||
else:
|
||||
if hasattr(self, "callspec"):
|
||||
callspec = self.callspec
|
||||
assert not callspec.funcargs
|
||||
if hasattr(callspec, "param"):
|
||||
self.param = callspec.param
|
||||
self._request = fixtures.FixtureRequest(self)
|
||||
|
||||
@property
|
||||
@@ -1447,9 +1459,6 @@ class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr):
|
||||
"(compatonly) for code expecting pytest-2.2 style request objects"
|
||||
return self
|
||||
|
||||
def _isyieldedfunction(self):
|
||||
return getattr(self, "_args", None) is not None
|
||||
|
||||
def runtest(self):
|
||||
""" execute the underlying test function. """
|
||||
self.ihook.pytest_pyfunc_call(pyfuncitem=self)
|
||||
|
||||
@@ -7,7 +7,6 @@ import warnings
|
||||
from decimal import Decimal
|
||||
from numbers import Number
|
||||
|
||||
import six
|
||||
from more_itertools.more import always_iterable
|
||||
from six.moves import filterfalse
|
||||
from six.moves import zip
|
||||
@@ -558,10 +557,16 @@ def raises(expected_exception, *args, **kwargs):
|
||||
Assert that a code block/function call raises ``expected_exception``
|
||||
or raise a failure exception otherwise.
|
||||
|
||||
:kwparam match: if specified, asserts that the exception matches a text or regex
|
||||
:kwparam match: if specified, a string containing a regular expression,
|
||||
or a regular expression object, that is tested against the string
|
||||
representation of the exception using ``re.match``. To match a literal
|
||||
string that may contain `special characters`__, the pattern can
|
||||
first be escaped with ``re.escape``.
|
||||
|
||||
__ https://docs.python.org/3/library/re.html#regular-expression-syntax
|
||||
|
||||
:kwparam message: **(deprecated since 4.1)** if specified, provides a custom failure message
|
||||
if the exception is not raised
|
||||
if the exception is not raised. See :ref:`the deprecation docs <raises message deprecated>` for a workaround.
|
||||
|
||||
.. currentmodule:: _pytest._code
|
||||
|
||||
@@ -597,6 +602,7 @@ def raises(expected_exception, *args, **kwargs):
|
||||
``message`` to specify a custom failure message that will be displayed
|
||||
in case the ``pytest.raises`` check fails. This has been deprecated as it
|
||||
is considered error prone as users often mean to use ``match`` instead.
|
||||
See :ref:`the deprecation docs <raises message deprecated>` for a workaround.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -682,7 +688,7 @@ def raises(expected_exception, *args, **kwargs):
|
||||
match_expr = kwargs.pop("match")
|
||||
if kwargs:
|
||||
msg = "Unexpected keyword arguments passed to pytest.raises: "
|
||||
msg += ", ".join(kwargs.keys())
|
||||
msg += ", ".join(sorted(kwargs))
|
||||
raise TypeError(msg)
|
||||
return RaisesContext(expected_exception, message, match_expr)
|
||||
elif isinstance(args[0], str):
|
||||
@@ -695,7 +701,7 @@ def raises(expected_exception, *args, **kwargs):
|
||||
# print "raises frame scope: %r" % frame.f_locals
|
||||
try:
|
||||
code = _pytest._code.Source(code).compile(_genframe=frame)
|
||||
six.exec_(code, frame.f_globals, loc)
|
||||
exec(code, frame.f_globals, loc)
|
||||
# XXX didn't mean f_globals == f_locals something special?
|
||||
# this is destroyed here ...
|
||||
except expected_exception:
|
||||
|
||||
@@ -102,7 +102,7 @@ def warns(expected_warning, *args, **kwargs):
|
||||
|
||||
with WarningsChecker(expected_warning):
|
||||
code = _pytest._code.Source(code).compile()
|
||||
six.exec_(code, frame.f_globals, loc)
|
||||
exec(code, frame.f_globals, loc)
|
||||
else:
|
||||
func = args[0]
|
||||
with WarningsChecker(expected_warning):
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
import py
|
||||
from pprint import pprint
|
||||
|
||||
import py
|
||||
import six
|
||||
|
||||
from _pytest._code.code import ExceptionInfo
|
||||
from _pytest._code.code import ReprEntry
|
||||
from _pytest._code.code import ReprEntryNative
|
||||
from _pytest._code.code import ReprExceptionInfo
|
||||
from _pytest._code.code import ReprFileLocation
|
||||
from _pytest._code.code import ReprFuncArgs
|
||||
from _pytest._code.code import ReprLocals
|
||||
from _pytest._code.code import ReprTraceback
|
||||
from _pytest._code.code import TerminalRepr
|
||||
from _pytest.outcomes import skip
|
||||
from _pytest.pathlib import Path
|
||||
|
||||
|
||||
def getslaveinfoline(node):
|
||||
@@ -20,6 +33,7 @@ def getslaveinfoline(node):
|
||||
|
||||
class BaseReport(object):
|
||||
when = None
|
||||
location = None
|
||||
|
||||
def __init__(self, **kw):
|
||||
self.__dict__.update(kw)
|
||||
@@ -97,12 +111,173 @@ class BaseReport(object):
|
||||
def fspath(self):
|
||||
return self.nodeid.split("::")[0]
|
||||
|
||||
@property
|
||||
def count_towards_summary(self):
|
||||
"""
|
||||
**Experimental**
|
||||
|
||||
Returns True if this report should be counted towards the totals shown at the end of the
|
||||
test session: "1 passed, 1 failure, etc".
|
||||
|
||||
.. note::
|
||||
|
||||
This function is considered **experimental**, so beware that it is subject to changes
|
||||
even in patch releases.
|
||||
"""
|
||||
return True
|
||||
|
||||
@property
|
||||
def head_line(self):
|
||||
"""
|
||||
**Experimental**
|
||||
|
||||
Returns the head line shown with longrepr output for this report, more commonly during
|
||||
traceback representation during failures::
|
||||
|
||||
________ Test.foo ________
|
||||
|
||||
|
||||
In the example above, the head_line is "Test.foo".
|
||||
|
||||
.. note::
|
||||
|
||||
This function is considered **experimental**, so beware that it is subject to changes
|
||||
even in patch releases.
|
||||
"""
|
||||
if self.location is not None:
|
||||
fspath, lineno, domain = self.location
|
||||
return domain
|
||||
|
||||
def _to_json(self):
|
||||
"""
|
||||
This was originally the serialize_report() function from xdist (ca03269).
|
||||
|
||||
Returns the contents of this report as a dict of builtin entries, suitable for
|
||||
serialization.
|
||||
|
||||
Experimental method.
|
||||
"""
|
||||
|
||||
def disassembled_report(rep):
|
||||
reprtraceback = rep.longrepr.reprtraceback.__dict__.copy()
|
||||
reprcrash = rep.longrepr.reprcrash.__dict__.copy()
|
||||
|
||||
new_entries = []
|
||||
for entry in reprtraceback["reprentries"]:
|
||||
entry_data = {
|
||||
"type": type(entry).__name__,
|
||||
"data": entry.__dict__.copy(),
|
||||
}
|
||||
for key, value in entry_data["data"].items():
|
||||
if hasattr(value, "__dict__"):
|
||||
entry_data["data"][key] = value.__dict__.copy()
|
||||
new_entries.append(entry_data)
|
||||
|
||||
reprtraceback["reprentries"] = new_entries
|
||||
|
||||
return {
|
||||
"reprcrash": reprcrash,
|
||||
"reprtraceback": reprtraceback,
|
||||
"sections": rep.longrepr.sections,
|
||||
}
|
||||
|
||||
d = self.__dict__.copy()
|
||||
if hasattr(self.longrepr, "toterminal"):
|
||||
if hasattr(self.longrepr, "reprtraceback") and hasattr(
|
||||
self.longrepr, "reprcrash"
|
||||
):
|
||||
d["longrepr"] = disassembled_report(self)
|
||||
else:
|
||||
d["longrepr"] = six.text_type(self.longrepr)
|
||||
else:
|
||||
d["longrepr"] = self.longrepr
|
||||
for name in d:
|
||||
if isinstance(d[name], (py.path.local, Path)):
|
||||
d[name] = str(d[name])
|
||||
elif name == "result":
|
||||
d[name] = None # for now
|
||||
return d
|
||||
|
||||
@classmethod
|
||||
def _from_json(cls, reportdict):
|
||||
"""
|
||||
This was originally the serialize_report() function from xdist (ca03269).
|
||||
|
||||
Factory method that returns either a TestReport or CollectReport, depending on the calling
|
||||
class. It's the callers responsibility to know which class to pass here.
|
||||
|
||||
Experimental method.
|
||||
"""
|
||||
if reportdict["longrepr"]:
|
||||
if (
|
||||
"reprcrash" in reportdict["longrepr"]
|
||||
and "reprtraceback" in reportdict["longrepr"]
|
||||
):
|
||||
|
||||
reprtraceback = reportdict["longrepr"]["reprtraceback"]
|
||||
reprcrash = reportdict["longrepr"]["reprcrash"]
|
||||
|
||||
unserialized_entries = []
|
||||
reprentry = None
|
||||
for entry_data in reprtraceback["reprentries"]:
|
||||
data = entry_data["data"]
|
||||
entry_type = entry_data["type"]
|
||||
if entry_type == "ReprEntry":
|
||||
reprfuncargs = None
|
||||
reprfileloc = None
|
||||
reprlocals = None
|
||||
if data["reprfuncargs"]:
|
||||
reprfuncargs = ReprFuncArgs(**data["reprfuncargs"])
|
||||
if data["reprfileloc"]:
|
||||
reprfileloc = ReprFileLocation(**data["reprfileloc"])
|
||||
if data["reprlocals"]:
|
||||
reprlocals = ReprLocals(data["reprlocals"]["lines"])
|
||||
|
||||
reprentry = ReprEntry(
|
||||
lines=data["lines"],
|
||||
reprfuncargs=reprfuncargs,
|
||||
reprlocals=reprlocals,
|
||||
filelocrepr=reprfileloc,
|
||||
style=data["style"],
|
||||
)
|
||||
elif entry_type == "ReprEntryNative":
|
||||
reprentry = ReprEntryNative(data["lines"])
|
||||
else:
|
||||
_report_unserialization_failure(entry_type, cls, reportdict)
|
||||
unserialized_entries.append(reprentry)
|
||||
reprtraceback["reprentries"] = unserialized_entries
|
||||
|
||||
exception_info = ReprExceptionInfo(
|
||||
reprtraceback=ReprTraceback(**reprtraceback),
|
||||
reprcrash=ReprFileLocation(**reprcrash),
|
||||
)
|
||||
|
||||
for section in reportdict["longrepr"]["sections"]:
|
||||
exception_info.addsection(*section)
|
||||
reportdict["longrepr"] = exception_info
|
||||
|
||||
return cls(**reportdict)
|
||||
|
||||
|
||||
def _report_unserialization_failure(type_name, report_class, reportdict):
|
||||
url = "https://github.com/pytest-dev/pytest/issues"
|
||||
stream = py.io.TextIO()
|
||||
pprint("-" * 100, stream=stream)
|
||||
pprint("INTERNALERROR: Unknown entry type returned: %s" % type_name, stream=stream)
|
||||
pprint("report_name: %s" % report_class, stream=stream)
|
||||
pprint(reportdict, stream=stream)
|
||||
pprint("Please report this bug at %s" % url, stream=stream)
|
||||
pprint("-" * 100, stream=stream)
|
||||
raise RuntimeError(stream.getvalue())
|
||||
|
||||
|
||||
class TestReport(BaseReport):
|
||||
""" Basic test report object (also used for setup and teardown calls if
|
||||
they fail).
|
||||
"""
|
||||
|
||||
__test__ = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
nodeid,
|
||||
@@ -159,6 +334,49 @@ class TestReport(BaseReport):
|
||||
self.outcome,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_item_and_call(cls, item, call):
|
||||
"""
|
||||
Factory method to create and fill a TestReport with standard item and call info.
|
||||
"""
|
||||
when = call.when
|
||||
duration = call.stop - call.start
|
||||
keywords = {x: 1 for x in item.keywords}
|
||||
excinfo = call.excinfo
|
||||
sections = []
|
||||
if not call.excinfo:
|
||||
outcome = "passed"
|
||||
longrepr = None
|
||||
else:
|
||||
if not isinstance(excinfo, ExceptionInfo):
|
||||
outcome = "failed"
|
||||
longrepr = excinfo
|
||||
elif excinfo.errisinstance(skip.Exception):
|
||||
outcome = "skipped"
|
||||
r = excinfo._getreprcrash()
|
||||
longrepr = (str(r.path), r.lineno, r.message)
|
||||
else:
|
||||
outcome = "failed"
|
||||
if call.when == "call":
|
||||
longrepr = item.repr_failure(excinfo)
|
||||
else: # exception in setup or teardown
|
||||
longrepr = item._repr_failure_py(
|
||||
excinfo, style=item.config.getoption("tbstyle", "auto")
|
||||
)
|
||||
for rwhen, key, content in item._report_sections:
|
||||
sections.append(("Captured %s %s" % (key, rwhen), content))
|
||||
return cls(
|
||||
item.nodeid,
|
||||
item.location,
|
||||
keywords,
|
||||
outcome,
|
||||
longrepr,
|
||||
when,
|
||||
sections,
|
||||
duration,
|
||||
user_properties=item.user_properties,
|
||||
)
|
||||
|
||||
|
||||
class CollectReport(BaseReport):
|
||||
when = "collect"
|
||||
@@ -189,3 +407,21 @@ class CollectErrorRepr(TerminalRepr):
|
||||
|
||||
def toterminal(self, out):
|
||||
out.line(self.longrepr, red=True)
|
||||
|
||||
|
||||
def pytest_report_to_serializable(report):
|
||||
if isinstance(report, (TestReport, CollectReport)):
|
||||
data = report._to_json()
|
||||
data["_report_type"] = report.__class__.__name__
|
||||
return data
|
||||
|
||||
|
||||
def pytest_report_from_serializable(data):
|
||||
if "_report_type" in data:
|
||||
if data["_report_type"] == "TestReport":
|
||||
return TestReport._from_json(data)
|
||||
elif data["_report_type"] == "CollectReport":
|
||||
return CollectReport._from_json(data)
|
||||
assert False, "Unknown report_type unserialize data: {}".format(
|
||||
data["_report_type"]
|
||||
)
|
||||
|
||||
@@ -87,9 +87,9 @@ def runtestprotocol(item, log=True, nextitem=None):
|
||||
rep = call_and_report(item, "setup", log)
|
||||
reports = [rep]
|
||||
if rep.passed:
|
||||
if item.config.option.setupshow:
|
||||
if item.config.getoption("setupshow", False):
|
||||
show_test_item(item)
|
||||
if not item.config.option.setuponly:
|
||||
if not item.config.getoption("setuponly", False):
|
||||
reports.append(call_and_report(item, "call", log))
|
||||
reports.append(call_and_report(item, "teardown", log, nextitem=nextitem))
|
||||
# after all teardown hooks have been called
|
||||
@@ -192,7 +192,7 @@ def call_runtest_hook(item, when, **kwds):
|
||||
hookname = "pytest_runtest_" + when
|
||||
ihook = getattr(item.ihook, hookname)
|
||||
reraise = (Exit,)
|
||||
if not item.config.getvalue("usepdb"):
|
||||
if not item.config.getoption("usepdb", False):
|
||||
reraise += (KeyboardInterrupt,)
|
||||
return CallInfo.from_call(
|
||||
lambda: ihook(item=item, **kwds), when=when, reraise=reraise
|
||||
@@ -246,43 +246,7 @@ class CallInfo(object):
|
||||
|
||||
|
||||
def pytest_runtest_makereport(item, call):
|
||||
when = call.when
|
||||
duration = call.stop - call.start
|
||||
keywords = {x: 1 for x in item.keywords}
|
||||
excinfo = call.excinfo
|
||||
sections = []
|
||||
if not call.excinfo:
|
||||
outcome = "passed"
|
||||
longrepr = None
|
||||
else:
|
||||
if not isinstance(excinfo, ExceptionInfo):
|
||||
outcome = "failed"
|
||||
longrepr = excinfo
|
||||
elif excinfo.errisinstance(skip.Exception):
|
||||
outcome = "skipped"
|
||||
r = excinfo._getreprcrash()
|
||||
longrepr = (str(r.path), r.lineno, r.message)
|
||||
else:
|
||||
outcome = "failed"
|
||||
if call.when == "call":
|
||||
longrepr = item.repr_failure(excinfo)
|
||||
else: # exception in setup or teardown
|
||||
longrepr = item._repr_failure_py(
|
||||
excinfo, style=item.config.option.tbstyle
|
||||
)
|
||||
for rwhen, key, content in item._report_sections:
|
||||
sections.append(("Captured %s %s" % (key, rwhen), content))
|
||||
return TestReport(
|
||||
item.nodeid,
|
||||
item.location,
|
||||
keywords,
|
||||
outcome,
|
||||
longrepr,
|
||||
when,
|
||||
sections,
|
||||
duration,
|
||||
user_properties=item.user_properties,
|
||||
)
|
||||
return TestReport.from_item_and_call(item, call)
|
||||
|
||||
|
||||
def pytest_make_collect_report(collector):
|
||||
|
||||
@@ -17,7 +17,7 @@ def pytest_addoption(parser):
|
||||
action="store_true",
|
||||
dest="runxfail",
|
||||
default=False,
|
||||
help="run tests even if they are marked xfail",
|
||||
help="report the results of xfail tests as if they were not marked",
|
||||
)
|
||||
|
||||
parser.addini(
|
||||
@@ -207,20 +207,22 @@ def pytest_terminal_summary(terminalreporter):
|
||||
def show_simple(terminalreporter, lines, stat):
|
||||
failed = terminalreporter.stats.get(stat)
|
||||
if failed:
|
||||
config = terminalreporter.config
|
||||
for rep in failed:
|
||||
verbose_word = _get_report_str(terminalreporter, rep)
|
||||
pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid)
|
||||
verbose_word = _get_report_str(config, rep)
|
||||
pos = _get_pos(config, rep)
|
||||
lines.append("%s %s" % (verbose_word, pos))
|
||||
|
||||
|
||||
def show_xfailed(terminalreporter, lines):
|
||||
xfailed = terminalreporter.stats.get("xfailed")
|
||||
if xfailed:
|
||||
config = terminalreporter.config
|
||||
for rep in xfailed:
|
||||
verbose_word = _get_report_str(terminalreporter, rep)
|
||||
pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid)
|
||||
reason = rep.wasxfail
|
||||
verbose_word = _get_report_str(config, rep)
|
||||
pos = _get_pos(config, rep)
|
||||
lines.append("%s %s" % (verbose_word, pos))
|
||||
reason = rep.wasxfail
|
||||
if reason:
|
||||
lines.append(" " + str(reason))
|
||||
|
||||
@@ -228,9 +230,10 @@ def show_xfailed(terminalreporter, lines):
|
||||
def show_xpassed(terminalreporter, lines):
|
||||
xpassed = terminalreporter.stats.get("xpassed")
|
||||
if xpassed:
|
||||
config = terminalreporter.config
|
||||
for rep in xpassed:
|
||||
verbose_word = _get_report_str(terminalreporter, rep)
|
||||
pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid)
|
||||
verbose_word = _get_report_str(config, rep)
|
||||
pos = _get_pos(config, rep)
|
||||
reason = rep.wasxfail
|
||||
lines.append("%s %s %s" % (verbose_word, pos, reason))
|
||||
|
||||
@@ -261,9 +264,9 @@ def show_skipped(terminalreporter, lines):
|
||||
tr = terminalreporter
|
||||
skipped = tr.stats.get("skipped", [])
|
||||
if skipped:
|
||||
verbose_word = _get_report_str(terminalreporter, report=skipped[0])
|
||||
fskips = folded_skips(skipped)
|
||||
if fskips:
|
||||
verbose_word = _get_report_str(terminalreporter.config, report=skipped[0])
|
||||
for num, fspath, lineno, reason in fskips:
|
||||
if reason.startswith("Skipped: "):
|
||||
reason = reason[9:]
|
||||
@@ -283,13 +286,18 @@ def shower(stat):
|
||||
return show_
|
||||
|
||||
|
||||
def _get_report_str(terminalreporter, report):
|
||||
_category, _short, verbose = terminalreporter.config.hook.pytest_report_teststatus(
|
||||
report=report, config=terminalreporter.config
|
||||
def _get_report_str(config, report):
|
||||
_category, _short, verbose = config.hook.pytest_report_teststatus(
|
||||
report=report, config=config
|
||||
)
|
||||
return verbose
|
||||
|
||||
|
||||
def _get_pos(config, rep):
|
||||
nodeid = config.cwd_relative_nodeid(rep.nodeid)
|
||||
return nodeid
|
||||
|
||||
|
||||
REPORTCHAR_ACTIONS = {
|
||||
"x": show_xfailed,
|
||||
"X": show_xpassed,
|
||||
|
||||
@@ -8,7 +8,7 @@ def pytest_addoption(parser):
|
||||
"--stepwise",
|
||||
action="store_true",
|
||||
dest="stepwise",
|
||||
help="exit on test fail and continue from last failing test next time",
|
||||
help="exit on test failure and continue from last failing test next time",
|
||||
)
|
||||
group.addoption(
|
||||
"--stepwise-skip",
|
||||
@@ -37,7 +37,10 @@ class StepwisePlugin:
|
||||
self.session = session
|
||||
|
||||
def pytest_collection_modifyitems(self, session, config, items):
|
||||
if not self.active or not self.lastfailed:
|
||||
if not self.active:
|
||||
return
|
||||
if not self.lastfailed:
|
||||
self.report_status = "no previously failed tests, not skipping."
|
||||
return
|
||||
|
||||
already_passed = []
|
||||
@@ -54,7 +57,12 @@ class StepwisePlugin:
|
||||
# If the previously failed test was not found among the test items,
|
||||
# do not skip any tests.
|
||||
if not found:
|
||||
self.report_status = "previously failed test not found, not skipping."
|
||||
already_passed = []
|
||||
else:
|
||||
self.report_status = "skipping {} already passed items.".format(
|
||||
len(already_passed)
|
||||
)
|
||||
|
||||
for item in already_passed:
|
||||
items.remove(item)
|
||||
@@ -94,6 +102,10 @@ class StepwisePlugin:
|
||||
if report.nodeid == self.lastfailed:
|
||||
self.lastfailed = None
|
||||
|
||||
def pytest_report_collectionfinish(self):
|
||||
if self.active and self.config.getoption("verbose") >= 0:
|
||||
return "stepwise: %s" % self.report_status
|
||||
|
||||
def pytest_sessionfinish(self, session):
|
||||
if self.active:
|
||||
self.config.cache.set("cache/stepwise", self.lastfailed)
|
||||
|
||||
@@ -26,6 +26,8 @@ from _pytest.main import EXIT_OK
|
||||
from _pytest.main import EXIT_TESTSFAILED
|
||||
from _pytest.main import EXIT_USAGEERROR
|
||||
|
||||
REPORT_COLLECTING_RESOLUTION = 0.5
|
||||
|
||||
|
||||
class MoreQuietAction(argparse.Action):
|
||||
"""
|
||||
@@ -197,6 +199,7 @@ class WarningReport(object):
|
||||
message = attr.ib()
|
||||
nodeid = attr.ib(default=None)
|
||||
fslocation = attr.ib(default=None)
|
||||
count_towards_summary = True
|
||||
|
||||
def get_location(self, config):
|
||||
"""
|
||||
@@ -245,10 +248,10 @@ class TerminalReporter(object):
|
||||
def _determine_show_progress_info(self):
|
||||
"""Return True if we should display progress information based on the current config"""
|
||||
# do not show progress if we are not capturing output (#3038)
|
||||
if self.config.getoption("capture") == "no":
|
||||
if self.config.getoption("capture", "no") == "no":
|
||||
return False
|
||||
# do not show progress if we are showing fixture setup/teardown
|
||||
if self.config.getoption("setupshow"):
|
||||
if self.config.getoption("setupshow", False):
|
||||
return False
|
||||
return self.config.getini("console_output_style") in ("progress", "count")
|
||||
|
||||
@@ -383,6 +386,7 @@ class TerminalReporter(object):
|
||||
self.write_fspath_result(fsid, "")
|
||||
|
||||
def pytest_runtest_logreport(self, report):
|
||||
self._tests_ran = True
|
||||
rep = report
|
||||
res = self.config.hook.pytest_report_teststatus(report=rep, config=self.config)
|
||||
category, letter, word = res
|
||||
@@ -391,7 +395,6 @@ class TerminalReporter(object):
|
||||
else:
|
||||
markup = None
|
||||
self.stats.setdefault(category, []).append(rep)
|
||||
self._tests_ran = True
|
||||
if not letter and not word:
|
||||
# probably passed setup/teardown
|
||||
return
|
||||
@@ -455,8 +458,6 @@ class TerminalReporter(object):
|
||||
self._tw.write(msg + "\n", cyan=True)
|
||||
|
||||
def _get_progress_information_message(self):
|
||||
if self.config.getoption("capture") == "no":
|
||||
return ""
|
||||
collected = self._session.testscollected
|
||||
if self.config.getini("console_output_style") == "count":
|
||||
if collected:
|
||||
@@ -513,7 +514,7 @@ class TerminalReporter(object):
|
||||
t = time.time()
|
||||
if (
|
||||
self._collect_report_last_write is not None
|
||||
and self._collect_report_last_write > t - 0.5
|
||||
and self._collect_report_last_write > t - REPORT_COLLECTING_RESOLUTION
|
||||
):
|
||||
return
|
||||
self._collect_report_last_write = t
|
||||
@@ -583,16 +584,21 @@ class TerminalReporter(object):
|
||||
self.write_line(line)
|
||||
|
||||
def pytest_report_header(self, config):
|
||||
inifile = ""
|
||||
line = "rootdir: %s" % config.rootdir
|
||||
|
||||
if config.inifile:
|
||||
inifile = " " + config.rootdir.bestrelpath(config.inifile)
|
||||
lines = ["rootdir: %s, inifile:%s" % (config.rootdir, inifile)]
|
||||
line += ", inifile: " + config.rootdir.bestrelpath(config.inifile)
|
||||
|
||||
testpaths = config.getini("testpaths")
|
||||
if testpaths and config.args == testpaths:
|
||||
rel_paths = [config.rootdir.bestrelpath(x) for x in testpaths]
|
||||
line += ", testpaths: {}".format(", ".join(rel_paths))
|
||||
result = [line]
|
||||
|
||||
plugininfo = config.pluginmanager.list_plugin_distinfo()
|
||||
if plugininfo:
|
||||
|
||||
lines.append("plugins: %s" % ", ".join(_plugin_nameversions(plugininfo)))
|
||||
return lines
|
||||
result.append("plugins: %s" % ", ".join(_plugin_nameversions(plugininfo)))
|
||||
return result
|
||||
|
||||
def pytest_collection_finish(self, session):
|
||||
if self.config.getoption("collectonly"):
|
||||
@@ -719,9 +725,8 @@ class TerminalReporter(object):
|
||||
return res + " "
|
||||
|
||||
def _getfailureheadline(self, rep):
|
||||
if hasattr(rep, "location"):
|
||||
fspath, lineno, domain = rep.location
|
||||
return domain
|
||||
if rep.head_line:
|
||||
return rep.head_line
|
||||
else:
|
||||
return "test session" # XXX?
|
||||
|
||||
@@ -869,18 +874,23 @@ class TerminalReporter(object):
|
||||
|
||||
|
||||
def build_summary_stats_line(stats):
|
||||
keys = ("failed passed skipped deselected xfailed xpassed warnings error").split()
|
||||
unknown_key_seen = False
|
||||
for key in stats.keys():
|
||||
if key not in keys:
|
||||
if key: # setup/teardown reports have an empty key, ignore them
|
||||
keys.append(key)
|
||||
unknown_key_seen = True
|
||||
known_types = (
|
||||
"failed passed skipped deselected xfailed xpassed warnings error".split()
|
||||
)
|
||||
unknown_type_seen = False
|
||||
for found_type in stats:
|
||||
if found_type not in known_types:
|
||||
if found_type: # setup/teardown reports have an empty key, ignore them
|
||||
known_types.append(found_type)
|
||||
unknown_type_seen = True
|
||||
parts = []
|
||||
for key in keys:
|
||||
val = stats.get(key, None)
|
||||
if val:
|
||||
parts.append("%d %s" % (len(val), key))
|
||||
for key in known_types:
|
||||
reports = stats.get(key, None)
|
||||
if reports:
|
||||
count = sum(
|
||||
1 for rep in reports if getattr(rep, "count_towards_summary", True)
|
||||
)
|
||||
parts.append("%d %s" % (count, key))
|
||||
|
||||
if parts:
|
||||
line = ", ".join(parts)
|
||||
@@ -889,14 +899,14 @@ def build_summary_stats_line(stats):
|
||||
|
||||
if "failed" in stats or "error" in stats:
|
||||
color = "red"
|
||||
elif "warnings" in stats or unknown_key_seen:
|
||||
elif "warnings" in stats or unknown_type_seen:
|
||||
color = "yellow"
|
||||
elif "passed" in stats:
|
||||
color = "green"
|
||||
else:
|
||||
color = "yellow"
|
||||
|
||||
return (line, color)
|
||||
return line, color
|
||||
|
||||
|
||||
def _plugin_nameversions(plugininfo):
|
||||
|
||||
@@ -103,8 +103,9 @@ def catch_warnings_for_item(config, ihook, when, item):
|
||||
|
||||
|
||||
def warning_record_to_str(warning_message):
|
||||
"""Convert a warnings.WarningMessage to a string, taking in account a lot of unicode shenaningans in Python 2.
|
||||
"""Convert a warnings.WarningMessage to a string.
|
||||
|
||||
This takes lot of unicode shenaningans into account for Python 2.
|
||||
When Python 2 support is dropped this function can be greatly simplified.
|
||||
"""
|
||||
warn_msg = warning_message.message
|
||||
|
||||
@@ -8,6 +8,7 @@ import sys
|
||||
import textwrap
|
||||
import types
|
||||
|
||||
import attr
|
||||
import py
|
||||
import six
|
||||
|
||||
@@ -108,6 +109,60 @@ class TestGeneralUsage(object):
|
||||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines(["*1 passed*"])
|
||||
|
||||
@pytest.mark.parametrize("load_cov_early", [True, False])
|
||||
def test_early_load_setuptools_name(self, testdir, monkeypatch, load_cov_early):
|
||||
pkg_resources = pytest.importorskip("pkg_resources")
|
||||
|
||||
testdir.makepyfile(mytestplugin1_module="")
|
||||
testdir.makepyfile(mytestplugin2_module="")
|
||||
testdir.makepyfile(mycov_module="")
|
||||
testdir.syspathinsert()
|
||||
|
||||
loaded = []
|
||||
|
||||
@attr.s
|
||||
class DummyEntryPoint(object):
|
||||
name = attr.ib()
|
||||
module = attr.ib()
|
||||
version = "1.0"
|
||||
|
||||
@property
|
||||
def project_name(self):
|
||||
return self.name
|
||||
|
||||
def load(self):
|
||||
__import__(self.module)
|
||||
loaded.append(self.name)
|
||||
return sys.modules[self.module]
|
||||
|
||||
@property
|
||||
def dist(self):
|
||||
return self
|
||||
|
||||
def _get_metadata(self, *args):
|
||||
return []
|
||||
|
||||
entry_points = [
|
||||
DummyEntryPoint("myplugin1", "mytestplugin1_module"),
|
||||
DummyEntryPoint("myplugin2", "mytestplugin2_module"),
|
||||
DummyEntryPoint("mycov", "mycov_module"),
|
||||
]
|
||||
|
||||
def my_iter(group, name=None):
|
||||
assert group == "pytest11"
|
||||
for ep in entry_points:
|
||||
if name is not None and ep.name != name:
|
||||
continue
|
||||
yield ep
|
||||
|
||||
monkeypatch.setattr(pkg_resources, "iter_entry_points", my_iter)
|
||||
params = ("-p", "mycov") if load_cov_early else ()
|
||||
testdir.runpytest_inprocess(*params)
|
||||
if load_cov_early:
|
||||
assert loaded == ["mycov", "myplugin1", "myplugin2"]
|
||||
else:
|
||||
assert loaded == ["myplugin1", "myplugin2", "mycov"]
|
||||
|
||||
def test_assertion_magic(self, testdir):
|
||||
p = testdir.makepyfile(
|
||||
"""
|
||||
@@ -430,7 +485,7 @@ class TestGeneralUsage(object):
|
||||
["*source code not available*", "E*fixture 'invalid_fixture' not found"]
|
||||
)
|
||||
|
||||
def test_plugins_given_as_strings(self, tmpdir, monkeypatch):
|
||||
def test_plugins_given_as_strings(self, tmpdir, monkeypatch, _sys_snapshot):
|
||||
"""test that str values passed to main() as `plugins` arg
|
||||
are interpreted as module names to be imported and registered.
|
||||
#855.
|
||||
@@ -622,6 +677,8 @@ class TestInvocationVariants(object):
|
||||
def test_cmdline_python_namespace_package(self, testdir, monkeypatch):
|
||||
"""
|
||||
test --pyargs option with namespace packages (#1567)
|
||||
|
||||
Ref: https://packaging.python.org/guides/packaging-namespace-packages/
|
||||
"""
|
||||
monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", raising=False)
|
||||
|
||||
@@ -978,7 +1035,7 @@ def test_pytest_plugins_as_module(testdir):
|
||||
}
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines("* 1 passed in *")
|
||||
result.stdout.fnmatch_lines(["* 1 passed in *"])
|
||||
|
||||
|
||||
def test_deferred_hook_checking(testdir):
|
||||
@@ -1118,9 +1175,37 @@ def test_fixture_mock_integration(testdir):
|
||||
"""Test that decorators applied to fixture are left working (#3774)"""
|
||||
p = testdir.copy_example("acceptance/fixture_mock_integration.py")
|
||||
result = testdir.runpytest(p)
|
||||
result.stdout.fnmatch_lines("*1 passed*")
|
||||
result.stdout.fnmatch_lines(["*1 passed*"])
|
||||
|
||||
|
||||
def test_usage_error_code(testdir):
|
||||
result = testdir.runpytest("-unknown-option-")
|
||||
assert result.ret == EXIT_USAGEERROR
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
sys.version_info[:2] < (3, 5), reason="async def syntax python 3.5+ only"
|
||||
)
|
||||
@pytest.mark.filterwarnings("default")
|
||||
def test_warn_on_async_function(testdir):
|
||||
testdir.makepyfile(
|
||||
test_async="""
|
||||
async def test_1():
|
||||
pass
|
||||
async def test_2():
|
||||
pass
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"test_async.py::test_1",
|
||||
"test_async.py::test_2",
|
||||
"*Coroutine functions are not natively supported*",
|
||||
"*2 skipped, 2 warnings in*",
|
||||
]
|
||||
)
|
||||
# ensure our warning message appears only once
|
||||
assert (
|
||||
result.stdout.str().count("Coroutine functions are not natively supported") == 1
|
||||
)
|
||||
|
||||
@@ -172,6 +172,10 @@ class TestExceptionInfo(object):
|
||||
exci = _pytest._code.ExceptionInfo.from_current()
|
||||
assert exci.getrepr()
|
||||
|
||||
def test_from_current_with_missing(self):
|
||||
with pytest.raises(AssertionError, match="no current exception"):
|
||||
_pytest._code.ExceptionInfo.from_current()
|
||||
|
||||
|
||||
class TestTracebackEntry(object):
|
||||
def test_getsource(self):
|
||||
|
||||
@@ -441,7 +441,7 @@ def test_match_raises_error(testdir):
|
||||
|
||||
class TestFormattedExcinfo(object):
|
||||
@pytest.fixture
|
||||
def importasmod(self, request):
|
||||
def importasmod(self, request, _sys_snapshot):
|
||||
def importasmod(source):
|
||||
source = textwrap.dedent(source)
|
||||
tmpdir = request.getfixturevalue("tmpdir")
|
||||
@@ -598,6 +598,35 @@ raise ValueError()
|
||||
assert reprlocals.lines[2] == "y = 5"
|
||||
assert reprlocals.lines[3] == "z = 7"
|
||||
|
||||
def test_repr_local_with_error(self):
|
||||
class ObjWithErrorInRepr:
|
||||
def __repr__(self):
|
||||
raise NotImplementedError
|
||||
|
||||
p = FormattedExcinfo(showlocals=True, truncate_locals=False)
|
||||
loc = {"x": ObjWithErrorInRepr(), "__builtins__": {}}
|
||||
reprlocals = p.repr_locals(loc)
|
||||
assert reprlocals.lines
|
||||
assert reprlocals.lines[0] == "__builtins__ = <builtins>"
|
||||
assert '[NotImplementedError("") raised in repr()]' in reprlocals.lines[1]
|
||||
|
||||
def test_repr_local_with_exception_in_class_property(self):
|
||||
class ExceptionWithBrokenClass(Exception):
|
||||
@property
|
||||
def __class__(self):
|
||||
raise TypeError("boom!")
|
||||
|
||||
class ObjWithErrorInRepr:
|
||||
def __repr__(self):
|
||||
raise ExceptionWithBrokenClass()
|
||||
|
||||
p = FormattedExcinfo(showlocals=True, truncate_locals=False)
|
||||
loc = {"x": ObjWithErrorInRepr(), "__builtins__": {}}
|
||||
reprlocals = p.repr_locals(loc)
|
||||
assert reprlocals.lines
|
||||
assert reprlocals.lines[0] == "__builtins__ = <builtins>"
|
||||
assert '[ExceptionWithBrokenClass("") raised in repr()]' in reprlocals.lines[1]
|
||||
|
||||
def test_repr_local_truncated(self):
|
||||
loc = {"l": [i for i in range(10)]}
|
||||
p = FormattedExcinfo(showlocals=True)
|
||||
|
||||
@@ -312,7 +312,7 @@ class TestSourceParsingAndCompiling(object):
|
||||
|
||||
def test_compile_and_getsource(self):
|
||||
co = self.source.compile()
|
||||
six.exec_(co, globals())
|
||||
exec(co, globals())
|
||||
f(7)
|
||||
excinfo = pytest.raises(AssertionError, f, 6)
|
||||
frame = excinfo.traceback[-1].frame
|
||||
@@ -376,7 +376,7 @@ def test_getfuncsource_dynamic():
|
||||
def g(): pass
|
||||
"""
|
||||
co = _pytest._code.compile(source)
|
||||
six.exec_(co, globals())
|
||||
exec(co, globals())
|
||||
assert str(_pytest._code.Source(f)).strip() == "def f():\n raise ValueError"
|
||||
assert str(_pytest._code.Source(g)).strip() == "def g(): pass"
|
||||
|
||||
@@ -410,7 +410,7 @@ def test_deindent():
|
||||
assert lines == ["def f():", " def g():", " pass"]
|
||||
|
||||
|
||||
def test_source_of_class_at_eof_without_newline(tmpdir):
|
||||
def test_source_of_class_at_eof_without_newline(tmpdir, _sys_snapshot):
|
||||
# this test fails because the implicit inspect.getsource(A) below
|
||||
# does not return the "x = 1" last line.
|
||||
source = _pytest._code.Source(
|
||||
|
||||
@@ -147,7 +147,7 @@ def test_pytest_plugins_in_non_top_level_conftest_unsupported_pyargs(
|
||||
if use_pyargs:
|
||||
assert msg not in res.stdout.str()
|
||||
else:
|
||||
res.stdout.fnmatch_lines("*{msg}*".format(msg=msg))
|
||||
res.stdout.fnmatch_lines(["*{msg}*".format(msg=msg)])
|
||||
|
||||
|
||||
def test_pytest_plugins_in_non_top_level_conftest_unsupported_no_top_level_conftest(
|
||||
|
||||
@@ -54,6 +54,7 @@ def test_root_logger_affected(testdir):
|
||||
"""
|
||||
import logging
|
||||
logger = logging.getLogger()
|
||||
|
||||
def test_foo():
|
||||
logger.info('info text ' + 'going to logger')
|
||||
logger.warning('warning text ' + 'going to logger')
|
||||
@@ -66,15 +67,14 @@ def test_root_logger_affected(testdir):
|
||||
result = testdir.runpytest("--log-level=ERROR", "--log-file=pytest.log")
|
||||
assert result.ret == 1
|
||||
|
||||
# the capture log calls in the stdout section only contain the
|
||||
# logger.error msg, because --log-level=ERROR
|
||||
# The capture log calls in the stdout section only contain the
|
||||
# logger.error msg, because of --log-level=ERROR.
|
||||
result.stdout.fnmatch_lines(["*error text going to logger*"])
|
||||
with pytest.raises(pytest.fail.Exception):
|
||||
result.stdout.fnmatch_lines(["*warning text going to logger*"])
|
||||
with pytest.raises(pytest.fail.Exception):
|
||||
result.stdout.fnmatch_lines(["*info text going to logger*"])
|
||||
stdout = result.stdout.str()
|
||||
assert "warning text going to logger" not in stdout
|
||||
assert "info text going to logger" not in stdout
|
||||
|
||||
# the log file should contain the warning and the error log messages and
|
||||
# The log file should contain the warning and the error log messages and
|
||||
# not the info one, because the default level of the root logger is
|
||||
# WARNING.
|
||||
assert os.path.isfile(log_file)
|
||||
@@ -635,7 +635,6 @@ def test_log_cli_auto_enable(testdir, request, cli_args):
|
||||
"""
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
import logging
|
||||
|
||||
def test_log_1():
|
||||
@@ -653,6 +652,7 @@ def test_log_cli_auto_enable(testdir, request, cli_args):
|
||||
)
|
||||
|
||||
result = testdir.runpytest(cli_args)
|
||||
stdout = result.stdout.str()
|
||||
if cli_args == "--log-cli-level=WARNING":
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
@@ -663,13 +663,13 @@ def test_log_cli_auto_enable(testdir, request, cli_args):
|
||||
"=* 1 passed in *=",
|
||||
]
|
||||
)
|
||||
assert "INFO" not in result.stdout.str()
|
||||
assert "INFO" not in stdout
|
||||
else:
|
||||
result.stdout.fnmatch_lines(
|
||||
["*test_log_cli_auto_enable*100%*", "=* 1 passed in *="]
|
||||
)
|
||||
assert "INFO" not in result.stdout.str()
|
||||
assert "WARNING" not in result.stdout.str()
|
||||
assert "INFO" not in stdout
|
||||
assert "WARNING" not in stdout
|
||||
|
||||
|
||||
def test_log_file_cli(testdir):
|
||||
@@ -747,7 +747,7 @@ def test_log_level_not_changed_by_default(testdir):
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest("-s")
|
||||
result.stdout.fnmatch_lines("* 1 passed in *")
|
||||
result.stdout.fnmatch_lines(["* 1 passed in *"])
|
||||
|
||||
|
||||
def test_log_file_ini(testdir):
|
||||
|
||||
@@ -34,8 +34,6 @@ class TestModule(object):
|
||||
)
|
||||
|
||||
def test_import_prepend_append(self, testdir, monkeypatch):
|
||||
syspath = list(sys.path)
|
||||
monkeypatch.setattr(sys, "path", syspath)
|
||||
root1 = testdir.mkdir("root1")
|
||||
root2 = testdir.mkdir("root2")
|
||||
root1.ensure("x456.py")
|
||||
@@ -560,7 +558,7 @@ class TestFunction(object):
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines("* 2 passed, 1 skipped in *")
|
||||
result.stdout.fnmatch_lines(["* 2 passed, 1 skipped in *"])
|
||||
|
||||
def test_parametrize_skip(self, testdir):
|
||||
testdir.makepyfile(
|
||||
@@ -575,7 +573,7 @@ class TestFunction(object):
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines("* 2 passed, 1 skipped in *")
|
||||
result.stdout.fnmatch_lines(["* 2 passed, 1 skipped in *"])
|
||||
|
||||
def test_parametrize_skipif_no_skip(self, testdir):
|
||||
testdir.makepyfile(
|
||||
@@ -590,7 +588,7 @@ class TestFunction(object):
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines("* 1 failed, 2 passed in *")
|
||||
result.stdout.fnmatch_lines(["* 1 failed, 2 passed in *"])
|
||||
|
||||
def test_parametrize_xfail(self, testdir):
|
||||
testdir.makepyfile(
|
||||
@@ -605,7 +603,7 @@ class TestFunction(object):
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines("* 2 passed, 1 xfailed in *")
|
||||
result.stdout.fnmatch_lines(["* 2 passed, 1 xfailed in *"])
|
||||
|
||||
def test_parametrize_passed(self, testdir):
|
||||
testdir.makepyfile(
|
||||
@@ -620,7 +618,7 @@ class TestFunction(object):
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines("* 2 passed, 1 xpassed in *")
|
||||
result.stdout.fnmatch_lines(["* 2 passed, 1 xpassed in *"])
|
||||
|
||||
def test_parametrize_xfail_passed(self, testdir):
|
||||
testdir.makepyfile(
|
||||
@@ -635,7 +633,7 @@ class TestFunction(object):
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines("* 3 passed in *")
|
||||
result.stdout.fnmatch_lines(["* 3 passed in *"])
|
||||
|
||||
def test_function_original_name(self, testdir):
|
||||
items = testdir.getitems(
|
||||
@@ -833,7 +831,7 @@ class TestConftestCustomization(object):
|
||||
)
|
||||
# Use runpytest_subprocess, since we're futzing with sys.meta_path.
|
||||
result = testdir.runpytest_subprocess()
|
||||
result.stdout.fnmatch_lines("*1 passed*")
|
||||
result.stdout.fnmatch_lines(["*1 passed*"])
|
||||
|
||||
|
||||
def test_setup_only_available_in_subdir(testdir):
|
||||
@@ -1298,14 +1296,14 @@ def test_keep_duplicates(testdir):
|
||||
def test_package_collection_infinite_recursion(testdir):
|
||||
testdir.copy_example("collect/package_infinite_recursion")
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines("*1 passed*")
|
||||
result.stdout.fnmatch_lines(["*1 passed*"])
|
||||
|
||||
|
||||
def test_package_collection_init_given_as_argument(testdir):
|
||||
"""Regression test for #3749"""
|
||||
p = testdir.copy_example("collect/package_init_given_as_arg")
|
||||
result = testdir.runpytest(p / "pkg" / "__init__.py")
|
||||
result.stdout.fnmatch_lines("*1 passed*")
|
||||
result.stdout.fnmatch_lines(["*1 passed*"])
|
||||
|
||||
|
||||
def test_package_with_modules(testdir):
|
||||
|
||||
@@ -536,7 +536,7 @@ class TestRequestBasic(object):
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest_subprocess()
|
||||
result.stdout.fnmatch_lines("* 1 passed in *")
|
||||
result.stdout.fnmatch_lines(["* 1 passed in *"])
|
||||
|
||||
def test_getfixturevalue_recursive(self, testdir):
|
||||
testdir.makeconftest(
|
||||
@@ -562,6 +562,44 @@ class TestRequestBasic(object):
|
||||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(passed=1)
|
||||
|
||||
def test_getfixturevalue_teardown(self, testdir):
|
||||
"""
|
||||
Issue #1895
|
||||
|
||||
`test_inner` requests `inner` fixture, which in turn requests `resource`
|
||||
using `getfixturevalue`. `test_func` then requests `resource`.
|
||||
|
||||
`resource` is teardown before `inner` because the fixture mechanism won't consider
|
||||
`inner` dependent on `resource` when it is used via `getfixturevalue`: `test_func`
|
||||
will then cause the `resource`'s finalizer to be called first because of this.
|
||||
"""
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def resource():
|
||||
r = ['value']
|
||||
yield r
|
||||
r.pop()
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def inner(request):
|
||||
resource = request.getfixturevalue('resource')
|
||||
assert resource == ['value']
|
||||
yield
|
||||
assert resource == ['value']
|
||||
|
||||
def test_inner(inner):
|
||||
pass
|
||||
|
||||
def test_func(resource):
|
||||
pass
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines(["* 2 passed in *"])
|
||||
|
||||
@pytest.mark.parametrize("getfixmethod", ("getfixturevalue", "getfuncargvalue"))
|
||||
def test_getfixturevalue(self, testdir, getfixmethod):
|
||||
item = testdir.getitem(
|
||||
@@ -749,7 +787,7 @@ class TestRequestBasic(object):
|
||||
"""Regression test for #3057"""
|
||||
testdir.copy_example("fixtures/test_getfixturevalue_dynamic.py")
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines("*1 passed*")
|
||||
result.stdout.fnmatch_lines(["*1 passed*"])
|
||||
|
||||
def test_funcargnames_compatattr(self, testdir):
|
||||
testdir.makepyfile(
|
||||
@@ -992,8 +1030,8 @@ class TestFixtureUsages(object):
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"*ScopeMismatch*involved factories*",
|
||||
"* def arg2*",
|
||||
"* def arg1*",
|
||||
"test_receives_funcargs_scope_mismatch.py:6: def arg2(arg1)",
|
||||
"test_receives_funcargs_scope_mismatch.py:2: def arg1()",
|
||||
"*1 error*",
|
||||
]
|
||||
)
|
||||
@@ -1033,9 +1071,7 @@ class TestFixtureUsages(object):
|
||||
)
|
||||
result = testdir.runpytest_inprocess()
|
||||
result.stdout.fnmatch_lines(
|
||||
(
|
||||
"*Fixture 'badscope' from test_invalid_scope.py got an unexpected scope value 'functions'"
|
||||
)
|
||||
"*Fixture 'badscope' from test_invalid_scope.py got an unexpected scope value 'functions'"
|
||||
)
|
||||
|
||||
def test_funcarg_parametrized_and_used_twice(self, testdir):
|
||||
@@ -1103,6 +1139,7 @@ class TestFixtureUsages(object):
|
||||
values = reprec.getfailedcollections()
|
||||
assert len(values) == 1
|
||||
|
||||
@pytest.mark.filterwarnings("ignore::pytest.PytestDeprecationWarning")
|
||||
def test_request_can_be_overridden(self, testdir):
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
@@ -1489,7 +1526,7 @@ class TestFixtureManagerParseFactories(object):
|
||||
def test_collect_custom_items(self, testdir):
|
||||
testdir.copy_example("fixtures/custom_item")
|
||||
result = testdir.runpytest("foo")
|
||||
result.stdout.fnmatch_lines("*passed*")
|
||||
result.stdout.fnmatch_lines(["*passed*"])
|
||||
|
||||
|
||||
class TestAutouseDiscovery(object):
|
||||
@@ -2571,7 +2608,7 @@ class TestFixtureMarker(object):
|
||||
)
|
||||
reprec = testdir.runpytest("-s")
|
||||
for test in ["test_browser"]:
|
||||
reprec.stdout.fnmatch_lines("*Finalized*")
|
||||
reprec.stdout.fnmatch_lines(["*Finalized*"])
|
||||
|
||||
def test_class_scope_with_normal_tests(self, testdir):
|
||||
testpath = testdir.makepyfile(
|
||||
@@ -3412,7 +3449,7 @@ class TestContextManagerFixtureFuncs(object):
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest("-s")
|
||||
result.stdout.fnmatch_lines("*mew*")
|
||||
result.stdout.fnmatch_lines(["*mew*"])
|
||||
|
||||
|
||||
class TestParameterizedSubRequest(object):
|
||||
@@ -11,6 +11,7 @@ import six
|
||||
|
||||
import _pytest.assertion as plugin
|
||||
import pytest
|
||||
from _pytest import outcomes
|
||||
from _pytest.assertion import truncate
|
||||
from _pytest.assertion import util
|
||||
|
||||
@@ -1305,3 +1306,13 @@ def test_issue_1944(testdir):
|
||||
"AttributeError: 'Module' object has no attribute '_obj'"
|
||||
not in result.stdout.str()
|
||||
)
|
||||
|
||||
|
||||
def test_exit_from_assertrepr_compare(monkeypatch):
|
||||
def raise_exit(obj):
|
||||
outcomes.exit("Quitting debugger")
|
||||
|
||||
monkeypatch.setattr(util, "istext", raise_exit)
|
||||
|
||||
with pytest.raises(outcomes.Exit, match="Quitting debugger"):
|
||||
callequal(1, 1)
|
||||
|
||||
@@ -52,7 +52,7 @@ def getmsg(f, extra_ns=None, must_pass=False):
|
||||
ns = {}
|
||||
if extra_ns is not None:
|
||||
ns.update(extra_ns)
|
||||
six.exec_(code, ns)
|
||||
exec(code, ns)
|
||||
func = ns[f.__name__]
|
||||
try:
|
||||
func()
|
||||
@@ -127,7 +127,7 @@ class TestAssertionRewrite(object):
|
||||
result = testdir.runpytest_subprocess()
|
||||
assert "warnings" not in "".join(result.outlines)
|
||||
|
||||
def test_name(self):
|
||||
def test_name(self, request):
|
||||
def f():
|
||||
assert False
|
||||
|
||||
@@ -147,17 +147,41 @@ class TestAssertionRewrite(object):
|
||||
def f():
|
||||
assert sys == 42
|
||||
|
||||
assert getmsg(f, {"sys": sys}) == "assert sys == 42"
|
||||
verbose = request.config.getoption("verbose")
|
||||
msg = getmsg(f, {"sys": sys})
|
||||
if verbose > 0:
|
||||
assert msg == (
|
||||
"assert <module 'sys' (built-in)> == 42\n"
|
||||
" -<module 'sys' (built-in)>\n"
|
||||
" +42"
|
||||
)
|
||||
else:
|
||||
assert msg == "assert sys == 42"
|
||||
|
||||
def f():
|
||||
assert cls == 42 # noqa
|
||||
assert cls == 42 # noqa: F821
|
||||
|
||||
class X(object):
|
||||
pass
|
||||
|
||||
assert getmsg(f, {"cls": X}) == "assert cls == 42"
|
||||
msg = getmsg(f, {"cls": X}).splitlines()
|
||||
if verbose > 0:
|
||||
if six.PY2:
|
||||
assert msg == [
|
||||
"assert <class 'test_assertrewrite.X'> == 42",
|
||||
" -<class 'test_assertrewrite.X'>",
|
||||
" +42",
|
||||
]
|
||||
else:
|
||||
assert msg == [
|
||||
"assert <class 'test_...e.<locals>.X'> == 42",
|
||||
" -<class 'test_assertrewrite.TestAssertionRewrite.test_name.<locals>.X'>",
|
||||
" +42",
|
||||
]
|
||||
else:
|
||||
assert msg == ["assert cls == 42"]
|
||||
|
||||
def test_dont_rewrite_if_hasattr_fails(self):
|
||||
def test_dont_rewrite_if_hasattr_fails(self, request):
|
||||
class Y(object):
|
||||
""" A class whos getattr fails, but not with `AttributeError` """
|
||||
|
||||
@@ -173,10 +197,16 @@ class TestAssertionRewrite(object):
|
||||
def f():
|
||||
assert cls().foo == 2 # noqa
|
||||
|
||||
message = getmsg(f, {"cls": Y})
|
||||
assert "assert 3 == 2" in message
|
||||
assert "+ where 3 = Y.foo" in message
|
||||
assert "+ where Y = cls()" in message
|
||||
# XXX: looks like the "where" should also be there in verbose mode?!
|
||||
message = getmsg(f, {"cls": Y}).splitlines()
|
||||
if request.config.getoption("verbose") > 0:
|
||||
assert message == ["assert 3 == 2", " -3", " +2"]
|
||||
else:
|
||||
assert message == [
|
||||
"assert 3 == 2",
|
||||
" + where 3 = Y.foo",
|
||||
" + where Y = cls()",
|
||||
]
|
||||
|
||||
def test_assert_already_has_message(self):
|
||||
def f():
|
||||
@@ -552,15 +582,16 @@ class TestAssertionRewrite(object):
|
||||
|
||||
getmsg(f, must_pass=True)
|
||||
|
||||
def test_len(self):
|
||||
def test_len(self, request):
|
||||
def f():
|
||||
values = list(range(10))
|
||||
assert len(values) == 11
|
||||
|
||||
assert getmsg(f).startswith(
|
||||
"""assert 10 == 11
|
||||
+ where 10 = len(["""
|
||||
)
|
||||
msg = getmsg(f)
|
||||
if request.config.getoption("verbose") > 0:
|
||||
assert msg == "assert 10 == 11\n -10\n +11"
|
||||
else:
|
||||
assert msg == "assert 10 == 11\n + where 10 = len([0, 1, 2, 3, 4, 5, ...])"
|
||||
|
||||
def test_custom_reprcompare(self, monkeypatch):
|
||||
def my_reprcompare(op, left, right):
|
||||
@@ -608,7 +639,7 @@ class TestAssertionRewrite(object):
|
||||
|
||||
assert getmsg(f).startswith("assert '%test' == 'test'")
|
||||
|
||||
def test_custom_repr(self):
|
||||
def test_custom_repr(self, request):
|
||||
def f():
|
||||
class Foo(object):
|
||||
a = 1
|
||||
@@ -619,7 +650,11 @@ class TestAssertionRewrite(object):
|
||||
f = Foo()
|
||||
assert 0 == f.a
|
||||
|
||||
assert r"where 1 = \n{ \n~ \n}.a" in util._format_lines([getmsg(f)])[0]
|
||||
lines = util._format_lines([getmsg(f)])
|
||||
if request.config.getoption("verbose") > 0:
|
||||
assert lines == ["assert 0 == 1\n -0\n +1"]
|
||||
else:
|
||||
assert lines == ["assert 0 == 1\n + where 1 = \\n{ \\n~ \\n}.a"]
|
||||
|
||||
def test_custom_repr_non_ascii(self):
|
||||
def f():
|
||||
@@ -796,7 +831,7 @@ def test_rewritten():
|
||||
)
|
||||
# needs to be a subprocess because pytester explicitly disables this warning
|
||||
result = testdir.runpytest_subprocess()
|
||||
result.stdout.fnmatch_lines("*Module already imported*: _pytest")
|
||||
result.stdout.fnmatch_lines(["*Module already imported*: _pytest"])
|
||||
|
||||
def test_rewrite_module_imported_from_conftest(self, testdir):
|
||||
testdir.makeconftest(
|
||||
@@ -1123,7 +1158,7 @@ class TestAssertionRewriteHookDetails(object):
|
||||
)
|
||||
path.join("data.txt").write("Hey")
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines("*1 passed*")
|
||||
result.stdout.fnmatch_lines(["*1 passed*"])
|
||||
|
||||
|
||||
def test_issue731(testdir):
|
||||
@@ -1154,7 +1189,7 @@ class TestIssue925(object):
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines("*E*assert (False == False) == False")
|
||||
result.stdout.fnmatch_lines(["*E*assert (False == False) == False"])
|
||||
|
||||
def test_long_case(self, testdir):
|
||||
testdir.makepyfile(
|
||||
@@ -1164,7 +1199,7 @@ class TestIssue925(object):
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines("*E*assert (False == True) == True")
|
||||
result.stdout.fnmatch_lines(["*E*assert (False == True) == True"])
|
||||
|
||||
def test_many_brackets(self, testdir):
|
||||
testdir.makepyfile(
|
||||
@@ -1174,7 +1209,7 @@ class TestIssue925(object):
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines("*E*assert True == ((False == True) == True)")
|
||||
result.stdout.fnmatch_lines(["*E*assert True == ((False == True) == True)"])
|
||||
|
||||
|
||||
class TestIssue2121:
|
||||
@@ -1194,7 +1229,33 @@ class TestIssue2121:
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines("*E*assert (1 + 1) == 3")
|
||||
result.stdout.fnmatch_lines(["*E*assert (1 + 1) == 3"])
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
sys.maxsize <= (2 ** 31 - 1), reason="Causes OverflowError on 32bit systems"
|
||||
)
|
||||
@pytest.mark.parametrize("offset", [-1, +1])
|
||||
def test_source_mtime_long_long(testdir, offset):
|
||||
"""Support modification dates after 2038 in rewritten files (#4903).
|
||||
|
||||
pytest would crash with:
|
||||
|
||||
fp.write(struct.pack("<ll", mtime, size))
|
||||
E struct.error: argument out of range
|
||||
"""
|
||||
p = testdir.makepyfile(
|
||||
"""
|
||||
def test(): pass
|
||||
"""
|
||||
)
|
||||
# use unsigned long timestamp which overflows signed long,
|
||||
# which was the cause of the bug
|
||||
# +1 offset also tests masking of 0xFFFFFFFF
|
||||
timestamp = 2 ** 32 + offset
|
||||
os.utime(str(p), (timestamp, timestamp))
|
||||
result = testdir.runpytest()
|
||||
assert result.ret == 0
|
||||
|
||||
|
||||
def test_rewrite_infinite_recursion(testdir, pytestconfig, monkeypatch):
|
||||
@@ -1312,7 +1373,7 @@ class TestEarlyRewriteBailout(object):
|
||||
# Setup conditions for py's fspath trying to import pathlib on py34
|
||||
# always (previously triggered via xdist only).
|
||||
# Ref: https://github.com/pytest-dev/py/pull/207
|
||||
monkeypatch.setattr(sys, "path", [""] + sys.path)
|
||||
monkeypatch.syspath_prepend("")
|
||||
monkeypatch.delitem(sys.modules, "pathlib", raising=False)
|
||||
|
||||
testdir.makepyfile(
|
||||
@@ -1333,4 +1394,4 @@ class TestEarlyRewriteBailout(object):
|
||||
}
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines("* 1 passed in *")
|
||||
result.stdout.fnmatch_lines(["* 1 passed in *"])
|
||||
|
||||
@@ -393,7 +393,7 @@ class TestLastFailed(object):
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines("*1 failed in*")
|
||||
result.stdout.fnmatch_lines(["*1 failed in*"])
|
||||
|
||||
def test_terminal_report_lastfailed(self, testdir):
|
||||
test_a = testdir.makepyfile(
|
||||
@@ -574,7 +574,7 @@ class TestLastFailed(object):
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines("*1 xfailed*")
|
||||
result.stdout.fnmatch_lines(["*1 xfailed*"])
|
||||
assert self.get_cached_last_failed(testdir) == []
|
||||
|
||||
def test_xfail_strict_considered_failure(self, testdir):
|
||||
@@ -587,7 +587,7 @@ class TestLastFailed(object):
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines("*1 failed*")
|
||||
result.stdout.fnmatch_lines(["*1 failed*"])
|
||||
assert self.get_cached_last_failed(testdir) == [
|
||||
"test_xfail_strict_considered_failure.py::test"
|
||||
]
|
||||
@@ -680,12 +680,12 @@ class TestLastFailed(object):
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest(test_bar)
|
||||
result.stdout.fnmatch_lines("*2 passed*")
|
||||
result.stdout.fnmatch_lines(["*2 passed*"])
|
||||
# ensure cache does not forget that test_foo_4 failed once before
|
||||
assert self.get_cached_last_failed(testdir) == ["test_foo.py::test_foo_4"]
|
||||
|
||||
result = testdir.runpytest("--last-failed")
|
||||
result.stdout.fnmatch_lines("*1 failed, 3 deselected*")
|
||||
result.stdout.fnmatch_lines(["*1 failed, 3 deselected*"])
|
||||
assert self.get_cached_last_failed(testdir) == ["test_foo.py::test_foo_4"]
|
||||
|
||||
# 3. fix test_foo_4, run only test_foo.py
|
||||
@@ -698,11 +698,11 @@ class TestLastFailed(object):
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest(test_foo, "--last-failed")
|
||||
result.stdout.fnmatch_lines("*1 passed, 1 deselected*")
|
||||
result.stdout.fnmatch_lines(["*1 passed, 1 deselected*"])
|
||||
assert self.get_cached_last_failed(testdir) == []
|
||||
|
||||
result = testdir.runpytest("--last-failed")
|
||||
result.stdout.fnmatch_lines("*4 passed*")
|
||||
result.stdout.fnmatch_lines(["*4 passed*"])
|
||||
assert self.get_cached_last_failed(testdir) == []
|
||||
|
||||
def test_lastfailed_no_failures_behavior_all_passed(self, testdir):
|
||||
@@ -884,7 +884,7 @@ class TestReadme(object):
|
||||
def test_readme_failed(self, testdir):
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
def test_always_passes():
|
||||
def test_always_fails():
|
||||
assert 0
|
||||
"""
|
||||
)
|
||||
|
||||
@@ -134,12 +134,22 @@ def test_capturing_bytes_in_utf8_encoding(testdir, method):
|
||||
def test_collect_capturing(testdir):
|
||||
p = testdir.makepyfile(
|
||||
"""
|
||||
import sys
|
||||
|
||||
print("collect %s failure" % 13)
|
||||
sys.stderr.write("collect %s_stderr failure" % 13)
|
||||
import xyz42123
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest(p)
|
||||
result.stdout.fnmatch_lines(["*Captured stdout*", "*collect 13 failure*"])
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"*Captured stdout*",
|
||||
"collect 13 failure",
|
||||
"*Captured stderr*",
|
||||
"collect 13_stderr failure",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class TestPerTestCapturing(object):
|
||||
@@ -566,7 +576,7 @@ class TestCaptureFixture(object):
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"*test_hello*",
|
||||
"*capsysbinary is only supported on python 3*",
|
||||
"*capsysbinary is only supported on Python 3*",
|
||||
"*1 error in*",
|
||||
]
|
||||
)
|
||||
@@ -673,7 +683,7 @@ class TestCaptureFixture(object):
|
||||
)
|
||||
)
|
||||
result = testdir.runpytest_subprocess()
|
||||
result.stdout.fnmatch_lines("*1 passed*")
|
||||
result.stdout.fnmatch_lines(["*1 passed*"])
|
||||
assert "stdout contents begin" not in result.stdout.str()
|
||||
assert "stderr contents begin" not in result.stdout.str()
|
||||
|
||||
@@ -1231,20 +1241,27 @@ class TestStdCaptureFDinvalidFD(object):
|
||||
"""
|
||||
import os
|
||||
from _pytest import capture
|
||||
|
||||
def StdCaptureFD(out=True, err=True, in_=True):
|
||||
return capture.MultiCapture(out, err, in_,
|
||||
Capture=capture.FDCapture)
|
||||
Capture=capture.FDCapture)
|
||||
|
||||
def test_stdout():
|
||||
os.close(1)
|
||||
cap = StdCaptureFD(out=True, err=False, in_=False)
|
||||
assert repr(cap.out) == "<FDCapture 1 oldfd=None>"
|
||||
cap.stop_capturing()
|
||||
|
||||
def test_stderr():
|
||||
os.close(2)
|
||||
cap = StdCaptureFD(out=False, err=True, in_=False)
|
||||
assert repr(cap.err) == "<FDCapture 2 oldfd=None>"
|
||||
cap.stop_capturing()
|
||||
|
||||
def test_stdin():
|
||||
os.close(0)
|
||||
cap = StdCaptureFD(out=False, err=False, in_=True)
|
||||
assert repr(cap.in_) == "<FDCapture 0 oldfd=None>"
|
||||
cap.stop_capturing()
|
||||
"""
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@ from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import pprint
|
||||
import sys
|
||||
import textwrap
|
||||
@@ -10,6 +11,7 @@ import py
|
||||
|
||||
import pytest
|
||||
from _pytest.main import _in_venv
|
||||
from _pytest.main import EXIT_INTERRUPTED
|
||||
from _pytest.main import EXIT_NOTESTSCOLLECTED
|
||||
from _pytest.main import Session
|
||||
|
||||
@@ -349,10 +351,10 @@ class TestCustomConftests(object):
|
||||
p = testdir.makepyfile("def test_hello(): pass")
|
||||
result = testdir.runpytest(p)
|
||||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines("*1 passed*")
|
||||
result.stdout.fnmatch_lines(["*1 passed*"])
|
||||
result = testdir.runpytest()
|
||||
assert result.ret == EXIT_NOTESTSCOLLECTED
|
||||
result.stdout.fnmatch_lines("*collected 0 items*")
|
||||
result.stdout.fnmatch_lines(["*collected 0 items*"])
|
||||
|
||||
def test_collectignore_exclude_on_option(self, testdir):
|
||||
testdir.makeconftest(
|
||||
@@ -389,10 +391,10 @@ class TestCustomConftests(object):
|
||||
testdir.makepyfile(test_welt="def test_hallo(): pass")
|
||||
result = testdir.runpytest()
|
||||
assert result.ret == EXIT_NOTESTSCOLLECTED
|
||||
result.stdout.fnmatch_lines("*collected 0 items*")
|
||||
result.stdout.fnmatch_lines(["*collected 0 items*"])
|
||||
result = testdir.runpytest("--XX")
|
||||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines("*2 passed*")
|
||||
result.stdout.fnmatch_lines(["*2 passed*"])
|
||||
|
||||
def test_pytest_fs_collect_hooks_are_seen(self, testdir):
|
||||
testdir.makeconftest(
|
||||
@@ -1108,7 +1110,7 @@ def test_collect_pyargs_with_testpaths(testdir, monkeypatch):
|
||||
"""
|
||||
)
|
||||
)
|
||||
monkeypatch.setenv("PYTHONPATH", str(testdir.tmpdir))
|
||||
monkeypatch.setenv("PYTHONPATH", str(testdir.tmpdir), prepend=os.pathsep)
|
||||
with root.as_cwd():
|
||||
result = testdir.runpytest_subprocess()
|
||||
result.stdout.fnmatch_lines(["*1 passed in*"])
|
||||
@@ -1233,3 +1235,20 @@ def test_collect_sub_with_symlinks(use_pkg, testdir):
|
||||
"*2 passed in*",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def test_collector_respects_tbstyle(testdir):
|
||||
p1 = testdir.makepyfile("assert 0")
|
||||
result = testdir.runpytest(p1, "--tb=native")
|
||||
assert result.ret == EXIT_INTERRUPTED
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"*_ ERROR collecting test_collector_respects_tbstyle.py _*",
|
||||
"Traceback (most recent call last):",
|
||||
' File "*/test_collector_respects_tbstyle.py", line 1, in <module>',
|
||||
" assert 0",
|
||||
"AssertionError: assert 0",
|
||||
"*! Interrupted: 1 errors during collection !*",
|
||||
"*= 1 error in *",
|
||||
]
|
||||
)
|
||||
|
||||
@@ -18,10 +18,10 @@ from _pytest.outcomes import OutcomeException
|
||||
|
||||
def test_is_generator():
|
||||
def zap():
|
||||
yield
|
||||
yield # pragma: no cover
|
||||
|
||||
def foo():
|
||||
pass
|
||||
pass # pragma: no cover
|
||||
|
||||
assert is_generator(zap)
|
||||
assert not is_generator(foo)
|
||||
@@ -37,15 +37,20 @@ def test_real_func_loop_limit():
|
||||
|
||||
def __getattr__(self, attr):
|
||||
if not self.left:
|
||||
raise RuntimeError("its over")
|
||||
raise RuntimeError("it's over") # pragma: no cover
|
||||
self.left -= 1
|
||||
return self
|
||||
|
||||
evil = Evil()
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
res = get_real_func(evil)
|
||||
print(res)
|
||||
with pytest.raises(
|
||||
ValueError,
|
||||
match=(
|
||||
"could not find real function of <Evil left=800>\n"
|
||||
"stopped at <Evil left=800>"
|
||||
),
|
||||
):
|
||||
get_real_func(evil)
|
||||
|
||||
|
||||
def test_get_real_func():
|
||||
@@ -54,14 +59,14 @@ def test_get_real_func():
|
||||
def decorator(f):
|
||||
@wraps(f)
|
||||
def inner():
|
||||
pass
|
||||
pass # pragma: no cover
|
||||
|
||||
if six.PY2:
|
||||
inner.__wrapped__ = f
|
||||
return inner
|
||||
|
||||
def func():
|
||||
pass
|
||||
pass # pragma: no cover
|
||||
|
||||
wrapped_func = decorator(decorator(func))
|
||||
assert get_real_func(wrapped_func) is func
|
||||
|
||||
@@ -5,6 +5,8 @@ from __future__ import print_function
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
import attr
|
||||
|
||||
import _pytest._code
|
||||
import pytest
|
||||
from _pytest.config import _iter_rewritable_modules
|
||||
@@ -13,6 +15,8 @@ from _pytest.config.findpaths import determine_setup
|
||||
from _pytest.config.findpaths import get_common_ancestor
|
||||
from _pytest.config.findpaths import getcfg
|
||||
from _pytest.main import EXIT_NOTESTSCOLLECTED
|
||||
from _pytest.main import EXIT_OK
|
||||
from _pytest.main import EXIT_TESTSFAILED
|
||||
from _pytest.main import EXIT_USAGEERROR
|
||||
|
||||
|
||||
@@ -42,6 +46,22 @@ class TestParseIni(object):
|
||||
"""correctly handle zero length arguments (a la pytest '')"""
|
||||
getcfg([""])
|
||||
|
||||
def test_setupcfg_uses_toolpytest_with_pytest(self, testdir):
|
||||
p1 = testdir.makepyfile("def test(): pass")
|
||||
testdir.makefile(
|
||||
".cfg",
|
||||
setup="""
|
||||
[tool:pytest]
|
||||
testpaths=%s
|
||||
[pytest]
|
||||
testpaths=ignored
|
||||
"""
|
||||
% p1.basename,
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines(["*, inifile: setup.cfg, *", "* 1 passed in *"])
|
||||
assert result.ret == 0
|
||||
|
||||
def test_append_parse_args(self, testdir, tmpdir, monkeypatch):
|
||||
monkeypatch.setenv("PYTEST_ADDOPTS", '--color no -rs --tb="short"')
|
||||
tmpdir.join("pytest.ini").write(
|
||||
@@ -416,7 +436,7 @@ class TestConfigAPI(object):
|
||||
|
||||
|
||||
class TestConfigFromdictargs(object):
|
||||
def test_basic_behavior(self):
|
||||
def test_basic_behavior(self, _sys_snapshot):
|
||||
from _pytest.config import Config
|
||||
|
||||
option_dict = {"verbose": 444, "foo": "bar", "capture": "no"}
|
||||
@@ -430,7 +450,7 @@ class TestConfigFromdictargs(object):
|
||||
assert config.option.capture == "no"
|
||||
assert config.args == args
|
||||
|
||||
def test_origargs(self):
|
||||
def test_origargs(self, _sys_snapshot):
|
||||
"""Show that fromdictargs can handle args in their "orig" format"""
|
||||
from _pytest.config import Config
|
||||
|
||||
@@ -622,7 +642,28 @@ def test_disable_plugin_autoload(testdir, monkeypatch, parse_args, should_load):
|
||||
pkg_resources = pytest.importorskip("pkg_resources")
|
||||
|
||||
def my_iter(group, name=None):
|
||||
raise AssertionError("Should not be called")
|
||||
assert group == "pytest11"
|
||||
assert name == "mytestplugin"
|
||||
return iter([DummyEntryPoint()])
|
||||
|
||||
@attr.s
|
||||
class DummyEntryPoint(object):
|
||||
name = "mytestplugin"
|
||||
version = "1.0"
|
||||
|
||||
@property
|
||||
def project_name(self):
|
||||
return self.name
|
||||
|
||||
def load(self):
|
||||
return sys.modules[self.name]
|
||||
|
||||
@property
|
||||
def dist(self):
|
||||
return self
|
||||
|
||||
def _get_metadata(self, *args):
|
||||
return []
|
||||
|
||||
class PseudoPlugin(object):
|
||||
x = 42
|
||||
@@ -674,9 +715,9 @@ def test_invalid_options_show_extra_information(testdir):
|
||||
["-v", "dir2", "dir1"],
|
||||
],
|
||||
)
|
||||
def test_consider_args_after_options_for_rootdir_and_inifile(testdir, args):
|
||||
def test_consider_args_after_options_for_rootdir(testdir, args):
|
||||
"""
|
||||
Consider all arguments in the command-line for rootdir and inifile
|
||||
Consider all arguments in the command-line for rootdir
|
||||
discovery, even if they happen to occur after an option. #949
|
||||
"""
|
||||
# replace "dir1" and "dir2" from "args" into their real directory
|
||||
@@ -690,7 +731,7 @@ def test_consider_args_after_options_for_rootdir_and_inifile(testdir, args):
|
||||
args[i] = d2
|
||||
with root.as_cwd():
|
||||
result = testdir.runpytest(*args)
|
||||
result.stdout.fnmatch_lines(["*rootdir: *myroot, inifile:"])
|
||||
result.stdout.fnmatch_lines(["*rootdir: *myroot"])
|
||||
|
||||
|
||||
@pytest.mark.skipif("sys.platform == 'win32'")
|
||||
@@ -729,7 +770,7 @@ def test_notify_exception(testdir, capfd):
|
||||
config = testdir.parseconfig()
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
raise ValueError(1)
|
||||
config.notify_exception(excinfo)
|
||||
config.notify_exception(excinfo, config.option)
|
||||
out, err = capfd.readouterr()
|
||||
assert "ValueError" in err
|
||||
|
||||
@@ -738,15 +779,20 @@ def test_notify_exception(testdir, capfd):
|
||||
return True
|
||||
|
||||
config.pluginmanager.register(A())
|
||||
config.notify_exception(excinfo)
|
||||
config.notify_exception(excinfo, config.option)
|
||||
out, err = capfd.readouterr()
|
||||
assert not err
|
||||
|
||||
config = testdir.parseconfig("-p", "no:terminal")
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
raise ValueError(1)
|
||||
config.notify_exception(excinfo, config.option)
|
||||
out, err = capfd.readouterr()
|
||||
assert "ValueError" in err
|
||||
|
||||
def test_load_initial_conftest_last_ordering(testdir):
|
||||
from _pytest.config import get_config
|
||||
|
||||
pm = get_config().pluginmanager
|
||||
def test_load_initial_conftest_last_ordering(testdir, _config_for_test):
|
||||
pm = _config_for_test.pluginmanager
|
||||
|
||||
class My(object):
|
||||
def pytest_load_initial_conftests(self):
|
||||
@@ -780,7 +826,7 @@ def test_collect_pytest_prefix_bug_integration(testdir):
|
||||
"""Integration test for issue #3775"""
|
||||
p = testdir.copy_example("config/collect_pytest_prefix")
|
||||
result = testdir.runpytest(p)
|
||||
result.stdout.fnmatch_lines("* 1 passed *")
|
||||
result.stdout.fnmatch_lines(["* 1 passed *"])
|
||||
|
||||
|
||||
def test_collect_pytest_prefix_bug(pytestconfig):
|
||||
@@ -1018,21 +1064,17 @@ class TestOverrideIniArgs(object):
|
||||
assert rootdir == tmpdir
|
||||
assert inifile is None
|
||||
|
||||
def test_addopts_before_initini(self, monkeypatch):
|
||||
def test_addopts_before_initini(self, monkeypatch, _config_for_test, _sys_snapshot):
|
||||
cache_dir = ".custom_cache"
|
||||
monkeypatch.setenv("PYTEST_ADDOPTS", "-o cache_dir=%s" % cache_dir)
|
||||
from _pytest.config import get_config
|
||||
|
||||
config = get_config()
|
||||
config = _config_for_test
|
||||
config._preparse([], addopts=True)
|
||||
assert config._override_ini == ["cache_dir=%s" % cache_dir]
|
||||
|
||||
def test_addopts_from_env_not_concatenated(self, monkeypatch):
|
||||
def test_addopts_from_env_not_concatenated(self, monkeypatch, _config_for_test):
|
||||
"""PYTEST_ADDOPTS should not take values from normal args (#4265)."""
|
||||
from _pytest.config import get_config
|
||||
|
||||
monkeypatch.setenv("PYTEST_ADDOPTS", "-o")
|
||||
config = get_config()
|
||||
config = _config_for_test
|
||||
with pytest.raises(UsageError) as excinfo:
|
||||
config._preparse(["cache_dir=ignored"], addopts=True)
|
||||
assert (
|
||||
@@ -1057,11 +1099,9 @@ class TestOverrideIniArgs(object):
|
||||
)
|
||||
assert result.ret == _pytest.main.EXIT_USAGEERROR
|
||||
|
||||
def test_override_ini_does_not_contain_paths(self):
|
||||
def test_override_ini_does_not_contain_paths(self, _config_for_test, _sys_snapshot):
|
||||
"""Check that -o no longer swallows all options after it (#3103)"""
|
||||
from _pytest.config import get_config
|
||||
|
||||
config = get_config()
|
||||
config = _config_for_test
|
||||
config._preparse(["-o", "cache_dir=/cache", "/some/test/path"])
|
||||
assert config._override_ini == ["cache_dir=/cache"]
|
||||
|
||||
@@ -1153,3 +1193,53 @@ def test_help_and_version_after_argument_error(testdir):
|
||||
["*pytest*{}*imported from*".format(pytest.__version__)]
|
||||
)
|
||||
assert result.ret == EXIT_USAGEERROR
|
||||
|
||||
|
||||
def test_config_does_not_load_blocked_plugin_from_args(testdir):
|
||||
"""This tests that pytest's config setup handles "-p no:X"."""
|
||||
p = testdir.makepyfile("def test(capfd): pass")
|
||||
result = testdir.runpytest(str(p), "-pno:capture")
|
||||
result.stdout.fnmatch_lines(["E fixture 'capfd' not found"])
|
||||
assert result.ret == EXIT_TESTSFAILED
|
||||
|
||||
result = testdir.runpytest(str(p), "-pno:capture", "-s")
|
||||
result.stderr.fnmatch_lines(["*: error: unrecognized arguments: -s"])
|
||||
assert result.ret == EXIT_USAGEERROR
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"plugin",
|
||||
[
|
||||
x
|
||||
for x in _pytest.config.default_plugins
|
||||
if x
|
||||
not in [
|
||||
"fixtures",
|
||||
"helpconfig", # Provides -p.
|
||||
"main",
|
||||
"mark",
|
||||
"python",
|
||||
"runner",
|
||||
"terminal", # works in OK case (no output), but not with failures.
|
||||
]
|
||||
],
|
||||
)
|
||||
def test_config_blocked_default_plugins(testdir, plugin):
|
||||
if plugin == "debugging":
|
||||
# https://github.com/pytest-dev/pytest-xdist/pull/422
|
||||
try:
|
||||
import xdist # noqa: F401
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
pytest.skip("does not work with xdist currently")
|
||||
|
||||
p = testdir.makepyfile("def test(): pass")
|
||||
result = testdir.runpytest(str(p), "-pno:%s" % plugin)
|
||||
assert result.ret == EXIT_OK
|
||||
result.stdout.fnmatch_lines(["* 1 passed in *"])
|
||||
|
||||
p = testdir.makepyfile("def test(): assert 0")
|
||||
result = testdir.runpytest(str(p), "-pno:%s" % plugin)
|
||||
assert result.ret == EXIT_TESTSFAILED
|
||||
result.stdout.fnmatch_lines(["* 1 failed in *"])
|
||||
|
||||
@@ -13,17 +13,6 @@ from _pytest.main import EXIT_OK
|
||||
from _pytest.main import EXIT_USAGEERROR
|
||||
|
||||
|
||||
@pytest.fixture(scope="module", params=["global", "inpackage"])
|
||||
def basedir(request, tmpdir_factory):
|
||||
tmpdir = tmpdir_factory.mktemp("basedir", numbered=True)
|
||||
tmpdir.ensure("adir/conftest.py").write("a=1 ; Directory = 3")
|
||||
tmpdir.ensure("adir/b/conftest.py").write("b=2 ; a = 1.5")
|
||||
if request.param == "inpackage":
|
||||
tmpdir.ensure("adir/__init__.py")
|
||||
tmpdir.ensure("adir/b/__init__.py")
|
||||
return tmpdir
|
||||
|
||||
|
||||
def ConftestWithSetinitial(path):
|
||||
conftest = PytestPluginManager()
|
||||
conftest_setinitial(conftest, [path])
|
||||
@@ -41,7 +30,19 @@ def conftest_setinitial(conftest, args, confcutdir=None):
|
||||
conftest._set_initial_conftests(Namespace())
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("_sys_snapshot")
|
||||
class TestConftestValueAccessGlobal(object):
|
||||
@pytest.fixture(scope="module", params=["global", "inpackage"])
|
||||
def basedir(self, request, tmpdir_factory):
|
||||
tmpdir = tmpdir_factory.mktemp("basedir", numbered=True)
|
||||
tmpdir.ensure("adir/conftest.py").write("a=1 ; Directory = 3")
|
||||
tmpdir.ensure("adir/b/conftest.py").write("b=2 ; a = 1.5")
|
||||
if request.param == "inpackage":
|
||||
tmpdir.ensure("adir/__init__.py")
|
||||
tmpdir.ensure("adir/b/__init__.py")
|
||||
|
||||
yield tmpdir
|
||||
|
||||
def test_basic_init(self, basedir):
|
||||
conftest = PytestPluginManager()
|
||||
p = basedir.join("adir")
|
||||
@@ -49,10 +50,10 @@ class TestConftestValueAccessGlobal(object):
|
||||
|
||||
def test_immediate_initialiation_and_incremental_are_the_same(self, basedir):
|
||||
conftest = PytestPluginManager()
|
||||
len(conftest._dirpath2confmods)
|
||||
assert not len(conftest._dirpath2confmods)
|
||||
conftest._getconftestmodules(basedir)
|
||||
snap1 = len(conftest._dirpath2confmods)
|
||||
# assert len(conftest._dirpath2confmods) == snap1 + 1
|
||||
assert snap1 == 1
|
||||
conftest._getconftestmodules(basedir.join("adir"))
|
||||
assert len(conftest._dirpath2confmods) == snap1 + 1
|
||||
conftest._getconftestmodules(basedir.join("b"))
|
||||
@@ -80,7 +81,7 @@ class TestConftestValueAccessGlobal(object):
|
||||
assert path.purebasename.startswith("conftest")
|
||||
|
||||
|
||||
def test_conftest_in_nonpkg_with_init(tmpdir):
|
||||
def test_conftest_in_nonpkg_with_init(tmpdir, _sys_snapshot):
|
||||
tmpdir.ensure("adir-1.0/conftest.py").write("a=1 ; Directory = 3")
|
||||
tmpdir.ensure("adir-1.0/b/conftest.py").write("b=2 ; a = 1.5")
|
||||
tmpdir.ensure("adir-1.0/b/__init__.py")
|
||||
|
||||
@@ -188,6 +188,18 @@ class TestDoctests(object):
|
||||
]
|
||||
)
|
||||
|
||||
def test_doctest_skip(self, testdir):
|
||||
testdir.maketxtfile(
|
||||
"""
|
||||
>>> 1
|
||||
1
|
||||
>>> import pytest
|
||||
>>> pytest.skip("")
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest("--doctest-modules")
|
||||
result.stdout.fnmatch_lines(["*1 skipped*"])
|
||||
|
||||
def test_docstring_partial_context_around_error(self, testdir):
|
||||
"""Test that we show some context before the actual line of a failing
|
||||
doctest.
|
||||
@@ -956,7 +968,7 @@ class TestDoctestAutoUseFixtures(object):
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest("--doctest-modules")
|
||||
result.stdout.fnmatch_lines("*2 passed*")
|
||||
result.stdout.fnmatch_lines(["*2 passed*"])
|
||||
|
||||
@pytest.mark.parametrize("scope", SCOPES)
|
||||
@pytest.mark.parametrize("enable_doctest", [True, False])
|
||||
|
||||
@@ -818,14 +818,11 @@ def test_invalid_xml_escape():
|
||||
|
||||
def test_logxml_path_expansion(tmpdir, monkeypatch):
|
||||
home_tilde = py.path.local(os.path.expanduser("~")).join("test.xml")
|
||||
|
||||
xml_tilde = LogXML("~%stest.xml" % tmpdir.sep, None)
|
||||
assert xml_tilde.logfile == home_tilde
|
||||
|
||||
# this is here for when $HOME is not set correct
|
||||
monkeypatch.setenv("HOME", str(tmpdir))
|
||||
home_var = os.path.normpath(os.path.expandvars("$HOME/test.xml"))
|
||||
|
||||
xml_var = LogXML("$HOME%stest.xml" % tmpdir.sep, None)
|
||||
assert xml_var.logfile == home_var
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user