Compare commits
302 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96784c2052 | ||
|
|
ebba3ebe67 | ||
|
|
1d09e1d8ce | ||
|
|
d59786fcc4 | ||
|
|
af34164858 | ||
|
|
167e9b954a | ||
|
|
9cde67c09c | ||
|
|
ae2fc27799 | ||
|
|
56989a8350 | ||
|
|
ce0b0518c7 | ||
|
|
bfa1993840 | ||
|
|
63691f5656 | ||
|
|
041044eef0 | ||
|
|
1af31a09f4 | ||
|
|
ccdb248397 | ||
|
|
2368fbb63c | ||
|
|
f6dfca7182 | ||
|
|
4a69104af3 | ||
|
|
2cf4c67e45 | ||
|
|
2ad43ee442 | ||
|
|
15278aacb9 | ||
|
|
57caa4e25e | ||
|
|
b6fa4e248f | ||
|
|
9df1b037d6 | ||
|
|
28dbffdaf2 | ||
|
|
8bced7415c | ||
|
|
72d98a7c52 | ||
|
|
0dd85157ea | ||
|
|
fadac0ffc0 | ||
|
|
799b72cf6f | ||
|
|
fc0f89ac14 | ||
|
|
771e860011 | ||
|
|
cb39bd0651 | ||
|
|
c94b2b227e | ||
|
|
cd72e23f3f | ||
|
|
b9990f77ff | ||
|
|
6b126997e1 | ||
|
|
02ae7d8531 | ||
|
|
db106d6085 | ||
|
|
b0eed7b56c | ||
|
|
51d358a7a9 | ||
|
|
c54cdd05b8 | ||
|
|
dde147641e | ||
|
|
2abd005cc9 | ||
|
|
366b88388b | ||
|
|
0fc1801d0d | ||
|
|
11ff14be1f | ||
|
|
dc0b4efffa | ||
|
|
864d7fef30 | ||
|
|
18035211f5 | ||
|
|
61080da89d | ||
|
|
ac5704290f | ||
|
|
cdd0e18ca8 | ||
|
|
e4871f7722 | ||
|
|
517ee588c6 | ||
|
|
1b51497936 | ||
|
|
a83ee197c6 | ||
|
|
12f94b81c0 | ||
|
|
e6b01b45f1 | ||
|
|
56dd7bc551 | ||
|
|
8a768b0db0 | ||
|
|
8dca8f3c9f | ||
|
|
cc335d44a0 | ||
|
|
4f4c91caf5 | ||
|
|
ba5a295544 | ||
|
|
93bdbf7572 | ||
|
|
ab8907f6f5 | ||
|
|
215a2ed3de | ||
|
|
f7285b6ab2 | ||
|
|
ee0a306ee4 | ||
|
|
7931b5b489 | ||
|
|
c5bbf8ac73 | ||
|
|
253c5786af | ||
|
|
c4550bc922 | ||
|
|
2c00f8aad1 | ||
|
|
456715a5c1 | ||
|
|
f858177495 | ||
|
|
54b8ad4554 | ||
|
|
d72154acda | ||
|
|
f55ded20a9 | ||
|
|
17655b54f2 | ||
|
|
12edc4e7b8 | ||
|
|
2868c31495 | ||
|
|
39a13d7064 | ||
|
|
e4e4fd1e52 | ||
|
|
e8c220b9bd | ||
|
|
9646a1cd7a | ||
|
|
9087ac4010 | ||
|
|
093e19a7d9 | ||
|
|
9e867ce864 | ||
|
|
8abf30ad71 | ||
|
|
ea25eb1ecc | ||
|
|
58b6e8616c | ||
|
|
f129ba617f | ||
|
|
99d957bd3d | ||
|
|
661013c3e9 | ||
|
|
141c51f0cb | ||
|
|
d65c7658d5 | ||
|
|
7855284ef7 | ||
|
|
5b0f88712b | ||
|
|
2e42d937dc | ||
|
|
27d932e882 | ||
|
|
40091ec2c7 | ||
|
|
76fb9970c8 | ||
|
|
d32f2c5c14 | ||
|
|
49defa2890 | ||
|
|
fe2dae4885 | ||
|
|
ced62f30ba | ||
|
|
bbd1cbb0b3 | ||
|
|
d4dfd526c1 | ||
|
|
d4351ac5a2 | ||
|
|
766d2daa06 | ||
|
|
836c9f82f1 | ||
|
|
46d6a3fc27 | ||
|
|
1dfa303b1e | ||
|
|
6258248865 | ||
|
|
4808145846 | ||
|
|
a0666354dd | ||
|
|
ce55dcf64c | ||
|
|
d7be039f1b | ||
|
|
7e1fac5f91 | ||
|
|
486ded3fca | ||
|
|
0be84cd68b | ||
|
|
323c846ce6 | ||
|
|
3bd9f981a2 | ||
|
|
7ded937e19 | ||
|
|
6d0667f1db | ||
|
|
7c380b19f3 | ||
|
|
5322f422e3 | ||
|
|
c6c326f076 | ||
|
|
d6832a8b56 | ||
|
|
3bfaa8ab84 | ||
|
|
9fb305b17b | ||
|
|
e3bf9cede4 | ||
|
|
4a49715614 | ||
|
|
86c7dcff68 | ||
|
|
7268462b33 | ||
|
|
448830e656 | ||
|
|
3683d92c53 | ||
|
|
d3d8d53e41 | ||
|
|
7a271a91b0 | ||
|
|
47f5c29002 | ||
|
|
27d2683a02 | ||
|
|
792f365c14 | ||
|
|
91b2797498 | ||
|
|
c27c8f41a8 | ||
|
|
ee54fb9a6b | ||
|
|
10ddc466bf | ||
|
|
24c83d725a | ||
|
|
6bf4692c7d | ||
|
|
81426c3d19 | ||
|
|
ed42ada373 | ||
|
|
e2667106a2 | ||
|
|
29d5849519 | ||
|
|
eabf15b626 | ||
|
|
2dc619cbf4 | ||
|
|
307fa7a42a | ||
|
|
ef97121d42 | ||
|
|
d46b6b2bc3 | ||
|
|
2cb3534679 | ||
|
|
8e11fe5304 | ||
|
|
36dc671843 | ||
|
|
dbaa9464ba | ||
|
|
933de16fe4 | ||
|
|
e8348a1d12 | ||
|
|
0f5263cdc3 | ||
|
|
4736b2bdfb | ||
|
|
8ecdd4e9ff | ||
|
|
b3940666a7 | ||
|
|
e20987ce82 | ||
|
|
584051aa90 | ||
|
|
16e2737da3 | ||
|
|
36c2a101cb | ||
|
|
ebd597b2fd | ||
|
|
94829c391b | ||
|
|
b82d6f7a0b | ||
|
|
4a436b5470 | ||
|
|
ad6f63edda | ||
|
|
3036914097 | ||
|
|
2831cb9ab5 | ||
|
|
00716177b4 | ||
|
|
85cc9b8f12 | ||
|
|
fed4f73a61 | ||
|
|
d76fa59b35 | ||
|
|
2532dc1dbb | ||
|
|
642cd86dd1 | ||
|
|
b3a5b0ebe1 | ||
|
|
8b4a29357e | ||
|
|
ab3637d486 | ||
|
|
66a690928c | ||
|
|
8e00280fc1 | ||
|
|
d053cdfbbb | ||
|
|
2e39fd89d1 | ||
|
|
b48e23d54c | ||
|
|
c9a85b0e78 | ||
|
|
bf265a424d | ||
|
|
5436e42990 | ||
|
|
67f40e18a7 | ||
|
|
52ff1eaf37 | ||
|
|
602e74c2a7 | ||
|
|
be511c1a05 | ||
|
|
f36f9d2698 | ||
|
|
dbfb3ccc42 | ||
|
|
d1322570dd | ||
|
|
4c9015c3b1 | ||
|
|
c14a23d4e4 | ||
|
|
b8fc3e569a | ||
|
|
e0f6fce9e9 | ||
|
|
d93de6cc67 | ||
|
|
aeb92accb2 | ||
|
|
943bbdd8ce | ||
|
|
9a3836a0cf | ||
|
|
4b164d947d | ||
|
|
11a07211b6 | ||
|
|
28a3f0fcb9 | ||
|
|
b8c30aab2b | ||
|
|
b8958168f5 | ||
|
|
aaaae0b232 | ||
|
|
c55d641963 | ||
|
|
4ec85f934c | ||
|
|
8393fdd51d | ||
|
|
4071c8a4a8 | ||
|
|
e8c10d4a98 | ||
|
|
a86035625c | ||
|
|
4f631440be | ||
|
|
3901569f26 | ||
|
|
689b856cb7 | ||
|
|
65545d8fb2 | ||
|
|
1caf6d5907 | ||
|
|
55871c68a4 | ||
|
|
fc11b81005 | ||
|
|
a6fb4c8268 | ||
|
|
48dcc67274 | ||
|
|
ccaec8d360 | ||
|
|
66609665f2 | ||
|
|
4b36f9aa64 | ||
|
|
5ecbb0acba | ||
|
|
20902deb75 | ||
|
|
ed5556bdac | ||
|
|
ee64f1fb9f | ||
|
|
8e0e862c84 | ||
|
|
42422a7f62 | ||
|
|
f3a173b736 | ||
|
|
5c38a5160d | ||
|
|
dcf9eb0104 | ||
|
|
dd225e1b9d | ||
|
|
900cef6397 | ||
|
|
0d095fc978 | ||
|
|
dcd635ba0c | ||
|
|
33f0338eeb | ||
|
|
d5e5433553 | ||
|
|
d2906950ce | ||
|
|
fe7050ba00 | ||
|
|
a1208f5631 | ||
|
|
870a93c37b | ||
|
|
96b2ae6654 | ||
|
|
b098292352 | ||
|
|
212937eb3e | ||
|
|
70c7273640 | ||
|
|
e5ab62b1b6 | ||
|
|
b8b9e8d41c | ||
|
|
e712adc226 | ||
|
|
f9ac60807c | ||
|
|
3f03625a5d | ||
|
|
29d3faed66 | ||
|
|
c5dec6056f | ||
|
|
642847c079 | ||
|
|
f102ccc8f0 | ||
|
|
20f93ae8fa | ||
|
|
1101a20408 | ||
|
|
df435fa8bd | ||
|
|
a5269b26e0 | ||
|
|
5010e02eda | ||
|
|
956b3aca97 | ||
|
|
5d8467bedc | ||
|
|
0d04aa7c59 | ||
|
|
e03a19f88d | ||
|
|
fcc5b6d604 | ||
|
|
42afce27b3 | ||
|
|
56d0b5a7e2 | ||
|
|
c30184709d | ||
|
|
83802d1494 | ||
|
|
18cc74b8d0 | ||
|
|
048342817b | ||
|
|
d1a3aa7b2b | ||
|
|
e967d4587a | ||
|
|
a79dc12f1e | ||
|
|
913c07e414 | ||
|
|
4a9f468aac | ||
|
|
05155e4db0 | ||
|
|
bceaede198 | ||
|
|
ae8f3695b5 | ||
|
|
da6830f19b | ||
|
|
32ee0b9c88 | ||
|
|
feb8240410 | ||
|
|
b7dd9154c3 | ||
|
|
482bd5efd2 | ||
|
|
bf074b37a3 | ||
|
|
495a55725b | ||
|
|
53c9124fc9 | ||
|
|
ab40696007 | ||
|
|
d12f46caef |
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -3,7 +3,7 @@ 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):
|
||||
|
||||
- [ ] Create a new changelog file in the `changelog` folder, with a name like `<ISSUE NUMBER>.<TYPE>.rst`. See [changelog/README.rst](/changelog/README.rst) for details.
|
||||
- [ ] Create a new changelog file in the `changelog` folder, with a name like `<ISSUE NUMBER>.<TYPE>.rst`. See [changelog/README.rst](https://github.com/pytest-dev/pytest/blob/master/changelog/README.rst) for details.
|
||||
- [ ] Target the `master` branch for bug fixes, documentation updates and trivial changes.
|
||||
- [ ] Target the `features` branch for new features and removals/deprecations.
|
||||
- [ ] Include documentation when adding new features.
|
||||
|
||||
@@ -7,26 +7,34 @@ repos:
|
||||
args: [--safe, --quiet]
|
||||
language_version: python3
|
||||
- repo: https://github.com/asottile/blacken-docs
|
||||
rev: v0.2.0
|
||||
rev: v0.3.0
|
||||
hooks:
|
||||
- id: blacken-docs
|
||||
additional_dependencies: [black==18.6b4]
|
||||
additional_dependencies: [black==18.9b0]
|
||||
language_version: python3
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v1.3.0
|
||||
rev: v2.0.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
- id: check-yaml
|
||||
- id: debug-statements
|
||||
exclude: _pytest/debugging.py
|
||||
language_version: python3
|
||||
- id: flake8
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v1.2.0
|
||||
language_version: python3
|
||||
- repo: https://github.com/asottile/reorder_python_imports
|
||||
rev: v1.3.2
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
- id: reorder-python-imports
|
||||
args: ['--application-directories=.:src']
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v1.8.0
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--keep-percent-format]
|
||||
- repo: https://github.com/pre-commit/pygrep-hooks
|
||||
rev: v1.0.0
|
||||
rev: v1.1.0
|
||||
hooks:
|
||||
- id: rst-backticks
|
||||
- repo: local
|
||||
@@ -40,6 +48,6 @@ repos:
|
||||
- id: changelogs-rst
|
||||
name: changelog filenames
|
||||
language: fail
|
||||
entry: 'changelog files must be named ####.(feature|bugfix|doc|removal|vendor|trivial).rst'
|
||||
exclude: changelog/(\d+\.(feature|bugfix|doc|removal|vendor|trivial).rst|README.rst|_template.rst)
|
||||
entry: 'changelog files must be named ####.(feature|bugfix|doc|deprecation|removal|vendor|trivial).rst'
|
||||
exclude: changelog/(\d+\.(feature|bugfix|doc|deprecation|removal|vendor|trivial).rst|README.rst|_template.rst)
|
||||
files: ^changelog/
|
||||
|
||||
35
.travis.yml
35
.travis.yml
@@ -12,21 +12,15 @@ install:
|
||||
- pip install --upgrade --pre tox
|
||||
env:
|
||||
matrix:
|
||||
# note: please use "tox --listenvs" to populate the build matrix below
|
||||
# please remove the linting env in all cases
|
||||
- TOXENV=py27-pexpect
|
||||
- TOXENV=py27-xdist
|
||||
- TOXENV=py27-trial
|
||||
- TOXENV=py27-numpy
|
||||
- TOXENV=py27-pluggymaster PYTEST_NO_COVERAGE=1
|
||||
- TOXENV=py36-pexpect
|
||||
- TOXENV=py36-xdist
|
||||
- TOXENV=py36-trial
|
||||
- TOXENV=py36-numpy
|
||||
- TOXENV=py36-pluggymaster PYTEST_NO_COVERAGE=1
|
||||
# Specialized factors for py27.
|
||||
- TOXENV=py27-pexpect,py27-trial,py27-numpy
|
||||
- TOXENV=py27-nobyte
|
||||
- TOXENV=doctesting
|
||||
- TOXENV=docs PYTEST_NO_COVERAGE=1
|
||||
- TOXENV=py27-xdist
|
||||
- TOXENV=py27-pluggymaster PYTEST_NO_COVERAGE=1
|
||||
# Specialized factors for py36.
|
||||
- TOXENV=py36-pexpect,py36-trial,py36-numpy
|
||||
- TOXENV=py36-xdist
|
||||
- TOXENV=py36-pluggymaster PYTEST_NO_COVERAGE=1
|
||||
|
||||
jobs:
|
||||
include:
|
||||
@@ -53,6 +47,11 @@ jobs:
|
||||
env: TOXENV=py37
|
||||
before_install:
|
||||
- brew update
|
||||
# remove c++ include files because upgrading python as of 2018-10-23, also
|
||||
# attempts to upgrade gcc, and it fails because the include files already
|
||||
# exist. removing the include files is one of the solutions recommended by brew
|
||||
# this workaround might not be necessary in the future
|
||||
- rm '/usr/local/include/c++'
|
||||
- brew upgrade python
|
||||
- brew unlink python
|
||||
- brew link python
|
||||
@@ -61,7 +60,7 @@ jobs:
|
||||
env: TOXENV=py27
|
||||
- env: TOXENV=py34
|
||||
- env: TOXENV=py36
|
||||
- env: TOXENV=linting PYTEST_NO_COVERAGE=1
|
||||
- env: TOXENV=linting,docs,doctesting PYTEST_NO_COVERAGE=1
|
||||
|
||||
- stage: deploy
|
||||
python: '3.6'
|
||||
@@ -99,12 +98,6 @@ after_success:
|
||||
coverage xml --ignore-errors
|
||||
coverage report -m --ignore-errors
|
||||
bash <(curl -s https://codecov.io/bash) -Z -X gcov -X coveragepy -X search -X xcode -X gcovout -X fix -f coverage.xml -F "${TOXENV//-/,},linux"
|
||||
|
||||
# Coveralls does not support merged reports.
|
||||
if [[ "$TOXENV" = py37 ]]; then
|
||||
pip install coveralls
|
||||
coveralls
|
||||
fi
|
||||
fi
|
||||
|
||||
notifications:
|
||||
|
||||
3
AUTHORS
3
AUTHORS
@@ -14,6 +14,7 @@ Allan Feldman
|
||||
Anatoly Bubenkoff
|
||||
Anders Hovmöller
|
||||
Andras Tim
|
||||
Andrea Cimatoribus
|
||||
Andreas Zeidler
|
||||
Andrzej Ostrowski
|
||||
Andy Freeland
|
||||
@@ -120,6 +121,7 @@ Katerina Koukiou
|
||||
Kevin Cox
|
||||
Kodi B. Arfer
|
||||
Kostis Anagnostopoulos
|
||||
Kyle Altendorf
|
||||
Lawrence Mitchell
|
||||
Lee Kamentsky
|
||||
Lev Maximov
|
||||
@@ -209,6 +211,7 @@ Thomas Hisch
|
||||
Tim Strazny
|
||||
Tom Dalton
|
||||
Tom Viner
|
||||
Tomer Keren
|
||||
Trevor Bekolay
|
||||
Tyler Goodlet
|
||||
Tzu-ping Chung
|
||||
|
||||
226
CHANGELOG.rst
226
CHANGELOG.rst
@@ -18,6 +18,232 @@ with advance notice in the **Deprecations** section of releases.
|
||||
|
||||
.. towncrier release notes start
|
||||
|
||||
pytest 3.9.3 (2018-10-27)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#4174 <https://github.com/pytest-dev/pytest/issues/4174>`_: Fix "ValueError: Plugin already registered" with conftest plugins via symlink.
|
||||
|
||||
|
||||
- `#4181 <https://github.com/pytest-dev/pytest/issues/4181>`_: Handle race condition between creation and deletion of temporary folders.
|
||||
|
||||
|
||||
- `#4221 <https://github.com/pytest-dev/pytest/issues/4221>`_: Fix bug where the warning summary at the end of the test session was not showing the test where the warning was originated.
|
||||
|
||||
|
||||
- `#4243 <https://github.com/pytest-dev/pytest/issues/4243>`_: Fix regression when ``stacklevel`` for warnings was passed as positional argument on python2.
|
||||
|
||||
|
||||
|
||||
Improved Documentation
|
||||
----------------------
|
||||
|
||||
- `#3851 <https://github.com/pytest-dev/pytest/issues/3851>`_: Add reference to ``empty_parameter_set_mark`` ini option in documentation of ``@pytest.mark.parametrize``
|
||||
|
||||
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- `#4028 <https://github.com/pytest-dev/pytest/issues/4028>`_: Revert patching of ``sys.breakpointhook`` since it appears to do nothing.
|
||||
|
||||
|
||||
- `#4233 <https://github.com/pytest-dev/pytest/issues/4233>`_: Apply an import sorter (``reorder-python-imports``) to the codebase.
|
||||
|
||||
|
||||
- `#4248 <https://github.com/pytest-dev/pytest/issues/4248>`_: Remove use of unnecessary compat shim, six.binary_type
|
||||
|
||||
|
||||
pytest 3.9.2 (2018-10-22)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#2909 <https://github.com/pytest-dev/pytest/issues/2909>`_: Improve error message when a recursive dependency between fixtures is detected.
|
||||
|
||||
|
||||
- `#3340 <https://github.com/pytest-dev/pytest/issues/3340>`_: Fix logging messages not shown in hooks ``pytest_sessionstart()`` and ``pytest_sessionfinish()``.
|
||||
|
||||
|
||||
- `#3533 <https://github.com/pytest-dev/pytest/issues/3533>`_: Fix unescaped XML raw objects in JUnit report for skipped tests
|
||||
|
||||
|
||||
- `#3691 <https://github.com/pytest-dev/pytest/issues/3691>`_: Python 2: safely format warning message about passing unicode strings to ``warnings.warn``, which may cause
|
||||
surprising ``MemoryError`` exception when monkey patching ``warnings.warn`` itself.
|
||||
|
||||
|
||||
- `#4026 <https://github.com/pytest-dev/pytest/issues/4026>`_: Improve error message when it is not possible to determine a function's signature.
|
||||
|
||||
|
||||
- `#4177 <https://github.com/pytest-dev/pytest/issues/4177>`_: Pin ``setuptools>=40.0`` to support ``py_modules`` in ``setup.cfg``
|
||||
|
||||
|
||||
- `#4179 <https://github.com/pytest-dev/pytest/issues/4179>`_: Restore the tmpdir behaviour of symlinking the current test run.
|
||||
|
||||
|
||||
- `#4192 <https://github.com/pytest-dev/pytest/issues/4192>`_: Fix filename reported by ``warnings.warn`` when using ``recwarn`` under python2.
|
||||
|
||||
|
||||
pytest 3.9.1 (2018-10-16)
|
||||
=========================
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- `#4159 <https://github.com/pytest-dev/pytest/issues/4159>`_: For test-suites containing test classes, the information about the subclassed
|
||||
module is now output only if a higher verbosity level is specified (at least
|
||||
"-vv").
|
||||
|
||||
|
||||
pytest 3.9.0 (2018-10-15 - not published due to a release automation bug)
|
||||
=========================================================================
|
||||
|
||||
Deprecations
|
||||
------------
|
||||
|
||||
- `#3616 <https://github.com/pytest-dev/pytest/issues/3616>`_: The following accesses have been documented as deprecated for years, but are now actually emitting deprecation warnings.
|
||||
|
||||
* Access of ``Module``, ``Function``, ``Class``, ``Instance``, ``File`` and ``Item`` through ``Node`` instances. Now
|
||||
users will this warning::
|
||||
|
||||
usage of Function.Module is deprecated, please use pytest.Module instead
|
||||
|
||||
Users should just ``import pytest`` and access those objects using the ``pytest`` module.
|
||||
|
||||
* ``request.cached_setup``, this was the precursor of the setup/teardown mechanism available to fixtures. You can
|
||||
consult `funcarg comparison section in the docs <https://docs.pytest.org/en/latest/funcarg_compare.html>`_.
|
||||
|
||||
* Using objects named ``"Class"`` as a way to customize the type of nodes that are collected in ``Collector``
|
||||
subclasses has been deprecated. Users instead should use ``pytest_collect_make_item`` to customize node types during
|
||||
collection.
|
||||
|
||||
This issue should affect only advanced plugins who create new collection types, so if you see this warning
|
||||
message please contact the authors so they can change the code.
|
||||
|
||||
* The warning that produces the message below has changed to ``RemovedInPytest4Warning``::
|
||||
|
||||
getfuncargvalue is deprecated, use getfixturevalue
|
||||
|
||||
|
||||
- `#3988 <https://github.com/pytest-dev/pytest/issues/3988>`_: Add a Deprecation warning for pytest.ensuretemp as it was deprecated since a while.
|
||||
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- `#2293 <https://github.com/pytest-dev/pytest/issues/2293>`_: Improve usage errors messages by hiding internal details which can be distracting and noisy.
|
||||
|
||||
This has the side effect that some error conditions that previously raised generic errors (such as
|
||||
``ValueError`` for unregistered marks) are now raising ``Failed`` exceptions.
|
||||
|
||||
|
||||
- `#3332 <https://github.com/pytest-dev/pytest/issues/3332>`_: Improve the error displayed when a ``conftest.py`` file could not be imported.
|
||||
|
||||
In order to implement this, a new ``chain`` parameter was added to ``ExceptionInfo.getrepr``
|
||||
to show or hide chained tracebacks in Python 3 (defaults to ``True``).
|
||||
|
||||
|
||||
- `#3849 <https://github.com/pytest-dev/pytest/issues/3849>`_: Add ``empty_parameter_set_mark=fail_at_collect`` ini option for raising an exception when parametrize collects an empty set.
|
||||
|
||||
|
||||
- `#3964 <https://github.com/pytest-dev/pytest/issues/3964>`_: Log messages generated in the collection phase are shown when
|
||||
live-logging is enabled and/or when they are logged to a file.
|
||||
|
||||
|
||||
- `#3985 <https://github.com/pytest-dev/pytest/issues/3985>`_: Introduce ``tmp_path`` as a fixture providing a Path object.
|
||||
|
||||
|
||||
- `#4013 <https://github.com/pytest-dev/pytest/issues/4013>`_: Deprecation warnings are now shown even if you customize the warnings filters yourself. In the previous version
|
||||
any customization would override pytest's filters and deprecation warnings would fall back to being hidden by default.
|
||||
|
||||
|
||||
- `#4073 <https://github.com/pytest-dev/pytest/issues/4073>`_: Allow specification of timeout for ``Testdir.runpytest_subprocess()`` and ``Testdir.run()``.
|
||||
|
||||
|
||||
- `#4098 <https://github.com/pytest-dev/pytest/issues/4098>`_: Add returncode argument to pytest.exit() to exit pytest with a specific return code.
|
||||
|
||||
|
||||
- `#4102 <https://github.com/pytest-dev/pytest/issues/4102>`_: Reimplement ``pytest.deprecated_call`` using ``pytest.warns`` so it supports the ``match='...'`` keyword argument.
|
||||
|
||||
This has the side effect that ``pytest.deprecated_call`` now raises ``pytest.fail.Exception`` instead
|
||||
of ``AssertionError``.
|
||||
|
||||
|
||||
- `#4149 <https://github.com/pytest-dev/pytest/issues/4149>`_: Require setuptools>=30.3 and move most of the metadata to ``setup.cfg``.
|
||||
|
||||
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#2535 <https://github.com/pytest-dev/pytest/issues/2535>`_: Improve error message when test functions of ``unittest.TestCase`` subclasses use a parametrized fixture.
|
||||
|
||||
|
||||
- `#3057 <https://github.com/pytest-dev/pytest/issues/3057>`_: ``request.fixturenames`` now correctly returns the name of fixtures created by ``request.getfixturevalue()``.
|
||||
|
||||
|
||||
- `#3946 <https://github.com/pytest-dev/pytest/issues/3946>`_: Warning filters passed as command line options using ``-W`` now take precedence over filters defined in ``ini``
|
||||
configuration files.
|
||||
|
||||
|
||||
- `#4066 <https://github.com/pytest-dev/pytest/issues/4066>`_: Fix source reindenting by using ``textwrap.dedent`` directly.
|
||||
|
||||
|
||||
- `#4102 <https://github.com/pytest-dev/pytest/issues/4102>`_: ``pytest.warn`` will capture previously-warned warnings in Python 2. Previously they were never raised.
|
||||
|
||||
|
||||
- `#4108 <https://github.com/pytest-dev/pytest/issues/4108>`_: Resolve symbolic links for args.
|
||||
|
||||
This fixes running ``pytest tests/test_foo.py::test_bar``, where ``tests``
|
||||
is a symlink to ``project/app/tests``:
|
||||
previously ``project/app/conftest.py`` would be ignored for fixtures then.
|
||||
|
||||
|
||||
- `#4132 <https://github.com/pytest-dev/pytest/issues/4132>`_: Fix duplicate printing of internal errors when using ``--pdb``.
|
||||
|
||||
|
||||
- `#4135 <https://github.com/pytest-dev/pytest/issues/4135>`_: pathlib based tmpdir cleanup now correctly handles symlinks in the folder.
|
||||
|
||||
|
||||
- `#4152 <https://github.com/pytest-dev/pytest/issues/4152>`_: Display the filename when encountering ``SyntaxWarning``.
|
||||
|
||||
|
||||
|
||||
Improved Documentation
|
||||
----------------------
|
||||
|
||||
- `#3713 <https://github.com/pytest-dev/pytest/issues/3713>`_: Update usefixtures documentation to clarify that it can't be used with fixture functions.
|
||||
|
||||
|
||||
- `#4058 <https://github.com/pytest-dev/pytest/issues/4058>`_: Update fixture documentation to specify that a fixture can be invoked twice in the scope it's defined for.
|
||||
|
||||
|
||||
- `#4064 <https://github.com/pytest-dev/pytest/issues/4064>`_: According to unittest.rst, setUpModule and tearDownModule were not implemented, but it turns out they are. So updated the documentation for unittest.
|
||||
|
||||
|
||||
- `#4151 <https://github.com/pytest-dev/pytest/issues/4151>`_: Add tempir testing example to CONTRIBUTING.rst guide
|
||||
|
||||
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- `#2293 <https://github.com/pytest-dev/pytest/issues/2293>`_: The internal ``MarkerError`` exception has been removed.
|
||||
|
||||
|
||||
- `#3988 <https://github.com/pytest-dev/pytest/issues/3988>`_: Port the implementation of tmpdir to pathlib.
|
||||
|
||||
|
||||
- `#4063 <https://github.com/pytest-dev/pytest/issues/4063>`_: Exclude 0.00 second entries from ``--duration`` output unless ``-vv`` is passed on the command-line.
|
||||
|
||||
|
||||
- `#4093 <https://github.com/pytest-dev/pytest/issues/4093>`_: Fixed formatting of string literals in internal tests.
|
||||
|
||||
|
||||
pytest 3.8.2 (2018-10-02)
|
||||
=========================
|
||||
|
||||
|
||||
@@ -280,6 +280,47 @@ Here is a simple overview, with pytest-specific bits:
|
||||
base: features # if it's a feature
|
||||
|
||||
|
||||
Writing Tests
|
||||
----------------------------
|
||||
|
||||
Writing tests for plugins or for pytest itself is often done using the `testdir fixture <https://docs.pytest.org/en/latest/reference.html#testdir>`_, as a "black-box" test.
|
||||
|
||||
For example, to ensure a simple test passes you can write:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_true_assertion(testdir):
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
def test_foo():
|
||||
assert True
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
result.assert_outcomes(failed=0, passed=1)
|
||||
|
||||
|
||||
Alternatively, it is possible to make checks based on the actual output of the termal using
|
||||
*glob-like* expressions:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_true_assertion(testdir):
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
def test_foo():
|
||||
assert False
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines(["*assert False*", "*1 failed*"])
|
||||
|
||||
When choosing a file where to write a new test, take a look at the existing files and see if there's
|
||||
one file which looks like a good fit. For example, a regression test about a bug in the ``--lf`` option
|
||||
should go into ``test_cacheprovider.py``, given that this option is implemented in ``cacheprovider.py``.
|
||||
If in doubt, go ahead and open a PR with your best guess and we can discuss this over the code.
|
||||
|
||||
|
||||
Joining the Development Team
|
||||
----------------------------
|
||||
|
||||
|
||||
29
appveyor.yml
29
appveyor.yml
@@ -1,30 +1,29 @@
|
||||
environment:
|
||||
matrix:
|
||||
- TOXENV: "linting"
|
||||
PYTEST_NO_COVERAGE: "1"
|
||||
- TOXENV: "py27"
|
||||
- TOXENV: "py34"
|
||||
- TOXENV: "py35"
|
||||
- TOXENV: "py36"
|
||||
- TOXENV: "py37"
|
||||
PYTEST_NO_COVERAGE: "1"
|
||||
- TOXENV: "linting,docs,doctesting"
|
||||
- TOXENV: "py36"
|
||||
- TOXENV: "py35"
|
||||
- TOXENV: "py34"
|
||||
- TOXENV: "pypy"
|
||||
PYTEST_NO_COVERAGE: "1"
|
||||
- TOXENV: "py27-xdist"
|
||||
- TOXENV: "py27-trial"
|
||||
- TOXENV: "py27-numpy"
|
||||
# Specialized factors for py27.
|
||||
- TOXENV: "py27-trial,py27-numpy,py27-nobyte"
|
||||
- TOXENV: "py27-pluggymaster"
|
||||
PYTEST_NO_COVERAGE: "1"
|
||||
- TOXENV: "py36-xdist"
|
||||
- TOXENV: "py36-trial"
|
||||
- TOXENV: "py36-numpy"
|
||||
- TOXENV: "py27-xdist"
|
||||
# Specialized factors for py36.
|
||||
- TOXENV: "py36-trial,py36-numpy"
|
||||
- TOXENV: "py36-pluggymaster"
|
||||
PYTEST_NO_COVERAGE: "1"
|
||||
- TOXENV: "py27-nobyte"
|
||||
- TOXENV: "doctesting"
|
||||
- TOXENV: "py36-freeze"
|
||||
PYTEST_NO_COVERAGE: "1"
|
||||
- TOXENV: "docs"
|
||||
PYTEST_NO_COVERAGE: "1"
|
||||
- TOXENV: "py36-xdist"
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
||||
install:
|
||||
- echo Installed Pythons
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
|
||||
|
||||
# 10000 iterations, just for relative comparison
|
||||
# 2.7.5 3.3.2
|
||||
# FilesCompleter 75.1109 69.2116
|
||||
# FastFilesCompleter 0.7383 1.0760
|
||||
|
||||
import timeit
|
||||
|
||||
imports = [
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from six.moves import range
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
||||
@@ -14,7 +14,8 @@ Each file should be named like ``<ISSUE>.<TYPE>.rst``, where
|
||||
* ``feature``: new user facing features, like new command-line options and new behavior.
|
||||
* ``bugfix``: fixes a reported bug.
|
||||
* ``doc``: documentation improvement, like rewording an entire session or adding missing docs.
|
||||
* ``removal``: feature deprecation or removal.
|
||||
* ``deprecation``: feature deprecation.
|
||||
* ``removal``: feature removal.
|
||||
* ``vendor``: changes in packages vendored in pytest.
|
||||
* ``trivial``: fixing a small typo or internal change that might be noteworthy.
|
||||
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
# flasky extensions. flasky pygments style based on tango style
|
||||
from pygments.style import Style
|
||||
from pygments.token import (
|
||||
Keyword,
|
||||
Name,
|
||||
Comment,
|
||||
String,
|
||||
Error,
|
||||
Number,
|
||||
Operator,
|
||||
Generic,
|
||||
Whitespace,
|
||||
Punctuation,
|
||||
Other,
|
||||
Literal,
|
||||
)
|
||||
from pygments.token import Comment
|
||||
from pygments.token import Error
|
||||
from pygments.token import Generic
|
||||
from pygments.token import Keyword
|
||||
from pygments.token import Literal
|
||||
from pygments.token import Name
|
||||
from pygments.token import Number
|
||||
from pygments.token import Operator
|
||||
from pygments.token import Other
|
||||
from pygments.token import Punctuation
|
||||
from pygments.token import String
|
||||
from pygments.token import Whitespace
|
||||
|
||||
|
||||
class FlaskyStyle(Style):
|
||||
|
||||
@@ -6,6 +6,10 @@ Release announcements
|
||||
:maxdepth: 2
|
||||
|
||||
|
||||
release-3.9.3
|
||||
release-3.9.2
|
||||
release-3.9.1
|
||||
release-3.9.0
|
||||
release-3.8.2
|
||||
release-3.8.1
|
||||
release-3.8.0
|
||||
|
||||
@@ -20,8 +20,7 @@ Thanks to all who contributed to this release, among them:
|
||||
* Ondřej Súkup
|
||||
* Ronny Pfannschmidt
|
||||
* T.E.A de Souza
|
||||
* Victor
|
||||
* victor
|
||||
* Victor Maryama
|
||||
|
||||
|
||||
Happy testing,
|
||||
|
||||
@@ -22,10 +22,9 @@ Thanks to all who contributed to this release, among them:
|
||||
* Ronny Pfannschmidt
|
||||
* Sankt Petersbug
|
||||
* Tyler Richard
|
||||
* Victor
|
||||
* Victor Maryama
|
||||
* Vlad Shcherbina
|
||||
* turturica
|
||||
* victor
|
||||
* wim glenn
|
||||
|
||||
|
||||
|
||||
43
doc/en/announce/release-3.9.0.rst
Normal file
43
doc/en/announce/release-3.9.0.rst
Normal file
@@ -0,0 +1,43 @@
|
||||
pytest-3.9.0
|
||||
=======================================
|
||||
|
||||
The pytest team is proud to announce the 3.9.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:
|
||||
|
||||
* Andrea Cimatoribus
|
||||
* Ankit Goel
|
||||
* Anthony Sottile
|
||||
* Ben Eyal
|
||||
* Bruno Oliveira
|
||||
* Daniel Hahler
|
||||
* Jeffrey Rackauckas
|
||||
* Jose Carlos Menezes
|
||||
* Kyle Altendorf
|
||||
* Niklas JQ
|
||||
* Palash Chatterjee
|
||||
* Ronny Pfannschmidt
|
||||
* Thomas Hess
|
||||
* Thomas Hisch
|
||||
* Tomer Keren
|
||||
* Victor Maryama
|
||||
|
||||
|
||||
Happy testing,
|
||||
The Pytest Development Team
|
||||
20
doc/en/announce/release-3.9.1.rst
Normal file
20
doc/en/announce/release-3.9.1.rst
Normal file
@@ -0,0 +1,20 @@
|
||||
pytest-3.9.1
|
||||
=======================================
|
||||
|
||||
pytest 3.9.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:
|
||||
|
||||
* Bruno Oliveira
|
||||
* Ronny Pfannschmidt
|
||||
* Thomas Hisch
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
||||
23
doc/en/announce/release-3.9.2.rst
Normal file
23
doc/en/announce/release-3.9.2.rst
Normal file
@@ -0,0 +1,23 @@
|
||||
pytest-3.9.2
|
||||
=======================================
|
||||
|
||||
pytest 3.9.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:
|
||||
|
||||
* Ankit Goel
|
||||
* Anthony Sottile
|
||||
* Bruno Oliveira
|
||||
* Ronny Pfannschmidt
|
||||
* Vincent Barbaresi
|
||||
* ykantor
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
||||
24
doc/en/announce/release-3.9.3.rst
Normal file
24
doc/en/announce/release-3.9.3.rst
Normal file
@@ -0,0 +1,24 @@
|
||||
pytest-3.9.3
|
||||
=======================================
|
||||
|
||||
pytest 3.9.3 has just been released to PyPI.
|
||||
|
||||
This is a bug-fix release, being a drop-in replacement. To upgrade::
|
||||
|
||||
pip install --upgrade pytest
|
||||
|
||||
The full changelog is available at https://docs.pytest.org/en/latest/changelog.html.
|
||||
|
||||
Thanks to all who contributed to this release, among them:
|
||||
|
||||
* Andreas Profous
|
||||
* Ankit Goel
|
||||
* Anthony Sottile
|
||||
* Bruno Oliveira
|
||||
* Daniel Hahler
|
||||
* Jon Dufresne
|
||||
* Ronny Pfannschmidt
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
||||
@@ -75,7 +75,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||
caplog
|
||||
Access and control log capturing.
|
||||
|
||||
Captured logs are available through the following methods::
|
||||
Captured logs are available through the following properties/methods::
|
||||
|
||||
* caplog.text -> string containing formatted log output
|
||||
* caplog.records -> list of logging.LogRecord instances
|
||||
@@ -104,7 +104,9 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||
See http://docs.python.org/library/warnings.html for information
|
||||
on warning categories.
|
||||
tmpdir_factory
|
||||
Return a TempdirFactory instance for the test session.
|
||||
Return a :class:`_pytest.tmpdir.TempdirFactory` instance for the test session.
|
||||
tmp_path_factory
|
||||
Return a :class:`_pytest.tmpdir.TempPathFactory` instance for the test session.
|
||||
tmpdir
|
||||
Return a temporary directory path object
|
||||
which is unique to each test function invocation,
|
||||
@@ -113,6 +115,16 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||
path object.
|
||||
|
||||
.. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html
|
||||
tmp_path
|
||||
Return a temporary directory path object
|
||||
which is unique to each test function invocation,
|
||||
created as a sub directory of the base temporary
|
||||
directory. The returned object is a :class:`pathlib.Path`
|
||||
object.
|
||||
|
||||
.. note::
|
||||
|
||||
in python < 3.6 this is a pathlib2.Path
|
||||
|
||||
no tests ran in 0.12 seconds
|
||||
|
||||
|
||||
@@ -10,17 +10,15 @@
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
# The short X.Y version.
|
||||
|
||||
import datetime
|
||||
import os
|
||||
import sys
|
||||
import datetime
|
||||
|
||||
from _pytest import __version__ as version
|
||||
|
||||
|
||||
@@ -14,6 +14,67 @@ 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>`.
|
||||
|
||||
Internal classes accessed through ``Node``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 3.9
|
||||
|
||||
Access of ``Module``, ``Function``, ``Class``, ``Instance``, ``File`` and ``Item`` through ``Node`` instances now issue
|
||||
this warning::
|
||||
|
||||
usage of Function.Module is deprecated, please use pytest.Module instead
|
||||
|
||||
Users should just ``import pytest`` and access those objects using the ``pytest`` module.
|
||||
|
||||
This has been documented as deprecated for years, but only now we are actually emitting deprecation warnings.
|
||||
|
||||
``cached_setup``
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 3.9
|
||||
|
||||
``request.cached_setup`` was the precursor of the setup/teardown mechanism available to fixtures.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.fixture
|
||||
def db_session():
|
||||
return request.cached_setup(
|
||||
setup=Session.create, teardown=lambda session: session.close(), scope="module"
|
||||
)
|
||||
|
||||
This should be updated to make use of standard fixture mechanisms:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def db_session():
|
||||
session = Session.create()
|
||||
yield session
|
||||
session.close()
|
||||
|
||||
|
||||
You can consult `funcarg comparison section in the docs <https://docs.pytest.org/en/latest/funcarg_compare.html>`_ for
|
||||
more information.
|
||||
|
||||
This has been documented as deprecated for years, but only now we are actually emitting deprecation warnings.
|
||||
|
||||
|
||||
Using ``Class`` in custom Collectors
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 3.9
|
||||
|
||||
Using objects named ``"Class"`` as a way to customize the type of nodes that are collected in ``Collector``
|
||||
subclasses has been deprecated. Users instead should use ``pytest_pycollect_makeitem`` to customize node types during
|
||||
collection.
|
||||
|
||||
This issue should affect only advanced plugins who create new collection types, so if you see this warning
|
||||
message please contact the authors so they can change the code.
|
||||
|
||||
|
||||
``Config.warn`` and ``Node.warn``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -243,7 +304,7 @@ This form of test function doesn't support fixtures properly, and users should s
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.parametrize("x, y", [(2, 4), (3, 9)])
|
||||
def test_squared():
|
||||
def test_squared(x, y):
|
||||
assert x ** x == y
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
from pytest import raises
|
||||
import _pytest._code
|
||||
import six
|
||||
|
||||
import _pytest._code
|
||||
from pytest import raises
|
||||
|
||||
|
||||
def otherfunc(a, b):
|
||||
assert a == b
|
||||
@@ -247,7 +248,7 @@ class TestCustomAssertMsg(object):
|
||||
b = 2
|
||||
assert (
|
||||
A.a == b
|
||||
), "A.a appears not to be b\n" "or does not appear to be b\none of those"
|
||||
), "A.a appears not to be b\nor does not appear to be b\none of those"
|
||||
|
||||
def test_custom_repr(self):
|
||||
class JSON(object):
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import pytest
|
||||
import py
|
||||
|
||||
import pytest
|
||||
|
||||
mydir = py.path.local(__file__).dirpath()
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
hello = "world"
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import py
|
||||
|
||||
failure_demo = py.path.local(__file__).dirpath("failure_demo.py")
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ You can then restrict a test run to only run tests marked with ``webtest``::
|
||||
|
||||
$ pytest -v -m webtest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
|
||||
cachedir: .pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 4 items / 3 deselected
|
||||
@@ -44,7 +44,7 @@ Or the inverse, running all tests except the webtest ones::
|
||||
|
||||
$ pytest -v -m "not webtest"
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
|
||||
cachedir: .pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 4 items / 1 deselected
|
||||
@@ -64,7 +64,7 @@ tests based on their module, class, method, or function name::
|
||||
|
||||
$ pytest -v test_server.py::TestClass::test_method
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
|
||||
cachedir: .pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 1 item
|
||||
@@ -77,7 +77,7 @@ You can also select on the class::
|
||||
|
||||
$ pytest -v test_server.py::TestClass
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
|
||||
cachedir: .pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 1 item
|
||||
@@ -90,7 +90,7 @@ Or select multiple nodes::
|
||||
|
||||
$ pytest -v test_server.py::TestClass test_server.py::test_send_http
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
|
||||
cachedir: .pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 2 items
|
||||
@@ -128,7 +128,7 @@ select tests based on their names::
|
||||
|
||||
$ pytest -v -k http # running with the above defined example module
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
|
||||
cachedir: .pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 4 items / 3 deselected
|
||||
@@ -141,7 +141,7 @@ And you can also run all tests except the ones that match the keyword::
|
||||
|
||||
$ pytest -k "not send_http" -v
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
|
||||
cachedir: .pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 4 items / 1 deselected
|
||||
@@ -156,7 +156,7 @@ Or to select "http" and "quick" tests::
|
||||
|
||||
$ pytest -k "http or quick" -v
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
|
||||
cachedir: .pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 4 items / 2 deselected
|
||||
|
||||
@@ -5,6 +5,7 @@ serialization via the pickle module.
|
||||
import textwrap
|
||||
|
||||
import py
|
||||
|
||||
import pytest
|
||||
|
||||
pythonlist = ["python2.7", "python3.4", "python3.5"]
|
||||
|
||||
@@ -59,7 +59,7 @@ consulted when reporting in ``verbose`` mode::
|
||||
|
||||
nonpython $ pytest -v
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
|
||||
cachedir: .pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
|
||||
collecting ... collected 2 items
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# content of conftest.py
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
||||
@@ -413,7 +413,7 @@ Running it results in some skips if we don't have all the python interpreters in
|
||||
. $ pytest -rs -q multipython.py
|
||||
...sss...sssssssss...sss... [100%]
|
||||
========================= short test summary info ==========================
|
||||
SKIP [15] $REGENDOC_TMPDIR/CWD/multipython.py:29: 'python3.4' not found
|
||||
SKIP [15] $REGENDOC_TMPDIR/CWD/multipython.py:30: 'python3.4' not found
|
||||
12 passed, 15 skipped in 0.12 seconds
|
||||
|
||||
Indirect parametrization of optional implementations/imports
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
py3 = sys.version_info[0] >= 3
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
|
||||
def test_exception_syntax():
|
||||
try:
|
||||
0/0
|
||||
0 / 0
|
||||
except ZeroDivisionError, e:
|
||||
pass
|
||||
assert e
|
||||
|
||||
@@ -2,4 +2,4 @@ def test_exception_syntax():
|
||||
try:
|
||||
0 / 0
|
||||
except ZeroDivisionError as e:
|
||||
pass
|
||||
assert e
|
||||
|
||||
@@ -26,7 +26,7 @@ get on the terminal - we are working on that)::
|
||||
> assert param1 * 2 < param2
|
||||
E assert (3 * 2) < 6
|
||||
|
||||
failure_demo.py:19: AssertionError
|
||||
failure_demo.py:20: AssertionError
|
||||
_________________________ TestFailing.test_simple __________________________
|
||||
|
||||
self = <failure_demo.TestFailing object at 0xdeadbeef>
|
||||
@@ -43,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:35: AssertionError
|
||||
failure_demo.py:36: AssertionError
|
||||
____________________ TestFailing.test_simple_multiline _____________________
|
||||
|
||||
self = <failure_demo.TestFailing object at 0xdeadbeef>
|
||||
@@ -51,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:38:
|
||||
failure_demo.py:39:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
a = 42, b = 54
|
||||
@@ -60,7 +60,7 @@ get on the terminal - we are working on that)::
|
||||
> assert a == b
|
||||
E assert 42 == 54
|
||||
|
||||
failure_demo.py:15: AssertionError
|
||||
failure_demo.py:16: AssertionError
|
||||
___________________________ TestFailing.test_not ___________________________
|
||||
|
||||
self = <failure_demo.TestFailing object at 0xdeadbeef>
|
||||
@@ -73,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:44: AssertionError
|
||||
failure_demo.py:45: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_eq_text _________________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -84,7 +84,7 @@ get on the terminal - we are working on that)::
|
||||
E - spam
|
||||
E + eggs
|
||||
|
||||
failure_demo.py:49: AssertionError
|
||||
failure_demo.py:50: AssertionError
|
||||
_____________ TestSpecialisedExplanations.test_eq_similar_text _____________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -97,7 +97,7 @@ get on the terminal - we are working on that)::
|
||||
E + foo 2 bar
|
||||
E ? ^
|
||||
|
||||
failure_demo.py:52: AssertionError
|
||||
failure_demo.py:53: AssertionError
|
||||
____________ TestSpecialisedExplanations.test_eq_multiline_text ____________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -110,7 +110,7 @@ get on the terminal - we are working on that)::
|
||||
E + eggs
|
||||
E bar
|
||||
|
||||
failure_demo.py:55: AssertionError
|
||||
failure_demo.py:56: AssertionError
|
||||
______________ TestSpecialisedExplanations.test_eq_long_text _______________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -127,7 +127,7 @@ get on the terminal - we are working on that)::
|
||||
E + 1111111111b222222222
|
||||
E ? ^
|
||||
|
||||
failure_demo.py:60: AssertionError
|
||||
failure_demo.py:61: AssertionError
|
||||
_________ TestSpecialisedExplanations.test_eq_long_text_multiline __________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -147,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:65: AssertionError
|
||||
failure_demo.py:66: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_eq_list _________________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -158,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:68: AssertionError
|
||||
failure_demo.py:69: AssertionError
|
||||
______________ TestSpecialisedExplanations.test_eq_list_long _______________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -171,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:73: AssertionError
|
||||
failure_demo.py:74: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_eq_dict _________________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -189,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:76: AssertionError
|
||||
failure_demo.py:77: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_eq_set __________________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -207,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:79: AssertionError
|
||||
failure_demo.py:80: AssertionError
|
||||
_____________ TestSpecialisedExplanations.test_eq_longer_list ______________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -218,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:82: AssertionError
|
||||
failure_demo.py:83: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_in_list _________________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -227,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:85: AssertionError
|
||||
failure_demo.py:86: AssertionError
|
||||
__________ TestSpecialisedExplanations.test_not_in_text_multiline __________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -246,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:89: AssertionError
|
||||
failure_demo.py:90: AssertionError
|
||||
___________ TestSpecialisedExplanations.test_not_in_text_single ____________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -259,7 +259,7 @@ get on the terminal - we are working on that)::
|
||||
E single foo line
|
||||
E ? +++
|
||||
|
||||
failure_demo.py:93: AssertionError
|
||||
failure_demo.py:94: AssertionError
|
||||
_________ TestSpecialisedExplanations.test_not_in_text_single_long _________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -272,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:97: AssertionError
|
||||
failure_demo.py:98: AssertionError
|
||||
______ TestSpecialisedExplanations.test_not_in_text_single_long_term _______
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -285,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:101: AssertionError
|
||||
failure_demo.py:102: AssertionError
|
||||
______________________________ test_attribute ______________________________
|
||||
|
||||
def test_attribute():
|
||||
@@ -297,7 +297,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:109: AssertionError
|
||||
failure_demo.py:110: AssertionError
|
||||
_________________________ test_attribute_instance __________________________
|
||||
|
||||
def test_attribute_instance():
|
||||
@@ -309,7 +309,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:116: AssertionError
|
||||
failure_demo.py:117: AssertionError
|
||||
__________________________ test_attribute_failure __________________________
|
||||
|
||||
def test_attribute_failure():
|
||||
@@ -322,7 +322,7 @@ get on the terminal - we are working on that)::
|
||||
i = Foo()
|
||||
> assert i.b == 2
|
||||
|
||||
failure_demo.py:127:
|
||||
failure_demo.py:128:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
self = <failure_demo.test_attribute_failure.<locals>.Foo object at 0xdeadbeef>
|
||||
@@ -331,7 +331,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:122: Exception
|
||||
failure_demo.py:123: Exception
|
||||
_________________________ test_attribute_multiple __________________________
|
||||
|
||||
def test_attribute_multiple():
|
||||
@@ -348,7 +348,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:137: AssertionError
|
||||
failure_demo.py:138: AssertionError
|
||||
__________________________ TestRaises.test_raises __________________________
|
||||
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||
@@ -357,7 +357,7 @@ get on the terminal - we are working on that)::
|
||||
s = "qwe" # NOQA
|
||||
> raises(TypeError, "int(s)")
|
||||
|
||||
failure_demo.py:147:
|
||||
failure_demo.py:148:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
> int(s)
|
||||
@@ -372,7 +372,7 @@ get on the terminal - we are working on that)::
|
||||
> raises(IOError, "int('3')")
|
||||
E Failed: DID NOT RAISE <class 'OSError'>
|
||||
|
||||
failure_demo.py:150: Failed
|
||||
failure_demo.py:151: Failed
|
||||
__________________________ TestRaises.test_raise ___________________________
|
||||
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||
@@ -381,7 +381,7 @@ get on the terminal - we are working on that)::
|
||||
> raise ValueError("demo error")
|
||||
E ValueError: demo error
|
||||
|
||||
failure_demo.py:153: ValueError
|
||||
failure_demo.py:154: ValueError
|
||||
________________________ TestRaises.test_tupleerror ________________________
|
||||
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||
@@ -390,7 +390,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:156: ValueError
|
||||
failure_demo.py:157: ValueError
|
||||
______ TestRaises.test_reinterpret_fails_with_print_for_the_fun_of_it ______
|
||||
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||
@@ -401,7 +401,7 @@ get on the terminal - we are working on that)::
|
||||
> a, b = items.pop()
|
||||
E TypeError: 'int' object is not iterable
|
||||
|
||||
failure_demo.py:161: TypeError
|
||||
failure_demo.py:162: TypeError
|
||||
--------------------------- Captured stdout call ---------------------------
|
||||
items is [1, 2, 3]
|
||||
________________________ TestRaises.test_some_error ________________________
|
||||
@@ -412,7 +412,7 @@ get on the terminal - we are working on that)::
|
||||
> if namenotexi: # NOQA
|
||||
E NameError: name 'namenotexi' is not defined
|
||||
|
||||
failure_demo.py:164: NameError
|
||||
failure_demo.py:165: NameError
|
||||
____________________ test_dynamic_compile_shows_nicely _____________________
|
||||
|
||||
def test_dynamic_compile_shows_nicely():
|
||||
@@ -427,14 +427,14 @@ get on the terminal - we are working on that)::
|
||||
sys.modules[name] = module
|
||||
> module.foo()
|
||||
|
||||
failure_demo.py:182:
|
||||
failure_demo.py:183:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
def foo():
|
||||
> assert 1 == 0
|
||||
E AssertionError
|
||||
|
||||
<2-codegen 'abc-123' $REGENDOC_TMPDIR/assertion/failure_demo.py:179>:2: AssertionError
|
||||
<2-codegen 'abc-123' $REGENDOC_TMPDIR/assertion/failure_demo.py:180>:2: AssertionError
|
||||
____________________ TestMoreErrors.test_complex_error _____________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -448,9 +448,9 @@ get on the terminal - we are working on that)::
|
||||
|
||||
> somefunc(f(), g())
|
||||
|
||||
failure_demo.py:193:
|
||||
failure_demo.py:194:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
failure_demo.py:11: in somefunc
|
||||
failure_demo.py:12: in somefunc
|
||||
otherfunc(x, y)
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
@@ -460,7 +460,7 @@ get on the terminal - we are working on that)::
|
||||
> assert a == b
|
||||
E assert 44 == 43
|
||||
|
||||
failure_demo.py:7: AssertionError
|
||||
failure_demo.py:8: AssertionError
|
||||
___________________ TestMoreErrors.test_z1_unpack_error ____________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -470,7 +470,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:197: ValueError
|
||||
failure_demo.py:198: ValueError
|
||||
____________________ TestMoreErrors.test_z2_type_error _____________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -480,7 +480,7 @@ get on the terminal - we are working on that)::
|
||||
> a, b = items
|
||||
E TypeError: 'int' object is not iterable
|
||||
|
||||
failure_demo.py:201: TypeError
|
||||
failure_demo.py:202: TypeError
|
||||
______________________ TestMoreErrors.test_startswith ______________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -493,7 +493,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:206: AssertionError
|
||||
failure_demo.py:207: AssertionError
|
||||
__________________ TestMoreErrors.test_startswith_nested ___________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -512,7 +512,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:215: AssertionError
|
||||
failure_demo.py:216: AssertionError
|
||||
_____________________ TestMoreErrors.test_global_func ______________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -523,7 +523,7 @@ get on the terminal - we are working on that)::
|
||||
E + where False = isinstance(43, float)
|
||||
E + where 43 = globf(42)
|
||||
|
||||
failure_demo.py:218: AssertionError
|
||||
failure_demo.py:219: AssertionError
|
||||
_______________________ TestMoreErrors.test_instance _______________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -534,7 +534,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:222: AssertionError
|
||||
failure_demo.py:223: AssertionError
|
||||
_______________________ TestMoreErrors.test_compare ________________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -544,7 +544,7 @@ get on the terminal - we are working on that)::
|
||||
E assert 11 < 5
|
||||
E + where 11 = globf(10)
|
||||
|
||||
failure_demo.py:225: AssertionError
|
||||
failure_demo.py:226: AssertionError
|
||||
_____________________ TestMoreErrors.test_try_finally ______________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -555,7 +555,7 @@ get on the terminal - we are working on that)::
|
||||
> assert x == 0
|
||||
E assert 1 == 0
|
||||
|
||||
failure_demo.py:230: AssertionError
|
||||
failure_demo.py:231: AssertionError
|
||||
___________________ TestCustomAssertMsg.test_single_line ___________________
|
||||
|
||||
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
|
||||
@@ -570,7 +570,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:241: AssertionError
|
||||
failure_demo.py:242: AssertionError
|
||||
____________________ TestCustomAssertMsg.test_multiline ____________________
|
||||
|
||||
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
|
||||
@@ -582,14 +582,14 @@ get on the terminal - we are working on that)::
|
||||
b = 2
|
||||
> assert (
|
||||
A.a == b
|
||||
), "A.a appears not to be b\n" "or does not appear to be b\none of those"
|
||||
), "A.a appears not to be b\nor does not appear to be b\none of those"
|
||||
E AssertionError: A.a appears not to be b
|
||||
E or does not appear to be b
|
||||
E one of those
|
||||
E assert 1 == 2
|
||||
E + where 1 = <class 'failure_demo.TestCustomAssertMsg.test_multiline.<locals>.A'>.a
|
||||
|
||||
failure_demo.py:248: AssertionError
|
||||
failure_demo.py:249: AssertionError
|
||||
___________________ TestCustomAssertMsg.test_custom_repr ___________________
|
||||
|
||||
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
|
||||
@@ -611,11 +611,12 @@ 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:261: AssertionError
|
||||
failure_demo.py:262: AssertionError
|
||||
============================= warnings summary =============================
|
||||
$REGENDOC_TMPDIR/assertion/failure_demo.py:24: RemovedInPytest4Warning: Metafunc.addcall is deprecated and scheduled to be removed in pytest 4.0.
|
||||
Please use Metafunc.parametrize instead.
|
||||
metafunc.addcall(funcargs=dict(param1=3, param2=6))
|
||||
failure_demo.py:25
|
||||
$REGENDOC_TMPDIR/assertion/failure_demo.py:25: RemovedInPytest4Warning: Metafunc.addcall is deprecated and scheduled to be removed in pytest 4.0.
|
||||
Please use Metafunc.parametrize instead.
|
||||
metafunc.addcall(funcargs=dict(param1=3, param2=6))
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/latest/warnings.html
|
||||
================== 42 failed, 1 warnings in 0.12 seconds ===================
|
||||
|
||||
@@ -357,7 +357,7 @@ which will add info only when run with "--v"::
|
||||
|
||||
$ pytest -v
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
|
||||
cachedir: .pytest_cache
|
||||
info1: did you know that ...
|
||||
did you?
|
||||
@@ -574,7 +574,7 @@ We can run this::
|
||||
file $REGENDOC_TMPDIR/b/test_error.py, line 1
|
||||
def test_root(db): # no db here, will error out
|
||||
E fixture 'db' not found
|
||||
> available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, monkeypatch, pytestconfig, record_property, record_xml_attribute, record_xml_property, recwarn, tmpdir, tmpdir_factory
|
||||
> available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, monkeypatch, pytestconfig, record_property, record_xml_attribute, record_xml_property, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory
|
||||
> use 'pytest --fixtures [testpath]' for help on them.
|
||||
|
||||
$REGENDOC_TMPDIR/b/test_error.py:1
|
||||
@@ -852,6 +852,8 @@ In that order.
|
||||
can be changed between releases (even bug fixes) so it shouldn't be relied on for scripting
|
||||
or automation.
|
||||
|
||||
.. _freezing-pytest:
|
||||
|
||||
Freezing pytest
|
||||
---------------
|
||||
|
||||
|
||||
@@ -259,6 +259,11 @@ instance, you can simply declare it:
|
||||
|
||||
Finally, the ``class`` scope will invoke the fixture once per test *class*.
|
||||
|
||||
.. note::
|
||||
|
||||
Pytest will only cache one instance of a fixture at a time.
|
||||
This means that when using a parametrized fixture, pytest may invoke a fixture more than once in the given scope.
|
||||
|
||||
|
||||
``package`` scope (experimental)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@@ -727,7 +732,7 @@ Running this test will *skip* the invocation of ``data_set`` with value ``2``::
|
||||
|
||||
$ pytest test_fixture_marks.py -v
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
|
||||
cachedir: .pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 3 items
|
||||
@@ -770,7 +775,7 @@ Here we declare an ``app`` fixture which receives the previously defined
|
||||
|
||||
$ pytest -v test_appsetup.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
|
||||
cachedir: .pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 2 items
|
||||
@@ -839,7 +844,7 @@ Let's run the tests in verbose mode and with looking at the print-output::
|
||||
|
||||
$ pytest -v -s test_module.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
|
||||
cachedir: .pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 8 items
|
||||
|
||||
@@ -175,3 +175,13 @@ Previous to version 2.4 to set a break point in code one needed to use ``pytest.
|
||||
This is no longer needed and one can use the native ``import pdb;pdb.set_trace()`` call directly.
|
||||
|
||||
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.
|
||||
|
||||
Users should just ``import pytest`` and access those objects using the ``pytest`` module.
|
||||
|
||||
@@ -114,6 +114,10 @@ Let's run this::
|
||||
The one parameter set which caused a failure previously now
|
||||
shows up as an "xfailed (expected to fail)" test.
|
||||
|
||||
In case the values provided to ``parametrize`` result in an empty list - for
|
||||
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::
|
||||
|
||||
|
||||
@@ -69,17 +69,15 @@ You may also discover more plugins through a `pytest- pypi.python.org search`_.
|
||||
Requiring/Loading plugins in a test module or conftest file
|
||||
-----------------------------------------------------------
|
||||
|
||||
You can require plugins in a test module or a conftest file like this::
|
||||
You can require plugins in a test module or a conftest file like this:
|
||||
|
||||
pytest_plugins = "myapp.testsupport.myplugin",
|
||||
.. code-block:: python
|
||||
|
||||
pytest_plugins = ("myapp.testsupport.myplugin",)
|
||||
|
||||
When the test module or conftest plugin is loaded the specified plugins
|
||||
will be loaded as well.
|
||||
|
||||
pytest_plugins = "myapp.testsupport.myplugin"
|
||||
|
||||
which will import the specified module as a ``pytest`` plugin.
|
||||
|
||||
.. note::
|
||||
Requiring plugins using a ``pytest_plugins`` variable in non-root
|
||||
``conftest.py`` files is deprecated. See
|
||||
|
||||
@@ -84,6 +84,12 @@ pytest.warns
|
||||
.. autofunction:: pytest.warns(expected_warning: Exception, [match])
|
||||
:with:
|
||||
|
||||
pytest.freeze_includes
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
**Tutorial**: :ref:`freezing-pytest`.
|
||||
|
||||
.. autofunction:: pytest.freeze_includes
|
||||
|
||||
.. _`marks ref`:
|
||||
|
||||
@@ -172,7 +178,7 @@ Mark a test function as using the given fixture names.
|
||||
|
||||
.. warning::
|
||||
|
||||
This mark can be used with *test functions* only, having no affect when applied
|
||||
This mark has no effect when applied
|
||||
to a **fixture** function.
|
||||
|
||||
.. py:function:: pytest.mark.usefixtures(*names)
|
||||
@@ -976,6 +982,7 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||
|
||||
* ``skip`` skips tests with an empty parameterset (default)
|
||||
* ``xfail`` marks tests with an empty parameterset as xfail(run=False)
|
||||
* ``fail_at_collect`` raises an exception if parametrize collects an empty parameter set
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
|
||||
@@ -58,18 +58,20 @@ by calling the ``pytest.skip(reason)`` function:
|
||||
if not valid_config():
|
||||
pytest.skip("unsupported configuration")
|
||||
|
||||
The imperative method is useful when it is not possible to evaluate the skip condition
|
||||
during import time.
|
||||
|
||||
It is also possible to skip the whole module using
|
||||
``pytest.skip(reason, allow_module_level=True)`` at the module level:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import sys
|
||||
import pytest
|
||||
|
||||
if not pytest.config.getoption("--custom-flag"):
|
||||
pytest.skip("--custom-flag is missing, skipping tests", allow_module_level=True)
|
||||
if not sys.platform.startswith("win"):
|
||||
pytest.skip("skipping windows-only tests", allow_module_level=True)
|
||||
|
||||
The imperative method is useful when it is not possible to evaluate the skip condition
|
||||
during import time.
|
||||
|
||||
**Reference**: :ref:`pytest.mark.skip ref`
|
||||
|
||||
|
||||
@@ -5,6 +5,76 @@
|
||||
Temporary directories and files
|
||||
================================================
|
||||
|
||||
The ``tmp_path`` fixture
|
||||
------------------------
|
||||
|
||||
.. versionadded:: 3.9
|
||||
|
||||
|
||||
You can use the ``tmp_path`` fixture which will
|
||||
provide a temporary directory unique to the test invocation,
|
||||
created in the `base temporary directory`_.
|
||||
|
||||
``tmp_path`` is a ``pathlib/pathlib2.Path`` object. Here is an example test usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_tmp_path.py
|
||||
import os
|
||||
|
||||
CONTENT = u"content"
|
||||
|
||||
|
||||
def test_create_file(tmp_path):
|
||||
d = tmp_path / "sub"
|
||||
d.mkdir()
|
||||
p = d / "hello.txt"
|
||||
p.write_text(CONTENT)
|
||||
assert p.read_text() == CONTENT
|
||||
assert len(list(tmp_path.iterdir())) == 1
|
||||
assert 0
|
||||
|
||||
Running this would result in a passed test except for the last
|
||||
``assert 0`` line which we use to look at values::
|
||||
|
||||
$ pytest test_tmp_path.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 item
|
||||
|
||||
test_tmp_path.py F [100%]
|
||||
|
||||
================================= FAILURES =================================
|
||||
_____________________________ test_create_file _____________________________
|
||||
|
||||
tmp_path = PosixPath('PYTEST_TMPDIR/test_create_file0')
|
||||
|
||||
def test_create_file(tmp_path):
|
||||
d = tmp_path / "sub"
|
||||
d.mkdir()
|
||||
p = d / "hello.txt"
|
||||
p.write_text(CONTENT)
|
||||
assert p.read_text() == CONTENT
|
||||
assert len(list(tmp_path.iterdir())) == 1
|
||||
> assert 0
|
||||
E assert 0
|
||||
|
||||
test_tmp_path.py:13: AssertionError
|
||||
========================= 1 failed in 0.12 seconds =========================
|
||||
|
||||
The ``tmp_path_factory`` fixture
|
||||
--------------------------------
|
||||
|
||||
.. versionadded:: 3.9
|
||||
|
||||
|
||||
The ``tmp_path_factory`` is a session-scoped fixture which can be used
|
||||
to create arbitrary temporary directories from any other fixture or test.
|
||||
|
||||
It is intended to replace ``tmpdir_factory``, and returns :class:`pathlib.Path` instances.
|
||||
|
||||
|
||||
The 'tmpdir' fixture
|
||||
--------------------
|
||||
|
||||
|
||||
@@ -22,16 +22,15 @@ Almost all ``unittest`` features are supported:
|
||||
|
||||
* ``@unittest.skip`` style decorators;
|
||||
* ``setUp/tearDown``;
|
||||
* ``setUpClass/tearDownClass()``;
|
||||
* ``setUpClass/tearDownClass``;
|
||||
* ``setUpModule/tearDownModule``;
|
||||
|
||||
.. _`load_tests protocol`: https://docs.python.org/3/library/unittest.html#load-tests-protocol
|
||||
.. _`setUpModule/tearDownModule`: https://docs.python.org/3/library/unittest.html#setupmodule-and-teardownmodule
|
||||
.. _`subtests`: https://docs.python.org/3/library/unittest.html#distinguishing-test-iterations-using-subtests
|
||||
|
||||
Up to this point pytest does not have support for the following features:
|
||||
|
||||
* `load_tests protocol`_;
|
||||
* `setUpModule/tearDownModule`_;
|
||||
* `subtests`_;
|
||||
|
||||
Benefits out of the box
|
||||
|
||||
@@ -269,6 +269,7 @@ To get a list of the slowest 10 test durations::
|
||||
|
||||
pytest --durations=10
|
||||
|
||||
By default, pytest will not show test durations that are too small (<0.01s) unless ``-vv`` is passed on the command-line.
|
||||
|
||||
Creating JUnitXML format files
|
||||
----------------------------------------------------
|
||||
|
||||
@@ -29,8 +29,9 @@ Running pytest now produces this output::
|
||||
test_show_warnings.py . [100%]
|
||||
|
||||
============================= warnings summary =============================
|
||||
$REGENDOC_TMPDIR/test_show_warnings.py:4: UserWarning: api v1, should use functions from v2
|
||||
warnings.warn(UserWarning("api v1, should use functions from v2"))
|
||||
test_show_warnings.py::test_one
|
||||
$REGENDOC_TMPDIR/test_show_warnings.py:4: 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
|
||||
=================== 1 passed, 1 warnings in 0.12 seconds ===================
|
||||
@@ -75,54 +76,6 @@ Both ``-W`` command-line option and ``filterwarnings`` ini option are based on P
|
||||
`-W option`_ and `warnings.simplefilter`_, so please refer to those sections in the Python
|
||||
documentation for other examples and advanced usage.
|
||||
|
||||
Disabling warning summary
|
||||
-------------------------
|
||||
|
||||
Although not recommended, you can use the ``--disable-warnings`` command-line option to suppress the
|
||||
warning summary entirely from the test run output.
|
||||
|
||||
Disabling warning capture entirely
|
||||
----------------------------------
|
||||
|
||||
This plugin is enabled by default but can be disabled entirely in your ``pytest.ini`` file with:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[pytest]
|
||||
addopts = -p no:warnings
|
||||
|
||||
Or passing ``-p no:warnings`` in the command-line. This might be useful if your test suites handles warnings
|
||||
using an external system.
|
||||
|
||||
|
||||
.. _`deprecation-warnings`:
|
||||
|
||||
DeprecationWarning and PendingDeprecationWarning
|
||||
------------------------------------------------
|
||||
|
||||
.. versionadded:: 3.8
|
||||
|
||||
By default pytest will display ``DeprecationWarning`` and ``PendingDeprecationWarning`` if no other warning filters
|
||||
are configured.
|
||||
|
||||
To disable showing ``DeprecationWarning`` and ``PendingDeprecationWarning`` warnings, you might define any warnings
|
||||
filter either in the command-line or in the ini file, or you can use:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[pytest]
|
||||
filterwarnings =
|
||||
ignore::DeprecationWarning
|
||||
ignore::PendingDeprecationWarning
|
||||
|
||||
.. note::
|
||||
This makes pytest more compliant with `PEP-0506 <https://www.python.org/dev/peps/pep-0565/#recommended-filter-settings-for-test-runners>`_ which suggests that those warnings should
|
||||
be shown by default by test runners, but pytest doesn't follow ``PEP-0506`` completely because resetting all
|
||||
warning filters like suggested in the PEP will break existing test suites that configure warning filters themselves
|
||||
by calling ``warnings.simplefilter`` (see issue `#2430 <https://github.com/pytest-dev/pytest/issues/2430>`_
|
||||
for an example of that).
|
||||
|
||||
|
||||
.. _`filterwarnings`:
|
||||
|
||||
``@pytest.mark.filterwarnings``
|
||||
@@ -161,24 +114,6 @@ decorator or to all tests in a module by setting the ``pytestmark`` variable:
|
||||
pytestmark = pytest.mark.filterwarnings("error")
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
Except for these features, pytest does not change the python warning filter; it only captures
|
||||
and displays the warnings which are issued with respect to the currently configured filter,
|
||||
including changes to the filter made by test functions or by the system under test.
|
||||
|
||||
.. note::
|
||||
|
||||
``DeprecationWarning`` and ``PendingDeprecationWarning`` are hidden by the standard library
|
||||
by default so you have to explicitly configure them to be displayed in your ``pytest.ini``:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[pytest]
|
||||
filterwarnings =
|
||||
once::DeprecationWarning
|
||||
once::PendingDeprecationWarning
|
||||
|
||||
|
||||
*Credits go to Florian Schulze for the reference implementation in the* `pytest-warnings`_
|
||||
*plugin.*
|
||||
@@ -187,6 +122,102 @@ decorator or to all tests in a module by setting the ``pytestmark`` variable:
|
||||
.. _warnings.simplefilter: https://docs.python.org/3/library/warnings.html#warnings.simplefilter
|
||||
.. _`pytest-warnings`: https://github.com/fschulze/pytest-warnings
|
||||
|
||||
Disabling warnings summary
|
||||
--------------------------
|
||||
|
||||
Although not recommended, you can use the ``--disable-warnings`` command-line option to suppress the
|
||||
warning summary entirely from the test run output.
|
||||
|
||||
Disabling warning capture entirely
|
||||
----------------------------------
|
||||
|
||||
This plugin is enabled by default but can be disabled entirely in your ``pytest.ini`` file with:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[pytest]
|
||||
addopts = -p no:warnings
|
||||
|
||||
Or passing ``-p no:warnings`` in the command-line. This might be useful if your test suites handles warnings
|
||||
using an external system.
|
||||
|
||||
|
||||
.. _`deprecation-warnings`:
|
||||
|
||||
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-0506 <https://www.python.org/dev/peps/pep-0565>`_.
|
||||
This helps users keep their code modern and avoid breakages when deprecated warnings are effectively removed.
|
||||
|
||||
Sometimes it is useful to hide some specific deprecation warnings that happen in code that you have no control over
|
||||
(such as third-party libraries), in which case you might use the warning filters options (ini or marks) to ignore
|
||||
those warnings.
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[pytest]
|
||||
filterwarnings =
|
||||
ignore:.*U.*mode is deprecated:DeprecationWarning
|
||||
|
||||
|
||||
This will ignore all warnings of type ``DeprecationWarning`` where the start of the message matches
|
||||
the regular expression ``".*U.*mode is deprecated"``.
|
||||
|
||||
.. note::
|
||||
If warnings are configured at the interpreter level, using
|
||||
the `PYTHONWARNINGS <https://docs.python.org/3/using/cmdline.html#envvar-PYTHONWARNINGS>`_ environment variable or the
|
||||
``-W`` command-line option, pytest will not configure any filters by default.
|
||||
|
||||
Also pytest doesn't follow ``PEP-0506`` suggestion of resetting all warning filters because
|
||||
it might break test suites that configure warning filters themselves
|
||||
by calling ``warnings.simplefilter`` (see issue `#2430 <https://github.com/pytest-dev/pytest/issues/2430>`_
|
||||
for an example of that).
|
||||
|
||||
|
||||
.. _`ensuring a function triggers a deprecation warning`:
|
||||
|
||||
.. _ensuring_function_triggers:
|
||||
|
||||
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``::
|
||||
|
||||
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')``::
|
||||
|
||||
import warnings
|
||||
import pytest
|
||||
|
||||
def test_deprecation(recwarn):
|
||||
warnings.simplefilter('always')
|
||||
warnings.warn("deprecated", DeprecationWarning)
|
||||
assert len(recwarn) == 1
|
||||
assert recwarn.pop(DeprecationWarning)
|
||||
|
||||
You can also use it as a contextmanager::
|
||||
|
||||
def test_global():
|
||||
with pytest.deprecated_call():
|
||||
myobject.deprecated_method()
|
||||
|
||||
|
||||
|
||||
.. _`asserting warnings`:
|
||||
|
||||
@@ -293,43 +324,6 @@ warnings, or index into it to get a particular recorded warning.
|
||||
|
||||
Full API: :class:`WarningsRecorder`.
|
||||
|
||||
.. _`ensuring a function triggers a deprecation warning`:
|
||||
|
||||
.. _ensuring_function_triggers:
|
||||
|
||||
Ensuring a function triggers a deprecation warning
|
||||
-------------------------------------------------------
|
||||
|
||||
You can also call a global helper for checking
|
||||
that a certain function call triggers a ``DeprecationWarning`` or
|
||||
``PendingDeprecationWarning``::
|
||||
|
||||
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')``::
|
||||
|
||||
import warnings
|
||||
import pytest
|
||||
|
||||
def test_deprecation(recwarn):
|
||||
warnings.simplefilter('always')
|
||||
warnings.warn("deprecated", DeprecationWarning)
|
||||
assert len(recwarn) == 1
|
||||
assert recwarn.pop(DeprecationWarning)
|
||||
|
||||
You can also use it as a contextmanager::
|
||||
|
||||
def test_global():
|
||||
with pytest.deprecated_call():
|
||||
myobject.deprecated_method()
|
||||
|
||||
|
||||
|
||||
.. _internal-warnings:
|
||||
|
||||
@@ -358,8 +352,9 @@ defines an ``__init__`` constructor, as this prevents the class from being insta
|
||||
$ pytest test_pytest_warnings.py -q
|
||||
|
||||
============================= warnings summary =============================
|
||||
$REGENDOC_TMPDIR/test_pytest_warnings.py:1: PytestWarning: cannot collect test class 'Test' because it has a __init__ constructor
|
||||
class Test:
|
||||
test_pytest_warnings.py:1
|
||||
$REGENDOC_TMPDIR/test_pytest_warnings.py:1: PytestWarning: cannot collect test class 'Test' because it has a __init__ constructor
|
||||
class Test:
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/latest/warnings.html
|
||||
1 warnings in 0.12 seconds
|
||||
|
||||
@@ -418,11 +418,24 @@ additionally it is possible to copy examples for a example folder before running
|
||||
test_example.py .. [100%]
|
||||
|
||||
============================= warnings summary =============================
|
||||
$REGENDOC_TMPDIR/test_example.py:4: PytestExperimentalApiWarning: testdir.copy_example is an experimental api that may change over time
|
||||
testdir.copy_example("test_example.py")
|
||||
test_example.py::test_plugin
|
||||
$REGENDOC_TMPDIR/test_example.py:4: PytestExperimentalApiWarning: testdir.copy_example is an experimental api that may change over time
|
||||
testdir.copy_example("test_example.py")
|
||||
$PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:332: RemovedInPytest4Warning: usage of Session.Class is deprecated, please use pytest.Class instead
|
||||
return getattr(object, name, default)
|
||||
$PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:332: RemovedInPytest4Warning: usage of Session.File is deprecated, please use pytest.File instead
|
||||
return getattr(object, name, default)
|
||||
$PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:332: RemovedInPytest4Warning: usage of Session.Function is deprecated, please use pytest.Function instead
|
||||
return getattr(object, name, default)
|
||||
$PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:332: RemovedInPytest4Warning: usage of Session.Instance is deprecated, please use pytest.Instance instead
|
||||
return getattr(object, name, default)
|
||||
$PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:332: RemovedInPytest4Warning: usage of Session.Item is deprecated, please use pytest.Item instead
|
||||
return getattr(object, name, default)
|
||||
$PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:332: RemovedInPytest4Warning: usage of Session.Module is deprecated, please use pytest.Module instead
|
||||
return getattr(object, name, default)
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/latest/warnings.html
|
||||
=================== 2 passed, 1 warnings in 0.12 seconds ===================
|
||||
=================== 2 passed, 7 warnings in 0.12 seconds ===================
|
||||
|
||||
For more information about the result object that ``runpytest()`` returns, and
|
||||
the methods that it provides please check out the :py:class:`RunResult
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import json
|
||||
|
||||
import py
|
||||
import requests
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[build-system]
|
||||
requires = [
|
||||
"setuptools",
|
||||
# sync with setup.py until we discard non-pep-517/518
|
||||
"setuptools>=40.0",
|
||||
"setuptools-scm",
|
||||
"wheel",
|
||||
]
|
||||
@@ -15,7 +16,12 @@ template = "changelog/_template.rst"
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "removal"
|
||||
name = "Deprecations and Removals"
|
||||
name = "Removals"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "deprecation"
|
||||
name = "Deprecations"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
|
||||
@@ -2,9 +2,13 @@
|
||||
Invoke development tasks.
|
||||
"""
|
||||
import argparse
|
||||
from colorama import init, Fore
|
||||
from pathlib import Path
|
||||
from subprocess import check_output, check_call, call
|
||||
from subprocess import call
|
||||
from subprocess import check_call
|
||||
from subprocess import check_output
|
||||
|
||||
from colorama import Fore
|
||||
from colorama import init
|
||||
|
||||
|
||||
def announce(version):
|
||||
|
||||
54
setup.cfg
54
setup.cfg
@@ -1,3 +1,55 @@
|
||||
[metadata]
|
||||
|
||||
name = pytest
|
||||
description = pytest: simple powerful testing with Python
|
||||
long_description = file: README.rst
|
||||
url = https://docs.pytest.org/en/latest/
|
||||
project_urls =
|
||||
Source=https://github.com/pytest-dev/pytest
|
||||
Tracker=https://github.com/pytest-dev/pytest/issues
|
||||
|
||||
author = Holger Krekel, Bruno Oliveira, Ronny Pfannschmidt, Floris Bruynooghe, Brianna Laugher, Florian Bruhin and others
|
||||
|
||||
license = MIT license
|
||||
license_file = LICENSE
|
||||
keywords = test, unittest
|
||||
classifiers =
|
||||
Development Status :: 6 - Mature
|
||||
Intended Audience :: Developers
|
||||
License :: OSI Approved :: MIT License
|
||||
Operating System :: POSIX
|
||||
Operating System :: Microsoft :: Windows
|
||||
Operating System :: MacOS :: MacOS X
|
||||
Topic :: Software Development :: Testing
|
||||
Topic :: Software Development :: Libraries
|
||||
Topic :: Utilities
|
||||
Programming Language :: Python :: 2
|
||||
Programming Language :: Python :: 2.7
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3.4
|
||||
Programming Language :: Python :: 3.5
|
||||
Programming Language :: Python :: 3.6
|
||||
Programming Language :: Python :: 3.7
|
||||
platforms = unix, linux, osx, cygwin, win32
|
||||
|
||||
[options]
|
||||
zip_safe = no
|
||||
packages =
|
||||
_pytest
|
||||
_pytest.assertion
|
||||
_pytest._code
|
||||
_pytest.mark
|
||||
_pytest.config
|
||||
|
||||
py_modules = pytest
|
||||
python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
|
||||
|
||||
|
||||
[options.entry_points]
|
||||
console_scripts =
|
||||
pytest=pytest:main
|
||||
py.test=pytest:main
|
||||
|
||||
[build_sphinx]
|
||||
source-dir = doc/en/
|
||||
build-dir = doc/build
|
||||
@@ -13,8 +65,6 @@ universal = 1
|
||||
ignore =
|
||||
_pytest/_version.py
|
||||
|
||||
[metadata]
|
||||
license_file = LICENSE
|
||||
|
||||
[devpi:upload]
|
||||
formats = sdist.tgz,bdist_wheel
|
||||
|
||||
131
setup.py
131
setup.py
@@ -1,126 +1,35 @@
|
||||
import os
|
||||
import sys
|
||||
import setuptools
|
||||
import pkg_resources
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
classifiers = [
|
||||
"Development Status :: 6 - Mature",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: POSIX",
|
||||
"Operating System :: Microsoft :: Windows",
|
||||
"Operating System :: MacOS :: MacOS X",
|
||||
"Topic :: Software Development :: Testing",
|
||||
"Topic :: Software Development :: Libraries",
|
||||
"Topic :: Utilities",
|
||||
] + [
|
||||
("Programming Language :: Python :: %s" % x)
|
||||
for x in "2 2.7 3 3.4 3.5 3.6 3.7".split()
|
||||
|
||||
# TODO: if py gets upgrade to >=1.6,
|
||||
# remove _width_of_current_line in terminal.py
|
||||
INSTALL_REQUIRES = [
|
||||
"py>=1.5.0",
|
||||
"six>=1.10.0",
|
||||
"setuptools",
|
||||
"attrs>=17.4.0",
|
||||
"more-itertools>=4.0.0",
|
||||
"atomicwrites>=1.0",
|
||||
'funcsigs;python_version<"3.0"',
|
||||
'pathlib2>=2.2.0;python_version<"3.6"',
|
||||
'colorama;sys_platform=="win32"',
|
||||
]
|
||||
|
||||
with open("README.rst") as fd:
|
||||
long_description = fd.read()
|
||||
|
||||
|
||||
def get_environment_marker_support_level():
|
||||
"""
|
||||
Tests how well setuptools supports PEP-426 environment marker.
|
||||
|
||||
The first known release to support it is 0.7 (and the earliest on PyPI seems to be 0.7.2
|
||||
so we're using that), see: https://setuptools.readthedocs.io/en/latest/history.html#id350
|
||||
|
||||
The support is later enhanced to allow direct conditional inclusions inside install_requires,
|
||||
which is now recommended by setuptools. It first appeared in 36.2.0, went broken with 36.2.1, and
|
||||
again worked since 36.2.2, so we're using that. See:
|
||||
https://setuptools.readthedocs.io/en/latest/history.html#v36-2-2
|
||||
https://github.com/pypa/setuptools/issues/1099
|
||||
|
||||
References:
|
||||
|
||||
* https://wheel.readthedocs.io/en/latest/index.html#defining-conditional-dependencies
|
||||
* https://www.python.org/dev/peps/pep-0426/#environment-markers
|
||||
* https://setuptools.readthedocs.io/en/latest/setuptools.html#declaring-platform-specific-dependencies
|
||||
"""
|
||||
try:
|
||||
version = pkg_resources.parse_version(setuptools.__version__)
|
||||
if version >= pkg_resources.parse_version("36.2.2"):
|
||||
return 2
|
||||
if version >= pkg_resources.parse_version("0.7.2"):
|
||||
return 1
|
||||
except Exception as exc:
|
||||
sys.stderr.write("Could not test setuptool's version: %s\n" % exc)
|
||||
|
||||
# as of testing on 2018-05-26 fedora was on version 37* and debian was on version 33+
|
||||
# we should consider erroring on those
|
||||
return 0
|
||||
# if _PYTEST_SETUP_SKIP_PLUGGY_DEP is set, skip installing pluggy;
|
||||
# used by tox.ini to test with pluggy master
|
||||
if "_PYTEST_SETUP_SKIP_PLUGGY_DEP" not in os.environ:
|
||||
INSTALL_REQUIRES.append("pluggy>=0.7")
|
||||
|
||||
|
||||
def main():
|
||||
extras_require = {}
|
||||
install_requires = [
|
||||
"py>=1.5.0", # if py gets upgrade to >=1.6, remove _width_of_current_line in terminal.py
|
||||
"six>=1.10.0",
|
||||
"setuptools",
|
||||
"attrs>=17.4.0",
|
||||
"more-itertools>=4.0.0",
|
||||
"atomicwrites>=1.0",
|
||||
]
|
||||
# if _PYTEST_SETUP_SKIP_PLUGGY_DEP is set, skip installing pluggy;
|
||||
# used by tox.ini to test with pluggy master
|
||||
if "_PYTEST_SETUP_SKIP_PLUGGY_DEP" not in os.environ:
|
||||
install_requires.append("pluggy>=0.7")
|
||||
environment_marker_support_level = get_environment_marker_support_level()
|
||||
if environment_marker_support_level >= 2:
|
||||
install_requires.append('funcsigs;python_version<"3.0"')
|
||||
install_requires.append('pathlib2>=2.2.0;python_version<"3.6"')
|
||||
install_requires.append('colorama;sys_platform=="win32"')
|
||||
elif environment_marker_support_level == 1:
|
||||
extras_require[':python_version<"3.0"'] = ["funcsigs"]
|
||||
extras_require[':python_version<"3.6"'] = ["pathlib2>=2.2.0"]
|
||||
extras_require[':sys_platform=="win32"'] = ["colorama"]
|
||||
else:
|
||||
if sys.platform == "win32":
|
||||
install_requires.append("colorama")
|
||||
if sys.version_info < (3, 0):
|
||||
install_requires.append("funcsigs")
|
||||
if sys.version_info < (3, 6):
|
||||
install_requires.append("pathlib2>=2.2.0")
|
||||
|
||||
setup(
|
||||
name="pytest",
|
||||
description="pytest: simple powerful testing with Python",
|
||||
long_description=long_description,
|
||||
use_scm_version={"write_to": "src/_pytest/_version.py"},
|
||||
url="https://docs.pytest.org/en/latest/",
|
||||
project_urls={
|
||||
"Source": "https://github.com/pytest-dev/pytest",
|
||||
"Tracker": "https://github.com/pytest-dev/pytest/issues",
|
||||
},
|
||||
license="MIT license",
|
||||
platforms=["unix", "linux", "osx", "cygwin", "win32"],
|
||||
author=(
|
||||
"Holger Krekel, Bruno Oliveira, Ronny Pfannschmidt, "
|
||||
"Floris Bruynooghe, Brianna Laugher, Florian Bruhin and others"
|
||||
),
|
||||
entry_points={"console_scripts": ["pytest=pytest:main", "py.test=pytest:main"]},
|
||||
classifiers=classifiers,
|
||||
keywords="test unittest",
|
||||
# the following should be enabled for release
|
||||
setup_requires=["setuptools-scm"],
|
||||
setup_requires=["setuptools-scm", "setuptools>=40.0"],
|
||||
package_dir={"": "src"},
|
||||
python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
|
||||
install_requires=install_requires,
|
||||
extras_require=extras_require,
|
||||
packages=[
|
||||
"_pytest",
|
||||
"_pytest.assertion",
|
||||
"_pytest._code",
|
||||
"_pytest.mark",
|
||||
"_pytest.config",
|
||||
],
|
||||
py_modules=["pytest"],
|
||||
zip_safe=False,
|
||||
install_requires=INSTALL_REQUIRES,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
"""allow bash-completion for argparse with argcomplete if installed
|
||||
needs argcomplete>=0.5.6 for python 3.2/3.3 (older versions fail
|
||||
to find the magic string, so _ARGCOMPLETE env. var is never set, and
|
||||
@@ -54,9 +53,12 @@ If things do not work right away:
|
||||
which should throw a KeyError: 'COMPLINE' (which is properly set by the
|
||||
global argcomplete script).
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
import sys
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import sys
|
||||
from glob import glob
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
""" python inspection/code generation API """
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
from .code import Code # noqa
|
||||
from .code import ExceptionInfo # noqa
|
||||
from .code import filter_traceback # noqa
|
||||
from .code import Frame # noqa
|
||||
from .code import Traceback # noqa
|
||||
from .code import getrawcode # noqa
|
||||
from .source import Source # noqa
|
||||
from .code import Traceback # noqa
|
||||
from .source import compile_ as compile # noqa
|
||||
from .source import getfslineno # noqa
|
||||
from .source import Source # noqa
|
||||
|
||||
@@ -2,8 +2,13 @@
|
||||
# CHANGES:
|
||||
# - some_str is replaced, trying to create unicode strings
|
||||
#
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import types
|
||||
|
||||
from six import text_type
|
||||
|
||||
|
||||
|
||||
@@ -1,17 +1,27 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import inspect
|
||||
import pprint
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
from inspect import CO_VARARGS, CO_VARKEYWORDS
|
||||
from inspect import CO_VARARGS
|
||||
from inspect import CO_VARKEYWORDS
|
||||
from weakref import ref
|
||||
|
||||
import attr
|
||||
import re
|
||||
from weakref import ref
|
||||
from _pytest.compat import _PY2, _PY3, PY35, safe_str
|
||||
from six import text_type
|
||||
import pluggy
|
||||
import py
|
||||
import six
|
||||
from six import text_type
|
||||
|
||||
import _pytest
|
||||
from _pytest.compat import _PY2
|
||||
from _pytest.compat import _PY3
|
||||
from _pytest.compat import PY35
|
||||
from _pytest.compat import safe_str
|
||||
|
||||
builtin_repr = repr
|
||||
|
||||
@@ -451,13 +461,35 @@ class ExceptionInfo(object):
|
||||
tbfilter=True,
|
||||
funcargs=False,
|
||||
truncate_locals=True,
|
||||
chain=True,
|
||||
):
|
||||
""" return str()able representation of this exception info.
|
||||
showlocals: show locals per traceback entry
|
||||
style: long|short|no|native traceback style
|
||||
tbfilter: hide entries (where __tracebackhide__ is true)
|
||||
"""
|
||||
Return str()able representation of this exception info.
|
||||
|
||||
in case of style==native, tbfilter and showlocals is ignored.
|
||||
:param bool showlocals:
|
||||
Show locals per traceback entry.
|
||||
Ignored if ``style=="native"``.
|
||||
|
||||
:param str style: long|short|no|native traceback style
|
||||
|
||||
:param bool abspath:
|
||||
If paths should be changed to absolute or left unchanged.
|
||||
|
||||
:param bool tbfilter:
|
||||
Hide entries that contain a local variable ``__tracebackhide__==True``.
|
||||
Ignored if ``style=="native"``.
|
||||
|
||||
:param bool funcargs:
|
||||
Show fixtures ("funcargs" for legacy purposes) per traceback entry.
|
||||
|
||||
:param bool truncate_locals:
|
||||
With ``showlocals==True``, make sure locals can be safely represented as strings.
|
||||
|
||||
:param bool chain: if chained exceptions in Python 3 should be shown.
|
||||
|
||||
.. versionchanged:: 3.9
|
||||
|
||||
Added the ``chain`` parameter.
|
||||
"""
|
||||
if style == "native":
|
||||
return ReprExceptionInfo(
|
||||
@@ -476,6 +508,7 @@ class ExceptionInfo(object):
|
||||
tbfilter=tbfilter,
|
||||
funcargs=funcargs,
|
||||
truncate_locals=truncate_locals,
|
||||
chain=chain,
|
||||
)
|
||||
return fmt.repr_excinfo(self)
|
||||
|
||||
@@ -516,6 +549,7 @@ class FormattedExcinfo(object):
|
||||
tbfilter = attr.ib(default=True)
|
||||
funcargs = attr.ib(default=False)
|
||||
truncate_locals = attr.ib(default=True)
|
||||
chain = attr.ib(default=True)
|
||||
astcache = attr.ib(default=attr.Factory(dict), init=False, repr=False)
|
||||
|
||||
def _getindent(self, source):
|
||||
@@ -735,7 +769,7 @@ class FormattedExcinfo(object):
|
||||
reprcrash = None
|
||||
|
||||
repr_chain += [(reprtraceback, reprcrash, descr)]
|
||||
if e.__cause__ is not None:
|
||||
if e.__cause__ is not None and self.chain:
|
||||
e = e.__cause__
|
||||
excinfo = (
|
||||
ExceptionInfo((type(e), e, e.__traceback__))
|
||||
@@ -743,7 +777,11 @@ class FormattedExcinfo(object):
|
||||
else None
|
||||
)
|
||||
descr = "The above exception was the direct cause of the following exception:"
|
||||
elif e.__context__ is not None and not e.__suppress_context__:
|
||||
elif (
|
||||
e.__context__ is not None
|
||||
and not e.__suppress_context__
|
||||
and self.chain
|
||||
):
|
||||
e = e.__context__
|
||||
excinfo = (
|
||||
ExceptionInfo((type(e), e, e.__traceback__))
|
||||
@@ -979,3 +1017,36 @@ else:
|
||||
return "maximum recursion depth exceeded" in str(excinfo.value)
|
||||
except UnicodeError:
|
||||
return False
|
||||
|
||||
|
||||
# relative paths that we use to filter traceback entries from appearing to the user;
|
||||
# see filter_traceback
|
||||
# note: if we need to add more paths than what we have now we should probably use a list
|
||||
# for better maintenance
|
||||
|
||||
_PLUGGY_DIR = py.path.local(pluggy.__file__.rstrip("oc"))
|
||||
# pluggy is either a package or a single module depending on the version
|
||||
if _PLUGGY_DIR.basename == "__init__.py":
|
||||
_PLUGGY_DIR = _PLUGGY_DIR.dirpath()
|
||||
_PYTEST_DIR = py.path.local(_pytest.__file__).dirpath()
|
||||
_PY_DIR = py.path.local(py.__file__).dirpath()
|
||||
|
||||
|
||||
def filter_traceback(entry):
|
||||
"""Return True if a TracebackEntry instance should be removed from tracebacks:
|
||||
* dynamically generated code (no code to show up for it);
|
||||
* internal traceback from pytest or its internal libraries, py and pluggy.
|
||||
"""
|
||||
# entry.path might sometimes return a str object when the entry
|
||||
# points to dynamically generated code
|
||||
# see https://bitbucket.org/pytest-dev/py/issues/71
|
||||
raw_filename = entry.frame.code.raw.co_filename
|
||||
is_generated = "<" in raw_filename and ">" in raw_filename
|
||||
if is_generated:
|
||||
return False
|
||||
# entry.path might point to a non-existing file, in which case it will
|
||||
# also return a str object. see #1133
|
||||
p = py.path.local(entry.path)
|
||||
return (
|
||||
not p.relto(_PLUGGY_DIR) and not p.relto(_PYTEST_DIR) and not p.relto(_PY_DIR)
|
||||
)
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import ast
|
||||
from ast import PyCF_ONLY_AST as _AST_FLAG
|
||||
from bisect import bisect_right
|
||||
import inspect
|
||||
import linecache
|
||||
import sys
|
||||
import six
|
||||
import inspect
|
||||
import textwrap
|
||||
import tokenize
|
||||
from ast import PyCF_ONLY_AST as _AST_FLAG
|
||||
from bisect import bisect_right
|
||||
|
||||
import py
|
||||
import six
|
||||
|
||||
cpy_compile = compile
|
||||
|
||||
@@ -23,7 +27,6 @@ class Source(object):
|
||||
def __init__(self, *parts, **kwargs):
|
||||
self.lines = lines = []
|
||||
de = kwargs.get("deindent", True)
|
||||
rstrip = kwargs.get("rstrip", True)
|
||||
for part in parts:
|
||||
if not part:
|
||||
partlines = []
|
||||
@@ -33,11 +36,6 @@ class Source(object):
|
||||
partlines = [x.rstrip("\n") for x in part]
|
||||
elif isinstance(part, six.string_types):
|
||||
partlines = part.split("\n")
|
||||
if rstrip:
|
||||
while partlines:
|
||||
if partlines[-1].strip():
|
||||
break
|
||||
partlines.pop()
|
||||
else:
|
||||
partlines = getsource(part, deindent=de).lines
|
||||
if de:
|
||||
@@ -115,17 +113,10 @@ class Source(object):
|
||||
ast, start, end = getstatementrange_ast(lineno, self)
|
||||
return start, end
|
||||
|
||||
def deindent(self, offset=None):
|
||||
""" return a new source object deindented by offset.
|
||||
If offset is None then guess an indentation offset from
|
||||
the first non-blank line. Subsequent lines which have a
|
||||
lower indentation offset will be copied verbatim as
|
||||
they are assumed to be part of multilines.
|
||||
"""
|
||||
# XXX maybe use the tokenizer to properly handle multiline
|
||||
# strings etc.pp?
|
||||
def deindent(self):
|
||||
"""return a new source object deindented."""
|
||||
newsource = Source()
|
||||
newsource.lines[:] = deindent(self.lines, offset)
|
||||
newsource.lines[:] = deindent(self.lines)
|
||||
return newsource
|
||||
|
||||
def isparseable(self, deindent=True):
|
||||
@@ -268,47 +259,8 @@ def getsource(obj, **kwargs):
|
||||
return Source(strsrc, **kwargs)
|
||||
|
||||
|
||||
def deindent(lines, offset=None):
|
||||
if offset is None:
|
||||
for line in lines:
|
||||
line = line.expandtabs()
|
||||
s = line.lstrip()
|
||||
if s:
|
||||
offset = len(line) - len(s)
|
||||
break
|
||||
else:
|
||||
offset = 0
|
||||
if offset == 0:
|
||||
return list(lines)
|
||||
newlines = []
|
||||
|
||||
def readline_generator(lines):
|
||||
for line in lines:
|
||||
yield line + "\n"
|
||||
|
||||
it = readline_generator(lines)
|
||||
|
||||
try:
|
||||
for _, _, (sline, _), (eline, _), _ in tokenize.generate_tokens(
|
||||
lambda: next(it)
|
||||
):
|
||||
if sline > len(lines):
|
||||
break # End of input reached
|
||||
if sline > len(newlines):
|
||||
line = lines[sline - 1].expandtabs()
|
||||
if line.lstrip() and line[:offset].isspace():
|
||||
line = line[offset:] # Deindent
|
||||
newlines.append(line)
|
||||
|
||||
for i in range(sline, eline):
|
||||
# Don't deindent continuing lines of
|
||||
# multiline tokens (i.e. multiline strings)
|
||||
newlines.append(lines[i])
|
||||
except (IndentationError, tokenize.TokenError):
|
||||
pass
|
||||
# Add any lines we didn't see. E.g. if an exception was raised.
|
||||
newlines.extend(lines[len(newlines) :])
|
||||
return newlines
|
||||
def deindent(lines):
|
||||
return textwrap.dedent("\n".join(lines)).splitlines()
|
||||
|
||||
|
||||
def get_statement_startend2(lineno, node):
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
"""
|
||||
support for presenting detailed information in failing assertions.
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import sys
|
||||
|
||||
import six
|
||||
|
||||
from _pytest.assertion import util
|
||||
from _pytest.assertion import rewrite
|
||||
from _pytest.assertion import truncate
|
||||
from _pytest.assertion import util
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
"""Rewrite assertion AST to produce nice error messages"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import ast
|
||||
import errno
|
||||
import itertools
|
||||
import imp
|
||||
import itertools
|
||||
import marshal
|
||||
import os
|
||||
import re
|
||||
import six
|
||||
import string
|
||||
import struct
|
||||
import sys
|
||||
@@ -15,10 +17,12 @@ import types
|
||||
|
||||
import atomicwrites
|
||||
import py
|
||||
import six
|
||||
|
||||
from _pytest.assertion import util
|
||||
from _pytest.compat import PurePath, spec_from_file_location
|
||||
from _pytest.paths import fnmatch_ex
|
||||
from _pytest.compat import spec_from_file_location
|
||||
from _pytest.pathlib import fnmatch_ex
|
||||
from _pytest.pathlib import PurePath
|
||||
|
||||
# pytest caches rewritten pycs in __pycache__.
|
||||
if hasattr(imp, "get_tag"):
|
||||
@@ -398,7 +402,7 @@ def _rewrite_test(config, fn):
|
||||
finally:
|
||||
del state._indecode
|
||||
try:
|
||||
tree = ast.parse(source)
|
||||
tree = ast.parse(source, filename=fn.strpath)
|
||||
except SyntaxError:
|
||||
# Let this pop up again in the real import.
|
||||
state.trace("failed to parse: %r" % (fn,))
|
||||
@@ -705,10 +709,9 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||
setattr(node, name, new)
|
||||
elif (
|
||||
isinstance(field, ast.AST)
|
||||
and
|
||||
# Don't recurse into expressions as they can't contain
|
||||
# asserts.
|
||||
not isinstance(field, ast.expr)
|
||||
and not isinstance(field, ast.expr)
|
||||
):
|
||||
nodes.append(field)
|
||||
|
||||
|
||||
@@ -4,7 +4,10 @@ Utilities for truncating assertion output.
|
||||
Current default behaviour is to truncate assertion explanations at
|
||||
~8 terminal lines, unless running in "-vv" mode or running on CI.
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
|
||||
import six
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
"""Utilities for assertion debugging"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import pprint
|
||||
|
||||
import _pytest._code
|
||||
import py
|
||||
import six
|
||||
|
||||
import _pytest._code
|
||||
from ..compat import Sequence
|
||||
|
||||
u = six.text_type
|
||||
@@ -199,7 +203,7 @@ def _diff_text(left, right, verbose=False):
|
||||
if i > 42:
|
||||
i -= 10 # Provide some context
|
||||
explanation = [
|
||||
u("Skipping %s identical leading " "characters in diff, use -v to show")
|
||||
u("Skipping %s identical leading characters in diff, use -v to show")
|
||||
% i
|
||||
]
|
||||
left = left[i:]
|
||||
|
||||
@@ -4,19 +4,22 @@ merged implementation of the cache provider
|
||||
the name cache was not chosen to ensure pluggy automatically
|
||||
ignores the external pytest-cache
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import json
|
||||
from collections import OrderedDict
|
||||
|
||||
import attr
|
||||
import py
|
||||
import six
|
||||
import attr
|
||||
|
||||
import pytest
|
||||
import json
|
||||
import shutil
|
||||
|
||||
from . import paths
|
||||
from .compat import _PY2 as PY2, Path
|
||||
from .compat import _PY2 as PY2
|
||||
from .pathlib import Path
|
||||
from .pathlib import resolve_from_str
|
||||
from .pathlib import rmtree
|
||||
|
||||
README_CONTENT = u"""\
|
||||
# pytest cache directory #
|
||||
@@ -39,13 +42,13 @@ class Cache(object):
|
||||
def for_config(cls, config):
|
||||
cachedir = cls.cache_dir_from_config(config)
|
||||
if config.getoption("cacheclear") and cachedir.exists():
|
||||
shutil.rmtree(str(cachedir))
|
||||
rmtree(cachedir, force=True)
|
||||
cachedir.mkdir()
|
||||
return cls(cachedir, config)
|
||||
|
||||
@staticmethod
|
||||
def cache_dir_from_config(config):
|
||||
return paths.resolve_from_str(config.getini("cache_dir"), config.rootdir)
|
||||
return resolve_from_str(config.getini("cache_dir"), config.rootdir)
|
||||
|
||||
def warn(self, fmt, **args):
|
||||
from _pytest.warnings import _issue_config_warning
|
||||
@@ -344,7 +347,7 @@ def cacheshow(config, session):
|
||||
key = valpath.relative_to(vdir)
|
||||
val = config.cache.get(key, dummy)
|
||||
if val is dummy:
|
||||
tw.line("%s contains unreadable content, " "will be ignored" % key)
|
||||
tw.line("%s contains unreadable content, will be ignored" % key)
|
||||
else:
|
||||
tw.line("%s contains:" % key)
|
||||
for line in pformat(val).splitlines():
|
||||
|
||||
@@ -2,17 +2,20 @@
|
||||
per-test stdout/stderr capturing mechanism.
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import collections
|
||||
import contextlib
|
||||
import sys
|
||||
import os
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
from io import UnsupportedOperation
|
||||
from tempfile import TemporaryFile
|
||||
|
||||
import six
|
||||
|
||||
import pytest
|
||||
from _pytest.compat import CaptureIO
|
||||
|
||||
@@ -654,7 +657,7 @@ class DontReadFromInput(six.Iterator):
|
||||
return self
|
||||
|
||||
def fileno(self):
|
||||
raise UnsupportedOperation("redirected stdin is pseudofile, " "has no fileno()")
|
||||
raise UnsupportedOperation("redirected stdin is pseudofile, has no fileno()")
|
||||
|
||||
def isatty(self):
|
||||
return False
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
"""
|
||||
python version compatibility code
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import codecs
|
||||
import functools
|
||||
@@ -11,11 +13,12 @@ import sys
|
||||
from contextlib import contextmanager
|
||||
|
||||
import py
|
||||
import six
|
||||
from six import text_type
|
||||
|
||||
import _pytest
|
||||
from _pytest.outcomes import fail
|
||||
from _pytest.outcomes import TEST_OUTCOME
|
||||
from six import text_type
|
||||
import six
|
||||
|
||||
try:
|
||||
import enum
|
||||
@@ -23,8 +26,6 @@ except ImportError: # pragma: no cover
|
||||
# Only available in Python 3.4+ or as a backport
|
||||
enum = None
|
||||
|
||||
__all__ = ["Path", "PurePath"]
|
||||
|
||||
_PY3 = sys.version_info > (3, 0)
|
||||
_PY2 = not _PY3
|
||||
|
||||
@@ -41,11 +42,6 @@ PY35 = sys.version_info[:2] >= (3, 5)
|
||||
PY36 = sys.version_info[:2] >= (3, 6)
|
||||
MODULE_NOT_FOUND_ERROR = "ModuleNotFoundError" if PY36 else "ImportError"
|
||||
|
||||
if PY36:
|
||||
from pathlib import Path, PurePath
|
||||
else:
|
||||
from pathlib2 import Path, PurePath
|
||||
|
||||
|
||||
if _PY3:
|
||||
from collections.abc import MutableMapping as MappingMixin
|
||||
@@ -138,9 +134,17 @@ def getfuncargnames(function, is_method=False, cls=None):
|
||||
# ordered mapping of parameter names to Parameter instances. This
|
||||
# creates a tuple of the names of the parameters that don't have
|
||||
# defaults.
|
||||
try:
|
||||
parameters = signature(function).parameters
|
||||
except (ValueError, TypeError) as e:
|
||||
fail(
|
||||
"Could not determine arguments of {!r}: {}".format(function, e),
|
||||
pytrace=False,
|
||||
)
|
||||
|
||||
arg_names = tuple(
|
||||
p.name
|
||||
for p in signature(function).parameters.values()
|
||||
for p in parameters.values()
|
||||
if (
|
||||
p.kind is Parameter.POSITIONAL_OR_KEYWORD
|
||||
or p.kind is Parameter.KEYWORD_ONLY
|
||||
@@ -275,7 +279,7 @@ def get_real_func(obj):
|
||||
obj = new_obj
|
||||
else:
|
||||
raise ValueError(
|
||||
("could not find real function of {start}" "\nstopped at {current}").format(
|
||||
("could not find real function of {start}\nstopped at {current}").format(
|
||||
start=py.io.saferepr(start_obj), current=py.io.saferepr(obj)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,34 +1,38 @@
|
||||
""" command line options, ini-file and conftest.py processing. """
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import copy
|
||||
import inspect
|
||||
import os
|
||||
import shlex
|
||||
import traceback
|
||||
import sys
|
||||
import types
|
||||
import warnings
|
||||
import copy
|
||||
import six
|
||||
import py
|
||||
|
||||
# DON't import pytest here because it causes import cycle troubles
|
||||
import sys
|
||||
import os
|
||||
from _pytest.outcomes import Skipped
|
||||
import py
|
||||
import six
|
||||
from pluggy import HookimplMarker
|
||||
from pluggy import HookspecMarker
|
||||
from pluggy import PluginManager
|
||||
|
||||
import _pytest._code
|
||||
import _pytest.hookspec # the extension point definitions
|
||||
import _pytest.assertion
|
||||
from pluggy import PluginManager, HookimplMarker, HookspecMarker
|
||||
import _pytest.hookspec # the extension point definitions
|
||||
from .exceptions import PrintHelp
|
||||
from .exceptions import UsageError
|
||||
from .findpaths import determine_setup
|
||||
from .findpaths import exists
|
||||
from _pytest._code import ExceptionInfo
|
||||
from _pytest._code import filter_traceback
|
||||
from _pytest.compat import safe_str
|
||||
from .exceptions import UsageError, PrintHelp
|
||||
from .findpaths import determine_setup, exists
|
||||
from _pytest.outcomes import Skipped
|
||||
|
||||
hookimpl = HookimplMarker("pytest")
|
||||
hookspec = HookspecMarker("pytest")
|
||||
|
||||
# pytest startup
|
||||
#
|
||||
|
||||
|
||||
class ConftestImportFailure(Exception):
|
||||
def __init__(self, path, excinfo):
|
||||
@@ -36,12 +40,6 @@ class ConftestImportFailure(Exception):
|
||||
self.path = path
|
||||
self.excinfo = excinfo
|
||||
|
||||
def __str__(self):
|
||||
etype, evalue, etb = self.excinfo
|
||||
formatted = traceback.format_tb(etb)
|
||||
# The level of the tracebacks we want to print is hand crafted :(
|
||||
return repr(evalue) + "\n" + "".join(formatted[2:])
|
||||
|
||||
|
||||
def main(args=None, plugins=None):
|
||||
""" return exit code, after performing an in-process test run.
|
||||
@@ -57,10 +55,20 @@ def main(args=None, plugins=None):
|
||||
try:
|
||||
config = _prepareconfig(args, plugins)
|
||||
except ConftestImportFailure as e:
|
||||
exc_info = ExceptionInfo(e.excinfo)
|
||||
tw = py.io.TerminalWriter(sys.stderr)
|
||||
for line in traceback.format_exception(*e.excinfo):
|
||||
tw.line(
|
||||
"ImportError while loading conftest '{e.path}'.".format(e=e), red=True
|
||||
)
|
||||
exc_info.traceback = exc_info.traceback.filter(filter_traceback)
|
||||
exc_repr = (
|
||||
exc_info.getrepr(style="short", chain=False)
|
||||
if exc_info.traceback
|
||||
else exc_info.exconly()
|
||||
)
|
||||
formatted_tb = safe_str(exc_repr)
|
||||
for line in formatted_tb.splitlines():
|
||||
tw.line(line.rstrip(), red=True)
|
||||
tw.line("ERROR: could not load %s\n" % (e.path,), red=True)
|
||||
return 4
|
||||
else:
|
||||
try:
|
||||
@@ -378,23 +386,25 @@ class PytestPluginManager(PluginManager):
|
||||
def _getconftestmodules(self, path):
|
||||
if self._noconftest:
|
||||
return []
|
||||
|
||||
try:
|
||||
return self._path2confmods[path]
|
||||
except KeyError:
|
||||
if path.isfile():
|
||||
clist = self._getconftestmodules(path.dirpath())
|
||||
directory = path.dirpath()
|
||||
else:
|
||||
# XXX these days we may rather want to use config.rootdir
|
||||
# and allow users to opt into looking into the rootdir parent
|
||||
# directories instead of requiring to specify confcutdir
|
||||
clist = []
|
||||
for parent in path.parts():
|
||||
if self._confcutdir and self._confcutdir.relto(parent):
|
||||
continue
|
||||
conftestpath = parent.join("conftest.py")
|
||||
if conftestpath.isfile():
|
||||
mod = self._importconftest(conftestpath)
|
||||
clist.append(mod)
|
||||
directory = path
|
||||
# XXX these days we may rather want to use config.rootdir
|
||||
# and allow users to opt into looking into the rootdir parent
|
||||
# directories instead of requiring to specify confcutdir
|
||||
clist = []
|
||||
for parent in directory.realpath().parts():
|
||||
if self._confcutdir and self._confcutdir.relto(parent):
|
||||
continue
|
||||
conftestpath = parent.join("conftest.py")
|
||||
if conftestpath.isfile():
|
||||
mod = self._importconftest(conftestpath)
|
||||
clist.append(mod)
|
||||
|
||||
self._path2confmods[path] = clist
|
||||
return clist
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import six
|
||||
import warnings
|
||||
import argparse
|
||||
|
||||
from gettext import gettext as _
|
||||
import sys as _sys
|
||||
import warnings
|
||||
from gettext import gettext as _
|
||||
|
||||
import py
|
||||
import six
|
||||
|
||||
from ..main import EXIT_USAGEERROR
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import py
|
||||
import os
|
||||
|
||||
import py
|
||||
|
||||
from .exceptions import UsageError
|
||||
|
||||
|
||||
@@ -103,21 +105,18 @@ def determine_setup(inifile, args, rootdir_cmd_arg=None, config=None):
|
||||
if inifile:
|
||||
iniconfig = py.iniconfig.IniConfig(inifile)
|
||||
is_cfg_file = str(inifile).endswith(".cfg")
|
||||
# TODO: [pytest] section in *.cfg files is depricated. Need refactoring.
|
||||
sections = ["tool:pytest", "pytest"] if is_cfg_file else ["pytest"]
|
||||
for section in sections:
|
||||
try:
|
||||
inicfg = iniconfig[section]
|
||||
if is_cfg_file and section == "pytest" and config is not None:
|
||||
from _pytest.deprecated import CFG_PYTEST_SECTION
|
||||
from _pytest.warning_types import RemovedInPytest4Warning
|
||||
from _pytest.warnings import _issue_config_warning
|
||||
|
||||
# TODO: [pytest] section in *.cfg files is deprecated. Need refactoring once
|
||||
# the deprecation expires.
|
||||
_issue_config_warning(
|
||||
RemovedInPytest4Warning(
|
||||
CFG_PYTEST_SECTION.format(filename=str(inifile))
|
||||
),
|
||||
config,
|
||||
CFG_PYTEST_SECTION.format(filename=str(inifile)), config
|
||||
)
|
||||
break
|
||||
except KeyError:
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
""" interactive debugging with PDB, the Python Debugger. """
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import pdb
|
||||
import sys
|
||||
import os
|
||||
from doctest import UnexpectedException
|
||||
|
||||
from _pytest import outcomes
|
||||
from _pytest.config import hookimpl
|
||||
|
||||
try:
|
||||
from builtins import breakpoint # noqa
|
||||
|
||||
SUPPORTS_BREAKPOINT_BUILTIN = True
|
||||
except ImportError:
|
||||
SUPPORTS_BREAKPOINT_BUILTIN = False
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("general")
|
||||
@@ -51,20 +47,12 @@ def pytest_configure(config):
|
||||
if config.getvalue("usepdb"):
|
||||
config.pluginmanager.register(PdbInvoke(), "pdbinvoke")
|
||||
|
||||
# Use custom Pdb class set_trace instead of default Pdb on breakpoint() call
|
||||
if SUPPORTS_BREAKPOINT_BUILTIN:
|
||||
_environ_pythonbreakpoint = os.environ.get("PYTHONBREAKPOINT", "")
|
||||
if _environ_pythonbreakpoint == "":
|
||||
sys.breakpointhook = pytestPDB.set_trace
|
||||
|
||||
old = (pdb.set_trace, pytestPDB._pluginmanager)
|
||||
|
||||
def fin():
|
||||
pdb.set_trace, pytestPDB._pluginmanager = old
|
||||
pytestPDB._config = None
|
||||
pytestPDB._pdb_cls = pdb.Pdb
|
||||
if SUPPORTS_BREAKPOINT_BUILTIN:
|
||||
sys.breakpointhook = sys.__breakpointhook__
|
||||
|
||||
pdb.set_trace = pytestPDB.set_trace
|
||||
pytestPDB._pluginmanager = config.pluginmanager
|
||||
@@ -109,9 +97,6 @@ class PdbInvoke(object):
|
||||
_enter_pdb(node, call.excinfo, report)
|
||||
|
||||
def pytest_internalerror(self, excrepr, excinfo):
|
||||
for line in str(excrepr).split("\n"):
|
||||
sys.stderr.write("INTERNALERROR> %s\n" % line)
|
||||
sys.stderr.flush()
|
||||
tb = _postmortem_traceback(excinfo)
|
||||
post_mortem(tb)
|
||||
|
||||
@@ -164,8 +149,9 @@ def _enter_pdb(node, excinfo, rep):
|
||||
rep.toterminal(tw)
|
||||
tw.sep(">", "entering PDB")
|
||||
tb = _postmortem_traceback(excinfo)
|
||||
post_mortem(tb)
|
||||
rep._pdbshown = True
|
||||
if post_mortem(tb):
|
||||
outcomes.exit("Quitting debugger")
|
||||
return rep
|
||||
|
||||
|
||||
@@ -196,3 +182,4 @@ def post_mortem(t):
|
||||
p = Pdb()
|
||||
p.reset()
|
||||
p.interaction(None, t)
|
||||
return p.quitting
|
||||
|
||||
@@ -4,10 +4,17 @@ that is planned to be removed in the next pytest release.
|
||||
|
||||
Keeping it in a central location makes it easy to track what is deprecated and should
|
||||
be removed when the time comes.
|
||||
|
||||
All constants defined in this module should be either PytestWarning instances or UnformattedWarning
|
||||
in case of warnings which need to format their messages.
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
from _pytest.warning_types import RemovedInPytest4Warning
|
||||
from _pytest.warning_types import UnformattedWarning
|
||||
|
||||
|
||||
MAIN_STR_ARGS = RemovedInPytest4Warning(
|
||||
"passing a string to pytest.main() is deprecated, "
|
||||
@@ -18,25 +25,48 @@ YIELD_TESTS = RemovedInPytest4Warning(
|
||||
"yield tests are deprecated, and scheduled to be removed in pytest 4.0"
|
||||
)
|
||||
|
||||
FUNCARG_PREFIX = (
|
||||
CACHED_SETUP = RemovedInPytest4Warning(
|
||||
"cached_setup is deprecated and will be removed in a future release. "
|
||||
"Use standard fixture functions instead."
|
||||
)
|
||||
|
||||
COMPAT_PROPERTY = UnformattedWarning(
|
||||
RemovedInPytest4Warning,
|
||||
"usage of {owner}.{name} is deprecated, please use pytest.{name} instead",
|
||||
)
|
||||
|
||||
CUSTOM_CLASS = UnformattedWarning(
|
||||
RemovedInPytest4Warning,
|
||||
'use of special named "{name}" objects in collectors of type "{type_name}" to '
|
||||
"customize the created nodes is deprecated. "
|
||||
"Use pytest_pycollect_makeitem(...) to create custom "
|
||||
"collection nodes instead.",
|
||||
)
|
||||
|
||||
FUNCARG_PREFIX = UnformattedWarning(
|
||||
RemovedInPytest4Warning,
|
||||
'{name}: declaring fixtures using "pytest_funcarg__" prefix is deprecated '
|
||||
"and scheduled to be removed in pytest 4.0. "
|
||||
"Please remove the prefix and use the @pytest.fixture decorator instead."
|
||||
"Please remove the prefix and use the @pytest.fixture decorator instead.",
|
||||
)
|
||||
|
||||
FIXTURE_FUNCTION_CALL = (
|
||||
FIXTURE_FUNCTION_CALL = UnformattedWarning(
|
||||
RemovedInPytest4Warning,
|
||||
'Fixture "{name}" called directly. Fixtures are not meant to be called directly, '
|
||||
"are created automatically when test functions request them as parameters. "
|
||||
"See https://docs.pytest.org/en/latest/fixture.html for more information."
|
||||
"See https://docs.pytest.org/en/latest/fixture.html for more information.",
|
||||
)
|
||||
|
||||
CFG_PYTEST_SECTION = (
|
||||
"[pytest] section in {filename} files is deprecated, use [tool:pytest] instead."
|
||||
CFG_PYTEST_SECTION = UnformattedWarning(
|
||||
RemovedInPytest4Warning,
|
||||
"[pytest] section in {filename} files is deprecated, use [tool:pytest] instead.",
|
||||
)
|
||||
|
||||
GETFUNCARGVALUE = "getfuncargvalue is deprecated, use getfixturevalue"
|
||||
GETFUNCARGVALUE = RemovedInPytest4Warning(
|
||||
"getfuncargvalue is deprecated, use getfixturevalue"
|
||||
)
|
||||
|
||||
RESULT_LOG = (
|
||||
RESULT_LOG = RemovedInPytest4Warning(
|
||||
"--result-log is deprecated and scheduled for removal in pytest 4.0.\n"
|
||||
"See https://docs.pytest.org/en/latest/usage.html#creating-resultlog-format-files for more information."
|
||||
)
|
||||
@@ -64,7 +94,7 @@ RECORD_XML_PROPERTY = RemovedInPytest4Warning(
|
||||
)
|
||||
|
||||
COLLECTOR_MAKEITEM = RemovedInPytest4Warning(
|
||||
"pycollector makeitem was removed " "as it is an accidentially leaked internal api"
|
||||
"pycollector makeitem was removed as it is an accidentially leaked internal api"
|
||||
)
|
||||
|
||||
METAFUNC_ADD_CALL = RemovedInPytest4Warning(
|
||||
@@ -81,3 +111,8 @@ PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST = RemovedInPytest4Warning(
|
||||
PYTEST_NAMESPACE = RemovedInPytest4Warning(
|
||||
"pytest_namespace is deprecated and will be removed soon"
|
||||
)
|
||||
|
||||
PYTEST_ENSURETEMP = RemovedInPytest4Warning(
|
||||
"pytest/tmpdir_factory.ensuretemp is deprecated, \n"
|
||||
"please use the tmp_path fixture or tmp_path_factory.mktemp"
|
||||
)
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
""" discover and run doctests in modules and test files."""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import traceback
|
||||
import sys
|
||||
import platform
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
import pytest
|
||||
from _pytest._code.code import ExceptionInfo, ReprFileLocation, TerminalRepr
|
||||
from _pytest._code.code import ExceptionInfo
|
||||
from _pytest._code.code import ReprFileLocation
|
||||
from _pytest._code.code import TerminalRepr
|
||||
from _pytest.fixtures import FixtureRequest
|
||||
|
||||
|
||||
|
||||
@@ -1,39 +1,41 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import functools
|
||||
import inspect
|
||||
import sys
|
||||
import warnings
|
||||
from collections import OrderedDict, deque, defaultdict
|
||||
|
||||
import six
|
||||
from more_itertools import flatten
|
||||
from collections import defaultdict
|
||||
from collections import deque
|
||||
from collections import OrderedDict
|
||||
|
||||
import attr
|
||||
import py
|
||||
import six
|
||||
from more_itertools import flatten
|
||||
from py._code.code import FormattedExcinfo
|
||||
|
||||
import _pytest
|
||||
from _pytest import nodes
|
||||
from _pytest._code.code import TerminalRepr
|
||||
from _pytest.compat import (
|
||||
NOTSET,
|
||||
exc_clear,
|
||||
_format_args,
|
||||
getfslineno,
|
||||
get_real_func,
|
||||
is_generator,
|
||||
isclass,
|
||||
getimfunc,
|
||||
getlocation,
|
||||
getfuncargnames,
|
||||
safe_getattr,
|
||||
FuncargnamesCompatAttr,
|
||||
get_real_method,
|
||||
_PytestWrapper,
|
||||
)
|
||||
from _pytest.deprecated import FIXTURE_FUNCTION_CALL, RemovedInPytest4Warning
|
||||
from _pytest.outcomes import fail, TEST_OUTCOME
|
||||
from _pytest.compat import _format_args
|
||||
from _pytest.compat import _PytestWrapper
|
||||
from _pytest.compat import exc_clear
|
||||
from _pytest.compat import FuncargnamesCompatAttr
|
||||
from _pytest.compat import get_real_func
|
||||
from _pytest.compat import get_real_method
|
||||
from _pytest.compat import getfslineno
|
||||
from _pytest.compat import getfuncargnames
|
||||
from _pytest.compat import getimfunc
|
||||
from _pytest.compat import getlocation
|
||||
from _pytest.compat import is_generator
|
||||
from _pytest.compat import isclass
|
||||
from _pytest.compat import NOTSET
|
||||
from _pytest.compat import safe_getattr
|
||||
from _pytest.deprecated import FIXTURE_FUNCTION_CALL
|
||||
from _pytest.outcomes import fail
|
||||
from _pytest.outcomes import TEST_OUTCOME
|
||||
|
||||
FIXTURE_MSG = 'fixtures cannot have "pytest_funcarg__" prefix and be decorated with @pytest.fixture:\n{}'
|
||||
|
||||
@@ -359,8 +361,10 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||
|
||||
@property
|
||||
def fixturenames(self):
|
||||
# backward incompatible note: now a readonly property
|
||||
return list(self._pyfuncitem._fixtureinfo.names_closure)
|
||||
"""names of all active fixtures in this request"""
|
||||
result = list(self._pyfuncitem._fixtureinfo.names_closure)
|
||||
result.extend(set(self._fixture_defs).difference(result))
|
||||
return result
|
||||
|
||||
@property
|
||||
def node(self):
|
||||
@@ -479,6 +483,9 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||
or ``session`` indicating the caching lifecycle of the resource.
|
||||
:arg extrakey: added to internal caching key of (funcargname, scope).
|
||||
"""
|
||||
from _pytest.deprecated import CACHED_SETUP
|
||||
|
||||
warnings.warn(CACHED_SETUP, stacklevel=2)
|
||||
if not hasattr(self.config, "_setupcache"):
|
||||
self.config._setupcache = {} # XXX weakref?
|
||||
cachekey = (self.fixturename, self._getscopeitem(scope), extrakey)
|
||||
@@ -512,7 +519,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||
""" Deprecated, use getfixturevalue. """
|
||||
from _pytest import deprecated
|
||||
|
||||
warnings.warn(deprecated.GETFUNCARGVALUE, DeprecationWarning, stacklevel=2)
|
||||
warnings.warn(deprecated.GETFUNCARGVALUE, stacklevel=2)
|
||||
return self.getfixturevalue(argname)
|
||||
|
||||
def _get_active_fixturedef(self, argname):
|
||||
@@ -562,7 +569,20 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||
except (AttributeError, ValueError):
|
||||
param = NOTSET
|
||||
param_index = 0
|
||||
if fixturedef.params is not None:
|
||||
has_params = fixturedef.params is not None
|
||||
fixtures_not_supported = getattr(funcitem, "nofuncargs", False)
|
||||
if has_params and fixtures_not_supported:
|
||||
msg = (
|
||||
"{name} does not support fixtures, maybe unittest.TestCase subclass?\n"
|
||||
"Node id: {nodeid}\n"
|
||||
"Function type: {typename}"
|
||||
).format(
|
||||
name=funcitem.name,
|
||||
nodeid=funcitem.nodeid,
|
||||
typename=type(funcitem).__name__,
|
||||
)
|
||||
fail(msg, pytrace=False)
|
||||
if has_params:
|
||||
frame = inspect.stack()[3]
|
||||
frameinfo = inspect.getframeinfo(frame[0])
|
||||
source_path = frameinfo.filename
|
||||
@@ -571,16 +591,18 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||
if source_path.relto(funcitem.config.rootdir):
|
||||
source_path = source_path.relto(funcitem.config.rootdir)
|
||||
msg = (
|
||||
"The requested fixture has no parameter defined for the "
|
||||
"current test.\n\nRequested fixture '{}' defined in:\n{}"
|
||||
"The requested fixture has no parameter defined for test:\n"
|
||||
" {}\n\n"
|
||||
"Requested fixture '{}' defined in:\n{}"
|
||||
"\n\nRequested here:\n{}:{}".format(
|
||||
funcitem.nodeid,
|
||||
fixturedef.argname,
|
||||
getlocation(fixturedef.func, funcitem.config.rootdir),
|
||||
source_path,
|
||||
source_lineno,
|
||||
)
|
||||
)
|
||||
fail(msg)
|
||||
fail(msg, pytrace=False)
|
||||
else:
|
||||
# indices might not be set if old-style metafunc.addcall() was used
|
||||
param_index = funcitem.callspec.indices.get(argname, 0)
|
||||
@@ -698,10 +720,11 @@ def scope2index(scope, descr, where=None):
|
||||
try:
|
||||
return scopes.index(scope)
|
||||
except ValueError:
|
||||
raise ValueError(
|
||||
"{} {}has an unsupported scope value '{}'".format(
|
||||
fail(
|
||||
"{} {}got an unexpected scope value '{}'".format(
|
||||
descr, "from {} ".format(where) if where else "", scope
|
||||
)
|
||||
),
|
||||
pytrace=False,
|
||||
)
|
||||
|
||||
|
||||
@@ -741,14 +764,19 @@ class FixtureLookupError(LookupError):
|
||||
|
||||
if msg is None:
|
||||
fm = self.request._fixturemanager
|
||||
available = []
|
||||
available = set()
|
||||
parentid = self.request._pyfuncitem.parent.nodeid
|
||||
for name, fixturedefs in fm._arg2fixturedefs.items():
|
||||
faclist = list(fm._matchfactories(fixturedefs, parentid))
|
||||
if faclist and name not in available:
|
||||
available.append(name)
|
||||
msg = "fixture %r not found" % (self.argname,)
|
||||
msg += "\n available fixtures: %s" % (", ".join(sorted(available)),)
|
||||
if faclist:
|
||||
available.add(name)
|
||||
if self.argname in available:
|
||||
msg = " recursive dependency involving fixture '{}' detected".format(
|
||||
self.argname
|
||||
)
|
||||
else:
|
||||
msg = "fixture '{}' not found".format(self.argname)
|
||||
msg += "\n available fixtures: {}".format(", ".join(sorted(available)))
|
||||
msg += "\n use 'pytest --fixtures [testpath]' for help on them."
|
||||
|
||||
return FixtureLookupErrorRepr(fspath, lineno, tblines, msg, self.argname)
|
||||
@@ -834,7 +862,9 @@ class FixtureDef(object):
|
||||
self.argname = argname
|
||||
self.scope = scope
|
||||
self.scopenum = scope2index(
|
||||
scope or "function", descr="fixture {}".format(func.__name__), where=baseid
|
||||
scope or "function",
|
||||
descr="Fixture '{}'".format(func.__name__),
|
||||
where=baseid,
|
||||
)
|
||||
self.params = params
|
||||
self.argnames = getfuncargnames(func, is_method=unittest)
|
||||
@@ -896,7 +926,7 @@ class FixtureDef(object):
|
||||
return hook.pytest_fixture_setup(fixturedef=self, request=request)
|
||||
|
||||
def __repr__(self):
|
||||
return "<FixtureDef name=%r scope=%r baseid=%r >" % (
|
||||
return "<FixtureDef name=%r scope=%r baseid=%r>" % (
|
||||
self.argname,
|
||||
self.scope,
|
||||
self.baseid,
|
||||
@@ -956,8 +986,9 @@ def wrap_function_to_warning_if_called_directly(function, fixture_marker):
|
||||
used as an argument in a test function.
|
||||
"""
|
||||
is_yield_function = is_generator(function)
|
||||
msg = FIXTURE_FUNCTION_CALL.format(name=fixture_marker.name or function.__name__)
|
||||
warning = RemovedInPytest4Warning(msg)
|
||||
warning = FIXTURE_FUNCTION_CALL.format(
|
||||
name=fixture_marker.name or function.__name__
|
||||
)
|
||||
|
||||
if is_yield_function:
|
||||
|
||||
@@ -996,7 +1027,7 @@ class FixtureFunctionMarker(object):
|
||||
|
||||
def __call__(self, function):
|
||||
if isclass(function):
|
||||
raise ValueError("class fixtures not supported (may be in the future)")
|
||||
raise ValueError("class fixtures not supported (maybe in the future)")
|
||||
|
||||
if getattr(function, "_pytestfixturefunction", False):
|
||||
raise ValueError(
|
||||
@@ -1151,7 +1182,7 @@ class FixtureManager(object):
|
||||
def pytest_plugin_registered(self, plugin):
|
||||
nodeid = None
|
||||
try:
|
||||
p = py.path.local(plugin.__file__)
|
||||
p = py.path.local(plugin.__file__).realpath()
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
@@ -1284,9 +1315,7 @@ class FixtureManager(object):
|
||||
|
||||
filename, lineno = getfslineno(obj)
|
||||
warnings.warn_explicit(
|
||||
RemovedInPytest4Warning(
|
||||
deprecated.FUNCARG_PREFIX.format(name=name)
|
||||
),
|
||||
deprecated.FUNCARG_PREFIX.format(name=name),
|
||||
category=None,
|
||||
filename=str(filename),
|
||||
lineno=lineno + 1,
|
||||
@@ -1349,8 +1378,7 @@ class FixtureManager(object):
|
||||
fixturedefs = self._arg2fixturedefs[argname]
|
||||
except KeyError:
|
||||
return None
|
||||
else:
|
||||
return tuple(self._matchfactories(fixturedefs, nodeid))
|
||||
return tuple(self._matchfactories(fixturedefs, nodeid))
|
||||
|
||||
def _matchfactories(self, fixturedefs, nodeid):
|
||||
for fixturedef in fixturedefs:
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
Provides a function to report all internal modules for using freezing tools
|
||||
pytest
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
|
||||
def freeze_includes():
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
""" version info, help messages, tracing configuration. """
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import py
|
||||
import pytest
|
||||
from _pytest.config import PrintHelp
|
||||
import os
|
||||
import sys
|
||||
from argparse import Action
|
||||
|
||||
import py
|
||||
|
||||
import pytest
|
||||
from _pytest.config import PrintHelp
|
||||
|
||||
|
||||
class HelpAction(Action):
|
||||
"""This is an argparse Action that will raise an exception in
|
||||
@@ -139,7 +143,7 @@ def showhelp(config):
|
||||
tw.line()
|
||||
tw.line()
|
||||
tw.line(
|
||||
"[pytest] ini-options in the first " "pytest.ini|tox.ini|setup.cfg file found:"
|
||||
"[pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg file found:"
|
||||
)
|
||||
tw.line()
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
""" hook specifications for pytest plugins, invoked from main.py and builtin plugins. """
|
||||
|
||||
from pluggy import HookspecMarker
|
||||
|
||||
from .deprecated import PYTEST_NAMESPACE
|
||||
|
||||
|
||||
|
||||
@@ -8,14 +8,18 @@ Based on initial code from Ross Lawley.
|
||||
Output conforms to https://github.com/jenkinsci/xunit-plugin/blob/master/
|
||||
src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import functools
|
||||
import py
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
|
||||
import py
|
||||
|
||||
import pytest
|
||||
from _pytest import nodes
|
||||
from _pytest.config import filename_arg
|
||||
@@ -38,7 +42,7 @@ class Junit(py.xml.Namespace):
|
||||
# this dynamically instead of hardcoding it. The spec range of valid
|
||||
# chars is: Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
|
||||
# | [#x10000-#x10FFFF]
|
||||
_legal_chars = (0x09, 0x0A, 0x0d)
|
||||
_legal_chars = (0x09, 0x0A, 0x0D)
|
||||
_legal_ranges = ((0x20, 0x7E), (0x80, 0xD7FF), (0xE000, 0xFFFD), (0x10000, 0x10FFFF))
|
||||
_legal_xml_re = [
|
||||
unicode("%s-%s") % (unichr(low), unichr(high))
|
||||
@@ -221,12 +225,14 @@ class _NodeReporter(object):
|
||||
else:
|
||||
filename, lineno, skipreason = report.longrepr
|
||||
if skipreason.startswith("Skipped: "):
|
||||
skipreason = bin_xml_escape(skipreason[9:])
|
||||
skipreason = skipreason[9:]
|
||||
details = "%s:%s: %s" % (filename, lineno, skipreason)
|
||||
|
||||
self.append(
|
||||
Junit.skipped(
|
||||
"%s:%s: %s" % (filename, lineno, skipreason),
|
||||
bin_xml_escape(details),
|
||||
type="pytest.skip",
|
||||
message=skipreason,
|
||||
message=bin_xml_escape(skipreason),
|
||||
)
|
||||
)
|
||||
self.write_captured_output(report)
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
""" Access and control log capturing. """
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import logging
|
||||
from contextlib import closing, contextmanager
|
||||
import re
|
||||
from contextlib import contextmanager
|
||||
|
||||
import py
|
||||
import six
|
||||
|
||||
import pytest
|
||||
from _pytest.compat import dummy_context_manager
|
||||
from _pytest.config import create_terminal_writer
|
||||
import pytest
|
||||
import py
|
||||
|
||||
|
||||
DEFAULT_LOG_FORMAT = "%(filename)-25s %(lineno)4d %(levelname)-8s %(message)s"
|
||||
@@ -213,7 +216,8 @@ class LogCaptureFixture(object):
|
||||
def __init__(self, item):
|
||||
"""Creates a new funcarg."""
|
||||
self._item = item
|
||||
self._initial_log_levels = {} # type: Dict[str, int] # dict of log name -> log level
|
||||
# dict of log name -> log level
|
||||
self._initial_log_levels = {} # type: Dict[str, int]
|
||||
|
||||
def _finalize(self):
|
||||
"""Finalizes the fixture.
|
||||
@@ -262,7 +266,7 @@ class LogCaptureFixture(object):
|
||||
|
||||
@property
|
||||
def record_tuples(self):
|
||||
"""Returns a list of a striped down version of log records intended
|
||||
"""Returns a list of a stripped down version of log records intended
|
||||
for use in assertion comparison.
|
||||
|
||||
The format of the tuple is:
|
||||
@@ -278,7 +282,7 @@ class LogCaptureFixture(object):
|
||||
Unlike 'records', which contains the format string and parameters for interpolation, log messages in this list
|
||||
are all interpolated.
|
||||
Unlike 'text', which contains the output from the handler, log messages in this list are unadorned with
|
||||
levels, timestamps, etc, making exact comparisions more reliable.
|
||||
levels, timestamps, etc, making exact comparisons more reliable.
|
||||
|
||||
Note that traceback or stack info (from :func:`logging.exception` or the `exc_info` or `stack_info` arguments
|
||||
to the logging functions) is not included, as this is added by the formatter in the handler.
|
||||
@@ -329,7 +333,7 @@ class LogCaptureFixture(object):
|
||||
def caplog(request):
|
||||
"""Access and control log capturing.
|
||||
|
||||
Captured logs are available through the following methods::
|
||||
Captured logs are available through the following properties/methods::
|
||||
|
||||
* caplog.text -> string containing formatted log output
|
||||
* caplog.records -> list of logging.LogRecord instances
|
||||
@@ -414,7 +418,6 @@ class LoggingPlugin(object):
|
||||
else:
|
||||
self.log_file_handler = None
|
||||
|
||||
# initialized during pytest_runtestloop
|
||||
self.log_cli_handler = None
|
||||
|
||||
def _log_cli_enabled(self):
|
||||
@@ -425,6 +428,22 @@ class LoggingPlugin(object):
|
||||
"--log-cli-level"
|
||||
) is not None or self._config.getini("log_cli")
|
||||
|
||||
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
|
||||
def pytest_collection(self):
|
||||
# This has to be called before the first log message is logged,
|
||||
# so we can access the terminal reporter plugin.
|
||||
self._setup_cli_logging()
|
||||
|
||||
with self.live_logs_context():
|
||||
if self.log_cli_handler:
|
||||
self.log_cli_handler.set_when("collection")
|
||||
|
||||
if self.log_file_handler is not None:
|
||||
with catching_logs(self.log_file_handler, level=self.log_file_level):
|
||||
yield
|
||||
else:
|
||||
yield
|
||||
|
||||
@contextmanager
|
||||
def _runtest_for(self, item, when):
|
||||
"""Implements the internals of pytest_runtest_xxx() hook."""
|
||||
@@ -481,25 +500,41 @@ class LoggingPlugin(object):
|
||||
with self._runtest_for(None, "finish"):
|
||||
yield
|
||||
|
||||
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
|
||||
def pytest_sessionfinish(self):
|
||||
with self.live_logs_context():
|
||||
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
|
||||
else:
|
||||
yield
|
||||
|
||||
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
|
||||
def pytest_sessionstart(self):
|
||||
self._setup_cli_logging()
|
||||
with self.live_logs_context():
|
||||
if self.log_cli_handler:
|
||||
self.log_cli_handler.set_when("sessionstart")
|
||||
if self.log_file_handler is not None:
|
||||
with catching_logs(self.log_file_handler, level=self.log_file_level):
|
||||
yield
|
||||
else:
|
||||
yield
|
||||
|
||||
@pytest.hookimpl(hookwrapper=True)
|
||||
def pytest_runtestloop(self, session):
|
||||
"""Runs all collected test items."""
|
||||
self._setup_cli_logging()
|
||||
with self.live_logs_context:
|
||||
with self.live_logs_context():
|
||||
if self.log_file_handler is not None:
|
||||
with closing(self.log_file_handler):
|
||||
with catching_logs(
|
||||
self.log_file_handler, level=self.log_file_level
|
||||
):
|
||||
yield # run all the tests
|
||||
with catching_logs(self.log_file_handler, level=self.log_file_level):
|
||||
yield # run all the tests
|
||||
else:
|
||||
yield # run all the tests
|
||||
|
||||
def _setup_cli_logging(self):
|
||||
"""Sets up the handler and logger for the Live Logs feature, if enabled.
|
||||
|
||||
This must be done right before starting the loop so we can access the terminal reporter plugin.
|
||||
"""
|
||||
"""Sets up the handler and logger for the Live Logs feature, if enabled."""
|
||||
terminal_reporter = self._config.pluginmanager.get_plugin("terminalreporter")
|
||||
if self._log_cli_enabled() and terminal_reporter is not None:
|
||||
capture_manager = self._config.pluginmanager.get_plugin("capturemanager")
|
||||
@@ -529,11 +564,14 @@ class LoggingPlugin(object):
|
||||
self._config, "log_cli_level", "log_level"
|
||||
)
|
||||
self.log_cli_handler = log_cli_handler
|
||||
self.live_logs_context = catching_logs(
|
||||
self.live_logs_context = lambda: catching_logs(
|
||||
log_cli_handler, formatter=log_cli_formatter, level=log_cli_level
|
||||
)
|
||||
else:
|
||||
self.live_logs_context = dummy_context_manager()
|
||||
self.live_logs_context = lambda: dummy_context_manager()
|
||||
# Note that the lambda for the live_logs_context is needed because
|
||||
# live_logs_context can otherwise not be entered multiple times due
|
||||
# to limitations of contextlib.contextmanager
|
||||
|
||||
|
||||
class _LiveLoggingStreamHandler(logging.StreamHandler):
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
""" core implementation of testing process: init, session, runtest loop. """
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import contextlib
|
||||
import functools
|
||||
import os
|
||||
import pkgutil
|
||||
import six
|
||||
import sys
|
||||
|
||||
import _pytest
|
||||
from _pytest import nodes
|
||||
import _pytest._code
|
||||
import py
|
||||
import six
|
||||
|
||||
from _pytest.config import directory_arg, UsageError, hookimpl
|
||||
import _pytest._code
|
||||
from _pytest import nodes
|
||||
from _pytest.config import directory_arg
|
||||
from _pytest.config import hookimpl
|
||||
from _pytest.config import UsageError
|
||||
from _pytest.outcomes import exit
|
||||
from _pytest.runner import collect_one_node
|
||||
|
||||
@@ -156,7 +159,10 @@ def pytest_addoption(parser):
|
||||
dest="basetemp",
|
||||
default=None,
|
||||
metavar="dir",
|
||||
help="base temporary directory for this test run.",
|
||||
help=(
|
||||
"base temporary directory for this test run."
|
||||
"(warning: this directory is removed if it exists)"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -182,10 +188,13 @@ def wrap_session(config, doit):
|
||||
session.exitstatus = EXIT_TESTSFAILED
|
||||
except KeyboardInterrupt:
|
||||
excinfo = _pytest._code.ExceptionInfo()
|
||||
if initstate < 2 and isinstance(excinfo.value, exit.Exception):
|
||||
exitstatus = EXIT_INTERRUPTED
|
||||
if initstate <= 2 and isinstance(excinfo.value, exit.Exception):
|
||||
sys.stderr.write("{}: {}\n".format(excinfo.typename, excinfo.value.msg))
|
||||
if excinfo.value.returncode is not None:
|
||||
exitstatus = excinfo.value.returncode
|
||||
config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
|
||||
session.exitstatus = EXIT_INTERRUPTED
|
||||
session.exitstatus = exitstatus
|
||||
except: # noqa
|
||||
excinfo = _pytest._code.ExceptionInfo()
|
||||
config.notify_exception(excinfo, config.option)
|
||||
@@ -487,7 +496,7 @@ class Session(nodes.FSCollector):
|
||||
from _pytest.python import Package
|
||||
|
||||
names = self._parsearg(arg)
|
||||
argpath = names.pop(0)
|
||||
argpath = names.pop(0).realpath()
|
||||
paths = []
|
||||
|
||||
root = self
|
||||
@@ -564,9 +573,7 @@ class Session(nodes.FSCollector):
|
||||
return True
|
||||
|
||||
def _tryconvertpyarg(self, x):
|
||||
"""Convert a dotted module name to path.
|
||||
|
||||
"""
|
||||
"""Convert a dotted module name to path."""
|
||||
try:
|
||||
with _patched_find_module():
|
||||
loader = pkgutil.find_loader(x)
|
||||
@@ -598,8 +605,7 @@ class Session(nodes.FSCollector):
|
||||
raise UsageError(
|
||||
"file or package not found: " + arg + " (missing __init__.py?)"
|
||||
)
|
||||
else:
|
||||
raise UsageError("file not found: " + arg)
|
||||
raise UsageError("file not found: " + arg)
|
||||
parts[0] = path
|
||||
return parts
|
||||
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
""" generic mechanism for marking and selecting python functions. """
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
from .legacy import matchkeyword
|
||||
from .legacy import matchmark
|
||||
from .structures import EMPTY_PARAMETERSET_OPTION
|
||||
from .structures import get_empty_parameterset_mark
|
||||
from .structures import Mark
|
||||
from .structures import MARK_GEN
|
||||
from .structures import MarkDecorator
|
||||
from .structures import MarkGenerator
|
||||
from .structures import MarkInfo
|
||||
from .structures import ParameterSet
|
||||
from .structures import transfer_markers
|
||||
from _pytest.config import UsageError
|
||||
from .structures import (
|
||||
ParameterSet,
|
||||
EMPTY_PARAMETERSET_OPTION,
|
||||
MARK_GEN,
|
||||
Mark,
|
||||
MarkInfo,
|
||||
MarkDecorator,
|
||||
MarkGenerator,
|
||||
transfer_markers,
|
||||
get_empty_parameterset_mark,
|
||||
)
|
||||
from .legacy import matchkeyword, matchmark
|
||||
|
||||
__all__ = [
|
||||
"Mark",
|
||||
@@ -24,11 +26,6 @@ __all__ = [
|
||||
]
|
||||
|
||||
|
||||
class MarkerError(Exception):
|
||||
|
||||
"""Error in use of a pytest marker/attribute."""
|
||||
|
||||
|
||||
def param(*values, **kw):
|
||||
"""Specify a parameter in `pytest.mark.parametrize`_ calls or
|
||||
:ref:`parametrized fixtures <fixture-parametrize-marks>`.
|
||||
@@ -163,9 +160,9 @@ def pytest_configure(config):
|
||||
|
||||
empty_parameterset = config.getini(EMPTY_PARAMETERSET_OPTION)
|
||||
|
||||
if empty_parameterset not in ("skip", "xfail", None, ""):
|
||||
if empty_parameterset not in ("skip", "xfail", "fail_at_collect", None, ""):
|
||||
raise UsageError(
|
||||
"{!s} must be one of skip and xfail,"
|
||||
"{!s} must be one of skip, xfail or fail_at_collect"
|
||||
" but it is {!r}".format(EMPTY_PARAMETERSET_OPTION, empty_parameterset)
|
||||
)
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import os
|
||||
import six
|
||||
import sys
|
||||
import platform
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from ..outcomes import fail, TEST_OUTCOME
|
||||
import six
|
||||
|
||||
from ..outcomes import fail
|
||||
from ..outcomes import TEST_OUTCOME
|
||||
|
||||
|
||||
def cached_eval(config, expr, d):
|
||||
@@ -90,7 +92,10 @@ class MarkEvaluator(object):
|
||||
else:
|
||||
if "reason" not in mark.kwargs:
|
||||
# XXX better be checked at collection time
|
||||
msg = "you need to specify reason=STRING " "when using booleans as conditions."
|
||||
msg = (
|
||||
"you need to specify reason=STRING "
|
||||
"when using booleans as conditions."
|
||||
)
|
||||
fail(msg)
|
||||
result = bool(expr)
|
||||
if result:
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
this is a place where we put datastructures used by legacy apis
|
||||
we hope ot remove
|
||||
"""
|
||||
import attr
|
||||
import keyword
|
||||
|
||||
import attr
|
||||
|
||||
from _pytest.config import UsageError
|
||||
|
||||
|
||||
|
||||
@@ -5,11 +5,15 @@ from functools import reduce
|
||||
from operator import attrgetter
|
||||
|
||||
import attr
|
||||
|
||||
from ..deprecated import MARK_PARAMETERSET_UNPACKING, MARK_INFO_ATTRIBUTE
|
||||
from ..compat import NOTSET, getfslineno, MappingMixin
|
||||
from six.moves import map
|
||||
|
||||
from ..compat import getfslineno
|
||||
from ..compat import MappingMixin
|
||||
from ..compat import NOTSET
|
||||
from ..deprecated import MARK_INFO_ATTRIBUTE
|
||||
from ..deprecated import MARK_PARAMETERSET_UNPACKING
|
||||
from _pytest.outcomes import fail
|
||||
|
||||
|
||||
EMPTY_PARAMETERSET_OPTION = "empty_parameter_set_mark"
|
||||
|
||||
@@ -32,11 +36,19 @@ def istestfunc(func):
|
||||
|
||||
|
||||
def get_empty_parameterset_mark(config, argnames, func):
|
||||
from ..nodes import Collector
|
||||
|
||||
requested_mark = config.getini(EMPTY_PARAMETERSET_OPTION)
|
||||
if requested_mark in ("", None, "skip"):
|
||||
mark = MARK_GEN.skip
|
||||
elif requested_mark == "xfail":
|
||||
mark = MARK_GEN.xfail(run=False)
|
||||
elif requested_mark == "fail_at_collect":
|
||||
f_name = func.__name__
|
||||
_, lineno = getfslineno(func)
|
||||
raise Collector.CollectError(
|
||||
"Empty parameter set in '%s' at line %d" % (f_name, lineno)
|
||||
)
|
||||
else:
|
||||
raise LookupError(requested_mark)
|
||||
fs, lineno = getfslineno(func)
|
||||
@@ -307,7 +319,7 @@ def _marked(func, mark):
|
||||
return any(mark == info.combined for info in func_mark)
|
||||
|
||||
|
||||
@attr.s
|
||||
@attr.s(repr=False)
|
||||
class MarkInfo(object):
|
||||
""" Marking object created by :class:`MarkDecorator` instances. """
|
||||
|
||||
@@ -385,7 +397,7 @@ class MarkGenerator(object):
|
||||
x = marker.split("(", 1)[0]
|
||||
values.add(x)
|
||||
if name not in self._markers:
|
||||
raise AttributeError("%r not a registered marker" % (name,))
|
||||
fail("{!r} not a registered marker".format(name), pytrace=False)
|
||||
|
||||
|
||||
MARK_GEN = MarkGenerator()
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
""" monkeypatching and mocking functionality. """
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
import sys
|
||||
import warnings
|
||||
from contextlib import contextmanager
|
||||
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import warnings
|
||||
|
||||
import six
|
||||
import py
|
||||
import attr
|
||||
import py
|
||||
import six
|
||||
|
||||
import _pytest
|
||||
import _pytest._code
|
||||
from _pytest.compat import getfslineno
|
||||
|
||||
from _pytest.mark.structures import NodeKeywords, MarkInfo
|
||||
from _pytest.mark.structures import MarkInfo
|
||||
from _pytest.mark.structures import NodeKeywords
|
||||
from _pytest.outcomes import fail
|
||||
|
||||
SEP = "/"
|
||||
|
||||
@@ -61,11 +64,11 @@ class _CompatProperty(object):
|
||||
if obj is None:
|
||||
return self
|
||||
|
||||
# TODO: reenable in the features branch
|
||||
# warnings.warn(
|
||||
# "usage of {owner!r}.{name} is deprecated, please use pytest.{name} instead".format(
|
||||
# name=self.name, owner=type(owner).__name__),
|
||||
# PendingDeprecationWarning, stacklevel=2)
|
||||
from _pytest.deprecated import COMPAT_PROPERTY
|
||||
|
||||
warnings.warn(
|
||||
COMPAT_PROPERTY.format(name=self.name, owner=owner.__name__), stacklevel=2
|
||||
)
|
||||
return getattr(__import__("pytest"), self.name)
|
||||
|
||||
|
||||
@@ -126,11 +129,10 @@ class Node(object):
|
||||
if isinstance(maybe_compatprop, _CompatProperty):
|
||||
return getattr(__import__("pytest"), name)
|
||||
else:
|
||||
from _pytest.deprecated import CUSTOM_CLASS
|
||||
|
||||
cls = getattr(self, name)
|
||||
# TODO: reenable in the features branch
|
||||
# warnings.warn("use of node.%s is deprecated, "
|
||||
# "use pytest_pycollect_makeitem(...) to create custom "
|
||||
# "collection nodes" % name, category=DeprecationWarning)
|
||||
self.warn(CUSTOM_CLASS.format(name=name, type_name=type(self).__name__))
|
||||
return cls
|
||||
|
||||
def __repr__(self):
|
||||
@@ -347,6 +349,9 @@ class Node(object):
|
||||
pass
|
||||
|
||||
def _repr_failure_py(self, excinfo, style=None):
|
||||
if excinfo.errisinstance(fail.Exception):
|
||||
if not excinfo.value.pytrace:
|
||||
return six.text_type(excinfo.value)
|
||||
fm = self.session._fixturemanager
|
||||
if excinfo.errisinstance(fm.FixtureLookupError):
|
||||
return excinfo.value.formatrepr()
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
""" run test suites written for nose. """
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import sys
|
||||
|
||||
from _pytest import unittest, runner, python
|
||||
from _pytest import python
|
||||
from _pytest import runner
|
||||
from _pytest import unittest
|
||||
from _pytest.config import hookimpl
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
exception classes and constants handling test outcomes
|
||||
as well as functions creating them
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
@@ -49,18 +52,24 @@ class Failed(OutcomeException):
|
||||
class Exit(KeyboardInterrupt):
|
||||
""" raised for immediate program exits (no tracebacks/summaries)"""
|
||||
|
||||
def __init__(self, msg="unknown reason"):
|
||||
def __init__(self, msg="unknown reason", returncode=None):
|
||||
self.msg = msg
|
||||
self.returncode = returncode
|
||||
KeyboardInterrupt.__init__(self, msg)
|
||||
|
||||
|
||||
# exposed helper methods
|
||||
|
||||
|
||||
def exit(msg):
|
||||
""" exit testing process as if KeyboardInterrupt was triggered. """
|
||||
def exit(msg, returncode=None):
|
||||
"""
|
||||
Exit testing process as if KeyboardInterrupt was triggered.
|
||||
|
||||
:param str msg: message to display upon exit.
|
||||
:param int returncode: return code to be used when exiting pytest.
|
||||
"""
|
||||
__tracebackhide__ = True
|
||||
raise Exit(msg)
|
||||
raise Exit(msg, returncode)
|
||||
|
||||
|
||||
exit.Exception = Exit
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
""" submit failure or test session information to a pastebin service. """
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import pytest
|
||||
import six
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
import six
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("terminal reporting")
|
||||
|
||||
313
src/_pytest/pathlib.py
Normal file
313
src/_pytest/pathlib.py
Normal file
@@ -0,0 +1,313 @@
|
||||
import atexit
|
||||
import errno
|
||||
import fnmatch
|
||||
import itertools
|
||||
import operator
|
||||
import os
|
||||
import shutil
|
||||
import stat
|
||||
import sys
|
||||
import uuid
|
||||
from functools import reduce
|
||||
from os.path import expanduser
|
||||
from os.path import expandvars
|
||||
from os.path import isabs
|
||||
from os.path import sep
|
||||
from posixpath import sep as posix_sep
|
||||
|
||||
import six
|
||||
from six.moves import map
|
||||
|
||||
from .compat import PY36
|
||||
|
||||
|
||||
if PY36:
|
||||
from pathlib import Path, PurePath
|
||||
else:
|
||||
from pathlib2 import Path, PurePath
|
||||
|
||||
__all__ = ["Path", "PurePath"]
|
||||
|
||||
|
||||
LOCK_TIMEOUT = 60 * 60 * 3
|
||||
|
||||
get_lock_path = operator.methodcaller("joinpath", ".lock")
|
||||
|
||||
|
||||
def ensure_reset_dir(path):
|
||||
"""
|
||||
ensures the given path is a empty directory
|
||||
"""
|
||||
if path.exists():
|
||||
rmtree(path, force=True)
|
||||
path.mkdir()
|
||||
|
||||
|
||||
def _shutil_rmtree_remove_writable(func, fspath, _):
|
||||
"Clear the readonly bit and reattempt the removal"
|
||||
os.chmod(fspath, stat.S_IWRITE)
|
||||
func(fspath)
|
||||
|
||||
|
||||
def rmtree(path, force=False):
|
||||
if force:
|
||||
# ignore_errors leaves dead folders around
|
||||
# python needs a rm -rf as a followup
|
||||
# the trick with _shutil_rmtree_remove_writable is unreliable
|
||||
shutil.rmtree(str(path), ignore_errors=True)
|
||||
else:
|
||||
shutil.rmtree(str(path))
|
||||
|
||||
|
||||
def find_prefixed(root, prefix):
|
||||
"""finds all elements in root that begin with the prefix, case insensitive"""
|
||||
l_prefix = prefix.lower()
|
||||
for x in root.iterdir():
|
||||
if x.name.lower().startswith(l_prefix):
|
||||
yield x
|
||||
|
||||
|
||||
def extract_suffixes(iter, prefix):
|
||||
"""
|
||||
:param iter: iterator over path names
|
||||
:param prefix: expected prefix of the path names
|
||||
:returns: the parts of the paths following the prefix
|
||||
"""
|
||||
p_len = len(prefix)
|
||||
for p in iter:
|
||||
yield p.name[p_len:]
|
||||
|
||||
|
||||
def find_suffixes(root, prefix):
|
||||
"""combines find_prefixes and extract_suffixes
|
||||
"""
|
||||
return extract_suffixes(find_prefixed(root, prefix), prefix)
|
||||
|
||||
|
||||
def parse_num(maybe_num):
|
||||
"""parses number path suffixes, returns -1 on error"""
|
||||
try:
|
||||
return int(maybe_num)
|
||||
except ValueError:
|
||||
return -1
|
||||
|
||||
|
||||
if six.PY2:
|
||||
|
||||
def _max(iterable, default):
|
||||
"""needed due to python2.7 lacking the default argument for max"""
|
||||
return reduce(max, iterable, default)
|
||||
|
||||
|
||||
else:
|
||||
_max = max
|
||||
|
||||
|
||||
def _force_symlink(root, target, link_to):
|
||||
"""helper to create the current symlink
|
||||
|
||||
its full of race conditions that are reasonably ok to ignore
|
||||
for the contex of best effort linking to the latest testrun
|
||||
|
||||
the presumption being thatin case of much parallelism
|
||||
the inaccuracy is going to be acceptable
|
||||
"""
|
||||
current_symlink = root.joinpath(target)
|
||||
try:
|
||||
current_symlink.unlink()
|
||||
except OSError:
|
||||
pass
|
||||
try:
|
||||
current_symlink.symlink_to(link_to)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def make_numbered_dir(root, prefix):
|
||||
"""create a directory with a increased number as suffix for the given prefix"""
|
||||
for i in range(10):
|
||||
# try up to 10 times to create the folder
|
||||
max_existing = _max(map(parse_num, find_suffixes(root, prefix)), default=-1)
|
||||
new_number = max_existing + 1
|
||||
new_path = root.joinpath("{}{}".format(prefix, new_number))
|
||||
try:
|
||||
new_path.mkdir()
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
_force_symlink(root, prefix + "current", new_path)
|
||||
return new_path
|
||||
else:
|
||||
raise EnvironmentError(
|
||||
"could not create numbered dir with prefix "
|
||||
"{prefix} in {root} after 10 tries".format(prefix=prefix, root=root)
|
||||
)
|
||||
|
||||
|
||||
def create_cleanup_lock(p):
|
||||
"""crates a lock to prevent premature folder cleanup"""
|
||||
lock_path = get_lock_path(p)
|
||||
try:
|
||||
fd = os.open(str(lock_path), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o644)
|
||||
except OSError as e:
|
||||
if e.errno == errno.EEXIST:
|
||||
six.raise_from(
|
||||
EnvironmentError("cannot create lockfile in {path}".format(path=p)), e
|
||||
)
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
pid = os.getpid()
|
||||
spid = str(pid)
|
||||
if not isinstance(spid, bytes):
|
||||
spid = spid.encode("ascii")
|
||||
os.write(fd, spid)
|
||||
os.close(fd)
|
||||
if not lock_path.is_file():
|
||||
raise EnvironmentError("lock path got renamed after sucessfull creation")
|
||||
return lock_path
|
||||
|
||||
|
||||
def register_cleanup_lock_removal(lock_path, register=atexit.register):
|
||||
"""registers a cleanup function for removing a lock, by default on atexit"""
|
||||
pid = os.getpid()
|
||||
|
||||
def cleanup_on_exit(lock_path=lock_path, original_pid=pid):
|
||||
current_pid = os.getpid()
|
||||
if current_pid != original_pid:
|
||||
# fork
|
||||
return
|
||||
try:
|
||||
lock_path.unlink()
|
||||
except (OSError, IOError):
|
||||
pass
|
||||
|
||||
return register(cleanup_on_exit)
|
||||
|
||||
|
||||
def maybe_delete_a_numbered_dir(path):
|
||||
"""removes a numbered directory if its lock can be obtained"""
|
||||
try:
|
||||
create_cleanup_lock(path)
|
||||
except (OSError, EnvironmentError):
|
||||
# known races:
|
||||
# * other process did a cleanup at the same time
|
||||
# * deletable folder was found
|
||||
return
|
||||
parent = path.parent
|
||||
|
||||
garbage = parent.joinpath("garbage-{}".format(uuid.uuid4()))
|
||||
path.rename(garbage)
|
||||
rmtree(garbage, force=True)
|
||||
|
||||
|
||||
def ensure_deletable(path, consider_lock_dead_if_created_before):
|
||||
"""checks if a lock exists and breaks it if its considered dead"""
|
||||
if path.is_symlink():
|
||||
return False
|
||||
lock = get_lock_path(path)
|
||||
if not lock.exists():
|
||||
return True
|
||||
try:
|
||||
lock_time = lock.stat().st_mtime
|
||||
except Exception:
|
||||
return False
|
||||
else:
|
||||
if lock_time < consider_lock_dead_if_created_before:
|
||||
lock.unlink()
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def try_cleanup(path, consider_lock_dead_if_created_before):
|
||||
"""tries to cleanup a folder if we can ensure its deletable"""
|
||||
if ensure_deletable(path, consider_lock_dead_if_created_before):
|
||||
maybe_delete_a_numbered_dir(path)
|
||||
|
||||
|
||||
def cleanup_candidates(root, prefix, keep):
|
||||
"""lists candidates for numbered directories to be removed - follows py.path"""
|
||||
max_existing = _max(map(parse_num, find_suffixes(root, prefix)), default=-1)
|
||||
max_delete = max_existing - keep
|
||||
paths = find_prefixed(root, prefix)
|
||||
paths, paths2 = itertools.tee(paths)
|
||||
numbers = map(parse_num, extract_suffixes(paths2, prefix))
|
||||
for path, number in zip(paths, numbers):
|
||||
if number <= max_delete:
|
||||
yield path
|
||||
|
||||
|
||||
def cleanup_numbered_dir(root, prefix, keep, consider_lock_dead_if_created_before):
|
||||
"""cleanup for lock driven numbered directories"""
|
||||
for path in cleanup_candidates(root, prefix, keep):
|
||||
try_cleanup(path, consider_lock_dead_if_created_before)
|
||||
for path in root.glob("garbage-*"):
|
||||
try_cleanup(path, consider_lock_dead_if_created_before)
|
||||
|
||||
|
||||
def make_numbered_dir_with_cleanup(root, prefix, keep, lock_timeout):
|
||||
"""creates a numbered dir with a cleanup lock and removes old ones"""
|
||||
e = None
|
||||
for i in range(10):
|
||||
try:
|
||||
p = make_numbered_dir(root, prefix)
|
||||
lock_path = create_cleanup_lock(p)
|
||||
register_cleanup_lock_removal(lock_path)
|
||||
except Exception as exc:
|
||||
e = exc
|
||||
else:
|
||||
consider_lock_dead_if_created_before = p.stat().st_mtime - lock_timeout
|
||||
cleanup_numbered_dir(
|
||||
root=root,
|
||||
prefix=prefix,
|
||||
keep=keep,
|
||||
consider_lock_dead_if_created_before=consider_lock_dead_if_created_before,
|
||||
)
|
||||
return p
|
||||
assert e is not None
|
||||
raise e
|
||||
|
||||
|
||||
def resolve_from_str(input, root):
|
||||
assert not isinstance(input, Path), "would break on py2"
|
||||
root = Path(root)
|
||||
input = expanduser(input)
|
||||
input = expandvars(input)
|
||||
if isabs(input):
|
||||
return Path(input)
|
||||
else:
|
||||
return root.joinpath(input)
|
||||
|
||||
|
||||
def fnmatch_ex(pattern, path):
|
||||
"""FNMatcher port from py.path.common which works with PurePath() instances.
|
||||
|
||||
The difference between this algorithm and PurePath.match() is that the latter matches "**" glob expressions
|
||||
for each part of the path, while this algorithm uses the whole path instead.
|
||||
|
||||
For example:
|
||||
"tests/foo/bar/doc/test_foo.py" matches pattern "tests/**/doc/test*.py" with this algorithm, but not with
|
||||
PurePath.match().
|
||||
|
||||
This algorithm was ported to keep backward-compatibility with existing settings which assume paths match according
|
||||
this logic.
|
||||
|
||||
References:
|
||||
* https://bugs.python.org/issue29249
|
||||
* https://bugs.python.org/issue34731
|
||||
"""
|
||||
path = PurePath(path)
|
||||
iswin32 = sys.platform.startswith("win")
|
||||
|
||||
if iswin32 and sep not in pattern and posix_sep in pattern:
|
||||
# Running on Windows, the pattern has no Windows path separators,
|
||||
# and the pattern has one or more Posix path separators. Replace
|
||||
# the Posix path separators with the Windows path separator.
|
||||
pattern = pattern.replace(posix_sep, sep)
|
||||
|
||||
if sep not in pattern:
|
||||
name = path.name
|
||||
else:
|
||||
name = six.text_type(path)
|
||||
return fnmatch.fnmatch(name, pattern)
|
||||
@@ -1,52 +0,0 @@
|
||||
from os.path import expanduser, expandvars, isabs, sep
|
||||
from posixpath import sep as posix_sep
|
||||
import fnmatch
|
||||
import sys
|
||||
|
||||
import six
|
||||
|
||||
from .compat import Path, PurePath
|
||||
|
||||
|
||||
def resolve_from_str(input, root):
|
||||
assert not isinstance(input, Path), "would break on py2"
|
||||
root = Path(root)
|
||||
input = expanduser(input)
|
||||
input = expandvars(input)
|
||||
if isabs(input):
|
||||
return Path(input)
|
||||
else:
|
||||
return root.joinpath(input)
|
||||
|
||||
|
||||
def fnmatch_ex(pattern, path):
|
||||
"""FNMatcher port from py.path.common which works with PurePath() instances.
|
||||
|
||||
The difference between this algorithm and PurePath.match() is that the latter matches "**" glob expressions
|
||||
for each part of the path, while this algorithm uses the whole path instead.
|
||||
|
||||
For example:
|
||||
"tests/foo/bar/doc/test_foo.py" matches pattern "tests/**/doc/test*.py" with this algorithm, but not with
|
||||
PurePath.match().
|
||||
|
||||
This algorithm was ported to keep backward-compatibility with existing settings which assume paths match according
|
||||
this logic.
|
||||
|
||||
References:
|
||||
* https://bugs.python.org/issue29249
|
||||
* https://bugs.python.org/issue34731
|
||||
"""
|
||||
path = PurePath(path)
|
||||
iswin32 = sys.platform.startswith("win")
|
||||
|
||||
if iswin32 and sep not in pattern and posix_sep in pattern:
|
||||
# Running on Windows, the pattern has no Windows path separators,
|
||||
# and the pattern has one or more Posix path separators. Replace
|
||||
# the Posix path separators with the Windows path separator.
|
||||
pattern = pattern.replace(posix_sep, sep)
|
||||
|
||||
if sep not in pattern:
|
||||
name = path.name
|
||||
else:
|
||||
name = six.text_type(path)
|
||||
return fnmatch.fnmatch(name, pattern)
|
||||
@@ -1,5 +1,7 @@
|
||||
"""(disabled by default) support for testing pytest and pytest plugins."""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import codecs
|
||||
import gc
|
||||
@@ -7,22 +9,25 @@ import os
|
||||
import platform
|
||||
import re
|
||||
import subprocess
|
||||
import six
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
from fnmatch import fnmatch
|
||||
|
||||
from weakref import WeakKeyDictionary
|
||||
|
||||
from _pytest.capture import MultiCapture, SysCapture
|
||||
from _pytest._code import Source
|
||||
import py
|
||||
import six
|
||||
|
||||
import pytest
|
||||
from _pytest.main import Session, EXIT_OK
|
||||
from _pytest._code import Source
|
||||
from _pytest.assertion.rewrite import AssertionRewritingHook
|
||||
from _pytest.compat import Path
|
||||
from _pytest.capture import MultiCapture
|
||||
from _pytest.capture import SysCapture
|
||||
from _pytest.compat import safe_str
|
||||
from _pytest.main import EXIT_INTERRUPTED
|
||||
from _pytest.main import EXIT_OK
|
||||
from _pytest.main import Session
|
||||
from _pytest.pathlib import Path
|
||||
|
||||
IGNORE_PAM = [ # filenames added when obtaining details about the current user
|
||||
u"/var/lib/sss/mc/passwd"
|
||||
@@ -61,6 +66,11 @@ def pytest_configure(config):
|
||||
config.pluginmanager.register(checker)
|
||||
|
||||
|
||||
def raise_on_kwargs(kwargs):
|
||||
if kwargs:
|
||||
raise TypeError("Unexpected arguments: {}".format(", ".join(sorted(kwargs))))
|
||||
|
||||
|
||||
class LsofFdLeakChecker(object):
|
||||
def get_open_files(self):
|
||||
out = self._exec_lsof()
|
||||
@@ -482,11 +492,16 @@ class Testdir(object):
|
||||
|
||||
"""
|
||||
|
||||
class TimeoutExpired(Exception):
|
||||
pass
|
||||
|
||||
def __init__(self, request, tmpdir_factory):
|
||||
self.request = request
|
||||
self._mod_collections = WeakKeyDictionary()
|
||||
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)
|
||||
self.plugins = []
|
||||
self._cwd_snapshot = CwdSnapshot()
|
||||
self._sys_path_snapshot = SysPathsSnapshot()
|
||||
@@ -513,6 +528,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)
|
||||
|
||||
def __take_sys_modules_snapshot(self):
|
||||
# some zope modules used by twisted-related tests keep internal state
|
||||
@@ -845,7 +861,7 @@ class Testdir(object):
|
||||
|
||||
# typically we reraise keyboard interrupts from the child run
|
||||
# because it's our user requesting interruption of the testing
|
||||
if ret == 2 and not kwargs.get("no_reraise_ctrlc"):
|
||||
if ret == EXIT_INTERRUPTED and not kwargs.get("no_reraise_ctrlc"):
|
||||
calls = reprec.getcalls("pytest_keyboard_interrupt")
|
||||
if calls and calls[-1].excinfo.type == KeyboardInterrupt:
|
||||
raise KeyboardInterrupt()
|
||||
@@ -1039,14 +1055,23 @@ class Testdir(object):
|
||||
|
||||
return popen
|
||||
|
||||
def run(self, *cmdargs):
|
||||
def run(self, *cmdargs, **kwargs):
|
||||
"""Run a command with arguments.
|
||||
|
||||
Run a process using subprocess.Popen saving the stdout and stderr.
|
||||
|
||||
:param args: the sequence of arguments to pass to `subprocess.Popen()`
|
||||
:param timeout: the period in seconds after which to timeout and raise
|
||||
:py:class:`Testdir.TimeoutExpired`
|
||||
|
||||
Returns a :py:class:`RunResult`.
|
||||
|
||||
"""
|
||||
__tracebackhide__ = True
|
||||
|
||||
timeout = kwargs.pop("timeout", None)
|
||||
raise_on_kwargs(kwargs)
|
||||
|
||||
cmdargs = [
|
||||
str(arg) if isinstance(arg, py.path.local) else arg for arg in cmdargs
|
||||
]
|
||||
@@ -1061,7 +1086,40 @@ class Testdir(object):
|
||||
popen = self.popen(
|
||||
cmdargs, stdout=f1, stderr=f2, close_fds=(sys.platform != "win32")
|
||||
)
|
||||
ret = popen.wait()
|
||||
|
||||
def handle_timeout():
|
||||
__tracebackhide__ = True
|
||||
|
||||
timeout_message = (
|
||||
"{seconds} second timeout expired running:"
|
||||
" {command}".format(seconds=timeout, command=cmdargs)
|
||||
)
|
||||
|
||||
popen.kill()
|
||||
popen.wait()
|
||||
raise self.TimeoutExpired(timeout_message)
|
||||
|
||||
if timeout is None:
|
||||
ret = popen.wait()
|
||||
elif six.PY3:
|
||||
try:
|
||||
ret = popen.wait(timeout)
|
||||
except subprocess.TimeoutExpired:
|
||||
handle_timeout()
|
||||
else:
|
||||
end = time.time() + timeout
|
||||
|
||||
resolution = min(0.1, timeout / 10)
|
||||
|
||||
while True:
|
||||
ret = popen.poll()
|
||||
if ret is not None:
|
||||
break
|
||||
|
||||
if time.time() > end:
|
||||
handle_timeout()
|
||||
|
||||
time.sleep(resolution)
|
||||
finally:
|
||||
f1.close()
|
||||
f2.close()
|
||||
@@ -1108,9 +1166,15 @@ class Testdir(object):
|
||||
with "runpytest-" so they do not conflict with the normal numbered
|
||||
pytest location for temporary files and directories.
|
||||
|
||||
:param args: the sequence of arguments to pass to the pytest subprocess
|
||||
:param timeout: the period in seconds after which to timeout and raise
|
||||
:py:class:`Testdir.TimeoutExpired`
|
||||
|
||||
Returns a :py:class:`RunResult`.
|
||||
|
||||
"""
|
||||
__tracebackhide__ = True
|
||||
|
||||
p = py.path.local.make_numbered_dir(
|
||||
prefix="runpytest-", keep=None, rootdir=self.tmpdir
|
||||
)
|
||||
@@ -1119,7 +1183,7 @@ class Testdir(object):
|
||||
if plugins:
|
||||
args = ("-p", plugins[0]) + args
|
||||
args = self._getpytestargs() + args
|
||||
return self.run(*args)
|
||||
return self.run(*args, timeout=kwargs.get("timeout"))
|
||||
|
||||
def spawn_pytest(self, string, expect_timeout=10.0):
|
||||
"""Run pytest using pexpect.
|
||||
@@ -1267,6 +1331,7 @@ class LineMatcher(object):
|
||||
matches and non-matches are also printed on stdout.
|
||||
|
||||
"""
|
||||
__tracebackhide__ = True
|
||||
self._match_lines(lines2, fnmatch, "fnmatch")
|
||||
|
||||
def re_match_lines(self, lines2):
|
||||
@@ -1278,6 +1343,7 @@ class LineMatcher(object):
|
||||
The matches and non-matches are also printed on stdout.
|
||||
|
||||
"""
|
||||
__tracebackhide__ = True
|
||||
self._match_lines(lines2, lambda name, pat: re.match(pat, name), "re.match")
|
||||
|
||||
def _match_lines(self, lines2, match_func, match_nickname):
|
||||
|
||||
@@ -1,82 +1,48 @@
|
||||
""" Python test discovery, setup and run of test functions. """
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import collections
|
||||
import fnmatch
|
||||
import inspect
|
||||
import sys
|
||||
import os
|
||||
import collections
|
||||
import sys
|
||||
import warnings
|
||||
from textwrap import dedent
|
||||
|
||||
|
||||
import py
|
||||
import six
|
||||
from _pytest.main import FSHookProxy
|
||||
from _pytest.mark import MarkerError
|
||||
from _pytest.config import hookimpl
|
||||
|
||||
import _pytest
|
||||
import pluggy
|
||||
from _pytest import deprecated
|
||||
from _pytest import fixtures
|
||||
from _pytest import nodes
|
||||
from _pytest import deprecated
|
||||
from _pytest.compat import (
|
||||
isclass,
|
||||
isfunction,
|
||||
is_generator,
|
||||
ascii_escaped,
|
||||
REGEX_TYPE,
|
||||
STRING_TYPES,
|
||||
NoneType,
|
||||
NOTSET,
|
||||
get_real_func,
|
||||
getfslineno,
|
||||
safe_getattr,
|
||||
safe_str,
|
||||
getlocation,
|
||||
enum,
|
||||
get_default_arg_names,
|
||||
getimfunc,
|
||||
)
|
||||
from _pytest._code import filter_traceback
|
||||
from _pytest.compat import ascii_escaped
|
||||
from _pytest.compat import enum
|
||||
from _pytest.compat import get_default_arg_names
|
||||
from _pytest.compat import get_real_func
|
||||
from _pytest.compat import getfslineno
|
||||
from _pytest.compat import getimfunc
|
||||
from _pytest.compat import getlocation
|
||||
from _pytest.compat import is_generator
|
||||
from _pytest.compat import isclass
|
||||
from _pytest.compat import isfunction
|
||||
from _pytest.compat import NoneType
|
||||
from _pytest.compat import NOTSET
|
||||
from _pytest.compat import REGEX_TYPE
|
||||
from _pytest.compat import safe_getattr
|
||||
from _pytest.compat import safe_str
|
||||
from _pytest.compat import STRING_TYPES
|
||||
from _pytest.config import hookimpl
|
||||
from _pytest.main import FSHookProxy
|
||||
from _pytest.mark.structures import get_unpacked_marks
|
||||
from _pytest.mark.structures import normalize_mark_list
|
||||
from _pytest.mark.structures import transfer_markers
|
||||
from _pytest.outcomes import fail
|
||||
from _pytest.mark.structures import (
|
||||
transfer_markers,
|
||||
get_unpacked_marks,
|
||||
normalize_mark_list,
|
||||
)
|
||||
from _pytest.warning_types import RemovedInPytest4Warning, PytestWarning
|
||||
|
||||
# relative paths that we use to filter traceback entries from appearing to the user;
|
||||
# see filter_traceback
|
||||
# note: if we need to add more paths than what we have now we should probably use a list
|
||||
# for better maintenance
|
||||
_pluggy_dir = py.path.local(pluggy.__file__.rstrip("oc"))
|
||||
# pluggy is either a package or a single module depending on the version
|
||||
if _pluggy_dir.basename == "__init__.py":
|
||||
_pluggy_dir = _pluggy_dir.dirpath()
|
||||
_pytest_dir = py.path.local(_pytest.__file__).dirpath()
|
||||
_py_dir = py.path.local(py.__file__).dirpath()
|
||||
|
||||
|
||||
def filter_traceback(entry):
|
||||
"""Return True if a TracebackEntry instance should be removed from tracebacks:
|
||||
* dynamically generated code (no code to show up for it);
|
||||
* internal traceback from pytest or its internal libraries, py and pluggy.
|
||||
"""
|
||||
# entry.path might sometimes return a str object when the entry
|
||||
# points to dynamically generated code
|
||||
# see https://bitbucket.org/pytest-dev/py/issues/71
|
||||
raw_filename = entry.frame.code.raw.co_filename
|
||||
is_generated = "<" in raw_filename and ">" in raw_filename
|
||||
if is_generated:
|
||||
return False
|
||||
# entry.path might point to a non-existing file, in which case it will
|
||||
# also return a str object. see #1133
|
||||
p = py.path.local(entry.path)
|
||||
return (
|
||||
not p.relto(_pluggy_dir) and not p.relto(_pytest_dir) and not p.relto(_py_dir)
|
||||
)
|
||||
from _pytest.warning_types import PytestWarning
|
||||
from _pytest.warning_types import RemovedInPytest4Warning
|
||||
|
||||
|
||||
def pyobj_property(name):
|
||||
@@ -131,7 +97,7 @@ def pytest_addoption(parser):
|
||||
"python_functions",
|
||||
type="args",
|
||||
default=["test"],
|
||||
help="prefixes or glob names for Python test function and " "method discovery",
|
||||
help="prefixes or glob names for Python test function and method discovery",
|
||||
)
|
||||
|
||||
group.addoption(
|
||||
@@ -159,8 +125,8 @@ def pytest_generate_tests(metafunc):
|
||||
alt_spellings = ["parameterize", "parametrise", "parameterise"]
|
||||
for attr in alt_spellings:
|
||||
if hasattr(metafunc.function, attr):
|
||||
msg = "{0} has '{1}', spelling should be 'parametrize'"
|
||||
raise MarkerError(msg.format(metafunc.function.__name__, attr))
|
||||
msg = "{0} has '{1}' mark, spelling should be 'parametrize'"
|
||||
fail(msg.format(metafunc.function.__name__, attr), pytrace=False)
|
||||
for marker in metafunc.definition.iter_markers(name="parametrize"):
|
||||
metafunc.parametrize(*marker.args, **marker.kwargs)
|
||||
|
||||
@@ -760,12 +726,6 @@ class FunctionMixin(PyobjMixin):
|
||||
for entry in excinfo.traceback[1:-1]:
|
||||
entry.set_repr_style("short")
|
||||
|
||||
def _repr_failure_py(self, excinfo, style="long"):
|
||||
if excinfo.errisinstance(fail.Exception):
|
||||
if not excinfo.value.pytrace:
|
||||
return six.text_type(excinfo.value)
|
||||
return super(FunctionMixin, self)._repr_failure_py(excinfo, style=style)
|
||||
|
||||
def repr_failure(self, excinfo, outerr=None):
|
||||
assert outerr is None, "XXX outerr usage is deprecated"
|
||||
style = self.config.option.tbstyle
|
||||
@@ -799,7 +759,10 @@ class Generator(FunctionMixin, PyCollector):
|
||||
"%r generated tests with non-unique name %r" % (self, name)
|
||||
)
|
||||
seen[name] = True
|
||||
values.append(self.Function(name, self, args=args, callobj=call))
|
||||
with warnings.catch_warnings():
|
||||
# ignore our own deprecation warning
|
||||
function_class = self.Function
|
||||
values.append(function_class(name, self, args=args, callobj=call))
|
||||
self.warn(deprecated.YIELD_TESTS)
|
||||
return values
|
||||
|
||||
@@ -984,7 +947,9 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
|
||||
|
||||
ids = self._resolve_arg_ids(argnames, ids, parameters, item=self.definition)
|
||||
|
||||
scopenum = scope2index(scope, descr="call to {}".format(self.parametrize))
|
||||
scopenum = scope2index(
|
||||
scope, descr="parametrize() call in {}".format(self.function.__name__)
|
||||
)
|
||||
|
||||
# create the new calls: if we are parametrize() multiple times (by applying the decorator
|
||||
# more than once) then we accumulate those calls generating the cartesian product
|
||||
@@ -1023,15 +988,16 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
|
||||
idfn = ids
|
||||
ids = None
|
||||
if ids:
|
||||
func_name = self.function.__name__
|
||||
if len(ids) != len(parameters):
|
||||
raise ValueError(
|
||||
"%d tests specified with %d ids" % (len(parameters), len(ids))
|
||||
)
|
||||
msg = "In {}: {} parameter sets specified, with different number of ids: {}"
|
||||
fail(msg.format(func_name, len(parameters), len(ids)), pytrace=False)
|
||||
for id_value in ids:
|
||||
if id_value is not None and not isinstance(id_value, six.string_types):
|
||||
msg = "ids must be list of strings, found: %s (type: %s)"
|
||||
raise ValueError(
|
||||
msg % (saferepr(id_value), type(id_value).__name__)
|
||||
msg = "In {}: ids must be list of strings, found: {} (type: {!r})"
|
||||
fail(
|
||||
msg.format(func_name, saferepr(id_value), type(id_value)),
|
||||
pytrace=False,
|
||||
)
|
||||
ids = idmaker(argnames, parameters, idfn, ids, self.config, item=item)
|
||||
return ids
|
||||
@@ -1056,9 +1022,11 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
|
||||
valtypes = dict.fromkeys(argnames, "funcargs")
|
||||
for arg in indirect:
|
||||
if arg not in argnames:
|
||||
raise ValueError(
|
||||
"indirect given to %r: fixture %r doesn't exist"
|
||||
% (self.function, arg)
|
||||
fail(
|
||||
"In {}: indirect fixture '{}' doesn't exist".format(
|
||||
self.function.__name__, arg
|
||||
),
|
||||
pytrace=False,
|
||||
)
|
||||
valtypes[arg] = "params"
|
||||
return valtypes
|
||||
@@ -1072,19 +1040,25 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
|
||||
:raise ValueError: if validation fails.
|
||||
"""
|
||||
default_arg_names = set(get_default_arg_names(self.function))
|
||||
func_name = self.function.__name__
|
||||
for arg in argnames:
|
||||
if arg not in self.fixturenames:
|
||||
if arg in default_arg_names:
|
||||
raise ValueError(
|
||||
"%r already takes an argument %r with a default value"
|
||||
% (self.function, arg)
|
||||
fail(
|
||||
"In {}: function already takes an argument '{}' with a default value".format(
|
||||
func_name, arg
|
||||
),
|
||||
pytrace=False,
|
||||
)
|
||||
else:
|
||||
if isinstance(indirect, (tuple, list)):
|
||||
name = "fixture" if arg in indirect else "argument"
|
||||
else:
|
||||
name = "fixture" if indirect else "argument"
|
||||
raise ValueError("%r uses no %s %r" % (self.function, name, arg))
|
||||
fail(
|
||||
"In {}: function uses no {} '{}'".format(func_name, name, arg),
|
||||
pytrace=False,
|
||||
)
|
||||
|
||||
def addcall(self, funcargs=None, id=NOTSET, param=NOTSET):
|
||||
""" Add a new call to the underlying test function during the collection phase of a test run.
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import math
|
||||
import pprint
|
||||
import sys
|
||||
from numbers import Number
|
||||
from decimal import Decimal
|
||||
from numbers import Number
|
||||
|
||||
import six
|
||||
from six.moves import zip, filterfalse
|
||||
from more_itertools.more import always_iterable
|
||||
from six.moves import filterfalse
|
||||
from six.moves import zip
|
||||
|
||||
from _pytest.compat import isclass
|
||||
|
||||
from _pytest.compat import Mapping, Sequence
|
||||
from _pytest.compat import STRING_TYPES
|
||||
|
||||
from _pytest.outcomes import fail
|
||||
import _pytest._code
|
||||
from _pytest.compat import isclass
|
||||
from _pytest.compat import Mapping
|
||||
from _pytest.compat import Sequence
|
||||
from _pytest.compat import STRING_TYPES
|
||||
from _pytest.outcomes import fail
|
||||
|
||||
BASE_TYPE = (type, STRING_TYPES)
|
||||
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
""" recording warnings during test function execution. """
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import inspect
|
||||
|
||||
import _pytest._code
|
||||
import re
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
import six
|
||||
|
||||
import _pytest._code
|
||||
from _pytest.fixtures import yield_fixture
|
||||
from _pytest.outcomes import fail
|
||||
|
||||
@@ -43,45 +44,10 @@ def deprecated_call(func=None, *args, **kwargs):
|
||||
in which case it will ensure calling ``func(*args, **kwargs)`` produces one of the warnings
|
||||
types above.
|
||||
"""
|
||||
if not func:
|
||||
return _DeprecatedCallContext()
|
||||
else:
|
||||
__tracebackhide__ = True
|
||||
with _DeprecatedCallContext():
|
||||
return func(*args, **kwargs)
|
||||
|
||||
|
||||
class _DeprecatedCallContext(object):
|
||||
"""Implements the logic to capture deprecation warnings as a context manager."""
|
||||
|
||||
def __enter__(self):
|
||||
self._captured_categories = []
|
||||
self._old_warn = warnings.warn
|
||||
self._old_warn_explicit = warnings.warn_explicit
|
||||
warnings.warn_explicit = self._warn_explicit
|
||||
warnings.warn = self._warn
|
||||
|
||||
def _warn_explicit(self, message, category, *args, **kwargs):
|
||||
self._captured_categories.append(category)
|
||||
|
||||
def _warn(self, message, category=None, *args, **kwargs):
|
||||
if isinstance(message, Warning):
|
||||
self._captured_categories.append(message.__class__)
|
||||
else:
|
||||
self._captured_categories.append(category)
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
warnings.warn_explicit = self._old_warn_explicit
|
||||
warnings.warn = self._old_warn
|
||||
|
||||
if exc_type is None:
|
||||
deprecation_categories = (DeprecationWarning, PendingDeprecationWarning)
|
||||
if not any(
|
||||
issubclass(c, deprecation_categories) for c in self._captured_categories
|
||||
):
|
||||
__tracebackhide__ = True
|
||||
msg = "Did not produce DeprecationWarning or PendingDeprecationWarning"
|
||||
raise AssertionError(msg)
|
||||
__tracebackhide__ = True
|
||||
if func is not None:
|
||||
args = (func,) + args
|
||||
return warns((DeprecationWarning, PendingDeprecationWarning), *args, **kwargs)
|
||||
|
||||
|
||||
def warns(expected_warning, *args, **kwargs):
|
||||
@@ -116,6 +82,7 @@ def warns(expected_warning, *args, **kwargs):
|
||||
Failed: DID NOT WARN. No warnings of type ...UserWarning... was emitted...
|
||||
|
||||
"""
|
||||
__tracebackhide__ = True
|
||||
match_expr = None
|
||||
if not args:
|
||||
if "match" in kwargs:
|
||||
@@ -183,12 +150,44 @@ class WarningsRecorder(warnings.catch_warnings):
|
||||
raise RuntimeError("Cannot enter %r twice" % self)
|
||||
self._list = super(WarningsRecorder, self).__enter__()
|
||||
warnings.simplefilter("always")
|
||||
# python3 keeps track of a "filter version", when the filters are
|
||||
# updated previously seen warnings can be re-warned. python2 has no
|
||||
# concept of this so we must reset the warnings registry manually.
|
||||
# trivial patching of `warnings.warn` seems to be enough somehow?
|
||||
if six.PY2:
|
||||
|
||||
def warn(message, category=None, stacklevel=1):
|
||||
# duplicate the stdlib logic due to
|
||||
# bad handing in the c version of warnings
|
||||
if isinstance(message, Warning):
|
||||
category = message.__class__
|
||||
# Check category argument
|
||||
if category is None:
|
||||
category = UserWarning
|
||||
assert issubclass(category, Warning)
|
||||
|
||||
# emulate resetting the warn registry
|
||||
f_globals = sys._getframe(stacklevel).f_globals
|
||||
if "__warningregistry__" in f_globals:
|
||||
orig = f_globals["__warningregistry__"]
|
||||
f_globals["__warningregistry__"] = None
|
||||
try:
|
||||
return self._saved_warn(message, category, stacklevel + 1)
|
||||
finally:
|
||||
f_globals["__warningregistry__"] = orig
|
||||
else:
|
||||
return self._saved_warn(message, category, stacklevel + 1)
|
||||
|
||||
warnings.warn, self._saved_warn = warn, warnings.warn
|
||||
return self
|
||||
|
||||
def __exit__(self, *exc_info):
|
||||
if not self._entered:
|
||||
__tracebackhide__ = True
|
||||
raise RuntimeError("Cannot exit %r without entering first" % self)
|
||||
# see above where `self._saved_warn` is assigned
|
||||
if six.PY2:
|
||||
warnings.warn = self._saved_warn
|
||||
super(WarningsRecorder, self).__exit__(*exc_info)
|
||||
|
||||
|
||||
@@ -196,7 +195,7 @@ class WarningsChecker(WarningsRecorder):
|
||||
def __init__(self, expected_warning=None, match_expr=None):
|
||||
super(WarningsChecker, self).__init__()
|
||||
|
||||
msg = "exceptions must be old-style classes or " "derived from Warning, not %s"
|
||||
msg = "exceptions must be old-style classes or derived from Warning, not %s"
|
||||
if isinstance(expected_warning, tuple):
|
||||
for exc in expected_warning:
|
||||
if not inspect.isclass(exc):
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import py
|
||||
|
||||
from _pytest._code.code import TerminalRepr
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
""" log machine-parseable test session result information in a plain
|
||||
text file.
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
|
||||
import py
|
||||
import os
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
@@ -31,10 +34,9 @@ def pytest_configure(config):
|
||||
config.pluginmanager.register(config._resultlog)
|
||||
|
||||
from _pytest.deprecated import RESULT_LOG
|
||||
from _pytest.warning_types import RemovedInPytest4Warning
|
||||
from _pytest.warnings import _issue_config_warning
|
||||
|
||||
_issue_config_warning(RemovedInPytest4Warning(RESULT_LOG), config)
|
||||
_issue_config_warning(RESULT_LOG, config)
|
||||
|
||||
|
||||
def pytest_unconfigure(config):
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
""" basic collect and runtest protocol implementations """
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import bdb
|
||||
import os
|
||||
@@ -7,10 +9,14 @@ import sys
|
||||
from time import time
|
||||
|
||||
import six
|
||||
from _pytest._code.code import ExceptionInfo
|
||||
from _pytest.outcomes import skip, Skipped, TEST_OUTCOME
|
||||
|
||||
from .reports import TestReport, CollectReport, CollectErrorRepr
|
||||
from .reports import CollectErrorRepr
|
||||
from .reports import CollectReport
|
||||
from .reports import TestReport
|
||||
from _pytest._code.code import ExceptionInfo
|
||||
from _pytest.outcomes import skip
|
||||
from _pytest.outcomes import Skipped
|
||||
from _pytest.outcomes import TEST_OUTCOME
|
||||
|
||||
#
|
||||
# pytest plugin hooks
|
||||
@@ -30,6 +36,7 @@ def pytest_addoption(parser):
|
||||
|
||||
def pytest_terminal_summary(terminalreporter):
|
||||
durations = terminalreporter.config.option.durations
|
||||
verbose = terminalreporter.config.getvalue("verbose")
|
||||
if durations is None:
|
||||
return
|
||||
tr = terminalreporter
|
||||
@@ -49,6 +56,10 @@ def pytest_terminal_summary(terminalreporter):
|
||||
dlist = dlist[:durations]
|
||||
|
||||
for rep in dlist:
|
||||
if verbose < 2 and rep.duration < 0.005:
|
||||
tr.write_line("")
|
||||
tr.write_line("(0.00 durations hidden. Use -vv to show these durations.)")
|
||||
break
|
||||
nodeid = rep.nodeid.replace("::()::", "::")
|
||||
tr.write_line("%02.2fs %-8s %s" % (rep.duration, rep.when, nodeid))
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
import sys
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
""" support for skip/xfail functions and markers. """
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
from _pytest.config import hookimpl
|
||||
from _pytest.mark.evaluate import MarkEvaluator
|
||||
from _pytest.outcomes import fail, skip, xfail
|
||||
from _pytest.outcomes import fail
|
||||
from _pytest.outcomes import skip
|
||||
from _pytest.outcomes import xfail
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
|
||||
@@ -2,8 +2,11 @@
|
||||
|
||||
This is a good source for looking at the various reporting hooks.
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import itertools
|
||||
import platform
|
||||
import sys
|
||||
@@ -17,16 +20,11 @@ from more_itertools import collapse
|
||||
|
||||
import pytest
|
||||
from _pytest import nodes
|
||||
from _pytest.main import (
|
||||
EXIT_OK,
|
||||
EXIT_TESTSFAILED,
|
||||
EXIT_INTERRUPTED,
|
||||
EXIT_USAGEERROR,
|
||||
EXIT_NOTESTSCOLLECTED,
|
||||
)
|
||||
|
||||
|
||||
import argparse
|
||||
from _pytest.main import EXIT_INTERRUPTED
|
||||
from _pytest.main import EXIT_NOTESTSCOLLECTED
|
||||
from _pytest.main import EXIT_OK
|
||||
from _pytest.main import EXIT_TESTSFAILED
|
||||
from _pytest.main import EXIT_USAGEERROR
|
||||
|
||||
|
||||
class MoreQuietAction(argparse.Action):
|
||||
@@ -676,7 +674,9 @@ class TerminalReporter(object):
|
||||
|
||||
if fspath:
|
||||
res = mkrel(nodeid).replace("::()", "") # parens-normalization
|
||||
if nodeid.split("::")[0] != fspath.replace("\\", nodes.SEP):
|
||||
if self.verbosity >= 2 and nodeid.split("::")[0] != fspath.replace(
|
||||
"\\", nodes.SEP
|
||||
):
|
||||
res += " <- " + self.startdir.bestrelpath(fspath)
|
||||
else:
|
||||
res = "[location]"
|
||||
@@ -723,11 +723,10 @@ class TerminalReporter(object):
|
||||
# legacy warnings show their location explicitly, while standard warnings look better without
|
||||
# it because the location is already formatted into the message
|
||||
warning_records = list(warning_records)
|
||||
is_legacy = warning_records[0].legacy
|
||||
if location and is_legacy:
|
||||
if location:
|
||||
self._tw.line(str(location))
|
||||
for w in warning_records:
|
||||
if is_legacy:
|
||||
if location:
|
||||
lines = w.message.splitlines()
|
||||
indented = "\n".join(" " + x for x in lines)
|
||||
message = indented.rstrip()
|
||||
@@ -835,9 +834,7 @@ def repr_pythonversion(v=None):
|
||||
|
||||
|
||||
def build_summary_stats_line(stats):
|
||||
keys = (
|
||||
"failed passed skipped deselected " "xfailed xpassed warnings error"
|
||||
).split()
|
||||
keys = ("failed passed skipped deselected xfailed xpassed warnings error").split()
|
||||
unknown_key_seen = False
|
||||
for key in stats.keys():
|
||||
if key not in keys:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user