Compare commits
414 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff015f6308 | ||
|
|
31c869b4c4 | ||
|
|
0395996756 | ||
|
|
986dd84375 | ||
|
|
a36e986920 | ||
|
|
68dc433bf5 | ||
|
|
e59fc730f8 | ||
|
|
4f9e835472 | ||
|
|
498b994eb4 | ||
|
|
40eef6da0c | ||
|
|
71373b04b0 | ||
|
|
6fb7269979 | ||
|
|
3460105def | ||
|
|
e3824d23bc | ||
|
|
80ad448590 | ||
|
|
59f69dd9e7 | ||
|
|
2106515f6d | ||
|
|
66b4709830 | ||
|
|
0b1f813c38 | ||
|
|
0b6960446e | ||
|
|
4bc2f96c93 | ||
|
|
0e4750d837 | ||
|
|
40cec637d7 | ||
|
|
407d4a0cf0 | ||
|
|
c84ae0bb7a | ||
|
|
1dbf440194 | ||
|
|
afaaa7e411 | ||
|
|
7b91952645 | ||
|
|
8726be27a6 | ||
|
|
e26c5bda6e | ||
|
|
f672b7e39e | ||
|
|
f0e6bf7604 | ||
|
|
f729d5d4ee | ||
|
|
04a941c818 | ||
|
|
215d537624 | ||
|
|
f63fbf8114 | ||
|
|
b595587031 | ||
|
|
82cc3d8cc2 | ||
|
|
e20e376881 | ||
|
|
8052d01a37 | ||
|
|
d03444db4a | ||
|
|
f9c1329dab | ||
|
|
a8003286b5 | ||
|
|
747a8ae3a6 | ||
|
|
b759ebdb93 | ||
|
|
b41632e9a8 | ||
|
|
b4be228330 | ||
|
|
31c948184a | ||
|
|
67dd10de26 | ||
|
|
f13935da53 | ||
|
|
dc8af18a0e | ||
|
|
7bee359459 | ||
|
|
61b9246afe | ||
|
|
9feb4941f4 | ||
|
|
237f690f8b | ||
|
|
386e801a5a | ||
|
|
5cf05ce149 | ||
|
|
aee67bb1a7 | ||
|
|
5e2d740829 | ||
|
|
82b8ec37fc | ||
|
|
f73fa47b1f | ||
|
|
fd1684e70b | ||
|
|
19501028ca | ||
|
|
ed01dc6567 | ||
|
|
3a366f451a | ||
|
|
7f6108beb1 | ||
|
|
76b0660f47 | ||
|
|
fc8800c71f | ||
|
|
75a12b9d2b | ||
|
|
9bcbf552d6 | ||
|
|
32c6d4f603 | ||
|
|
b4b2f58eab | ||
|
|
a131cd6c3b | ||
|
|
9c03196e79 | ||
|
|
8b92d10fb3 | ||
|
|
8e220f0e6f | ||
|
|
e191a65ebb | ||
|
|
5ca81596bb | ||
|
|
64e8185ff7 | ||
|
|
7bb504b807 | ||
|
|
9be069f899 | ||
|
|
913a2da6e5 | ||
|
|
ea732464aa | ||
|
|
ddbea29c12 | ||
|
|
4c7ddb8d9b | ||
|
|
a1fcd6e445 | ||
|
|
7b8fd0cc12 | ||
|
|
391dc549c0 | ||
|
|
526f4a95cc | ||
|
|
4cd268dc5d | ||
|
|
59e6fb94b5 | ||
|
|
2f083504ee | ||
|
|
3384ffc6eb | ||
|
|
e276bd3332 | ||
|
|
7445b5345f | ||
|
|
429485e621 | ||
|
|
52d497570b | ||
|
|
1876a928d3 | ||
|
|
2dc2a19db5 | ||
|
|
0c5e717f43 | ||
|
|
54af0f4c65 | ||
|
|
678dfaa6eb | ||
|
|
fc5d4654e5 | ||
|
|
19c93d16d1 | ||
|
|
0ce8b910ca | ||
|
|
c780d1fa7c | ||
|
|
584c052da4 | ||
|
|
315374008b | ||
|
|
a9457345ee | ||
|
|
726e165932 | ||
|
|
2264db7f4a | ||
|
|
4e93dc2c97 | ||
|
|
8003d8d279 | ||
|
|
f0ecb25acd | ||
|
|
7ec1a1407a | ||
|
|
7dbe40092d | ||
|
|
e53563ebbe | ||
|
|
c2c9b27771 | ||
|
|
c3d7340542 | ||
|
|
2461a43e00 | ||
|
|
7dcd9bf5ad | ||
|
|
fa979a4290 | ||
|
|
e2a15c79e7 | ||
|
|
02962fabda | ||
|
|
b77d168d58 | ||
|
|
c0e6543b5a | ||
|
|
b96e162131 | ||
|
|
1dc16ad77b | ||
|
|
acece23697 | ||
|
|
e5f823a3a7 | ||
|
|
b41dc03930 | ||
|
|
ade5f2c8c5 | ||
|
|
3e0e819158 | ||
|
|
eb92e57509 | ||
|
|
7ad499ad76 | ||
|
|
2d7582bd92 | ||
|
|
7e8e593a45 | ||
|
|
6c3b86369f | ||
|
|
6aba60ab08 | ||
|
|
d720312df0 | ||
|
|
5b09eb1d74 | ||
|
|
5119abe498 | ||
|
|
1c5009c3fb | ||
|
|
8a1afe4213 | ||
|
|
fd4289dae0 | ||
|
|
977adf1354 | ||
|
|
c1fe07276c | ||
|
|
c166b80a8c | ||
|
|
afe9fd5ffd | ||
|
|
5567c772cd | ||
|
|
c75bd08807 | ||
|
|
f7d7555521 | ||
|
|
16f8cdac95 | ||
|
|
9905a73ae0 | ||
|
|
51dd738b1a | ||
|
|
067f2c6148 | ||
|
|
e2cd2cd409 | ||
|
|
37aab5dd6b | ||
|
|
7ddfc04793 | ||
|
|
7b10474fed | ||
|
|
8cf097635e | ||
|
|
2d18546870 | ||
|
|
0f546c4670 | ||
|
|
6d38868950 | ||
|
|
8723eb16ea | ||
|
|
daf39112e7 | ||
|
|
9543d1901f | ||
|
|
ba452dbcf0 | ||
|
|
a2954578aa | ||
|
|
1bcb2f91cc | ||
|
|
92a2c1a9c4 | ||
|
|
9f86e83478 | ||
|
|
f01f434311 | ||
|
|
0c6ca0da62 | ||
|
|
095ce2ca7f | ||
|
|
dbb6c18c44 | ||
|
|
653c685667 | ||
|
|
ec5e279f93 | ||
|
|
e69b1255d7 | ||
|
|
57bf9d6740 | ||
|
|
677a7d06da | ||
|
|
f28b834426 | ||
|
|
04bd147d46 | ||
|
|
6154a5ac02 | ||
|
|
1a04e8903a | ||
|
|
85c5fa9f64 | ||
|
|
8967976443 | ||
|
|
bcacc40775 | ||
|
|
4ecf29380a | ||
|
|
aaa7d36bc9 | ||
|
|
8937e39afd | ||
|
|
343430c537 | ||
|
|
335cc5d651 | ||
|
|
2e551c32b6 | ||
|
|
af2ee1e80a | ||
|
|
e2a9aaf24b | ||
|
|
4947eb85c0 | ||
|
|
1a358df998 | ||
|
|
5903f4596a | ||
|
|
5bb0be1e24 | ||
|
|
6504746652 | ||
|
|
42bb0b3904 | ||
|
|
1bb463a980 | ||
|
|
16546b7342 | ||
|
|
f2174c16cc | ||
|
|
e48f68953d | ||
|
|
25081d8e30 | ||
|
|
34eeda1c09 | ||
|
|
3efb26ae7f | ||
|
|
77da4f118c | ||
|
|
3241fc3103 | ||
|
|
acb3e8e8a7 | ||
|
|
5f16ff3acc | ||
|
|
71a745270a | ||
|
|
5dcb370f78 | ||
|
|
0f918b1a9d | ||
|
|
a6988aa0b9 | ||
|
|
2359663437 | ||
|
|
bace28517e | ||
|
|
9f6d9efc1d | ||
|
|
a0ab5a7cd8 | ||
|
|
ba8b3be61a | ||
|
|
2467831913 | ||
|
|
e4a21b11d5 | ||
|
|
f3b6425324 | ||
|
|
7ee03e0996 | ||
|
|
081accb62c | ||
|
|
fe4835c15e | ||
|
|
df3b5557d1 | ||
|
|
948a5d5ac6 | ||
|
|
85055a9efe | ||
|
|
149620f858 | ||
|
|
6ee5d431a0 | ||
|
|
a4c426b1a8 | ||
|
|
4f38c610c3 | ||
|
|
38adb23bd2 | ||
|
|
e24031fb36 | ||
|
|
99ef8c6d16 | ||
|
|
e8152207c4 | ||
|
|
d7465895d0 | ||
|
|
5d0bcb4419 | ||
|
|
01151ff566 | ||
|
|
d0e9b4812f | ||
|
|
5a8e674e92 | ||
|
|
e380d4306b | ||
|
|
9d297c06e8 | ||
|
|
e24fdb138d | ||
|
|
0da5531c7c | ||
|
|
0c4898670c | ||
|
|
be7eb22e88 | ||
|
|
8b48621687 | ||
|
|
56aecfc081 | ||
|
|
82a0308bc6 | ||
|
|
7f671586b0 | ||
|
|
9e62f9d64e | ||
|
|
81c2780d2b | ||
|
|
b39b69a730 | ||
|
|
30c7a7bd69 | ||
|
|
cf5a9aebb2 | ||
|
|
1a9979a803 | ||
|
|
1eef53b6fe | ||
|
|
388aff16c8 | ||
|
|
83ec0228d1 | ||
|
|
2dc8cc1e48 | ||
|
|
658fa35642 | ||
|
|
b2c4ed9a2b | ||
|
|
b5cd43bc95 | ||
|
|
134ace98d9 | ||
|
|
134641fcb5 | ||
|
|
8f8d3114dd | ||
|
|
102ffc69e8 | ||
|
|
64a353f2b6 | ||
|
|
b258764ffe | ||
|
|
3947b859dc | ||
|
|
9f9f6ee48b | ||
|
|
58fc918d0a | ||
|
|
ece01b0f56 | ||
|
|
c378cb4793 | ||
|
|
d888d5c933 | ||
|
|
b2d3ae257a | ||
|
|
a93f41233a | ||
|
|
9138419379 | ||
|
|
197fd69ddc | ||
|
|
c400d8b2d8 | ||
|
|
0115766df3 | ||
|
|
8563364d8b | ||
|
|
0a40ae4c6a | ||
|
|
e63c7a13ff | ||
|
|
b7e8171cf8 | ||
|
|
75e93e5168 | ||
|
|
843d00c219 | ||
|
|
c6d27d8224 | ||
|
|
84390acccc | ||
|
|
f04d3c8b7d | ||
|
|
60773e0a97 | ||
|
|
3cf44b3037 | ||
|
|
1499778d5e | ||
|
|
a7e401656e | ||
|
|
6e1b1abfa7 | ||
|
|
8e287c5c77 | ||
|
|
231863b133 | ||
|
|
ae5d5b8f59 | ||
|
|
fd48cd57f9 | ||
|
|
98987177a0 | ||
|
|
437f44a1f4 | ||
|
|
b76104e722 | ||
|
|
1e80a9cb34 | ||
|
|
26d202a7bd | ||
|
|
b390c66dc4 | ||
|
|
f96e1b6f3e | ||
|
|
15b0a89fb1 | ||
|
|
5b83417afc | ||
|
|
9b3be870dc | ||
|
|
7a600ea3eb | ||
|
|
110fe2473f | ||
|
|
f8d31d2400 | ||
|
|
e3d30f8ebf | ||
|
|
5d79baf3f8 | ||
|
|
ec4507d12a | ||
|
|
b1e766c30e | ||
|
|
316cca204f | ||
|
|
0bccfc44a7 | ||
|
|
3cd11617ea | ||
|
|
9839ceffe0 | ||
|
|
a44776ed48 | ||
|
|
cfbd387a5d | ||
|
|
bb363c8ff2 | ||
|
|
ebe0a88226 | ||
|
|
3445eae737 | ||
|
|
8152b6837e | ||
|
|
0e4e8e00a9 | ||
|
|
7b1cb885c7 | ||
|
|
fc4aa27cae | ||
|
|
038f1f94c2 | ||
|
|
539d3dc34d | ||
|
|
0db5ccb0dd | ||
|
|
76884c73bf | ||
|
|
5ebacc49c6 | ||
|
|
8a0ed7e2b3 | ||
|
|
62b8f2f731 | ||
|
|
8fd60483ef | ||
|
|
7a7ad0c120 | ||
|
|
41031fce2f | ||
|
|
e1e4b226c6 | ||
|
|
26d27df6fc | ||
|
|
3e6f1fa2db | ||
|
|
aaf7f7fcca | ||
|
|
e0c2ab1901 | ||
|
|
59a11b6a5d | ||
|
|
9fc9b2926f | ||
|
|
1654b77ca0 | ||
|
|
d237197de3 | ||
|
|
5db46d2087 | ||
|
|
b88c3f8f82 | ||
|
|
4a3c8e22d7 | ||
|
|
a131f0acf6 | ||
|
|
4ffa13728d | ||
|
|
44b74c8c25 | ||
|
|
40b85d7ee8 | ||
|
|
090f7ff449 | ||
|
|
b05061dcd2 | ||
|
|
06dc6e3490 | ||
|
|
63f38de38e | ||
|
|
e0ba1cbf8d | ||
|
|
847eacea19 | ||
|
|
b531f7d585 | ||
|
|
7eb28f9eb7 | ||
|
|
1d86247b2c | ||
|
|
1bba0a9714 | ||
|
|
5cf69fae7d | ||
|
|
b73e083d9d | ||
|
|
7d3ca68be6 | ||
|
|
e9b2475e29 | ||
|
|
59f65230b5 | ||
|
|
5bee396e4b | ||
|
|
33b877cc01 | ||
|
|
63f90a2bcd | ||
|
|
f987b368e8 | ||
|
|
5d2e2377ff | ||
|
|
2eaf3db6ae | ||
|
|
88bf01a31e | ||
|
|
d894bf4535 | ||
|
|
9ed63c607e | ||
|
|
d52ea4b6cf | ||
|
|
0ffb8ddd7f | ||
|
|
95c6d591f7 | ||
|
|
9a1e518cc3 | ||
|
|
9ca0ab6e2b | ||
|
|
8395b9e25d | ||
|
|
3d92d5a659 | ||
|
|
50e3783f07 | ||
|
|
6e85febf20 | ||
|
|
ba17363d75 | ||
|
|
92a2884b09 | ||
|
|
2754a13f86 | ||
|
|
4eddf634e7 | ||
|
|
1a8d9bf254 | ||
|
|
62967b3110 | ||
|
|
5872e1c35a | ||
|
|
ba457f5feb | ||
|
|
ed91d5f086 | ||
|
|
b83e97802e | ||
|
|
2bffd6829e | ||
|
|
4e99c80425 | ||
|
|
a663f60b05 | ||
|
|
e1e81e315e | ||
|
|
025d160dfc | ||
|
|
a3e388a73a | ||
|
|
1847cc7420 | ||
|
|
87b019d5f9 | ||
|
|
1184db8273 | ||
|
|
a0ba881c22 | ||
|
|
d42f1e87c3 | ||
|
|
9769bc05c6 |
2
.github/config.yml
vendored
Normal file
2
.github/config.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
rtd:
|
||||
project: pytest
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -44,3 +44,7 @@ coverage.xml
|
||||
.pydevproject
|
||||
.project
|
||||
.settings
|
||||
.vscode
|
||||
|
||||
# generated by pip
|
||||
pip-wheel-metadata/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
exclude: doc/en/example/py2py3/test_py2.py
|
||||
repos:
|
||||
- repo: https://github.com/ambv/black
|
||||
rev: 18.6b4
|
||||
rev: 18.9b0
|
||||
hooks:
|
||||
- id: black
|
||||
args: [--safe, --quiet]
|
||||
@@ -13,7 +13,7 @@ repos:
|
||||
additional_dependencies: [black==18.9b0]
|
||||
language_version: python3
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v2.0.0
|
||||
rev: v2.1.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
@@ -21,20 +21,23 @@ repos:
|
||||
- id: debug-statements
|
||||
exclude: _pytest/debugging.py
|
||||
language_version: python3
|
||||
- repo: https://gitlab.com/pycqa/flake8
|
||||
rev: 3.7.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
language_version: python3
|
||||
- repo: https://github.com/asottile/reorder_python_imports
|
||||
rev: v1.3.3
|
||||
rev: v1.3.5
|
||||
hooks:
|
||||
- id: reorder-python-imports
|
||||
args: ['--application-directories=.:src']
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v1.10.1
|
||||
rev: v1.11.1
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--keep-percent-format]
|
||||
- repo: https://github.com/pre-commit/pygrep-hooks
|
||||
rev: v1.1.0
|
||||
rev: v1.2.0
|
||||
hooks:
|
||||
- id: rst-backticks
|
||||
- repo: local
|
||||
@@ -51,3 +54,17 @@ repos:
|
||||
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/
|
||||
- id: py-deprecated
|
||||
name: py library is deprecated
|
||||
language: pygrep
|
||||
entry: >
|
||||
(?x)\bpy\.(
|
||||
_code\.|
|
||||
builtin\.|
|
||||
code\.|
|
||||
io\.(BytesIO|saferepr)|
|
||||
path\.local\.sysfind|
|
||||
process\.|
|
||||
std\.
|
||||
)
|
||||
types: [python]
|
||||
|
||||
25
.travis.yml
25
.travis.yml
@@ -1,4 +1,3 @@
|
||||
sudo: false
|
||||
language: python
|
||||
dist: xenial
|
||||
stages:
|
||||
@@ -17,23 +16,28 @@ env:
|
||||
# Specialized factors for py27.
|
||||
- TOXENV=py27-nobyte
|
||||
- TOXENV=py27-xdist
|
||||
- TOXENV=py27-pluggymaster PYTEST_NO_COVERAGE=1
|
||||
- TOXENV=py27-pluggymaster
|
||||
# Specialized factors for py37.
|
||||
- TOXENV=py37-pexpect,py37-trial,py37-numpy
|
||||
- TOXENV=py37-pluggymaster PYTEST_NO_COVERAGE=1
|
||||
- TOXENV=py37-pluggymaster
|
||||
- TOXENV=py37-freeze PYTEST_NO_COVERAGE=1
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- python: '3.8-dev'
|
||||
env: TOXENV=py38-xdist
|
||||
|
||||
jobs:
|
||||
include:
|
||||
# Coverage tracking is slow with pypy, skip it.
|
||||
- env: TOXENV=pypy PYTEST_NO_COVERAGE=1
|
||||
python: 'pypy-5.4'
|
||||
dist: trusty
|
||||
- env: TOXENV=py34
|
||||
- env: TOXENV=py34-xdist
|
||||
python: '3.4'
|
||||
- env: TOXENV=py35
|
||||
- env: TOXENV=py35-xdist
|
||||
python: '3.5'
|
||||
- env: TOXENV=py36
|
||||
- env: TOXENV=py36-xdist
|
||||
python: '3.6'
|
||||
- env: TOXENV=py37
|
||||
- &test-macos
|
||||
@@ -43,15 +47,20 @@ jobs:
|
||||
sudo: required
|
||||
install:
|
||||
- python -m pip install --pre tox
|
||||
env: TOXENV=py27
|
||||
env: TOXENV=py27-xdist
|
||||
- <<: *test-macos
|
||||
env: TOXENV=py37
|
||||
env: TOXENV=py37-xdist
|
||||
before_install:
|
||||
- brew update
|
||||
- brew upgrade python
|
||||
- brew unlink python
|
||||
- brew link python
|
||||
|
||||
# Jobs only run via Travis cron jobs (currently daily).
|
||||
- env: TOXENV=py38-xdist
|
||||
python: '3.8-dev'
|
||||
if: type = cron
|
||||
|
||||
- stage: baseline
|
||||
env: TOXENV=py27-pexpect,py27-trial,py27-numpy
|
||||
- env: TOXENV=py37-xdist
|
||||
|
||||
14
AUTHORS
14
AUTHORS
@@ -6,22 +6,29 @@ Contributors include::
|
||||
Aaron Coleman
|
||||
Abdeali JK
|
||||
Abhijeet Kasurde
|
||||
Adam Johnson
|
||||
Adam Uhlir
|
||||
Ahn Ki-Wook
|
||||
Alan Velasco
|
||||
Alexander Johnson
|
||||
Alexei Kozlenok
|
||||
Allan Feldman
|
||||
Aly Sivji
|
||||
Anatoly Bubenkoff
|
||||
Anders Hovmöller
|
||||
Andras Mitzki
|
||||
Andras Tim
|
||||
Andrea Cimatoribus
|
||||
Andreas Zeidler
|
||||
Andrey Paramonov
|
||||
Andrzej Ostrowski
|
||||
Andy Freeland
|
||||
Anthon van der Neut
|
||||
Anthony Shaw
|
||||
Anthony Sottile
|
||||
Anton Lodder
|
||||
Antony Lee
|
||||
Arel Cordero
|
||||
Armin Rigo
|
||||
Aron Coyle
|
||||
Aron Curzon
|
||||
@@ -45,9 +52,11 @@ Charles Cloud
|
||||
Charnjit SiNGH (CCSJ)
|
||||
Chris Lamb
|
||||
Christian Boelsen
|
||||
Christian Fetzer
|
||||
Christian Theunert
|
||||
Christian Tismer
|
||||
Christopher Gilling
|
||||
Christopher Dignam
|
||||
CrazyMerlyn
|
||||
Cyrus Maden
|
||||
Dhiren Serai
|
||||
@@ -114,6 +123,7 @@ Jonas Obrist
|
||||
Jordan Guymon
|
||||
Jordan Moldow
|
||||
Jordan Speicher
|
||||
Joseph Hunkeler
|
||||
Joshua Bronson
|
||||
Jurko Gospodnetić
|
||||
Justyna Janczyszyn
|
||||
@@ -123,6 +133,7 @@ Katerina Koukiou
|
||||
Kevin Cox
|
||||
Kodi B. Arfer
|
||||
Kostis Anagnostopoulos
|
||||
Kristoffer Nordström
|
||||
Kyle Altendorf
|
||||
Lawrence Mitchell
|
||||
Lee Kamentsky
|
||||
@@ -164,6 +175,8 @@ Miro Hrončok
|
||||
Nathaniel Waisbrot
|
||||
Ned Batchelder
|
||||
Neven Mundar
|
||||
Nicholas Devenish
|
||||
Nicholas Murphy
|
||||
Niclas Olofsson
|
||||
Nicolas Delaby
|
||||
Oleg Pidsadnyi
|
||||
@@ -172,6 +185,7 @@ Oliver Bestwalter
|
||||
Omar Kohl
|
||||
Omer Hadari
|
||||
Ondřej Súkup
|
||||
Oscar Benjamin
|
||||
Patrick Hayes
|
||||
Paweł Adamczak
|
||||
Pedro Algarvio
|
||||
|
||||
441
CHANGELOG.rst
441
CHANGELOG.rst
@@ -18,6 +18,443 @@ with advance notice in the **Deprecations** section of releases.
|
||||
|
||||
.. towncrier release notes start
|
||||
|
||||
pytest 4.3.0 (2019-02-16)
|
||||
=========================
|
||||
|
||||
Deprecations
|
||||
------------
|
||||
|
||||
- `#4724 <https://github.com/pytest-dev/pytest/issues/4724>`_: ``pytest.warns()`` now emits a warning when it receives unknown keyword arguments.
|
||||
|
||||
This will be changed into an error in the future.
|
||||
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- `#2753 <https://github.com/pytest-dev/pytest/issues/2753>`_: Usage errors from argparse are mapped to pytest's ``UsageError``.
|
||||
|
||||
|
||||
- `#3711 <https://github.com/pytest-dev/pytest/issues/3711>`_: Add the ``--ignore-glob`` parameter to exclude test-modules with Unix shell-style wildcards.
|
||||
Add the ``collect_ignore_glob`` for ``conftest.py`` to exclude test-modules with Unix shell-style wildcards.
|
||||
|
||||
|
||||
- `#4698 <https://github.com/pytest-dev/pytest/issues/4698>`_: The warning about Python 2.7 and 3.4 not being supported in pytest 5.0 has been removed.
|
||||
|
||||
In the end it was considered to be more
|
||||
of a nuisance than actual utility and users of those Python versions shouldn't have problems as ``pip`` will not
|
||||
install pytest 5.0 on those interpreters.
|
||||
|
||||
|
||||
- `#4707 <https://github.com/pytest-dev/pytest/issues/4707>`_: With the help of new ``set_log_path()`` method there is a way to set ``log_file`` paths from hooks.
|
||||
|
||||
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#4651 <https://github.com/pytest-dev/pytest/issues/4651>`_: ``--help`` and ``--version`` are handled with ``UsageError``.
|
||||
|
||||
|
||||
- `#4782 <https://github.com/pytest-dev/pytest/issues/4782>`_: Fix ``AssertionError`` with collection of broken symlinks with packages.
|
||||
|
||||
|
||||
pytest 4.2.1 (2019-02-12)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#2895 <https://github.com/pytest-dev/pytest/issues/2895>`_: The ``pytest_report_collectionfinish`` hook now is also called with ``--collect-only``.
|
||||
|
||||
|
||||
- `#3899 <https://github.com/pytest-dev/pytest/issues/3899>`_: Do not raise ``UsageError`` when an imported package has a ``pytest_plugins.py`` child module.
|
||||
|
||||
|
||||
- `#4347 <https://github.com/pytest-dev/pytest/issues/4347>`_: Fix output capturing when using pdb++ with recursive debugging.
|
||||
|
||||
|
||||
- `#4592 <https://github.com/pytest-dev/pytest/issues/4592>`_: Fix handling of ``collect_ignore`` via parent ``conftest.py``.
|
||||
|
||||
|
||||
- `#4700 <https://github.com/pytest-dev/pytest/issues/4700>`_: Fix regression where ``setUpClass`` would always be called in subclasses even if all tests
|
||||
were skipped by a ``unittest.skip()`` decorator applied in the subclass.
|
||||
|
||||
|
||||
- `#4739 <https://github.com/pytest-dev/pytest/issues/4739>`_: Fix ``parametrize(... ids=<function>)`` when the function returns non-strings.
|
||||
|
||||
|
||||
- `#4745 <https://github.com/pytest-dev/pytest/issues/4745>`_: Fix/improve collection of args when passing in ``__init__.py`` and a test file.
|
||||
|
||||
|
||||
- `#4770 <https://github.com/pytest-dev/pytest/issues/4770>`_: ``more_itertools`` is now constrained to <6.0.0 when required for Python 2.7 compatibility.
|
||||
|
||||
|
||||
- `#526 <https://github.com/pytest-dev/pytest/issues/526>`_: Fix "ValueError: Plugin already registered" exceptions when running in build directories that symlink to actual source.
|
||||
|
||||
|
||||
|
||||
Improved Documentation
|
||||
----------------------
|
||||
|
||||
- `#3899 <https://github.com/pytest-dev/pytest/issues/3899>`_: Add note to ``plugins.rst`` that ``pytest_plugins`` should not be used as a name for a user module containing plugins.
|
||||
|
||||
|
||||
- `#4324 <https://github.com/pytest-dev/pytest/issues/4324>`_: Document how to use ``raises`` and ``does_not_raise`` to write parametrized tests with conditional raises.
|
||||
|
||||
|
||||
- `#4709 <https://github.com/pytest-dev/pytest/issues/4709>`_: Document how to customize test failure messages when using
|
||||
``pytest.warns``.
|
||||
|
||||
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- `#4741 <https://github.com/pytest-dev/pytest/issues/4741>`_: Some verbosity related attributes of the TerminalReporter plugin are now
|
||||
read only properties.
|
||||
|
||||
|
||||
pytest 4.2.0 (2019-01-30)
|
||||
=========================
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- `#3094 <https://github.com/pytest-dev/pytest/issues/3094>`_: `Classic xunit-style <https://docs.pytest.org/en/latest/xunit_setup.html>`__ functions and methods
|
||||
now obey the scope of *autouse* fixtures.
|
||||
|
||||
This fixes a number of surprising issues like ``setup_method`` being called before session-scoped
|
||||
autouse fixtures (see `#517 <https://github.com/pytest-dev/pytest/issues/517>`__ for an example).
|
||||
|
||||
|
||||
- `#4627 <https://github.com/pytest-dev/pytest/issues/4627>`_: Display a message at the end of the test session when running under Python 2.7 and 3.4 that pytest 5.0 will no longer
|
||||
support those Python versions.
|
||||
|
||||
|
||||
- `#4660 <https://github.com/pytest-dev/pytest/issues/4660>`_: The number of *selected* tests now are also displayed when the ``-k`` or ``-m`` flags are used.
|
||||
|
||||
|
||||
- `#4688 <https://github.com/pytest-dev/pytest/issues/4688>`_: ``pytest_report_teststatus`` hook now can also receive a ``config`` parameter.
|
||||
|
||||
|
||||
- `#4691 <https://github.com/pytest-dev/pytest/issues/4691>`_: ``pytest_terminal_summary`` hook now can also receive a ``config`` parameter.
|
||||
|
||||
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#3547 <https://github.com/pytest-dev/pytest/issues/3547>`_: ``--junitxml`` can emit XML compatible with Jenkins xUnit.
|
||||
``junit_family`` INI option accepts ``legacy|xunit1``, which produces old style output, and ``xunit2`` that conforms more strictly to https://github.com/jenkinsci/xunit-plugin/blob/xunit-2.3.2/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd
|
||||
|
||||
|
||||
- `#4280 <https://github.com/pytest-dev/pytest/issues/4280>`_: Improve quitting from pdb, especially with ``--trace``.
|
||||
|
||||
Using ``q[quit]`` after ``pdb.set_trace()`` will quit pytest also.
|
||||
|
||||
|
||||
- `#4402 <https://github.com/pytest-dev/pytest/issues/4402>`_: Warning summary now groups warnings by message instead of by test id.
|
||||
|
||||
This makes the output more compact and better conveys the general idea of how much code is
|
||||
actually generating warnings, instead of how many tests call that code.
|
||||
|
||||
|
||||
- `#4536 <https://github.com/pytest-dev/pytest/issues/4536>`_: ``monkeypatch.delattr`` handles class descriptors like ``staticmethod``/``classmethod``.
|
||||
|
||||
|
||||
- `#4649 <https://github.com/pytest-dev/pytest/issues/4649>`_: Restore marks being considered keywords for keyword expressions.
|
||||
|
||||
|
||||
- `#4653 <https://github.com/pytest-dev/pytest/issues/4653>`_: ``tmp_path`` fixture and other related ones provides resolved path (a.k.a real path)
|
||||
|
||||
|
||||
- `#4667 <https://github.com/pytest-dev/pytest/issues/4667>`_: ``pytest_terminal_summary`` uses result from ``pytest_report_teststatus`` hook, rather than hardcoded strings.
|
||||
|
||||
|
||||
- `#4669 <https://github.com/pytest-dev/pytest/issues/4669>`_: Correctly handle ``unittest.SkipTest`` exception containing non-ascii characters on Python 2.
|
||||
|
||||
|
||||
- `#4680 <https://github.com/pytest-dev/pytest/issues/4680>`_: Ensure the ``tmpdir`` and the ``tmp_path`` fixtures are the same folder.
|
||||
|
||||
|
||||
- `#4681 <https://github.com/pytest-dev/pytest/issues/4681>`_: Ensure ``tmp_path`` is always a real path.
|
||||
|
||||
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- `#4643 <https://github.com/pytest-dev/pytest/issues/4643>`_: Use ``a.item()`` instead of the deprecated ``np.asscalar(a)`` in ``pytest.approx``.
|
||||
|
||||
``np.asscalar`` has been `deprecated <https://github.com/numpy/numpy/blob/master/doc/release/1.16.0-notes.rst#new-deprecations>`__ in ``numpy 1.16.``.
|
||||
|
||||
|
||||
- `#4657 <https://github.com/pytest-dev/pytest/issues/4657>`_: Copy saferepr from pylib
|
||||
|
||||
|
||||
- `#4668 <https://github.com/pytest-dev/pytest/issues/4668>`_: The verbose word for expected failures in the teststatus report changes from ``xfail`` to ``XFAIL`` to be consistent with other test outcomes.
|
||||
|
||||
|
||||
pytest 4.1.1 (2019-01-12)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#2256 <https://github.com/pytest-dev/pytest/issues/2256>`_: Show full repr with ``assert a==b`` and ``-vv``.
|
||||
|
||||
|
||||
- `#3456 <https://github.com/pytest-dev/pytest/issues/3456>`_: Extend Doctest-modules to ignore mock objects.
|
||||
|
||||
|
||||
- `#4617 <https://github.com/pytest-dev/pytest/issues/4617>`_: Fixed ``pytest.warns`` bug when context manager is reused (e.g. multiple parametrization).
|
||||
|
||||
|
||||
- `#4631 <https://github.com/pytest-dev/pytest/issues/4631>`_: Don't rewrite assertion when ``__getattr__`` is broken
|
||||
|
||||
|
||||
|
||||
Improved Documentation
|
||||
----------------------
|
||||
|
||||
- `#3375 <https://github.com/pytest-dev/pytest/issues/3375>`_: Document that using ``setup.cfg`` may crash other tools or cause hard to track down problems because it uses a different parser than ``pytest.ini`` or ``tox.ini`` files.
|
||||
|
||||
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- `#4602 <https://github.com/pytest-dev/pytest/issues/4602>`_: Uninstall ``hypothesis`` in regen tox env.
|
||||
|
||||
|
||||
pytest 4.1.0 (2019-01-05)
|
||||
=========================
|
||||
|
||||
Removals
|
||||
--------
|
||||
|
||||
- `#2169 <https://github.com/pytest-dev/pytest/issues/2169>`_: ``pytest.mark.parametrize``: in previous versions, errors raised by id functions were suppressed and changed into warnings. Now the exceptions are propagated, along with a pytest message informing the node, parameter value and index where the exception occurred.
|
||||
|
||||
|
||||
- `#3078 <https://github.com/pytest-dev/pytest/issues/3078>`_: Remove legacy internal warnings system: ``config.warn``, ``Node.warn``. The ``pytest_logwarning`` now issues a warning when implemented.
|
||||
|
||||
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#config-warn-and-node-warn>`__ on information on how to update your code.
|
||||
|
||||
|
||||
- `#3079 <https://github.com/pytest-dev/pytest/issues/3079>`_: Removed support for yield tests - they are fundamentally broken because they don't support fixtures properly since collection and test execution were separated.
|
||||
|
||||
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#yield-tests>`__ on information on how to update your code.
|
||||
|
||||
|
||||
- `#3082 <https://github.com/pytest-dev/pytest/issues/3082>`_: Removed support for applying marks directly to values in ``@pytest.mark.parametrize``. Use ``pytest.param`` instead.
|
||||
|
||||
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#marks-in-pytest-mark-parametrize>`__ on information on how to update your code.
|
||||
|
||||
|
||||
- `#3083 <https://github.com/pytest-dev/pytest/issues/3083>`_: Removed ``Metafunc.addcall``. This was the predecessor mechanism to ``@pytest.mark.parametrize``.
|
||||
|
||||
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#metafunc-addcall>`__ on information on how to update your code.
|
||||
|
||||
|
||||
- `#3085 <https://github.com/pytest-dev/pytest/issues/3085>`_: Removed support for passing strings to ``pytest.main``. Now, always pass a list of strings instead.
|
||||
|
||||
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#passing-command-line-string-to-pytest-main>`__ on information on how to update your code.
|
||||
|
||||
|
||||
- `#3086 <https://github.com/pytest-dev/pytest/issues/3086>`_: ``[pytest]`` section in **setup.cfg** files is not longer supported, use ``[tool:pytest]`` instead. ``setup.cfg`` files
|
||||
are meant for use with ``distutils``, and a section named ``pytest`` has notoriously been a source of conflicts and bugs.
|
||||
|
||||
Note that for **pytest.ini** and **tox.ini** files the section remains ``[pytest]``.
|
||||
|
||||
|
||||
- `#3616 <https://github.com/pytest-dev/pytest/issues/3616>`_: Removed the deprecated compat properties for ``node.Class/Function/Module`` - use ``pytest.Class/Function/Module`` now.
|
||||
|
||||
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#internal-classes-accessed-through-node>`__ on information on how to update your code.
|
||||
|
||||
|
||||
- `#4421 <https://github.com/pytest-dev/pytest/issues/4421>`_: Removed the implementation of the ``pytest_namespace`` hook.
|
||||
|
||||
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#pytest-namespace>`__ on information on how to update your code.
|
||||
|
||||
|
||||
- `#4489 <https://github.com/pytest-dev/pytest/issues/4489>`_: Removed ``request.cached_setup``. This was the predecessor mechanism to modern fixtures.
|
||||
|
||||
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#cached-setup>`__ on information on how to update your code.
|
||||
|
||||
|
||||
- `#4535 <https://github.com/pytest-dev/pytest/issues/4535>`_: Removed the deprecated ``PyCollector.makeitem`` method. This method was made public by mistake a long time ago.
|
||||
|
||||
|
||||
- `#4543 <https://github.com/pytest-dev/pytest/issues/4543>`_: Removed support to define fixtures using the ``pytest_funcarg__`` prefix. Use the ``@pytest.fixture`` decorator instead.
|
||||
|
||||
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#pytest-funcarg-prefix>`__ on information on how to update your code.
|
||||
|
||||
|
||||
- `#4545 <https://github.com/pytest-dev/pytest/issues/4545>`_: Calling fixtures directly is now always an error instead of a warning.
|
||||
|
||||
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#calling-fixtures-directly>`__ on information on how to update your code.
|
||||
|
||||
|
||||
- `#4546 <https://github.com/pytest-dev/pytest/issues/4546>`_: Remove ``Node.get_marker(name)`` the return value was not usable for more than a existence check.
|
||||
|
||||
Use ``Node.get_closest_marker(name)`` as a replacement.
|
||||
|
||||
|
||||
- `#4547 <https://github.com/pytest-dev/pytest/issues/4547>`_: The deprecated ``record_xml_property`` fixture has been removed, use the more generic ``record_property`` instead.
|
||||
|
||||
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#record-xml-property>`__ for more information.
|
||||
|
||||
|
||||
- `#4548 <https://github.com/pytest-dev/pytest/issues/4548>`_: An error is now raised if the ``pytest_plugins`` variable is defined in a non-top-level ``conftest.py`` file (i.e., not residing in the ``rootdir``).
|
||||
|
||||
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#pytest-plugins-in-non-top-level-conftest-files>`__ for more information.
|
||||
|
||||
|
||||
- `#891 <https://github.com/pytest-dev/pytest/issues/891>`_: Remove ``testfunction.markername`` attributes - use ``Node.iter_markers(name=None)`` to iterate them.
|
||||
|
||||
|
||||
|
||||
Deprecations
|
||||
------------
|
||||
|
||||
- `#3050 <https://github.com/pytest-dev/pytest/issues/3050>`_: Deprecated the ``pytest.config`` global.
|
||||
|
||||
See https://docs.pytest.org/en/latest/deprecations.html#pytest-config-global for rationale.
|
||||
|
||||
|
||||
- `#3974 <https://github.com/pytest-dev/pytest/issues/3974>`_: Passing the ``message`` parameter of ``pytest.raises`` now issues a ``DeprecationWarning``.
|
||||
|
||||
It is a common mistake to think this parameter will match the exception message, while in fact
|
||||
it only serves to provide a custom message in case the ``pytest.raises`` check fails. To avoid this
|
||||
mistake and because it is believed to be little used, pytest is deprecating it without providing
|
||||
an alternative for the moment.
|
||||
|
||||
If you have concerns about this, please comment on `issue #3974 <https://github.com/pytest-dev/pytest/issues/3974>`__.
|
||||
|
||||
|
||||
- `#4435 <https://github.com/pytest-dev/pytest/issues/4435>`_: Deprecated ``raises(..., 'code(as_a_string)')`` and ``warns(..., 'code(as_a_string)')``.
|
||||
|
||||
See https://docs.pytest.org/en/latest/deprecations.html#raises-warns-exec for rationale and examples.
|
||||
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- `#3191 <https://github.com/pytest-dev/pytest/issues/3191>`_: A warning is now issued when assertions are made for ``None``.
|
||||
|
||||
This is a common source of confusion among new users, which write:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
assert mocked_object.assert_called_with(3, 4, 5, key="value")
|
||||
|
||||
When they should write:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
mocked_object.assert_called_with(3, 4, 5, key="value")
|
||||
|
||||
Because the ``assert_called_with`` method of mock objects already executes an assertion.
|
||||
|
||||
This warning will not be issued when ``None`` is explicitly checked. An assertion like:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
assert variable is None
|
||||
|
||||
will not issue the warning.
|
||||
|
||||
|
||||
- `#3632 <https://github.com/pytest-dev/pytest/issues/3632>`_: Richer equality comparison introspection on ``AssertionError`` for objects created using `attrs <http://www.attrs.org/en/stable/>`__ or `dataclasses <https://docs.python.org/3/library/dataclasses.html>`_ (Python 3.7+, `backported to 3.6 <https://pypi.org/project/dataclasses>`__).
|
||||
|
||||
|
||||
- `#4278 <https://github.com/pytest-dev/pytest/issues/4278>`_: ``CACHEDIR.TAG`` files are now created inside cache directories.
|
||||
|
||||
Those files are part of the `Cache Directory Tagging Standard <http://www.bford.info/cachedir/spec.html>`__, and can
|
||||
be used by backup or synchronization programs to identify pytest's cache directory as such.
|
||||
|
||||
|
||||
- `#4292 <https://github.com/pytest-dev/pytest/issues/4292>`_: ``pytest.outcomes.Exit`` is derived from ``SystemExit`` instead of ``KeyboardInterrupt``. This allows us to better handle ``pdb`` exiting.
|
||||
|
||||
|
||||
- `#4371 <https://github.com/pytest-dev/pytest/issues/4371>`_: Updated the ``--collect-only`` option to display test descriptions when ran using ``--verbose``.
|
||||
|
||||
|
||||
- `#4386 <https://github.com/pytest-dev/pytest/issues/4386>`_: Restructured ``ExceptionInfo`` object construction and ensure incomplete instances have a ``repr``/``str``.
|
||||
|
||||
|
||||
- `#4416 <https://github.com/pytest-dev/pytest/issues/4416>`_: pdb: added support for keyword arguments with ``pdb.set_trace``.
|
||||
|
||||
It handles ``header`` similar to Python 3.7 does it, and forwards any
|
||||
other keyword arguments to the ``Pdb`` constructor.
|
||||
|
||||
This allows for ``__import__("pdb").set_trace(skip=["foo.*"])``.
|
||||
|
||||
|
||||
- `#4483 <https://github.com/pytest-dev/pytest/issues/4483>`_: Added ini parameter ``junit_duration_report`` to optionally report test call durations, excluding setup and teardown times.
|
||||
|
||||
The JUnit XML specification and the default pytest behavior is to include setup and teardown times in the test duration
|
||||
report. You can include just the call durations instead (excluding setup and teardown) by adding this to your ``pytest.ini`` file:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[pytest]
|
||||
junit_duration_report = call
|
||||
|
||||
|
||||
- `#4532 <https://github.com/pytest-dev/pytest/issues/4532>`_: ``-ra`` now will show errors and failures last, instead of as the first items in the summary.
|
||||
|
||||
This makes it easier to obtain a list of errors and failures to run tests selectively.
|
||||
|
||||
|
||||
- `#4599 <https://github.com/pytest-dev/pytest/issues/4599>`_: ``pytest.importorskip`` now supports a ``reason`` parameter, which will be shown when the
|
||||
requested module cannot be imported.
|
||||
|
||||
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#3532 <https://github.com/pytest-dev/pytest/issues/3532>`_: ``-p`` now accepts its argument without a space between the value, for example ``-pmyplugin``.
|
||||
|
||||
|
||||
- `#4327 <https://github.com/pytest-dev/pytest/issues/4327>`_: ``approx`` again works with more generic containers, more precisely instances of ``Iterable`` and ``Sized`` instead of more restrictive ``Sequence``.
|
||||
|
||||
|
||||
- `#4397 <https://github.com/pytest-dev/pytest/issues/4397>`_: Ensure that node ids are printable.
|
||||
|
||||
|
||||
- `#4435 <https://github.com/pytest-dev/pytest/issues/4435>`_: Fixed ``raises(..., 'code(string)')`` frame filename.
|
||||
|
||||
|
||||
- `#4458 <https://github.com/pytest-dev/pytest/issues/4458>`_: Display actual test ids in ``--collect-only``.
|
||||
|
||||
|
||||
|
||||
Improved Documentation
|
||||
----------------------
|
||||
|
||||
- `#4557 <https://github.com/pytest-dev/pytest/issues/4557>`_: Markers example documentation page updated to support latest pytest version.
|
||||
|
||||
|
||||
- `#4558 <https://github.com/pytest-dev/pytest/issues/4558>`_: Update cache documentation example to correctly show cache hit and miss.
|
||||
|
||||
|
||||
- `#4580 <https://github.com/pytest-dev/pytest/issues/4580>`_: Improved detailed summary report documentation.
|
||||
|
||||
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- `#4447 <https://github.com/pytest-dev/pytest/issues/4447>`_: Changed the deprecation type of ``--result-log`` to ``PytestDeprecationWarning``.
|
||||
|
||||
It was decided to remove this feature at the next major revision.
|
||||
|
||||
|
||||
pytest 4.0.2 (2018-12-13)
|
||||
=========================
|
||||
|
||||
@@ -1022,7 +1459,7 @@ Features
|
||||
- Revamp the internals of the ``pytest.mark`` implementation with correct per
|
||||
node handling which fixes a number of long standing bugs caused by the old
|
||||
design. This introduces new ``Node.iter_markers(name)`` and
|
||||
``Node.get_closest_mark(name)`` APIs. Users are **strongly encouraged** to
|
||||
``Node.get_closest_marker(name)`` APIs. Users are **strongly encouraged** to
|
||||
read the `reasons for the revamp in the docs
|
||||
<https://docs.pytest.org/en/latest/mark.html#marker-revamp-and-iteration>`_,
|
||||
or jump over to details about `updating existing code to use the new APIs
|
||||
@@ -1757,7 +2194,7 @@ Bug Fixes
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- pytest now depends on `attrs <https://pypi.org/project/attrs/>`_ for internal
|
||||
- pytest now depends on `attrs <https://pypi.org/project/attrs/>`__ for internal
|
||||
structures to ease code maintainability. (`#2641
|
||||
<https://github.com/pytest-dev/pytest/issues/2641>`_)
|
||||
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2004-2017 Holger Krekel and others
|
||||
Copyright (c) 2004-2019 Holger Krekel and others
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
|
||||
@@ -111,7 +111,7 @@ Consult the `Changelog <https://docs.pytest.org/en/latest/changelog.html>`__ pag
|
||||
License
|
||||
-------
|
||||
|
||||
Copyright Holger Krekel and others, 2004-2018.
|
||||
Copyright Holger Krekel and others, 2004-2019.
|
||||
|
||||
Distributed under the terms of the `MIT`_ license, pytest is free and open source software.
|
||||
|
||||
|
||||
10
appveyor.yml
10
appveyor.yml
@@ -2,22 +2,18 @@ environment:
|
||||
matrix:
|
||||
- TOXENV: "py37-xdist"
|
||||
- TOXENV: "py27-xdist"
|
||||
- TOXENV: "py27"
|
||||
- TOXENV: "py37"
|
||||
- TOXENV: "linting,docs,doctesting"
|
||||
- TOXENV: "py36"
|
||||
- TOXENV: "py35"
|
||||
- TOXENV: "py34"
|
||||
- TOXENV: "py34-xdist"
|
||||
- TOXENV: "py35-xdist"
|
||||
- TOXENV: "py36-xdist"
|
||||
- TOXENV: "pypy"
|
||||
PYTEST_NO_COVERAGE: "1"
|
||||
# Specialized factors for py27.
|
||||
- TOXENV: "py27-trial,py27-numpy,py27-nobyte"
|
||||
- TOXENV: "py27-pluggymaster"
|
||||
PYTEST_NO_COVERAGE: "1"
|
||||
# Specialized factors for py37.
|
||||
- TOXENV: "py37-trial,py37-numpy"
|
||||
- TOXENV: "py37-pluggymaster"
|
||||
PYTEST_NO_COVERAGE: "1"
|
||||
- TOXENV: "py37-freeze"
|
||||
PYTEST_NO_COVERAGE: "1"
|
||||
|
||||
|
||||
45
azure-pipelines.yml
Normal file
45
azure-pipelines.yml
Normal file
@@ -0,0 +1,45 @@
|
||||
trigger:
|
||||
- master
|
||||
- features
|
||||
|
||||
variables:
|
||||
PYTEST_ADDOPTS: "--junitxml=build/test-results/$(tox.env).xml"
|
||||
|
||||
jobs:
|
||||
|
||||
- job: 'Test'
|
||||
pool:
|
||||
vmImage: "vs2017-win2016"
|
||||
strategy:
|
||||
matrix:
|
||||
py27:
|
||||
python.version: '2.7'
|
||||
tox.env: 'py27'
|
||||
py35:
|
||||
python.version: '3.5'
|
||||
tox.env: 'py35'
|
||||
py36:
|
||||
python.version: '3.6'
|
||||
tox.env: 'py36'
|
||||
py37:
|
||||
python.version: '3.7'
|
||||
tox.env: 'py37'
|
||||
maxParallel: 4
|
||||
|
||||
steps:
|
||||
- task: UsePythonVersion@0
|
||||
inputs:
|
||||
versionSpec: '$(python.version)'
|
||||
architecture: 'x64'
|
||||
|
||||
- script: python -m pip install --upgrade pip && pip install tox
|
||||
displayName: 'Install tox'
|
||||
|
||||
- script: python -m tox -e $(tox.env)
|
||||
displayName: 'Run tests'
|
||||
|
||||
- task: PublishTestResults@2
|
||||
inputs:
|
||||
testResultsFiles: 'build/test-results/$(tox.env).xml'
|
||||
testRunTitle: '$(tox.env)'
|
||||
condition: succeededOrFailed()
|
||||
@@ -39,7 +39,7 @@ clean:
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
|
||||
regen:
|
||||
PYTHONDONTWRITEBYTECODE=1 PYTEST_ADDOPT=-pno:hypothesis COLUMNS=76 regendoc --update *.rst */*.rst ${REGENDOC_ARGS}
|
||||
PYTHONDONTWRITEBYTECODE=1 PYTEST_ADDOPTS=-pno:hypothesis COLUMNS=76 regendoc --update *.rst */*.rst ${REGENDOC_ARGS}
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
|
||||
@@ -6,6 +6,11 @@ Release announcements
|
||||
:maxdepth: 2
|
||||
|
||||
|
||||
release-4.3.0
|
||||
release-4.2.1
|
||||
release-4.2.0
|
||||
release-4.1.1
|
||||
release-4.1.0
|
||||
release-4.0.2
|
||||
release-4.0.1
|
||||
release-4.0.0
|
||||
|
||||
44
doc/en/announce/release-4.1.0.rst
Normal file
44
doc/en/announce/release-4.1.0.rst
Normal file
@@ -0,0 +1,44 @@
|
||||
pytest-4.1.0
|
||||
=======================================
|
||||
|
||||
The pytest team is proud to announce the 4.1.0 release!
|
||||
|
||||
pytest is a mature Python testing tool with more than a 2000 tests
|
||||
against itself, passing on many different interpreters and platforms.
|
||||
|
||||
This release contains a number of bugs fixes and improvements, so users are encouraged
|
||||
to take a look at the CHANGELOG:
|
||||
|
||||
https://docs.pytest.org/en/latest/changelog.html
|
||||
|
||||
For complete documentation, please visit:
|
||||
|
||||
https://docs.pytest.org/en/latest/
|
||||
|
||||
As usual, you can upgrade from pypi via:
|
||||
|
||||
pip install -U pytest
|
||||
|
||||
Thanks to all who contributed to this release, among them:
|
||||
|
||||
* Adam Johnson
|
||||
* Aly Sivji
|
||||
* Andrey Paramonov
|
||||
* Anthony Sottile
|
||||
* Bruno Oliveira
|
||||
* Daniel Hahler
|
||||
* David Vo
|
||||
* Hyunchel Kim
|
||||
* Jeffrey Rackauckas
|
||||
* Kanguros
|
||||
* Nicholas Devenish
|
||||
* Pedro Algarvio
|
||||
* Randy Barlow
|
||||
* Ronny Pfannschmidt
|
||||
* Tomer Keren
|
||||
* feuillemorte
|
||||
* wim glenn
|
||||
|
||||
|
||||
Happy testing,
|
||||
The Pytest Development Team
|
||||
27
doc/en/announce/release-4.1.1.rst
Normal file
27
doc/en/announce/release-4.1.1.rst
Normal file
@@ -0,0 +1,27 @@
|
||||
pytest-4.1.1
|
||||
=======================================
|
||||
|
||||
pytest 4.1.1 has just been released to PyPI.
|
||||
|
||||
This is a bug-fix release, being a drop-in replacement. To upgrade::
|
||||
|
||||
pip install --upgrade pytest
|
||||
|
||||
The full changelog is available at https://docs.pytest.org/en/latest/changelog.html.
|
||||
|
||||
Thanks to all who contributed to this release, among them:
|
||||
|
||||
* Anthony Sottile
|
||||
* Anton Lodder
|
||||
* Bruno Oliveira
|
||||
* Daniel Hahler
|
||||
* David Vo
|
||||
* Oscar Benjamin
|
||||
* Ronny Pfannschmidt
|
||||
* Victor Maryama
|
||||
* Yoav Caspi
|
||||
* dmitry.dygalo
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
||||
37
doc/en/announce/release-4.2.0.rst
Normal file
37
doc/en/announce/release-4.2.0.rst
Normal file
@@ -0,0 +1,37 @@
|
||||
pytest-4.2.0
|
||||
=======================================
|
||||
|
||||
The pytest team is proud to announce the 4.2.0 release!
|
||||
|
||||
pytest is a mature Python testing tool with more than a 2000 tests
|
||||
against itself, passing on many different interpreters and platforms.
|
||||
|
||||
This release contains a number of bugs fixes and improvements, so users are encouraged
|
||||
to take a look at the CHANGELOG:
|
||||
|
||||
https://docs.pytest.org/en/latest/changelog.html
|
||||
|
||||
For complete documentation, please visit:
|
||||
|
||||
https://docs.pytest.org/en/latest/
|
||||
|
||||
As usual, you can upgrade from pypi via:
|
||||
|
||||
pip install -U pytest
|
||||
|
||||
Thanks to all who contributed to this release, among them:
|
||||
|
||||
* Adam Uhlir
|
||||
* Anthony Sottile
|
||||
* Bruno Oliveira
|
||||
* Christopher Dignam
|
||||
* Daniel Hahler
|
||||
* Joseph Hunkeler
|
||||
* Kristoffer Nordstroem
|
||||
* Ronny Pfannschmidt
|
||||
* Thomas Hisch
|
||||
* wim glenn
|
||||
|
||||
|
||||
Happy testing,
|
||||
The Pytest Development Team
|
||||
30
doc/en/announce/release-4.2.1.rst
Normal file
30
doc/en/announce/release-4.2.1.rst
Normal file
@@ -0,0 +1,30 @@
|
||||
pytest-4.2.1
|
||||
=======================================
|
||||
|
||||
pytest 4.2.1 has just been released to PyPI.
|
||||
|
||||
This is a bug-fix release, being a drop-in replacement. To upgrade::
|
||||
|
||||
pip install --upgrade pytest
|
||||
|
||||
The full changelog is available at https://docs.pytest.org/en/latest/changelog.html.
|
||||
|
||||
Thanks to all who contributed to this release, among them:
|
||||
|
||||
* Anthony Sottile
|
||||
* Arel Cordero
|
||||
* Bruno Oliveira
|
||||
* Daniel Hahler
|
||||
* Holger Kohr
|
||||
* Kevin J. Foley
|
||||
* Nick Murphy
|
||||
* Paweł Stradomski
|
||||
* Raphael Pierzina
|
||||
* Ronny Pfannschmidt
|
||||
* Sam Brightman
|
||||
* Thomas Hisch
|
||||
* Zac Hatfield-Dodds
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
||||
36
doc/en/announce/release-4.3.0.rst
Normal file
36
doc/en/announce/release-4.3.0.rst
Normal file
@@ -0,0 +1,36 @@
|
||||
pytest-4.3.0
|
||||
=======================================
|
||||
|
||||
The pytest team is proud to announce the 4.3.0 release!
|
||||
|
||||
pytest is a mature Python testing tool with more than a 2000 tests
|
||||
against itself, passing on many different interpreters and platforms.
|
||||
|
||||
This release contains a number of bugs fixes and improvements, so users are encouraged
|
||||
to take a look at the CHANGELOG:
|
||||
|
||||
https://docs.pytest.org/en/latest/changelog.html
|
||||
|
||||
For complete documentation, please visit:
|
||||
|
||||
https://docs.pytest.org/en/latest/
|
||||
|
||||
As usual, you can upgrade from pypi via:
|
||||
|
||||
pip install -U pytest
|
||||
|
||||
Thanks to all who contributed to this release, among them:
|
||||
|
||||
* Andras Mitzki
|
||||
* Anthony Sottile
|
||||
* Bruno Oliveira
|
||||
* Christian Fetzer
|
||||
* Daniel Hahler
|
||||
* Grygorii Iermolenko
|
||||
* R. Alex Matevish
|
||||
* Ronny Pfannschmidt
|
||||
* cclauss
|
||||
|
||||
|
||||
Happy testing,
|
||||
The Pytest Development Team
|
||||
@@ -29,6 +29,7 @@ you will see the return value of the function call:
|
||||
$ pytest test_assert1.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 item
|
||||
|
||||
@@ -87,24 +88,30 @@ and if you need to have access to the actual exception info you may use::
|
||||
the actual exception raised. The main attributes of interest are
|
||||
``.type``, ``.value`` and ``.traceback``.
|
||||
|
||||
.. versionchanged:: 3.0
|
||||
You can pass a ``match`` keyword parameter to the context-manager to test
|
||||
that a regular expression matches on the string representation of an exception
|
||||
(similar to the ``TestCase.assertRaisesRegexp`` method from ``unittest``)::
|
||||
|
||||
In the context manager form you may use the keyword argument
|
||||
``message`` to specify a custom failure message::
|
||||
import pytest
|
||||
|
||||
>>> with raises(ZeroDivisionError, message="Expecting ZeroDivisionError"):
|
||||
... pass
|
||||
... Failed: Expecting ZeroDivisionError
|
||||
def myfunc():
|
||||
raise ValueError("Exception 123 raised")
|
||||
|
||||
If you want to write test code that works on Python 2.4 as well,
|
||||
you may also use two other ways to test for an expected exception::
|
||||
def test_match():
|
||||
with pytest.raises(ValueError, match=r'.* 123 .*'):
|
||||
myfunc()
|
||||
|
||||
The regexp parameter of the ``match`` method is matched with the ``re.search``
|
||||
function, so in the above example ``match='123'`` would have worked as
|
||||
well.
|
||||
|
||||
There's an alternate form of the ``pytest.raises`` function where you pass
|
||||
a function that will be executed with the given ``*args`` and ``**kwargs`` and
|
||||
assert that the given exception is raised::
|
||||
|
||||
pytest.raises(ExpectedException, func, *args, **kwargs)
|
||||
pytest.raises(ExpectedException, "func(*args, **kwargs)")
|
||||
|
||||
both of which execute the specified function with args and kwargs and
|
||||
asserts that the given ``ExpectedException`` is raised. The reporter will
|
||||
provide you with helpful output in case of failures such as *no
|
||||
The reporter will provide you with helpful output in case of failures such as *no
|
||||
exception* or *wrong exception*.
|
||||
|
||||
Note that it is also possible to specify a "raises" argument to
|
||||
@@ -121,23 +128,6 @@ exceptions your own code is deliberately raising, whereas using
|
||||
like documenting unfixed bugs (where the test describes what "should" happen)
|
||||
or bugs in dependencies.
|
||||
|
||||
Also, the context manager form accepts a ``match`` keyword parameter to test
|
||||
that a regular expression matches on the string representation of an exception
|
||||
(like the ``TestCase.assertRaisesRegexp`` method from ``unittest``)::
|
||||
|
||||
import pytest
|
||||
|
||||
def myfunc():
|
||||
raise ValueError("Exception 123 raised")
|
||||
|
||||
def test_match():
|
||||
with pytest.raises(ValueError, match=r'.* 123 .*'):
|
||||
myfunc()
|
||||
|
||||
The regexp parameter of the ``match`` method is matched with the ``re.search``
|
||||
function. So in the above example ``match='123'`` would have worked as
|
||||
well.
|
||||
|
||||
|
||||
.. _`assertwarns`:
|
||||
|
||||
@@ -174,6 +164,7 @@ if you run this module:
|
||||
$ pytest test_assert2.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 item
|
||||
|
||||
@@ -204,8 +195,8 @@ Special comparisons are done for a number of cases:
|
||||
|
||||
See the :ref:`reporting demo <tbreportdemo>` for many more examples.
|
||||
|
||||
Defining your own assertion comparison
|
||||
----------------------------------------------
|
||||
Defining your own explanation for failed assertions
|
||||
---------------------------------------------------
|
||||
|
||||
It is possible to add your own detailed explanations by implementing
|
||||
the ``pytest_assertrepr_compare`` hook.
|
||||
|
||||
@@ -68,8 +68,6 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||
|
||||
def test_function(record_property):
|
||||
record_property("example_key", 1)
|
||||
record_xml_property
|
||||
(Deprecated) use record_property.
|
||||
record_xml_attribute
|
||||
Add extra xml attributes to the tag for the calling test.
|
||||
The fixture is callable with ``(name, value)``, with value being
|
||||
|
||||
@@ -81,8 +81,9 @@ If you then run it with ``--lf``:
|
||||
$ pytest --lf
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 50 items / 48 deselected
|
||||
collected 50 items / 48 deselected / 2 selected
|
||||
run-last-failure: rerun previous 2 failures
|
||||
|
||||
test_50.py FF [100%]
|
||||
@@ -124,6 +125,7 @@ of ``FF`` and dots):
|
||||
$ pytest --ff
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 50 items
|
||||
run-last-failure: rerun previous 2 failures first
|
||||
@@ -185,11 +187,14 @@ across pytest invocations::
|
||||
import pytest
|
||||
import time
|
||||
|
||||
def expensive_computation():
|
||||
print("running expensive computation...")
|
||||
|
||||
@pytest.fixture
|
||||
def mydata(request):
|
||||
val = request.config.cache.get("example/value", None)
|
||||
if val is None:
|
||||
time.sleep(9*0.6) # expensive computation :)
|
||||
expensive_computation()
|
||||
val = 42
|
||||
request.config.cache.set("example/value", val)
|
||||
return val
|
||||
@@ -197,8 +202,7 @@ across pytest invocations::
|
||||
def test_function(mydata):
|
||||
assert mydata == 23
|
||||
|
||||
If you run this command once, it will take a while because
|
||||
of the sleep:
|
||||
If you run this command for the first time, you can see the print statement:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
@@ -212,12 +216,16 @@ of the sleep:
|
||||
def test_function(mydata):
|
||||
> assert mydata == 23
|
||||
E assert 42 == 23
|
||||
E -42
|
||||
E +23
|
||||
|
||||
test_caching.py:14: AssertionError
|
||||
test_caching.py:17: AssertionError
|
||||
-------------------------- Captured stdout setup ---------------------------
|
||||
running expensive computation...
|
||||
1 failed in 0.12 seconds
|
||||
|
||||
If you run it a second time the value will be retrieved from
|
||||
the cache and this will be quick:
|
||||
the cache and nothing will be printed:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
@@ -231,8 +239,10 @@ the cache and this will be quick:
|
||||
def test_function(mydata):
|
||||
> assert mydata == 23
|
||||
E assert 42 == 23
|
||||
E -42
|
||||
E +23
|
||||
|
||||
test_caching.py:14: AssertionError
|
||||
test_caching.py:17: AssertionError
|
||||
1 failed in 0.12 seconds
|
||||
|
||||
See the :ref:`cache-api` for more details.
|
||||
@@ -249,11 +259,17 @@ You can always peek at the content of the cache using the
|
||||
$ pytest --cache-show
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
cachedir: $REGENDOC_TMPDIR/.pytest_cache
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
------------------------------- cache values -------------------------------
|
||||
cache/lastfailed contains:
|
||||
{'test_caching.py::test_function': True}
|
||||
{'test_50.py::test_num[17]': True,
|
||||
'test_50.py::test_num[25]': True,
|
||||
'test_assert1.py::test_function': True,
|
||||
'test_assert2.py::test_set_comparison': True,
|
||||
'test_caching.py::test_function': True,
|
||||
'test_foocompare.py::test_compare': True}
|
||||
cache/nodeids contains:
|
||||
['test_caching.py::test_function']
|
||||
cache/stepwise contains:
|
||||
|
||||
@@ -68,6 +68,7 @@ of the failing function and hide the other one:
|
||||
$ pytest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 2 items
|
||||
|
||||
|
||||
@@ -42,10 +42,11 @@ todo_include_todos = 1
|
||||
extensions = [
|
||||
"pygments_pytest",
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinx.ext.todo",
|
||||
"sphinx.ext.autosummary",
|
||||
"sphinx.ext.intersphinx",
|
||||
"sphinx.ext.todo",
|
||||
"sphinx.ext.viewcode",
|
||||
"sphinx_removed_in",
|
||||
"sphinxcontrib_trio",
|
||||
]
|
||||
|
||||
@@ -64,7 +65,7 @@ master_doc = "contents"
|
||||
# General information about the project.
|
||||
project = u"pytest"
|
||||
year = datetime.datetime.utcnow().year
|
||||
copyright = u"2015–2018 , holger krekel and pytest-dev team"
|
||||
copyright = u"2015–2019 , holger krekel and pytest-dev team"
|
||||
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
|
||||
@@ -41,6 +41,7 @@ Full pytest documentation
|
||||
|
||||
backwards-compatibility
|
||||
deprecations
|
||||
py27-py34-deprecation
|
||||
historical-notes
|
||||
license
|
||||
contributing
|
||||
|
||||
@@ -7,6 +7,11 @@ This page lists all pytest features that are currently deprecated or have been r
|
||||
The objective is to give users a clear rationale why a certain feature has been removed, and what alternatives
|
||||
should be used instead.
|
||||
|
||||
.. contents::
|
||||
:depth: 3
|
||||
:local:
|
||||
|
||||
|
||||
Deprecated Features
|
||||
-------------------
|
||||
|
||||
@@ -14,24 +19,226 @@ 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``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
``"message"`` parameter of ``pytest.raises``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 3.9
|
||||
.. deprecated:: 4.1
|
||||
|
||||
Access of ``Module``, ``Function``, ``Class``, ``Instance``, ``File`` and ``Item`` through ``Node`` instances now issue
|
||||
this warning::
|
||||
It is a common mistake to think this parameter will match the exception message, while in fact
|
||||
it only serves to provide a custom message in case the ``pytest.raises`` check fails. To prevent
|
||||
users from making this mistake, and because it is believed to be little used, pytest is
|
||||
deprecating it without providing an alternative for the moment.
|
||||
|
||||
usage of Function.Module is deprecated, please use pytest.Module instead
|
||||
If you have a valid use case for this parameter, consider that to obtain the same results
|
||||
you can just call ``pytest.fail`` manually at the end of the ``with`` statement.
|
||||
|
||||
Users should just ``import pytest`` and access those objects using the ``pytest`` module.
|
||||
For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
with pytest.raises(TimeoutError, message="Client got unexpected message"):
|
||||
wait_for(websocket.recv(), 0.5)
|
||||
|
||||
|
||||
Becomes:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
with pytest.raises(TimeoutError):
|
||||
wait_for(websocket.recv(), 0.5)
|
||||
pytest.fail("Client got unexpected message")
|
||||
|
||||
|
||||
If you still have concerns about this deprecation and future removal, please comment on
|
||||
`issue #3974 <https://github.com/pytest-dev/pytest/issues/3974>`__.
|
||||
|
||||
|
||||
``pytest.config`` global
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 4.1
|
||||
|
||||
The ``pytest.config`` global object is deprecated. Instead use
|
||||
``request.config`` (via the ``request`` fixture) or if you are a plugin author
|
||||
use the ``pytest_configure(config)`` hook.
|
||||
|
||||
.. _raises-warns-exec:
|
||||
|
||||
``raises`` / ``warns`` with a string as the second argument
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 4.1
|
||||
|
||||
Use the context manager form of these instead. When necessary, invoke ``exec``
|
||||
directly.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
pytest.raises(ZeroDivisionError, "1 / 0")
|
||||
pytest.raises(SyntaxError, "a $ b")
|
||||
|
||||
pytest.warns(DeprecationWarning, "my_function()")
|
||||
pytest.warns(SyntaxWarning, "assert(1, 2)")
|
||||
|
||||
Becomes:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
with pytest.raises(ZeroDivisionError):
|
||||
1 / 0
|
||||
with pytest.raises(SyntaxError):
|
||||
exec("a $ b") # exec is required for invalid syntax
|
||||
|
||||
with pytest.warns(DeprecationWarning):
|
||||
my_function()
|
||||
with pytest.warns(SyntaxWarning):
|
||||
exec("assert(1, 2)") # exec is used to avoid a top-level warning
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Result log (``--result-log``)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 3.0
|
||||
|
||||
The ``--resultlog`` command line option has been deprecated: it is little used
|
||||
and there are more modern and better alternatives, for example `pytest-tap <https://tappy.readthedocs.io/en/latest/>`_.
|
||||
|
||||
This feature will be effectively removed in pytest 4.0 as the team intends to include a better alternative in the core.
|
||||
|
||||
If you have any concerns, please don't hesitate to `open an issue <https://github.com/pytest-dev/pytest/issues>`__.
|
||||
|
||||
Removed Features
|
||||
----------------
|
||||
|
||||
As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after
|
||||
an appropriate period of deprecation has passed.
|
||||
|
||||
Using ``Class`` in custom Collectors
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. versionremoved:: 4.0
|
||||
|
||||
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.
|
||||
|
||||
|
||||
marks in ``pytest.mark.parametrize``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. versionremoved:: 4.0
|
||||
|
||||
Applying marks to values of a ``pytest.mark.parametrize`` call is now deprecated. For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"a, b",
|
||||
[
|
||||
(3, 9),
|
||||
pytest.mark.xfail(reason="flaky")(6, 36),
|
||||
(10, 100),
|
||||
(20, 200),
|
||||
(40, 400),
|
||||
(50, 500),
|
||||
],
|
||||
)
|
||||
def test_foo(a, b):
|
||||
...
|
||||
|
||||
This code applies the ``pytest.mark.xfail(reason="flaky")`` mark to the ``(6, 36)`` value of the above parametrization
|
||||
call.
|
||||
|
||||
This was considered hard to read and understand, and also its implementation presented problems to the code preventing
|
||||
further internal improvements in the marks architecture.
|
||||
|
||||
To update the code, use ``pytest.param``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"a, b",
|
||||
[
|
||||
(3, 9),
|
||||
pytest.param(6, 36, marks=pytest.mark.xfail(reason="flaky")),
|
||||
(10, 100),
|
||||
(20, 200),
|
||||
(40, 400),
|
||||
(50, 500),
|
||||
],
|
||||
)
|
||||
def test_foo(a, b):
|
||||
...
|
||||
|
||||
|
||||
``pytest_funcarg__`` prefix
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. versionremoved:: 4.0
|
||||
|
||||
In very early pytest versions fixtures could be defined using the ``pytest_funcarg__`` prefix:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def pytest_funcarg__data():
|
||||
return SomeData()
|
||||
|
||||
Switch over to the ``@pytest.fixture`` decorator:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.fixture
|
||||
def data():
|
||||
return SomeData()
|
||||
|
||||
|
||||
|
||||
[pytest] section in setup.cfg files
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. versionremoved:: 4.0
|
||||
|
||||
``[pytest]`` sections in ``setup.cfg`` files should now be named ``[tool:pytest]``
|
||||
to avoid conflicts with other distutils commands.
|
||||
|
||||
|
||||
Metafunc.addcall
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
.. versionremoved:: 4.0
|
||||
|
||||
:meth:`_pytest.python.Metafunc.addcall` was a precursor to the current parametrized mechanism. Users should use
|
||||
:meth:`_pytest.python.Metafunc.parametrize` instead.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def pytest_generate_tests(metafunc):
|
||||
metafunc.addcall({"i": 1}, id="1")
|
||||
metafunc.addcall({"i": 2}, id="2")
|
||||
|
||||
Becomes:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def pytest_generate_tests(metafunc):
|
||||
metafunc.parametrize("i", [1, 2], ids=["1", "2"])
|
||||
|
||||
This has been documented as deprecated for years, but only now we are actually emitting deprecation warnings.
|
||||
|
||||
``cached_setup``
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 3.9
|
||||
.. versionremoved:: 4.0
|
||||
|
||||
``request.cached_setup`` was the precursor of the setup/teardown mechanism available to fixtures.
|
||||
|
||||
@@ -59,26 +266,21 @@ This should be updated to make use of standard fixture mechanisms:
|
||||
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.
|
||||
|
||||
pytest_plugins in non-top-level conftest files
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Using ``Class`` in custom Collectors
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
.. versionremoved:: 4.0
|
||||
|
||||
.. 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.
|
||||
Defining ``pytest_plugins`` is now deprecated in non-top-level conftest.py
|
||||
files because they will activate referenced plugins *globally*, which is surprising because for all other pytest
|
||||
features ``conftest.py`` files are only *active* for tests at or below it.
|
||||
|
||||
|
||||
``Config.warn`` and ``Node.warn``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 3.8
|
||||
.. versionremoved:: 4.0
|
||||
|
||||
Those methods were part of the internal pytest warnings system, but since ``3.8`` pytest is using the builtin warning
|
||||
system for its own warnings, so those two functions are now deprecated.
|
||||
@@ -100,47 +302,57 @@ Becomes:
|
||||
* ``node.warn(PytestWarning("some message"))``: is now the **recommended** way to call this function.
|
||||
The warning instance must be a PytestWarning or subclass.
|
||||
|
||||
* ``node.warn("CI", "some message")``: this code/message form is now **deprecated** and should be converted to the warning instance form above.
|
||||
* ``node.warn("CI", "some message")``: this code/message form has been **removed** and should be converted to the warning instance form above.
|
||||
|
||||
record_xml_property
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``pytest_namespace``
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
.. versionremoved:: 4.0
|
||||
|
||||
.. deprecated:: 3.7
|
||||
The ``record_xml_property`` fixture is now deprecated in favor of the more generic ``record_property``, which
|
||||
can be used by other consumers (for example ``pytest-html``) to obtain custom information about the test run.
|
||||
|
||||
This hook is deprecated because it greatly complicates the pytest internals regarding configuration and initialization, making some
|
||||
bug fixes and refactorings impossible.
|
||||
|
||||
Example of usage:
|
||||
This is just a matter of renaming the fixture as the API is the same:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class MySymbol:
|
||||
def test_foo(record_xml_property):
|
||||
...
|
||||
|
||||
Change to:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_foo(record_property):
|
||||
...
|
||||
|
||||
|
||||
def pytest_namespace():
|
||||
return {"my_symbol": MySymbol()}
|
||||
Passing command-line string to ``pytest.main()``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. versionremoved:: 4.0
|
||||
|
||||
Plugin authors relying on this hook should instead require that users now import the plugin modules directly (with an appropriate public API).
|
||||
|
||||
As a stopgap measure, plugin authors may still inject their names into pytest's namespace, usually during ``pytest_configure``:
|
||||
Passing a command-line string to ``pytest.main()`` is deprecated:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import pytest
|
||||
pytest.main("-v -s")
|
||||
|
||||
Pass a list instead:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
pytest.main(["-v", "-s"])
|
||||
|
||||
|
||||
def pytest_configure():
|
||||
pytest.my_symbol = MySymbol()
|
||||
|
||||
By passing a string, users expect that pytest will interpret that command-line using the shell rules they are working
|
||||
on (for example ``bash`` or ``Powershell``), but this is very hard/impossible to do in a portable way.
|
||||
|
||||
|
||||
Calling fixtures directly
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 3.7
|
||||
.. versionremoved:: 4.0
|
||||
|
||||
Calling a fixture function directly, as opposed to request them in a test function, is deprecated.
|
||||
|
||||
@@ -175,116 +387,27 @@ In those cases just request the function directly in the dependent fixture:
|
||||
cell.make_full()
|
||||
return cell
|
||||
|
||||
``Node.get_marker``
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 3.6
|
||||
|
||||
As part of a large :ref:`marker-revamp`, :meth:`_pytest.nodes.Node.get_marker` is deprecated. See
|
||||
:ref:`the documentation <update marker code>` on tips on how to update your code.
|
||||
|
||||
|
||||
record_xml_property
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 3.5
|
||||
|
||||
The ``record_xml_property`` fixture is now deprecated in favor of the more generic ``record_property``, which
|
||||
can be used by other consumers (for example ``pytest-html``) to obtain custom information about the test run.
|
||||
|
||||
This is just a matter of renaming the fixture as the API is the same:
|
||||
Alternatively if the fixture function is called multiple times inside a test (making it hard to apply the above pattern) or
|
||||
if you would like to make minimal changes to the code, you can create a fixture which calls the original function together
|
||||
with the ``name`` parameter:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_foo(record_xml_property):
|
||||
...
|
||||
|
||||
Change to:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_foo(record_property):
|
||||
...
|
||||
|
||||
pytest_plugins in non-top-level conftest files
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 3.5
|
||||
|
||||
Defining ``pytest_plugins`` is now deprecated in non-top-level conftest.py
|
||||
files because they will activate referenced plugins *globally*, which is surprising because for all other pytest
|
||||
features ``conftest.py`` files are only *active* for tests at or below it.
|
||||
|
||||
Metafunc.addcall
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 3.3
|
||||
|
||||
:meth:`_pytest.python.Metafunc.addcall` was a precursor to the current parametrized mechanism. Users should use
|
||||
:meth:`_pytest.python.Metafunc.parametrize` instead.
|
||||
|
||||
marks in ``pytest.mark.parametrize``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 3.2
|
||||
|
||||
Applying marks to values of a ``pytest.mark.parametrize`` call is now deprecated. For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"a, b", [(3, 9), pytest.mark.xfail(reason="flaky")(6, 36), (10, 100)]
|
||||
)
|
||||
def test_foo(a, b):
|
||||
...
|
||||
|
||||
This code applies the ``pytest.mark.xfail(reason="flaky")`` mark to the ``(6, 36)`` value of the above parametrization
|
||||
call.
|
||||
|
||||
This was considered hard to read and understand, and also its implementation presented problems to the code preventing
|
||||
further internal improvements in the marks architecture.
|
||||
|
||||
To update the code, use ``pytest.param``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"a, b",
|
||||
[(3, 9), pytest.param((6, 36), marks=pytest.mark.xfail(reason="flaky")), (10, 100)],
|
||||
)
|
||||
def test_foo(a, b):
|
||||
...
|
||||
def cell():
|
||||
return ...
|
||||
|
||||
|
||||
|
||||
Passing command-line string to ``pytest.main()``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 3.0
|
||||
|
||||
Passing a command-line string to ``pytest.main()`` is deprecated:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
pytest.main("-v -s")
|
||||
|
||||
Pass a list instead:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
pytest.main(["-v", "-s"])
|
||||
|
||||
|
||||
By passing a string, users expect that pytest will interpret that command-line using the shell rules they are working
|
||||
on (for example ``bash`` or ``Powershell``), but this is very hard/impossible to do in a portable way.
|
||||
@pytest.fixture(name="cell")
|
||||
def cell_fixture():
|
||||
return cell()
|
||||
|
||||
|
||||
``yield`` tests
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 3.0
|
||||
.. versionremoved:: 4.0
|
||||
|
||||
pytest supports ``yield``-style tests, where a test function actually ``yield`` functions and values
|
||||
pytest supported ``yield``-style tests, where a test function actually ``yield`` functions and values
|
||||
that are then turned into proper test methods. Example:
|
||||
|
||||
.. code-block:: python
|
||||
@@ -307,54 +430,77 @@ This form of test function doesn't support fixtures properly, and users should s
|
||||
def test_squared(x, y):
|
||||
assert x ** x == y
|
||||
|
||||
Internal classes accessed through ``Node``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``pytest_funcarg__`` prefix
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
.. versionremoved:: 4.0
|
||||
|
||||
.. deprecated:: 3.0
|
||||
Access of ``Module``, ``Function``, ``Class``, ``Instance``, ``File`` and ``Item`` through ``Node`` instances now issue
|
||||
this warning::
|
||||
|
||||
In very early pytest versions fixtures could be defined using the ``pytest_funcarg__`` prefix:
|
||||
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.
|
||||
|
||||
``Node.get_marker``
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. versionremoved:: 4.0
|
||||
|
||||
As part of a large :ref:`marker-revamp`, :meth:`_pytest.nodes.Node.get_marker` is deprecated. See
|
||||
:ref:`the documentation <update marker code>` on tips on how to update your code.
|
||||
|
||||
|
||||
``somefunction.markname``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. versionremoved:: 4.0
|
||||
|
||||
As part of a large :ref:`marker-revamp` we already deprecated using ``MarkInfo``
|
||||
the only correct way to get markers of an element is via ``node.iter_markers(name)``.
|
||||
|
||||
|
||||
``pytest_namespace``
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. versionremoved:: 4.0
|
||||
|
||||
This hook is deprecated because it greatly complicates the pytest internals regarding configuration and initialization, making some
|
||||
bug fixes and refactorings impossible.
|
||||
|
||||
Example of usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def pytest_funcarg__data():
|
||||
return SomeData()
|
||||
class MySymbol:
|
||||
...
|
||||
|
||||
Switch over to the ``@pytest.fixture`` decorator:
|
||||
|
||||
def pytest_namespace():
|
||||
return {"my_symbol": MySymbol()}
|
||||
|
||||
|
||||
Plugin authors relying on this hook should instead require that users now import the plugin modules directly (with an appropriate public API).
|
||||
|
||||
As a stopgap measure, plugin authors may still inject their names into pytest's namespace, usually during ``pytest_configure``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.fixture
|
||||
def data():
|
||||
return SomeData()
|
||||
import pytest
|
||||
|
||||
[pytest] section in setup.cfg files
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 3.0
|
||||
def pytest_configure():
|
||||
pytest.my_symbol = MySymbol()
|
||||
|
||||
``[pytest]`` sections in ``setup.cfg`` files should now be named ``[tool:pytest]``
|
||||
to avoid conflicts with other distutils commands.
|
||||
|
||||
Result log (``--result-log``)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 3.0
|
||||
|
||||
The ``--resultlog`` command line option has been deprecated: it is little used
|
||||
and there are more modern and better alternatives, for example `pytest-tap <https://tappy.readthedocs.io/en/latest/>`_.
|
||||
|
||||
Removed Features
|
||||
----------------
|
||||
|
||||
As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after
|
||||
an appropriate period of deprecation has passed.
|
||||
|
||||
|
||||
Reinterpretation mode (``--assert=reinterp``)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
*Removed in version 3.0.*
|
||||
.. versionremoved:: 3.0
|
||||
|
||||
Reinterpretation mode has now been removed and only plain and rewrite
|
||||
mode are available, consequently the ``--assert=reinterp`` option is
|
||||
@@ -366,7 +512,7 @@ explicitly turn on assertion rewriting for those files.
|
||||
Removed command-line options
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
*Removed in version 3.0.*
|
||||
.. versionremoved:: 3.0
|
||||
|
||||
The following deprecated commandline options were removed:
|
||||
|
||||
@@ -378,7 +524,7 @@ The following deprecated commandline options were removed:
|
||||
py.test-X* entry points
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
*Removed in version 3.0.*
|
||||
.. versionremoved:: 3.0
|
||||
|
||||
Removed all ``py.test-X*`` entry points. The versioned, suffixed entry points
|
||||
were never documented and a leftover from a pre-virtualenv era. These entry
|
||||
|
||||
@@ -65,6 +65,7 @@ then you can just invoke ``pytest`` without command line options:
|
||||
$ pytest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
|
||||
collected 1 item
|
||||
|
||||
|
||||
@@ -98,6 +98,30 @@ class TestSpecialisedExplanations(object):
|
||||
text = "head " * 50 + "f" * 70 + "tail " * 20
|
||||
assert "f" * 70 not in text
|
||||
|
||||
def test_eq_dataclass(self):
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class Foo(object):
|
||||
a: int
|
||||
b: str
|
||||
|
||||
left = Foo(1, "b")
|
||||
right = Foo(1, "c")
|
||||
assert left == right
|
||||
|
||||
def test_eq_attrs(self):
|
||||
import attr
|
||||
|
||||
@attr.s
|
||||
class Foo(object):
|
||||
a = attr.ib()
|
||||
b = attr.ib()
|
||||
|
||||
left = Foo(1, "b")
|
||||
right = Foo(1, "c")
|
||||
assert left == right
|
||||
|
||||
|
||||
def test_attribute():
|
||||
class Foo(object):
|
||||
@@ -141,11 +165,11 @@ def globf(x):
|
||||
|
||||
class TestRaises(object):
|
||||
def test_raises(self):
|
||||
s = "qwe" # NOQA
|
||||
raises(TypeError, "int(s)")
|
||||
s = "qwe"
|
||||
raises(TypeError, int, s)
|
||||
|
||||
def test_raises_doesnt(self):
|
||||
raises(IOError, "int('3')")
|
||||
raises(IOError, int, "3")
|
||||
|
||||
def test_raise(self):
|
||||
raise ValueError("demo error")
|
||||
|
||||
@@ -9,5 +9,5 @@ def test_failure_demo_fails_properly(testdir):
|
||||
failure_demo.copy(target)
|
||||
failure_demo.copy(testdir.tmpdir.join(failure_demo.basename))
|
||||
result = testdir.runpytest(target, syspathinsert=True)
|
||||
result.stdout.fnmatch_lines(["*42 failed*"])
|
||||
result.stdout.fnmatch_lines(["*44 failed*"])
|
||||
assert result.ret != 0
|
||||
|
||||
@@ -24,10 +24,10 @@ example: specifying and selecting acceptance tests
|
||||
pytest.skip("specify -A to run acceptance tests")
|
||||
self.tmpdir = request.config.mktemp(request.function.__name__, numbered=True)
|
||||
|
||||
def run(self, cmd):
|
||||
def run(self, *cmd):
|
||||
""" called by test code to execute an acceptance test. """
|
||||
self.tmpdir.chdir()
|
||||
return py.process.cmdexec(cmd)
|
||||
return subprocess.check_output(cmd).decode()
|
||||
|
||||
|
||||
and the actual test function example:
|
||||
@@ -36,7 +36,7 @@ and the actual test function example:
|
||||
|
||||
def test_some_acceptance_aspect(accept):
|
||||
accept.tmpdir.mkdir("somesub")
|
||||
result = accept.run("ls -la")
|
||||
result = accept.run("ls", "-la")
|
||||
assert "somesub" in result
|
||||
|
||||
If you run this test without specifying a command line option
|
||||
|
||||
@@ -33,10 +33,10 @@ 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-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
|
||||
cachedir: .pytest_cache
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 4 items / 3 deselected
|
||||
collecting ... collected 4 items / 3 deselected / 1 selected
|
||||
|
||||
test_server.py::test_send_http PASSED [100%]
|
||||
|
||||
@@ -48,10 +48,10 @@ 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-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
|
||||
cachedir: .pytest_cache
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 4 items / 1 deselected
|
||||
collecting ... collected 4 items / 1 deselected / 3 selected
|
||||
|
||||
test_server.py::test_something_quick PASSED [ 33%]
|
||||
test_server.py::test_another PASSED [ 66%]
|
||||
@@ -70,8 +70,8 @@ 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-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
|
||||
cachedir: .pytest_cache
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 1 item
|
||||
|
||||
@@ -85,8 +85,8 @@ You can also select on the class:
|
||||
|
||||
$ pytest -v test_server.py::TestClass
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
|
||||
cachedir: .pytest_cache
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 1 item
|
||||
|
||||
@@ -100,8 +100,8 @@ 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-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
|
||||
cachedir: .pytest_cache
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 2 items
|
||||
|
||||
@@ -140,10 +140,10 @@ 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-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
|
||||
cachedir: .pytest_cache
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 4 items / 3 deselected
|
||||
collecting ... collected 4 items / 3 deselected / 1 selected
|
||||
|
||||
test_server.py::test_send_http PASSED [100%]
|
||||
|
||||
@@ -155,10 +155,10 @@ 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-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
|
||||
cachedir: .pytest_cache
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 4 items / 1 deselected
|
||||
collecting ... collected 4 items / 1 deselected / 3 selected
|
||||
|
||||
test_server.py::test_something_quick PASSED [ 33%]
|
||||
test_server.py::test_another PASSED [ 66%]
|
||||
@@ -172,10 +172,10 @@ Or to select "http" and "quick" tests:
|
||||
|
||||
$ pytest -k "http or quick" -v
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
|
||||
cachedir: .pytest_cache
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 4 items / 2 deselected
|
||||
collecting ... collected 4 items / 2 deselected / 2 selected
|
||||
|
||||
test_server.py::test_send_http PASSED [ 50%]
|
||||
test_server.py::test_something_quick PASSED [100%]
|
||||
@@ -308,7 +308,7 @@ apply a marker to an individual test instance::
|
||||
@pytest.mark.foo
|
||||
@pytest.mark.parametrize(("n", "expected"), [
|
||||
(1, 2),
|
||||
pytest.mark.bar((1, 3)),
|
||||
pytest.param((1, 3), marks=pytest.mark.bar),
|
||||
(2, 3),
|
||||
])
|
||||
def test_increment(n, expected):
|
||||
@@ -318,15 +318,6 @@ In this example the mark "foo" will apply to each of the three
|
||||
tests, whereas the "bar" mark is only applied to the second test.
|
||||
Skip and xfail marks can also be applied in this way, see :ref:`skip/xfail with parametrize`.
|
||||
|
||||
.. note::
|
||||
|
||||
If the data you are parametrizing happen to be single callables, you need to be careful
|
||||
when marking these items. ``pytest.mark.xfail(my_func)`` won't work because it's also the
|
||||
signature of a function being decorated. To resolve this ambiguity, you need to pass a
|
||||
reason argument:
|
||||
``pytest.mark.xfail(func_bar, reason="Issue#7")``.
|
||||
|
||||
|
||||
.. _`adding a custom marker from a plugin`:
|
||||
|
||||
Custom marker and command line option to control test runs
|
||||
@@ -374,6 +365,7 @@ the test needs:
|
||||
$ pytest -E stage2
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 item
|
||||
|
||||
@@ -388,6 +380,7 @@ and here is one that specifies exactly the environment needed:
|
||||
$ pytest -E stage1
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 item
|
||||
|
||||
@@ -555,12 +548,13 @@ then you will see two tests skipped and two executed tests as expected:
|
||||
$ pytest -rs # this option reports skip reasons
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 4 items
|
||||
|
||||
test_plat.py s.s. [100%]
|
||||
========================= short test summary info ==========================
|
||||
SKIP [2] $REGENDOC_TMPDIR/conftest.py:12: cannot run on platform linux
|
||||
SKIPPED [2] $REGENDOC_TMPDIR/conftest.py:12: cannot run on platform linux
|
||||
|
||||
=================== 2 passed, 2 skipped in 0.12 seconds ====================
|
||||
|
||||
@@ -571,8 +565,9 @@ Note that if you specify a platform via the marker-command line option like this
|
||||
$ pytest -m linux
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 4 items / 3 deselected
|
||||
collected 4 items / 3 deselected / 1 selected
|
||||
|
||||
test_plat.py . [100%]
|
||||
|
||||
@@ -624,8 +619,9 @@ We can now use the ``-m option`` to select one set:
|
||||
$ pytest -m interface --tb=short
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 4 items / 2 deselected
|
||||
collected 4 items / 2 deselected / 2 selected
|
||||
|
||||
test_module.py FF [100%]
|
||||
|
||||
@@ -647,8 +643,9 @@ or to select both "event" and "interface" tests:
|
||||
$ pytest -m "interface or event" --tb=short
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 4 items / 1 deselected
|
||||
collected 4 items / 1 deselected / 3 selected
|
||||
|
||||
test_module.py FFF [100%]
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
module containing a parametrized tests testing cross-python
|
||||
serialization via the pickle module.
|
||||
"""
|
||||
import distutils.spawn
|
||||
import subprocess
|
||||
import textwrap
|
||||
|
||||
import py
|
||||
|
||||
import pytest
|
||||
|
||||
pythonlist = ["python2.7", "python3.4", "python3.5"]
|
||||
@@ -24,7 +24,7 @@ def python2(request, python1):
|
||||
|
||||
class Python(object):
|
||||
def __init__(self, version, picklefile):
|
||||
self.pythonpath = py.path.local.sysfind(version)
|
||||
self.pythonpath = distutils.spawn.find_executable(version)
|
||||
if not self.pythonpath:
|
||||
pytest.skip("{!r} not found".format(version))
|
||||
self.picklefile = picklefile
|
||||
@@ -43,7 +43,7 @@ class Python(object):
|
||||
)
|
||||
)
|
||||
)
|
||||
py.process.cmdexec("{} {}".format(self.pythonpath, dumpfile))
|
||||
subprocess.check_call((self.pythonpath, str(dumpfile)))
|
||||
|
||||
def load_and_is_true(self, expression):
|
||||
loadfile = self.picklefile.dirpath("load.py")
|
||||
@@ -63,7 +63,7 @@ class Python(object):
|
||||
)
|
||||
)
|
||||
print(loadfile)
|
||||
py.process.cmdexec("{} {}".format(self.pythonpath, loadfile))
|
||||
subprocess.check_call((self.pythonpath, str(loadfile)))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("obj", [42, {}, {1: 3}])
|
||||
|
||||
@@ -30,6 +30,7 @@ now execute the test specification:
|
||||
nonpython $ pytest test_simple.yml
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
|
||||
collected 2 items
|
||||
|
||||
@@ -63,8 +64,8 @@ consulted when reporting in ``verbose`` mode:
|
||||
|
||||
nonpython $ pytest -v
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
|
||||
cachedir: .pytest_cache
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
|
||||
collecting ... collected 2 items
|
||||
|
||||
@@ -88,11 +89,12 @@ interesting to just look at the collection tree:
|
||||
nonpython $ pytest --collect-only
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
|
||||
collected 2 items
|
||||
<Package '$REGENDOC_TMPDIR/nonpython'>
|
||||
<YamlFile 'test_simple.yml'>
|
||||
<YamlItem 'hello'>
|
||||
<YamlItem 'ok'>
|
||||
<Package $REGENDOC_TMPDIR/nonpython>
|
||||
<YamlFile test_simple.yml>
|
||||
<YamlItem hello>
|
||||
<YamlItem ok>
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
|
||||
@@ -145,17 +145,18 @@ objects, they are still using the default pytest representation:
|
||||
$ pytest test_time.py --collect-only
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 8 items
|
||||
<Module 'test_time.py'>
|
||||
<Function 'test_timedistance_v0[a0-b0-expected0]'>
|
||||
<Function 'test_timedistance_v0[a1-b1-expected1]'>
|
||||
<Function 'test_timedistance_v1[forward]'>
|
||||
<Function 'test_timedistance_v1[backward]'>
|
||||
<Function 'test_timedistance_v2[20011212-20011211-expected0]'>
|
||||
<Function 'test_timedistance_v2[20011211-20011212-expected1]'>
|
||||
<Function 'test_timedistance_v3[forward]'>
|
||||
<Function 'test_timedistance_v3[backward]'>
|
||||
<Module test_time.py>
|
||||
<Function test_timedistance_v0[a0-b0-expected0]>
|
||||
<Function test_timedistance_v0[a1-b1-expected1]>
|
||||
<Function test_timedistance_v1[forward]>
|
||||
<Function test_timedistance_v1[backward]>
|
||||
<Function test_timedistance_v2[20011212-20011211-expected0]>
|
||||
<Function test_timedistance_v2[20011211-20011212-expected1]>
|
||||
<Function test_timedistance_v3[forward]>
|
||||
<Function test_timedistance_v3[backward]>
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
|
||||
@@ -203,6 +204,7 @@ this is a fully self-contained example which you can run with:
|
||||
$ pytest test_scenarios.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 4 items
|
||||
|
||||
@@ -217,14 +219,15 @@ If you just collect tests you'll also nicely see 'advanced' and 'basic' as varia
|
||||
$ pytest --collect-only test_scenarios.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 4 items
|
||||
<Module 'test_scenarios.py'>
|
||||
<Class 'TestSampleWithScenarios'>
|
||||
<Function 'test_demo1[basic]'>
|
||||
<Function 'test_demo2[basic]'>
|
||||
<Function 'test_demo1[advanced]'>
|
||||
<Function 'test_demo2[advanced]'>
|
||||
<Module test_scenarios.py>
|
||||
<Class TestSampleWithScenarios>
|
||||
<Function test_demo1[basic]>
|
||||
<Function test_demo2[basic]>
|
||||
<Function test_demo1[advanced]>
|
||||
<Function test_demo2[advanced]>
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
|
||||
@@ -283,11 +286,12 @@ Let's first see how it looks like at collection time:
|
||||
$ pytest test_backends.py --collect-only
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 2 items
|
||||
<Module 'test_backends.py'>
|
||||
<Function 'test_db_initialized[d1]'>
|
||||
<Function 'test_db_initialized[d2]'>
|
||||
<Module test_backends.py>
|
||||
<Function test_db_initialized[d1]>
|
||||
<Function test_db_initialized[d2]>
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
|
||||
@@ -348,10 +352,11 @@ The result of this test will be successful:
|
||||
$ pytest test_indirect_list.py --collect-only
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 item
|
||||
<Module 'test_indirect_list.py'>
|
||||
<Function 'test_indirect[a-b]'>
|
||||
<Module test_indirect_list.py>
|
||||
<Function test_indirect[a-b]>
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
|
||||
@@ -388,7 +393,8 @@ parametrizer`_ but in a lot less code::
|
||||
assert a == b
|
||||
|
||||
def test_zerodivision(self, a, b):
|
||||
pytest.raises(ZeroDivisionError, "a/b")
|
||||
with pytest.raises(ZeroDivisionError):
|
||||
a / b
|
||||
|
||||
Our test generator looks up a class-level definition which specifies which
|
||||
argument sets to use for each test function. Let's run it:
|
||||
@@ -405,6 +411,8 @@ argument sets to use for each test function. Let's run it:
|
||||
def test_equals(self, a, b):
|
||||
> assert a == b
|
||||
E assert 1 == 2
|
||||
E -1
|
||||
E +2
|
||||
|
||||
test_parametrize.py:18: AssertionError
|
||||
1 failed, 2 passed in 0.12 seconds
|
||||
@@ -428,10 +436,8 @@ Running it results in some skips if we don't have all the python interpreters in
|
||||
.. code-block:: pytest
|
||||
|
||||
. $ pytest -rs -q multipython.py
|
||||
...sss...sssssssss...sss... [100%]
|
||||
========================= short test summary info ==========================
|
||||
SKIP [15] $REGENDOC_TMPDIR/CWD/multipython.py:30: 'python3.4' not found
|
||||
12 passed, 15 skipped in 0.12 seconds
|
||||
........................... [100%]
|
||||
27 passed in 0.12 seconds
|
||||
|
||||
Indirect parametrization of optional implementations/imports
|
||||
--------------------------------------------------------------------
|
||||
@@ -481,12 +487,13 @@ If you run this with reporting for skips enabled:
|
||||
$ pytest -rs test_module.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 2 items
|
||||
|
||||
test_module.py .s [100%]
|
||||
========================= short test summary info ==========================
|
||||
SKIP [1] $REGENDOC_TMPDIR/conftest.py:11: could not import 'opt2'
|
||||
SKIPPED [1] $REGENDOC_TMPDIR/conftest.py:11: could not import 'opt2'
|
||||
|
||||
=================== 1 passed, 1 skipped in 0.12 seconds ====================
|
||||
|
||||
@@ -537,14 +544,14 @@ Then run ``pytest`` with verbose mode and with only the ``basic`` marker:
|
||||
|
||||
$ pytest -v -m basic
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
|
||||
cachedir: .pytest_cache
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 17 items / 14 deselected
|
||||
collecting ... collected 17 items / 14 deselected / 3 selected
|
||||
|
||||
test_pytest_param_example.py::test_eval[1+7-8] PASSED [ 33%]
|
||||
test_pytest_param_example.py::test_eval[basic_2+4] PASSED [ 66%]
|
||||
test_pytest_param_example.py::test_eval[basic_6*9] xfail [100%]
|
||||
test_pytest_param_example.py::test_eval[basic_6*9] XFAIL [100%]
|
||||
|
||||
============ 2 passed, 14 deselected, 1 xfailed in 0.12 seconds ============
|
||||
|
||||
@@ -556,3 +563,50 @@ As the result:
|
||||
- The test ``test_eval[1+7-8]`` passed, but the name is autogenerated and confusing.
|
||||
- The test ``test_eval[basic_2+4]`` passed.
|
||||
- The test ``test_eval[basic_6*9]`` was expected to fail and did fail.
|
||||
|
||||
.. _`parametrizing_conditional_raising`:
|
||||
|
||||
Parametrizing conditional raising
|
||||
--------------------------------------------------------------------
|
||||
|
||||
Use :func:`pytest.raises` with the
|
||||
:ref:`pytest.mark.parametrize ref` decorator to write parametrized tests
|
||||
in which some tests raise exceptions and others do not.
|
||||
|
||||
It is helpful to define a no-op context manager ``does_not_raise`` to serve
|
||||
as a complement to ``raises``. For example::
|
||||
|
||||
from contextlib import contextmanager
|
||||
import pytest
|
||||
|
||||
@contextmanager
|
||||
def does_not_raise():
|
||||
yield
|
||||
|
||||
|
||||
@pytest.mark.parametrize('example_input,expectation', [
|
||||
(3, does_not_raise()),
|
||||
(2, does_not_raise()),
|
||||
(1, does_not_raise()),
|
||||
(0, pytest.raises(ZeroDivisionError)),
|
||||
])
|
||||
def test_division(example_input, expectation):
|
||||
"""Test how much I know division."""
|
||||
with expectation:
|
||||
assert (6 / example_input) is not None
|
||||
|
||||
In the example above, the first three test cases should run unexceptionally,
|
||||
while the fourth should raise ``ZeroDivisionError``.
|
||||
|
||||
If you're only supporting Python 3.7+, you can simply use ``nullcontext``
|
||||
to define ``does_not_raise``::
|
||||
|
||||
from contextlib import nullcontext as does_not_raise
|
||||
|
||||
Or, if you're supporting Python 3.3+ you can use::
|
||||
|
||||
from contextlib import ExitStack as does_not_raise
|
||||
|
||||
Or, if desired, you can ``pip install contextlib2`` and use::
|
||||
|
||||
from contextlib2 import ExitStack as does_not_raise
|
||||
|
||||
@@ -41,6 +41,9 @@ you will see that ``pytest`` only collects test-modules, which do not match the
|
||||
|
||||
========================= 5 passed in 0.02 seconds =========================
|
||||
|
||||
The ``--ignore-glob`` option allows to ignore test file paths based on Unix shell-style wildcards.
|
||||
If you want to exclude test-modules that end with ``_01.py``, execute ``pytest`` with ``--ignore-glob='*_01.py'``.
|
||||
|
||||
Deselect tests during test collection
|
||||
-------------------------------------
|
||||
|
||||
@@ -132,12 +135,13 @@ The test collection would look like this:
|
||||
$ pytest --collect-only
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
|
||||
collected 2 items
|
||||
<Module 'check_myapp.py'>
|
||||
<Class 'CheckMyApp'>
|
||||
<Function 'simple_check'>
|
||||
<Function 'complex_check'>
|
||||
<Module check_myapp.py>
|
||||
<Class CheckMyApp>
|
||||
<Function simple_check>
|
||||
<Function complex_check>
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
|
||||
@@ -187,13 +191,14 @@ You can always peek at the collection tree without running tests like this:
|
||||
. $ pytest --collect-only pythoncollection.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
|
||||
collected 3 items
|
||||
<Module 'CWD/pythoncollection.py'>
|
||||
<Function 'test_function'>
|
||||
<Class 'TestClass'>
|
||||
<Function 'test_method'>
|
||||
<Function 'test_anothermethod'>
|
||||
<Module CWD/pythoncollection.py>
|
||||
<Function test_function>
|
||||
<Class TestClass>
|
||||
<Function test_method>
|
||||
<Function test_anothermethod>
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
|
||||
@@ -259,7 +264,22 @@ file will be left out:
|
||||
$ pytest --collect-only
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
|
||||
collected 0 items
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
|
||||
It's also possible to ignore files based on Unix shell-style wildcards by adding
|
||||
patterns to ``collect_ignore_glob``.
|
||||
|
||||
The following example ``conftest.py`` ignores the file ``setup.py`` and in
|
||||
addition all files that end with ``*_py2.py`` when executed with a Python 3
|
||||
interpreter::
|
||||
|
||||
# content of conftest.py
|
||||
import sys
|
||||
|
||||
collect_ignore = ["setup.py"]
|
||||
if sys.version_info[0] > 2:
|
||||
collect_ignore_glob = ["*_py2.py"]
|
||||
|
||||
@@ -14,10 +14,11 @@ get on the terminal - we are working on that):
|
||||
assertion $ pytest failure_demo.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR/assertion, inifile:
|
||||
collected 42 items
|
||||
collected 44 items
|
||||
|
||||
failure_demo.py FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF [100%]
|
||||
failure_demo.py FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF [100%]
|
||||
|
||||
================================= FAILURES =================================
|
||||
___________________________ test_generative[3-6] ___________________________
|
||||
@@ -289,6 +290,48 @@ get on the terminal - we are working on that):
|
||||
E ? ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
failure_demo.py:99: AssertionError
|
||||
______________ TestSpecialisedExplanations.test_eq_dataclass _______________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
|
||||
def test_eq_dataclass(self):
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class Foo(object):
|
||||
a: int
|
||||
b: str
|
||||
|
||||
left = Foo(1, "b")
|
||||
right = Foo(1, "c")
|
||||
> assert left == right
|
||||
E AssertionError: assert TestSpecialis...oo(a=1, b='b') == TestSpecialise...oo(a=1, b='c')
|
||||
E Omitting 1 identical items, use -vv to show
|
||||
E Differing attributes:
|
||||
E b: 'b' != 'c'
|
||||
|
||||
failure_demo.py:111: AssertionError
|
||||
________________ TestSpecialisedExplanations.test_eq_attrs _________________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
|
||||
def test_eq_attrs(self):
|
||||
import attr
|
||||
|
||||
@attr.s
|
||||
class Foo(object):
|
||||
a = attr.ib()
|
||||
b = attr.ib()
|
||||
|
||||
left = Foo(1, "b")
|
||||
right = Foo(1, "c")
|
||||
> assert left == right
|
||||
E AssertionError: assert Foo(a=1, b='b') == Foo(a=1, b='c')
|
||||
E Omitting 1 identical items, use -vv to show
|
||||
E Differing attributes:
|
||||
E b: 'b' != 'c'
|
||||
|
||||
failure_demo.py:123: AssertionError
|
||||
______________________________ test_attribute ______________________________
|
||||
|
||||
def test_attribute():
|
||||
@@ -300,7 +343,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:107: AssertionError
|
||||
failure_demo.py:131: AssertionError
|
||||
_________________________ test_attribute_instance __________________________
|
||||
|
||||
def test_attribute_instance():
|
||||
@@ -312,7 +355,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:114: AssertionError
|
||||
failure_demo.py:138: AssertionError
|
||||
__________________________ test_attribute_failure __________________________
|
||||
|
||||
def test_attribute_failure():
|
||||
@@ -325,7 +368,7 @@ get on the terminal - we are working on that):
|
||||
i = Foo()
|
||||
> assert i.b == 2
|
||||
|
||||
failure_demo.py:125:
|
||||
failure_demo.py:149:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
self = <failure_demo.test_attribute_failure.<locals>.Foo object at 0xdeadbeef>
|
||||
@@ -334,7 +377,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:120: Exception
|
||||
failure_demo.py:144: Exception
|
||||
_________________________ test_attribute_multiple __________________________
|
||||
|
||||
def test_attribute_multiple():
|
||||
@@ -351,31 +394,26 @@ 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:135: AssertionError
|
||||
failure_demo.py:159: AssertionError
|
||||
__________________________ TestRaises.test_raises __________________________
|
||||
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||
|
||||
def test_raises(self):
|
||||
s = "qwe" # NOQA
|
||||
> raises(TypeError, "int(s)")
|
||||
s = "qwe"
|
||||
> raises(TypeError, int, s)
|
||||
E ValueError: invalid literal for int() with base 10: 'qwe'
|
||||
|
||||
failure_demo.py:145:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
> int(s)
|
||||
E ValueError: invalid literal for int() with base 10: 'qwe'
|
||||
|
||||
<0-codegen $REGENDOC_TMPDIR/assertion/failure_demo.py:145>:1: ValueError
|
||||
failure_demo.py:169: ValueError
|
||||
______________________ TestRaises.test_raises_doesnt _______________________
|
||||
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||
|
||||
def test_raises_doesnt(self):
|
||||
> raises(IOError, "int('3')")
|
||||
> raises(IOError, int, "3")
|
||||
E Failed: DID NOT RAISE <class 'OSError'>
|
||||
|
||||
failure_demo.py:148: Failed
|
||||
failure_demo.py:172: Failed
|
||||
__________________________ TestRaises.test_raise ___________________________
|
||||
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||
@@ -384,7 +422,7 @@ get on the terminal - we are working on that):
|
||||
> raise ValueError("demo error")
|
||||
E ValueError: demo error
|
||||
|
||||
failure_demo.py:151: ValueError
|
||||
failure_demo.py:175: ValueError
|
||||
________________________ TestRaises.test_tupleerror ________________________
|
||||
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||
@@ -393,7 +431,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:154: ValueError
|
||||
failure_demo.py:178: ValueError
|
||||
______ TestRaises.test_reinterpret_fails_with_print_for_the_fun_of_it ______
|
||||
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||
@@ -404,7 +442,7 @@ get on the terminal - we are working on that):
|
||||
> a, b = items.pop()
|
||||
E TypeError: 'int' object is not iterable
|
||||
|
||||
failure_demo.py:159: TypeError
|
||||
failure_demo.py:183: TypeError
|
||||
--------------------------- Captured stdout call ---------------------------
|
||||
items is [1, 2, 3]
|
||||
________________________ TestRaises.test_some_error ________________________
|
||||
@@ -415,7 +453,7 @@ get on the terminal - we are working on that):
|
||||
> if namenotexi: # NOQA
|
||||
E NameError: name 'namenotexi' is not defined
|
||||
|
||||
failure_demo.py:162: NameError
|
||||
failure_demo.py:186: NameError
|
||||
____________________ test_dynamic_compile_shows_nicely _____________________
|
||||
|
||||
def test_dynamic_compile_shows_nicely():
|
||||
@@ -430,14 +468,14 @@ get on the terminal - we are working on that):
|
||||
sys.modules[name] = module
|
||||
> module.foo()
|
||||
|
||||
failure_demo.py:180:
|
||||
failure_demo.py:204:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
def foo():
|
||||
> assert 1 == 0
|
||||
E AssertionError
|
||||
|
||||
<2-codegen 'abc-123' $REGENDOC_TMPDIR/assertion/failure_demo.py:177>:2: AssertionError
|
||||
<0-codegen 'abc-123' $REGENDOC_TMPDIR/assertion/failure_demo.py:201>:2: AssertionError
|
||||
____________________ TestMoreErrors.test_complex_error _____________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -451,7 +489,7 @@ get on the terminal - we are working on that):
|
||||
|
||||
> somefunc(f(), g())
|
||||
|
||||
failure_demo.py:191:
|
||||
failure_demo.py:215:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
failure_demo.py:13: in somefunc
|
||||
otherfunc(x, y)
|
||||
@@ -473,7 +511,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:195: ValueError
|
||||
failure_demo.py:219: ValueError
|
||||
____________________ TestMoreErrors.test_z2_type_error _____________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -483,7 +521,7 @@ get on the terminal - we are working on that):
|
||||
> a, b = items
|
||||
E TypeError: 'int' object is not iterable
|
||||
|
||||
failure_demo.py:199: TypeError
|
||||
failure_demo.py:223: TypeError
|
||||
______________________ TestMoreErrors.test_startswith ______________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -496,7 +534,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:204: AssertionError
|
||||
failure_demo.py:228: AssertionError
|
||||
__________________ TestMoreErrors.test_startswith_nested ___________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -515,7 +553,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:213: AssertionError
|
||||
failure_demo.py:237: AssertionError
|
||||
_____________________ TestMoreErrors.test_global_func ______________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -526,7 +564,7 @@ get on the terminal - we are working on that):
|
||||
E + where False = isinstance(43, float)
|
||||
E + where 43 = globf(42)
|
||||
|
||||
failure_demo.py:216: AssertionError
|
||||
failure_demo.py:240: AssertionError
|
||||
_______________________ TestMoreErrors.test_instance _______________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -537,7 +575,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:220: AssertionError
|
||||
failure_demo.py:244: AssertionError
|
||||
_______________________ TestMoreErrors.test_compare ________________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -547,7 +585,7 @@ get on the terminal - we are working on that):
|
||||
E assert 11 < 5
|
||||
E + where 11 = globf(10)
|
||||
|
||||
failure_demo.py:223: AssertionError
|
||||
failure_demo.py:247: AssertionError
|
||||
_____________________ TestMoreErrors.test_try_finally ______________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -558,7 +596,7 @@ get on the terminal - we are working on that):
|
||||
> assert x == 0
|
||||
E assert 1 == 0
|
||||
|
||||
failure_demo.py:228: AssertionError
|
||||
failure_demo.py:252: AssertionError
|
||||
___________________ TestCustomAssertMsg.test_single_line ___________________
|
||||
|
||||
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
|
||||
@@ -573,7 +611,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:239: AssertionError
|
||||
failure_demo.py:263: AssertionError
|
||||
____________________ TestCustomAssertMsg.test_multiline ____________________
|
||||
|
||||
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
|
||||
@@ -592,7 +630,7 @@ get on the terminal - we are working on that):
|
||||
E assert 1 == 2
|
||||
E + where 1 = <class 'failure_demo.TestCustomAssertMsg.test_multiline.<locals>.A'>.a
|
||||
|
||||
failure_demo.py:246: AssertionError
|
||||
failure_demo.py:270: AssertionError
|
||||
___________________ TestCustomAssertMsg.test_custom_repr ___________________
|
||||
|
||||
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
|
||||
@@ -614,5 +652,5 @@ get on the terminal - we are working on that):
|
||||
E assert 1 == 2
|
||||
E + where 1 = This is JSON\n{\n 'foo': 'bar'\n}.a
|
||||
|
||||
failure_demo.py:259: AssertionError
|
||||
======================== 42 failed in 0.12 seconds =========================
|
||||
failure_demo.py:283: AssertionError
|
||||
======================== 44 failed in 0.12 seconds =========================
|
||||
|
||||
@@ -128,6 +128,7 @@ directory with the above conftest.py:
|
||||
$ pytest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 0 items
|
||||
|
||||
@@ -188,12 +189,13 @@ and when running it will see a skipped "slow" test:
|
||||
$ pytest -rs # "-rs" means report details on the little 's'
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 2 items
|
||||
|
||||
test_module.py .s [100%]
|
||||
========================= short test summary info ==========================
|
||||
SKIP [1] test_module.py:8: need --runslow option to run
|
||||
SKIPPED [1] test_module.py:8: need --runslow option to run
|
||||
|
||||
=================== 1 passed, 1 skipped in 0.12 seconds ====================
|
||||
|
||||
@@ -204,6 +206,7 @@ Or run it including the ``slow`` marked test:
|
||||
$ pytest --runslow
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 2 items
|
||||
|
||||
@@ -346,6 +349,7 @@ which will add the string to the test header accordingly:
|
||||
$ pytest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
project deps: mylib-1.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 0 items
|
||||
@@ -373,8 +377,8 @@ which will add info only when run with "--v":
|
||||
|
||||
$ pytest -v
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
|
||||
cachedir: .pytest_cache
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
info1: did you know that ...
|
||||
did you?
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
@@ -389,6 +393,7 @@ and nothing when run plainly:
|
||||
$ pytest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 0 items
|
||||
|
||||
@@ -428,6 +433,7 @@ Now we can profile which test functions execute the slowest:
|
||||
$ pytest --durations=3
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 3 items
|
||||
|
||||
@@ -502,6 +508,7 @@ If we run this:
|
||||
$ pytest -rx
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 4 items
|
||||
|
||||
@@ -585,6 +592,7 @@ We can run this:
|
||||
$ pytest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 7 items
|
||||
|
||||
@@ -598,7 +606,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, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory
|
||||
> available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, monkeypatch, pytestconfig, record_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory
|
||||
> use 'pytest --fixtures [testpath]' for help on them.
|
||||
|
||||
$REGENDOC_TMPDIR/b/test_error.py:1
|
||||
@@ -698,6 +706,7 @@ and run them:
|
||||
$ pytest test_module.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 2 items
|
||||
|
||||
@@ -799,6 +808,7 @@ and run it:
|
||||
$ pytest -s test_module.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 3 items
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ marked ``smtp_connection`` fixture function. Running the test looks like this:
|
||||
$ pytest test_smtpsimple.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 item
|
||||
|
||||
@@ -213,6 +214,7 @@ inspect what is going on and can now run the tests:
|
||||
$ pytest test_module.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 2 items
|
||||
|
||||
@@ -628,7 +630,7 @@ So let's just do another run:
|
||||
response, msg = smtp_connection.ehlo()
|
||||
assert response == 250
|
||||
> assert b"smtp.gmail.com" in msg
|
||||
E AssertionError: assert b'smtp.gmail.com' in b'mail.python.org\nPIPELINING\nSIZE 51200000\nETRN\nSTARTTLS\nAUTH DIGEST-MD5 NTLM CRAM-MD5\nENHANCEDSTATUSCODES\n8BITMIME\nDSN\nSMTPUTF8'
|
||||
E AssertionError: assert b'smtp.gmail.com' in b'mail.python.org\nPIPELINING\nSIZE 51200000\nETRN\nSTARTTLS\nAUTH DIGEST-MD5 NTLM CRAM-MD5\nENHANCEDSTATUSCODES\n8BITMIME\nDSN\nSMTPUTF8\nCHUNKING'
|
||||
|
||||
test_module.py:5: AssertionError
|
||||
-------------------------- Captured stdout setup ---------------------------
|
||||
@@ -701,21 +703,22 @@ Running the above tests results in the following test IDs being used:
|
||||
$ pytest --collect-only
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 10 items
|
||||
<Module 'test_anothersmtp.py'>
|
||||
<Function 'test_showhelo[smtp.gmail.com]'>
|
||||
<Function 'test_showhelo[mail.python.org]'>
|
||||
<Module 'test_ids.py'>
|
||||
<Function 'test_a[spam]'>
|
||||
<Function 'test_a[ham]'>
|
||||
<Function 'test_b[eggs]'>
|
||||
<Function 'test_b[1]'>
|
||||
<Module 'test_module.py'>
|
||||
<Function 'test_ehlo[smtp.gmail.com]'>
|
||||
<Function 'test_noop[smtp.gmail.com]'>
|
||||
<Function 'test_ehlo[mail.python.org]'>
|
||||
<Function 'test_noop[mail.python.org]'>
|
||||
<Module test_anothersmtp.py>
|
||||
<Function test_showhelo[smtp.gmail.com]>
|
||||
<Function test_showhelo[mail.python.org]>
|
||||
<Module test_ids.py>
|
||||
<Function test_a[spam]>
|
||||
<Function test_a[ham]>
|
||||
<Function test_b[eggs]>
|
||||
<Function test_b[1]>
|
||||
<Module test_module.py>
|
||||
<Function test_ehlo[smtp.gmail.com]>
|
||||
<Function test_noop[smtp.gmail.com]>
|
||||
<Function test_ehlo[mail.python.org]>
|
||||
<Function test_noop[mail.python.org]>
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
|
||||
@@ -744,8 +747,8 @@ 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-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
|
||||
cachedir: .pytest_cache
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 3 items
|
||||
|
||||
@@ -789,8 +792,8 @@ 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-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
|
||||
cachedir: .pytest_cache
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 2 items
|
||||
|
||||
@@ -804,7 +807,7 @@ different ``App`` instances and respective smtp servers. There is no
|
||||
need for the ``app`` fixture to be aware of the ``smtp_connection``
|
||||
parametrization because pytest will fully analyse the fixture dependency graph.
|
||||
|
||||
Note, that the ``app`` fixture has a scope of ``module`` and uses a
|
||||
Note that the ``app`` fixture has a scope of ``module`` and uses a
|
||||
module-scoped ``smtp_connection`` fixture. The example would still work if
|
||||
``smtp_connection`` was cached on a ``session`` scope: it is fine for fixtures to use
|
||||
"broader" scoped fixtures but not the other way round:
|
||||
@@ -860,8 +863,8 @@ 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-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
|
||||
cachedir: .pytest_cache
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 8 items
|
||||
|
||||
|
||||
@@ -7,9 +7,6 @@ Installation and Getting Started
|
||||
|
||||
**PyPI package name**: `pytest <https://pypi.org/project/pytest/>`_
|
||||
|
||||
**Dependencies**: `py <https://pypi.org/project/py/>`_,
|
||||
`colorama (Windows) <https://pypi.org/project/colorama/>`_,
|
||||
|
||||
**Documentation as PDF**: `download latest <https://media.readthedocs.org/pdf/pytest/latest/pytest.pdf>`_
|
||||
|
||||
``pytest`` is a framework that makes building simple and scalable tests easy. Tests are expressive and readable—no boilerplate code required. Get started in minutes with a small unit test or complex functional test for your application or library.
|
||||
@@ -50,6 +47,7 @@ That’s it. You can now execute the test function:
|
||||
$ pytest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 item
|
||||
|
||||
|
||||
@@ -72,8 +72,18 @@ to keep tests separate from actual application code (often a good idea)::
|
||||
test_view.py
|
||||
...
|
||||
|
||||
This way your tests can run easily against an installed version
|
||||
of ``mypkg``.
|
||||
This has the following benefits:
|
||||
|
||||
* Your tests can run against an installed version after executing ``pip install .``.
|
||||
* Your tests can run against the local copy with an editable install after executing ``pip install --editable .``.
|
||||
* If you don't have a ``setup.py`` file and are relying on the fact that Python by default puts the current
|
||||
directory in ``sys.path`` to import your package, you can execute ``python -m pytest`` to execute the tests against the
|
||||
local copy directly, without using ``pip``.
|
||||
|
||||
.. note::
|
||||
|
||||
See :ref:`pythonpath` for more information about the difference between calling ``pytest`` and
|
||||
``python -m pytest``.
|
||||
|
||||
Note that using this scheme your test files must have **unique names**, because
|
||||
``pytest`` will import them as *top-level* modules since there are no packages
|
||||
|
||||
@@ -29,6 +29,7 @@ To execute it:
|
||||
$ pytest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 item
|
||||
|
||||
|
||||
@@ -198,6 +198,9 @@ option names are:
|
||||
* ``log_file_format``
|
||||
* ``log_file_date_format``
|
||||
|
||||
You can call ``set_log_path()`` to customize the log_file path dynamically. This functionality
|
||||
is considered **experimental**.
|
||||
|
||||
.. _log_release_notes:
|
||||
|
||||
Release notes
|
||||
|
||||
@@ -57,6 +57,7 @@ them in turn:
|
||||
$ pytest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 3 items
|
||||
|
||||
@@ -108,6 +109,7 @@ Let's run this:
|
||||
$ pytest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 3 items
|
||||
|
||||
@@ -207,7 +209,7 @@ list:
|
||||
$ pytest -q -rs test_strings.py
|
||||
s [100%]
|
||||
========================= short test summary info ==========================
|
||||
SKIP [1] test_strings.py: got empty parameter set ['stringinput'], function test_valid_string at $REGENDOC_TMPDIR/test_strings.py:1
|
||||
SKIPPED [1] test_strings.py: got empty parameter set ['stringinput'], function test_valid_string at $REGENDOC_TMPDIR/test_strings.py:1
|
||||
1 skipped in 0.12 seconds
|
||||
|
||||
Note that when calling ``metafunc.parametrize`` multiple times with different parameter sets, all parameter names across
|
||||
|
||||
@@ -84,6 +84,11 @@ will be loaded as well.
|
||||
:ref:`full explanation <requiring plugins in non-root conftests>`
|
||||
in the Writing plugins section.
|
||||
|
||||
.. note::
|
||||
The name ``pytest_plugins`` is reserved and should not be used as a
|
||||
name for a custom plugin module.
|
||||
|
||||
|
||||
.. _`findpluginname`:
|
||||
|
||||
Finding out which plugins are active
|
||||
|
||||
22
doc/en/py27-py34-deprecation.rst
Normal file
22
doc/en/py27-py34-deprecation.rst
Normal file
@@ -0,0 +1,22 @@
|
||||
Python 2.7 and 3.4 support plan
|
||||
===============================
|
||||
|
||||
Python 2.7 EOL is fast approaching, with
|
||||
upstream support `ending in 2020 <https://legacy.python.org/dev/peps/pep-0373/#id4>`__.
|
||||
Python 3.4's last release is scheduled for
|
||||
`March 2019 <https://www.python.org/dev/peps/pep-0429/#release-schedule>`__. pytest is one of
|
||||
the participating projects of the https://python3statement.org.
|
||||
|
||||
We plan to drop support for Python 2.7 and 3.4 at the same time with the release of **pytest 5.0**,
|
||||
scheduled to be released by **mid-2019**. Thanks to the `python_requires <https://packaging.python.org/guides/distributing-packages-using-setuptools/#python-requires>`__ ``setuptools`` option,
|
||||
Python 2.7 and Python 3.4 users using a modern ``pip`` version
|
||||
will install the last compatible pytest ``4.X`` version automatically even if ``5.0`` or later
|
||||
are available on PyPI.
|
||||
|
||||
During the period **from mid-2019 and 2020**, the pytest core team plans to make
|
||||
bug-fix releases of the pytest ``4.X`` series by back-porting patches to the ``4.x-maintenance``
|
||||
branch.
|
||||
|
||||
**After 2020**, the core team will no longer actively back port-patches, but the ``4.x-maintenance``
|
||||
branch will continue to exist so the community itself can contribute patches. The
|
||||
core team will be happy to accept those patches and make new ``4.X`` releases **until mid-2020**.
|
||||
@@ -618,7 +618,6 @@ Session related reporting hooks:
|
||||
.. autofunction:: pytest_terminal_summary
|
||||
.. autofunction:: pytest_fixture_setup
|
||||
.. autofunction:: pytest_fixture_post_finalizer
|
||||
.. autofunction:: pytest_logwarning
|
||||
.. autofunction:: pytest_warning_captured
|
||||
|
||||
And here is the central hook for reporting about
|
||||
@@ -725,13 +724,6 @@ MarkGenerator
|
||||
:members:
|
||||
|
||||
|
||||
MarkInfo
|
||||
~~~~~~~~
|
||||
|
||||
.. autoclass:: _pytest.mark.MarkInfo
|
||||
:members:
|
||||
|
||||
|
||||
Mark
|
||||
~~~~
|
||||
|
||||
@@ -805,6 +797,33 @@ Special Variables
|
||||
pytest treats some global variables in a special manner when defined in a test module.
|
||||
|
||||
|
||||
collect_ignore
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
**Tutorial**: :ref:`customizing-test-collection`
|
||||
|
||||
Can be declared in *conftest.py files* to exclude test directories or modules.
|
||||
Needs to be ``list[str]``.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
collect_ignore = ["setup.py"]
|
||||
|
||||
|
||||
collect_ignore_glob
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
**Tutorial**: :ref:`customizing-test-collection`
|
||||
|
||||
Can be declared in *conftest.py files* to exclude test directories or modules
|
||||
with Unix shell-style wildcards. Needs to be ``list[str]`` where ``str`` can
|
||||
contain glob patterns.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
collect_ignore_glob = ["*_ignore.py"]
|
||||
|
||||
|
||||
pytest_plugins
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
@@ -897,6 +916,12 @@ Here is a list of builtin configuration options that may be written in a ``pytes
|
||||
file, usually located at the root of your repository. All options must be under a ``[pytest]`` section
|
||||
(``[tool:pytest]`` for ``setup.cfg`` files).
|
||||
|
||||
.. warning::
|
||||
Usage of ``setup.cfg`` is not recommended unless for very simple use cases. ``.cfg``
|
||||
files use a different parser than ``pytest.ini`` and ``tox.ini`` which might cause hard to track
|
||||
down problems.
|
||||
When possible, it is recommended to use the latter files to hold your pytest configuration.
|
||||
|
||||
Configuration file options may be overwritten in the command-line by using ``-o/--override``, which can also be
|
||||
passed multiple times. The expected format is ``name=value``. For example::
|
||||
|
||||
@@ -1017,6 +1042,20 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||
This tells pytest to ignore deprecation warnings and turn all other warnings
|
||||
into errors. For more information please refer to :ref:`warnings`.
|
||||
|
||||
.. confval:: junit_family
|
||||
|
||||
.. versionadded:: 4.2
|
||||
|
||||
Configures the format of the generated JUnit XML file. The possible options are:
|
||||
|
||||
* ``xunit1`` (or ``legacy``): produces old style output, compatible with the xunit 1.0 format. **This is the default**.
|
||||
* ``xunit2``: produces `xunit 2.0 style output <https://github.com/jenkinsci/xunit-plugin/blob/xunit-2.3.2/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd>`__,
|
||||
which should be more compatible with latest Jenkins versions.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[pytest]
|
||||
junit_family = xunit2
|
||||
|
||||
.. confval:: junit_suite_name
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pygments-pytest>=1.1.0
|
||||
sphinx>=1.8.2
|
||||
sphinxcontrib-trio
|
||||
sphinx-removed-in>=0.1.3
|
||||
|
||||
@@ -330,6 +330,7 @@ Running it with the report-on-xfail option gives this output:
|
||||
example $ pytest -rx xfail_demo.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR/example, inifile:
|
||||
collected 7 items
|
||||
|
||||
|
||||
@@ -25,6 +25,9 @@ Talks and blog postings
|
||||
|
||||
- pytest: recommendations, basic packages for testing in Python and Django, Andreu Vallbona, PyconES 2017 (`slides in english <http://talks.apsl.io/testing-pycones-2017/>`_, `video in spanish <https://www.youtube.com/watch?v=K20GeR-lXDk>`_)
|
||||
|
||||
- `pytest advanced, Andrew Svetlov (Russian, PyCon Russia, 2016)
|
||||
<https://www.youtube.com/watch?v=7KgihdKTWY4>`_.
|
||||
|
||||
- `Pythonic testing, Igor Starikov (Russian, PyNsk, November 2016)
|
||||
<https://www.youtube.com/watch?v=_92nfdd5nK8>`_.
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ Running this would result in a passed test except for the last
|
||||
$ pytest test_tmp_path.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 item
|
||||
|
||||
@@ -104,6 +105,7 @@ Running this would result in a passed test except for the last
|
||||
$ pytest test_tmpdir.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 item
|
||||
|
||||
|
||||
@@ -129,6 +129,7 @@ the ``self.db`` values in the traceback:
|
||||
$ pytest test_unittest_db.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 2 items
|
||||
|
||||
|
||||
178
doc/en/usage.rst
178
doc/en/usage.rst
@@ -147,20 +147,83 @@ Detailed summary report
|
||||
|
||||
.. versionadded:: 2.9
|
||||
|
||||
The ``-r`` flag can be used to display test results summary at the end of the test session,
|
||||
The ``-r`` flag can be used to display a "short test summary info" at the end of the test session,
|
||||
making it easy in large test suites to get a clear picture of all failures, skips, xfails, etc.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_example.py
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def error_fixture():
|
||||
assert 0
|
||||
|
||||
|
||||
def test_ok():
|
||||
print("ok")
|
||||
|
||||
|
||||
def test_fail():
|
||||
assert 0
|
||||
|
||||
|
||||
def test_error(error_fixture):
|
||||
pass
|
||||
|
||||
|
||||
def test_skip():
|
||||
pytest.skip("skipping this test")
|
||||
|
||||
|
||||
def test_xfail():
|
||||
pytest.xfail("xfailing this test")
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason="always xfail")
|
||||
def test_xpass():
|
||||
pass
|
||||
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -ra
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 0 items
|
||||
collected 6 items
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
test_example.py .FEsxX [100%]
|
||||
|
||||
================================== ERRORS ==================================
|
||||
_______________________ ERROR at setup of test_error _______________________
|
||||
|
||||
@pytest.fixture
|
||||
def error_fixture():
|
||||
> assert 0
|
||||
E assert 0
|
||||
|
||||
test_example.py:6: AssertionError
|
||||
================================= FAILURES =================================
|
||||
________________________________ test_fail _________________________________
|
||||
|
||||
def test_fail():
|
||||
> assert 0
|
||||
E assert 0
|
||||
|
||||
test_example.py:14: AssertionError
|
||||
========================= short test summary info ==========================
|
||||
SKIPPED [1] $REGENDOC_TMPDIR/test_example.py:23: skipping this test
|
||||
XFAIL test_example.py::test_xfail
|
||||
reason: xfailing this test
|
||||
XPASS test_example.py::test_xpass always xfail
|
||||
ERROR test_example.py::test_error
|
||||
FAILED test_example.py::test_fail
|
||||
1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12 seconds
|
||||
|
||||
The ``-r`` options accepts a number of characters after it, with ``a`` used above meaning "all except passes".
|
||||
|
||||
@@ -182,10 +245,72 @@ More than one character can be used, so for example to only see failed and skipp
|
||||
$ pytest -rfs
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 0 items
|
||||
collected 6 items
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
test_example.py .FEsxX [100%]
|
||||
|
||||
================================== ERRORS ==================================
|
||||
_______________________ ERROR at setup of test_error _______________________
|
||||
|
||||
@pytest.fixture
|
||||
def error_fixture():
|
||||
> assert 0
|
||||
E assert 0
|
||||
|
||||
test_example.py:6: AssertionError
|
||||
================================= FAILURES =================================
|
||||
________________________________ test_fail _________________________________
|
||||
|
||||
def test_fail():
|
||||
> assert 0
|
||||
E assert 0
|
||||
|
||||
test_example.py:14: AssertionError
|
||||
========================= short test summary info ==========================
|
||||
FAILED test_example.py::test_fail
|
||||
SKIPPED [1] $REGENDOC_TMPDIR/test_example.py:23: skipping this test
|
||||
1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12 seconds
|
||||
|
||||
Using ``p`` lists the passing tests, whilst ``P`` adds an extra section "PASSES" with those tests that passed but had
|
||||
captured output:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -rpP
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 6 items
|
||||
|
||||
test_example.py .FEsxX [100%]
|
||||
|
||||
================================== ERRORS ==================================
|
||||
_______________________ ERROR at setup of test_error _______________________
|
||||
|
||||
@pytest.fixture
|
||||
def error_fixture():
|
||||
> assert 0
|
||||
E assert 0
|
||||
|
||||
test_example.py:6: AssertionError
|
||||
================================= FAILURES =================================
|
||||
________________________________ test_fail _________________________________
|
||||
|
||||
def test_fail():
|
||||
> assert 0
|
||||
E assert 0
|
||||
|
||||
test_example.py:14: AssertionError
|
||||
========================= short test summary info ==========================
|
||||
PASSED test_example.py::test_ok
|
||||
================================== PASSES ==================================
|
||||
_________________________________ test_ok __________________________________
|
||||
--------------------------- Captured stdout call ---------------------------
|
||||
ok
|
||||
1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12 seconds
|
||||
|
||||
.. _pdb-option:
|
||||
|
||||
@@ -294,6 +419,20 @@ To set the name of the root test suite xml item, you can configure the ``junit_s
|
||||
[pytest]
|
||||
junit_suite_name = my_suite
|
||||
|
||||
.. versionadded:: 4.0
|
||||
|
||||
JUnit XML specification seems to indicate that ``"time"`` attribute
|
||||
should report total test execution times, including setup and teardown
|
||||
(`1 <http://windyroad.com.au/dl/Open%20Source/JUnit.xsd>`_, `2
|
||||
<https://www.ibm.com/support/knowledgecenter/en/SSQ2R2_14.1.0/com.ibm.rsar.analysis.codereview.cobol.doc/topics/cac_useresults_junit.html>`_).
|
||||
It is the default pytest behavior. To report just call durations
|
||||
instead, configure the ``junit_duration_report`` option like this:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[pytest]
|
||||
junit_duration_report = call
|
||||
|
||||
.. _record_property example:
|
||||
|
||||
record_property
|
||||
@@ -483,14 +622,10 @@ Creating resultlog format files
|
||||
|
||||
.. deprecated:: 3.0
|
||||
|
||||
This option is rarely used and is scheduled for removal in 4.0.
|
||||
This option is rarely used and is scheduled for removal in 5.0.
|
||||
|
||||
An alternative for users which still need similar functionality is to use the
|
||||
`pytest-tap <https://pypi.org/project/pytest-tap/>`_ plugin which provides
|
||||
a stream of test data.
|
||||
|
||||
If you have any concerns, please don't hesitate to
|
||||
`open an issue <https://github.com/pytest-dev/pytest/issues>`_.
|
||||
See `the deprecation docs <https://docs.pytest.org/en/latest/deprecations.html#result-log-result-log>`__
|
||||
for more information.
|
||||
|
||||
To create plain-text machine-readable result files you can issue::
|
||||
|
||||
@@ -561,8 +696,25 @@ Running it will show that ``MyPlugin`` was added and its
|
||||
hook was invoked::
|
||||
|
||||
$ python myinvoke.py
|
||||
. [100%]*** test run reporting finishing
|
||||
.FEsxX. [100%]*** test run reporting finishing
|
||||
|
||||
================================== ERRORS ==================================
|
||||
_______________________ ERROR at setup of test_error _______________________
|
||||
|
||||
@pytest.fixture
|
||||
def error_fixture():
|
||||
> assert 0
|
||||
E assert 0
|
||||
|
||||
test_example.py:6: AssertionError
|
||||
================================= FAILURES =================================
|
||||
________________________________ test_fail _________________________________
|
||||
|
||||
def test_fail():
|
||||
> assert 0
|
||||
E assert 0
|
||||
|
||||
test_example.py:14: AssertionError
|
||||
|
||||
.. note::
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ Running pytest now produces this output:
|
||||
$ pytest test_show_warnings.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 item
|
||||
|
||||
@@ -155,7 +156,7 @@ DeprecationWarning and PendingDeprecationWarning
|
||||
.. 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>`_.
|
||||
user code and third-party libraries, as recommended by `PEP-0565 <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
|
||||
@@ -232,7 +233,7 @@ You can also use it as a contextmanager::
|
||||
.. _warns:
|
||||
|
||||
Asserting warnings with the warns function
|
||||
-----------------------------------------------
|
||||
------------------------------------------
|
||||
|
||||
.. versionadded:: 2.8
|
||||
|
||||
@@ -290,7 +291,7 @@ Alternatively, you can examine raised warnings in detail using the
|
||||
.. _recwarn:
|
||||
|
||||
Recording warnings
|
||||
------------------------
|
||||
------------------
|
||||
|
||||
You can record raised warnings either using ``pytest.warns`` or with
|
||||
the ``recwarn`` fixture.
|
||||
@@ -328,6 +329,26 @@ warnings, or index into it to get a particular recorded warning.
|
||||
|
||||
Full API: :class:`WarningsRecorder`.
|
||||
|
||||
.. _custom_failure_messages:
|
||||
|
||||
Custom failure messages
|
||||
-----------------------
|
||||
|
||||
Recording warnings provides an opportunity to produce custom test
|
||||
failure messages for when no warnings are issued or other conditions
|
||||
are met.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test():
|
||||
with pytest.warns(Warning) as record:
|
||||
f()
|
||||
if not record:
|
||||
pytest.fail("Expected a warning!")
|
||||
|
||||
If no warnings are issued when calling ``f``, then ``not record`` will
|
||||
evaluate to ``True``. You can then call ``pytest.fail`` with a
|
||||
custom error message.
|
||||
|
||||
.. _internal-warnings:
|
||||
|
||||
|
||||
@@ -413,6 +413,7 @@ additionally it is possible to copy examples for an example folder before runnin
|
||||
$ pytest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
|
||||
collected 2 items
|
||||
|
||||
|
||||
@@ -93,7 +93,15 @@ Remarks:
|
||||
|
||||
* It is possible for setup/teardown pairs to be invoked multiple times
|
||||
per testing process.
|
||||
|
||||
* teardown functions are not called if the corresponding setup function existed
|
||||
and failed/was skipped.
|
||||
|
||||
* Prior to pytest-4.2, xunit-style functions did not obey the scope rules of fixtures, so
|
||||
it was possible, for example, for a ``setup_method`` to be called before a
|
||||
session-scoped autouse fixture.
|
||||
|
||||
Now the xunit-style functions are integrated with the fixture mechanism and obey the proper
|
||||
scope rules of fixtures involved in the call.
|
||||
|
||||
.. _`unittest.py module`: http://docs.python.org/library/unittest.html
|
||||
|
||||
@@ -5,6 +5,7 @@ requires = [
|
||||
"setuptools-scm",
|
||||
"wheel",
|
||||
]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.towncrier]
|
||||
package = "pytest"
|
||||
|
||||
@@ -36,10 +36,11 @@ platforms = unix, linux, osx, cygwin, win32
|
||||
zip_safe = no
|
||||
packages =
|
||||
_pytest
|
||||
_pytest.assertion
|
||||
_pytest._code
|
||||
_pytest.mark
|
||||
_pytest._io
|
||||
_pytest.assertion
|
||||
_pytest.config
|
||||
_pytest.mark
|
||||
|
||||
py_modules = pytest
|
||||
python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
|
||||
|
||||
3
setup.py
3
setup.py
@@ -10,7 +10,8 @@ INSTALL_REQUIRES = [
|
||||
"six>=1.10.0",
|
||||
"setuptools",
|
||||
"attrs>=17.4.0",
|
||||
"more-itertools>=4.0.0",
|
||||
'more-itertools>=4.0.0,<6.0.0;python_version<="2.7"',
|
||||
'more-itertools>=4.0.0;python_version>"2.7"',
|
||||
"atomicwrites>=1.0",
|
||||
'funcsigs;python_version<"3.0"',
|
||||
'pathlib2>=2.2.0;python_version<"3.6"',
|
||||
|
||||
@@ -18,13 +18,12 @@ import six
|
||||
from six import text_type
|
||||
|
||||
import _pytest
|
||||
from _pytest._io.saferepr import saferepr
|
||||
from _pytest.compat import _PY2
|
||||
from _pytest.compat import _PY3
|
||||
from _pytest.compat import PY35
|
||||
from _pytest.compat import safe_str
|
||||
|
||||
builtin_repr = repr
|
||||
|
||||
if _PY3:
|
||||
from traceback import format_exception_only
|
||||
else:
|
||||
@@ -144,7 +143,7 @@ class Frame(object):
|
||||
def repr(self, object):
|
||||
""" return a 'safe' (non-recursive, one-line) string repr for 'object'
|
||||
"""
|
||||
return py.io.saferepr(object)
|
||||
return saferepr(object)
|
||||
|
||||
def is_true(self, object):
|
||||
return object
|
||||
@@ -391,44 +390,85 @@ co_equal = compile(
|
||||
)
|
||||
|
||||
|
||||
@attr.s(repr=False)
|
||||
class ExceptionInfo(object):
|
||||
""" wraps sys.exc_info() objects and offers
|
||||
help for navigating the traceback.
|
||||
"""
|
||||
|
||||
_striptext = ""
|
||||
_assert_start_repr = (
|
||||
"AssertionError(u'assert " if _PY2 else "AssertionError('assert "
|
||||
)
|
||||
|
||||
def __init__(self, tup=None, exprinfo=None):
|
||||
import _pytest._code
|
||||
_excinfo = attr.ib()
|
||||
_striptext = attr.ib(default="")
|
||||
_traceback = attr.ib(default=None)
|
||||
|
||||
if tup is None:
|
||||
tup = sys.exc_info()
|
||||
if exprinfo is None and isinstance(tup[1], AssertionError):
|
||||
exprinfo = getattr(tup[1], "msg", None)
|
||||
if exprinfo is None:
|
||||
exprinfo = py.io.saferepr(tup[1])
|
||||
if exprinfo and exprinfo.startswith(self._assert_start_repr):
|
||||
self._striptext = "AssertionError: "
|
||||
self._excinfo = tup
|
||||
#: the exception class
|
||||
self.type = tup[0]
|
||||
#: the exception instance
|
||||
self.value = tup[1]
|
||||
#: the exception raw traceback
|
||||
self.tb = tup[2]
|
||||
#: the exception type name
|
||||
self.typename = self.type.__name__
|
||||
#: the exception traceback (_pytest._code.Traceback instance)
|
||||
self.traceback = _pytest._code.Traceback(self.tb, excinfo=ref(self))
|
||||
@classmethod
|
||||
def from_current(cls, exprinfo=None):
|
||||
"""returns an ExceptionInfo matching the current traceback
|
||||
|
||||
.. warning::
|
||||
|
||||
Experimental API
|
||||
|
||||
|
||||
:param exprinfo: a text string helping to determine if we should
|
||||
strip ``AssertionError`` from the output, defaults
|
||||
to the exception message/``__str__()``
|
||||
"""
|
||||
tup = sys.exc_info()
|
||||
_striptext = ""
|
||||
if exprinfo is None and isinstance(tup[1], AssertionError):
|
||||
exprinfo = getattr(tup[1], "msg", None)
|
||||
if exprinfo is None:
|
||||
exprinfo = saferepr(tup[1])
|
||||
if exprinfo and exprinfo.startswith(cls._assert_start_repr):
|
||||
_striptext = "AssertionError: "
|
||||
|
||||
return cls(tup, _striptext)
|
||||
|
||||
@classmethod
|
||||
def for_later(cls):
|
||||
"""return an unfilled ExceptionInfo
|
||||
"""
|
||||
return cls(None)
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
"""the exception class"""
|
||||
return self._excinfo[0]
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
"""the exception value"""
|
||||
return self._excinfo[1]
|
||||
|
||||
@property
|
||||
def tb(self):
|
||||
"""the exception raw traceback"""
|
||||
return self._excinfo[2]
|
||||
|
||||
@property
|
||||
def typename(self):
|
||||
"""the type name of the exception"""
|
||||
return self.type.__name__
|
||||
|
||||
@property
|
||||
def traceback(self):
|
||||
"""the traceback"""
|
||||
if self._traceback is None:
|
||||
self._traceback = Traceback(self.tb, excinfo=ref(self))
|
||||
return self._traceback
|
||||
|
||||
@traceback.setter
|
||||
def traceback(self, value):
|
||||
self._traceback = value
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
return "<ExceptionInfo %s tblen=%d>" % (self.typename, len(self.traceback))
|
||||
except AttributeError:
|
||||
return "<ExceptionInfo uninitialized>"
|
||||
if self._excinfo is None:
|
||||
return "<ExceptionInfo for raises contextmanager>"
|
||||
return "<ExceptionInfo %s tblen=%d>" % (self.typename, len(self.traceback))
|
||||
|
||||
def exconly(self, tryshort=False):
|
||||
""" return the exception as a string
|
||||
@@ -516,13 +556,11 @@ class ExceptionInfo(object):
|
||||
return fmt.repr_excinfo(self)
|
||||
|
||||
def __str__(self):
|
||||
try:
|
||||
entry = self.traceback[-1]
|
||||
except AttributeError:
|
||||
if self._excinfo is None:
|
||||
return repr(self)
|
||||
else:
|
||||
loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
|
||||
return str(loc)
|
||||
entry = self.traceback[-1]
|
||||
loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
|
||||
return str(loc)
|
||||
|
||||
def __unicode__(self):
|
||||
entry = self.traceback[-1]
|
||||
@@ -581,7 +619,7 @@ class FormattedExcinfo(object):
|
||||
return source
|
||||
|
||||
def _saferepr(self, obj):
|
||||
return py.io.saferepr(obj)
|
||||
return saferepr(obj)
|
||||
|
||||
def repr_args(self, entry):
|
||||
if self.funcargs:
|
||||
@@ -908,8 +946,6 @@ class ReprEntryNative(TerminalRepr):
|
||||
|
||||
|
||||
class ReprEntry(TerminalRepr):
|
||||
localssep = "_ "
|
||||
|
||||
def __init__(self, lines, reprfuncargs, reprlocals, filelocrepr, style):
|
||||
self.lines = lines
|
||||
self.reprfuncargs = reprfuncargs
|
||||
@@ -931,7 +967,6 @@ class ReprEntry(TerminalRepr):
|
||||
red = line.startswith("E ")
|
||||
tw.line(line, bold=True, red=red)
|
||||
if self.reprlocals:
|
||||
# tw.sep(self.localssep, "Locals")
|
||||
tw.line("")
|
||||
self.reprlocals.toterminal(tw)
|
||||
if self.reprfileloc:
|
||||
|
||||
@@ -237,9 +237,7 @@ def getfslineno(obj):
|
||||
def findsource(obj):
|
||||
try:
|
||||
sourcelines, lineno = inspect.findsource(obj)
|
||||
except py.builtin._sysex:
|
||||
raise
|
||||
except: # noqa
|
||||
except Exception:
|
||||
return None, -1
|
||||
source = Source()
|
||||
source.lines = [line.rstrip() for line in sourcelines]
|
||||
|
||||
0
src/_pytest/_io/__init__.py
Normal file
0
src/_pytest/_io/__init__.py
Normal file
72
src/_pytest/_io/saferepr.py
Normal file
72
src/_pytest/_io/saferepr.py
Normal file
@@ -0,0 +1,72 @@
|
||||
import sys
|
||||
|
||||
from six.moves import reprlib
|
||||
|
||||
|
||||
class SafeRepr(reprlib.Repr):
|
||||
"""subclass of repr.Repr that limits the resulting size of repr()
|
||||
and includes information on exceptions raised during the call.
|
||||
"""
|
||||
|
||||
def repr(self, x):
|
||||
return self._callhelper(reprlib.Repr.repr, self, x)
|
||||
|
||||
def repr_unicode(self, x, level):
|
||||
# Strictly speaking wrong on narrow builds
|
||||
def repr(u):
|
||||
if "'" not in u:
|
||||
return u"'%s'" % u
|
||||
elif '"' not in u:
|
||||
return u'"%s"' % u
|
||||
else:
|
||||
return u"'%s'" % u.replace("'", r"\'")
|
||||
|
||||
s = repr(x[: self.maxstring])
|
||||
if len(s) > self.maxstring:
|
||||
i = max(0, (self.maxstring - 3) // 2)
|
||||
j = max(0, self.maxstring - 3 - i)
|
||||
s = repr(x[:i] + x[len(x) - j :])
|
||||
s = s[:i] + "..." + s[len(s) - j :]
|
||||
return s
|
||||
|
||||
def repr_instance(self, x, level):
|
||||
return self._callhelper(repr, x)
|
||||
|
||||
def _callhelper(self, call, x, *args):
|
||||
try:
|
||||
# Try the vanilla repr and make sure that the result is a string
|
||||
s = call(x, *args)
|
||||
except Exception:
|
||||
cls, e, tb = sys.exc_info()
|
||||
exc_name = getattr(cls, "__name__", "unknown")
|
||||
try:
|
||||
exc_info = str(e)
|
||||
except Exception:
|
||||
exc_info = "unknown"
|
||||
return '<[%s("%s") raised in repr()] %s object at 0x%x>' % (
|
||||
exc_name,
|
||||
exc_info,
|
||||
x.__class__.__name__,
|
||||
id(x),
|
||||
)
|
||||
else:
|
||||
if len(s) > self.maxsize:
|
||||
i = max(0, (self.maxsize - 3) // 2)
|
||||
j = max(0, self.maxsize - 3 - i)
|
||||
s = s[:i] + "..." + s[len(s) - j :]
|
||||
return s
|
||||
|
||||
|
||||
def saferepr(obj, maxsize=240):
|
||||
"""return a size-limited safe repr-string for the given object.
|
||||
Failing __repr__ functions of user instances will be represented
|
||||
with a short exception info and 'saferepr' generally takes
|
||||
care to never raise exceptions itself. This function is a wrapper
|
||||
around the Repr/reprlib functionality of the standard 2.6 lib.
|
||||
"""
|
||||
# review exception handling
|
||||
srepr = SafeRepr()
|
||||
srepr.maxstring = maxsize
|
||||
srepr.maxsize = maxsize
|
||||
srepr.maxother = 160
|
||||
return srepr.repr(obj)
|
||||
@@ -19,6 +19,7 @@ import atomicwrites
|
||||
import py
|
||||
import six
|
||||
|
||||
from _pytest._io.saferepr import saferepr
|
||||
from _pytest.assertion import util
|
||||
from _pytest.compat import spec_from_file_location
|
||||
from _pytest.pathlib import fnmatch_ex
|
||||
@@ -265,11 +266,11 @@ class AssertionRewritingHook(object):
|
||||
|
||||
def _warn_already_imported(self, name):
|
||||
from _pytest.warning_types import PytestWarning
|
||||
from _pytest.warnings import _issue_config_warning
|
||||
from _pytest.warnings import _issue_warning_captured
|
||||
|
||||
_issue_config_warning(
|
||||
_issue_warning_captured(
|
||||
PytestWarning("Module already imported so cannot be rewritten: %s" % name),
|
||||
self.config,
|
||||
self.config.hook,
|
||||
stacklevel=5,
|
||||
)
|
||||
|
||||
@@ -471,7 +472,7 @@ def _saferepr(obj):
|
||||
JSON reprs.
|
||||
|
||||
"""
|
||||
r = py.io.saferepr(obj)
|
||||
r = saferepr(obj)
|
||||
# only occurs in python2.x, repr must return text in python3+
|
||||
if isinstance(r, bytes):
|
||||
# Represent unprintable bytes as `\x##`
|
||||
@@ -490,7 +491,7 @@ def _format_assertmsg(obj):
|
||||
|
||||
For strings this simply replaces newlines with '\n~' so that
|
||||
util.format_explanation() will preserve them instead of escaping
|
||||
newlines. For other objects py.io.saferepr() is used first.
|
||||
newlines. For other objects saferepr() is used first.
|
||||
|
||||
"""
|
||||
# reprlib appears to have a bug which means that if a string
|
||||
@@ -499,7 +500,7 @@ def _format_assertmsg(obj):
|
||||
# However in either case we want to preserve the newline.
|
||||
replaces = [(u"\n", u"\n~"), (u"%", u"%%")]
|
||||
if not isinstance(obj, six.string_types):
|
||||
obj = py.io.saferepr(obj)
|
||||
obj = saferepr(obj)
|
||||
replaces.append((u"\\n", u"\n~"))
|
||||
|
||||
if isinstance(obj, bytes):
|
||||
@@ -512,7 +513,13 @@ def _format_assertmsg(obj):
|
||||
|
||||
|
||||
def _should_repr_global_name(obj):
|
||||
return not hasattr(obj, "__name__") and not callable(obj)
|
||||
if callable(obj):
|
||||
return False
|
||||
|
||||
try:
|
||||
return not hasattr(obj, "__name__")
|
||||
except Exception:
|
||||
return True
|
||||
|
||||
|
||||
def _format_boolop(explanations, is_or):
|
||||
@@ -659,7 +666,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||
# Insert some special imports at the top of the module but after any
|
||||
# docstrings and __future__ imports.
|
||||
aliases = [
|
||||
ast.alias(py.builtin.builtins.__name__, "@py_builtins"),
|
||||
ast.alias(six.moves.builtins.__name__, "@py_builtins"),
|
||||
ast.alias("_pytest.assertion.rewrite", "@pytest_ar"),
|
||||
]
|
||||
doc = getattr(mod, "docstring", None)
|
||||
@@ -734,7 +741,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||
return ast.Name(name, ast.Load())
|
||||
|
||||
def display(self, expr):
|
||||
"""Call py.io.saferepr on the expression."""
|
||||
"""Call saferepr on the expression."""
|
||||
return self.helper("saferepr", expr)
|
||||
|
||||
def helper(self, name, *args):
|
||||
@@ -828,6 +835,13 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||
self.push_format_context()
|
||||
# Rewrite assert into a bunch of statements.
|
||||
top_condition, explanation = self.visit(assert_.test)
|
||||
# If in a test module, check if directly asserting None, in order to warn [Issue #3191]
|
||||
if self.module_path is not None:
|
||||
self.statements.append(
|
||||
self.warn_about_none_ast(
|
||||
top_condition, module_path=self.module_path, lineno=assert_.lineno
|
||||
)
|
||||
)
|
||||
# Create failure message.
|
||||
body = self.on_failure
|
||||
negation = ast.UnaryOp(ast.Not(), top_condition)
|
||||
@@ -858,6 +872,33 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||
set_location(stmt, assert_.lineno, assert_.col_offset)
|
||||
return self.statements
|
||||
|
||||
def warn_about_none_ast(self, node, module_path, lineno):
|
||||
"""
|
||||
Returns an AST issuing a warning if the value of node is `None`.
|
||||
This is used to warn the user when asserting a function that asserts
|
||||
internally already.
|
||||
See issue #3191 for more details.
|
||||
"""
|
||||
|
||||
# Using parse because it is different between py2 and py3.
|
||||
AST_NONE = ast.parse("None").body[0].value
|
||||
val_is_none = ast.Compare(node, [ast.Is()], [AST_NONE])
|
||||
send_warning = ast.parse(
|
||||
"""
|
||||
from _pytest.warning_types import PytestWarning
|
||||
from warnings import warn_explicit
|
||||
warn_explicit(
|
||||
PytestWarning('asserting the value None, please use "assert is None"'),
|
||||
category=None,
|
||||
filename={filename!r},
|
||||
lineno={lineno},
|
||||
)
|
||||
""".format(
|
||||
filename=module_path.strpath, lineno=lineno
|
||||
)
|
||||
).body
|
||||
return ast.If(val_is_none, send_warning, [])
|
||||
|
||||
def visit_Name(self, name):
|
||||
# Display the repr of the name if it's a local variable or
|
||||
# _should_repr_global_name() thinks it's acceptable.
|
||||
|
||||
@@ -5,11 +5,11 @@ from __future__ import print_function
|
||||
|
||||
import pprint
|
||||
|
||||
import py
|
||||
import six
|
||||
|
||||
import _pytest._code
|
||||
from ..compat import Sequence
|
||||
from _pytest._io.saferepr import saferepr
|
||||
|
||||
# The _reprcompare attribute on the util module is used by the new assertion
|
||||
# interpretation code and assertion rewriter to detect this plugin was
|
||||
@@ -105,8 +105,8 @@ except NameError:
|
||||
def assertrepr_compare(config, op, left, right):
|
||||
"""Return specialised explanations for some operators/operands"""
|
||||
width = 80 - 15 - len(op) - 2 # 15 chars indentation, 1 space around op
|
||||
left_repr = py.io.saferepr(left, maxsize=int(width // 2))
|
||||
right_repr = py.io.saferepr(right, maxsize=width - len(left_repr))
|
||||
left_repr = saferepr(left, maxsize=int(width // 2))
|
||||
right_repr = saferepr(right, maxsize=width - len(left_repr))
|
||||
|
||||
summary = u"%s %s %s" % (ecu(left_repr), op, ecu(right_repr))
|
||||
|
||||
@@ -122,6 +122,12 @@ def assertrepr_compare(config, op, left, right):
|
||||
def isset(x):
|
||||
return isinstance(x, (set, frozenset))
|
||||
|
||||
def isdatacls(obj):
|
||||
return getattr(obj, "__dataclass_fields__", None) is not None
|
||||
|
||||
def isattrs(obj):
|
||||
return getattr(obj, "__attrs_attrs__", None) is not None
|
||||
|
||||
def isiterable(obj):
|
||||
try:
|
||||
iter(obj)
|
||||
@@ -142,6 +148,11 @@ def assertrepr_compare(config, op, left, right):
|
||||
explanation = _compare_eq_set(left, right, verbose)
|
||||
elif isdict(left) and isdict(right):
|
||||
explanation = _compare_eq_dict(left, right, verbose)
|
||||
elif type(left) == type(right) and (isdatacls(left) or isattrs(left)):
|
||||
type_fn = (isdatacls, isattrs)
|
||||
explanation = _compare_eq_cls(left, right, verbose, type_fn)
|
||||
elif verbose:
|
||||
explanation = _compare_eq_verbose(left, right)
|
||||
if isiterable(left) and isiterable(right):
|
||||
expl = _compare_eq_iterable(left, right, verbose)
|
||||
if explanation is not None:
|
||||
@@ -155,7 +166,7 @@ def assertrepr_compare(config, op, left, right):
|
||||
explanation = [
|
||||
u"(pytest_assertion plugin: representation of details failed. "
|
||||
u"Probably an object has a faulty __repr__.)",
|
||||
six.text_type(_pytest._code.ExceptionInfo()),
|
||||
six.text_type(_pytest._code.ExceptionInfo.from_current()),
|
||||
]
|
||||
|
||||
if not explanation:
|
||||
@@ -227,6 +238,18 @@ def _diff_text(left, right, verbose=False):
|
||||
return explanation
|
||||
|
||||
|
||||
def _compare_eq_verbose(left, right):
|
||||
keepends = True
|
||||
left_lines = repr(left).splitlines(keepends)
|
||||
right_lines = repr(right).splitlines(keepends)
|
||||
|
||||
explanation = []
|
||||
explanation += [u"-" + line for line in left_lines]
|
||||
explanation += [u"+" + line for line in right_lines]
|
||||
|
||||
return explanation
|
||||
|
||||
|
||||
def _compare_eq_iterable(left, right, verbose=False):
|
||||
if not verbose:
|
||||
return [u"Use -v to get the full diff"]
|
||||
@@ -259,12 +282,12 @@ def _compare_eq_sequence(left, right, verbose=False):
|
||||
if len(left) > len(right):
|
||||
explanation += [
|
||||
u"Left contains more items, first extra item: %s"
|
||||
% py.io.saferepr(left[len(right)])
|
||||
% saferepr(left[len(right)])
|
||||
]
|
||||
elif len(left) < len(right):
|
||||
explanation += [
|
||||
u"Right contains more items, first extra item: %s"
|
||||
% py.io.saferepr(right[len(left)])
|
||||
% saferepr(right[len(left)])
|
||||
]
|
||||
return explanation
|
||||
|
||||
@@ -276,11 +299,11 @@ def _compare_eq_set(left, right, verbose=False):
|
||||
if diff_left:
|
||||
explanation.append(u"Extra items in the left set:")
|
||||
for item in diff_left:
|
||||
explanation.append(py.io.saferepr(item))
|
||||
explanation.append(saferepr(item))
|
||||
if diff_right:
|
||||
explanation.append(u"Extra items in the right set:")
|
||||
for item in diff_right:
|
||||
explanation.append(py.io.saferepr(item))
|
||||
explanation.append(saferepr(item))
|
||||
return explanation
|
||||
|
||||
|
||||
@@ -297,9 +320,7 @@ def _compare_eq_dict(left, right, verbose=False):
|
||||
if diff:
|
||||
explanation += [u"Differing items:"]
|
||||
for k in diff:
|
||||
explanation += [
|
||||
py.io.saferepr({k: left[k]}) + " != " + py.io.saferepr({k: right[k]})
|
||||
]
|
||||
explanation += [saferepr({k: left[k]}) + " != " + saferepr({k: right[k]})]
|
||||
extra_left = set(left) - set(right)
|
||||
if extra_left:
|
||||
explanation.append(u"Left contains more items:")
|
||||
@@ -315,13 +336,45 @@ def _compare_eq_dict(left, right, verbose=False):
|
||||
return explanation
|
||||
|
||||
|
||||
def _compare_eq_cls(left, right, verbose, type_fns):
|
||||
isdatacls, isattrs = type_fns
|
||||
if isdatacls(left):
|
||||
all_fields = left.__dataclass_fields__
|
||||
fields_to_check = [field for field, info in all_fields.items() if info.compare]
|
||||
elif isattrs(left):
|
||||
all_fields = left.__attrs_attrs__
|
||||
fields_to_check = [field.name for field in all_fields if field.cmp]
|
||||
|
||||
same = []
|
||||
diff = []
|
||||
for field in fields_to_check:
|
||||
if getattr(left, field) == getattr(right, field):
|
||||
same.append(field)
|
||||
else:
|
||||
diff.append(field)
|
||||
|
||||
explanation = []
|
||||
if same and verbose < 2:
|
||||
explanation.append(u"Omitting %s identical items, use -vv to show" % len(same))
|
||||
elif same:
|
||||
explanation += [u"Matching attributes:"]
|
||||
explanation += pprint.pformat(same).splitlines()
|
||||
if diff:
|
||||
explanation += [u"Differing attributes:"]
|
||||
for field in diff:
|
||||
explanation += [
|
||||
(u"%s: %r != %r") % (field, getattr(left, field), getattr(right, field))
|
||||
]
|
||||
return explanation
|
||||
|
||||
|
||||
def _notin_text(term, text, verbose=False):
|
||||
index = text.find(term)
|
||||
head = text[:index]
|
||||
tail = text[index + len(term) :]
|
||||
correct_text = head + tail
|
||||
diff = _diff_text(correct_text, text, verbose)
|
||||
newdiff = [u"%s is contained here:" % py.io.saferepr(term, maxsize=42)]
|
||||
newdiff = [u"%s is contained here:" % saferepr(term, maxsize=42)]
|
||||
for line in diff:
|
||||
if line.startswith(u"Skipping"):
|
||||
continue
|
||||
|
||||
@@ -33,6 +33,13 @@ which provides the `--lf` and `--ff` options, as well as the `cache` fixture.
|
||||
See [the docs](https://docs.pytest.org/en/latest/cache.html) for more information.
|
||||
"""
|
||||
|
||||
CACHEDIR_TAG_CONTENT = b"""\
|
||||
Signature: 8a477f597d28d172789f06886806bc55
|
||||
# This file is a cache directory tag created by pytest.
|
||||
# For information about cache directory tags, see:
|
||||
# http://www.bford.info/cachedir/spec.html
|
||||
"""
|
||||
|
||||
|
||||
@attr.s
|
||||
class Cache(object):
|
||||
@@ -52,12 +59,12 @@ class Cache(object):
|
||||
return resolve_from_str(config.getini("cache_dir"), config.rootdir)
|
||||
|
||||
def warn(self, fmt, **args):
|
||||
from _pytest.warnings import _issue_config_warning
|
||||
from _pytest.warnings import _issue_warning_captured
|
||||
from _pytest.warning_types import PytestWarning
|
||||
|
||||
_issue_config_warning(
|
||||
_issue_warning_captured(
|
||||
PytestWarning(fmt.format(**args) if args else fmt),
|
||||
self._config,
|
||||
self._config.hook,
|
||||
stacklevel=3,
|
||||
)
|
||||
|
||||
@@ -140,6 +147,10 @@ class Cache(object):
|
||||
msg = u"# Created by pytest automatically.\n*"
|
||||
gitignore_path.write_text(msg, encoding="UTF-8")
|
||||
|
||||
cachedir_tag_path = self._cachedir.joinpath("CACHEDIR.TAG")
|
||||
if not cachedir_tag_path.is_file():
|
||||
cachedir_tag_path.write_bytes(CACHEDIR_TAG_CONTENT)
|
||||
|
||||
|
||||
class LFPlugin(object):
|
||||
""" Plugin which implements the --lf (run last-failing) option """
|
||||
|
||||
@@ -773,9 +773,9 @@ def _py36_windowsconsoleio_workaround(stream):
|
||||
f.line_buffering,
|
||||
)
|
||||
|
||||
sys.__stdin__ = sys.stdin = _reopen_stdio(sys.stdin, "rb")
|
||||
sys.__stdout__ = sys.stdout = _reopen_stdio(sys.stdout, "wb")
|
||||
sys.__stderr__ = sys.stderr = _reopen_stdio(sys.stderr, "wb")
|
||||
sys.stdin = _reopen_stdio(sys.stdin, "rb")
|
||||
sys.stdout = _reopen_stdio(sys.stdout, "wb")
|
||||
sys.stderr = _reopen_stdio(sys.stderr, "wb")
|
||||
|
||||
|
||||
def _attempt_to_close_capture_file(f):
|
||||
|
||||
@@ -17,6 +17,7 @@ import six
|
||||
from six import text_type
|
||||
|
||||
import _pytest
|
||||
from _pytest._io.saferepr import saferepr
|
||||
from _pytest.outcomes import fail
|
||||
from _pytest.outcomes import TEST_OUTCOME
|
||||
|
||||
@@ -45,11 +46,11 @@ MODULE_NOT_FOUND_ERROR = "ModuleNotFoundError" if PY36 else "ImportError"
|
||||
|
||||
if _PY3:
|
||||
from collections.abc import MutableMapping as MappingMixin
|
||||
from collections.abc import Mapping, Sequence
|
||||
from collections.abc import Iterable, Mapping, Sequence, Sized
|
||||
else:
|
||||
# those raise DeprecationWarnings in Python >=3.7
|
||||
from collections import MutableMapping as MappingMixin # noqa
|
||||
from collections import Mapping, Sequence # noqa
|
||||
from collections import Iterable, Mapping, Sequence, Sized # noqa
|
||||
|
||||
|
||||
if sys.version_info >= (3, 4):
|
||||
@@ -182,6 +183,18 @@ def get_default_arg_names(function):
|
||||
)
|
||||
|
||||
|
||||
_non_printable_ascii_translate_table = {
|
||||
i: u"\\x{:02x}".format(i) for i in range(128) if i not in range(32, 127)
|
||||
}
|
||||
_non_printable_ascii_translate_table.update(
|
||||
{ord("\t"): u"\\t", ord("\r"): u"\\r", ord("\n"): u"\\n"}
|
||||
)
|
||||
|
||||
|
||||
def _translate_non_printable(s):
|
||||
return s.translate(_non_printable_ascii_translate_table)
|
||||
|
||||
|
||||
if _PY3:
|
||||
STRING_TYPES = bytes, str
|
||||
UNICODE_TYPES = six.text_type
|
||||
@@ -221,9 +234,10 @@ if _PY3:
|
||||
|
||||
"""
|
||||
if isinstance(val, bytes):
|
||||
return _bytes_to_ascii(val)
|
||||
ret = _bytes_to_ascii(val)
|
||||
else:
|
||||
return val.encode("unicode_escape").decode("ascii")
|
||||
ret = val.encode("unicode_escape").decode("ascii")
|
||||
return _translate_non_printable(ret)
|
||||
|
||||
|
||||
else:
|
||||
@@ -241,11 +255,12 @@ else:
|
||||
"""
|
||||
if isinstance(val, bytes):
|
||||
try:
|
||||
return val.encode("ascii")
|
||||
ret = val.decode("ascii")
|
||||
except UnicodeDecodeError:
|
||||
return val.encode("string-escape")
|
||||
ret = val.encode("string-escape").decode("ascii")
|
||||
else:
|
||||
return val.encode("unicode-escape")
|
||||
ret = val.encode("unicode-escape").decode("ascii")
|
||||
return _translate_non_printable(ret)
|
||||
|
||||
|
||||
class _PytestWrapper(object):
|
||||
@@ -280,7 +295,7 @@ def get_real_func(obj):
|
||||
else:
|
||||
raise ValueError(
|
||||
("could not find real function of {start}\nstopped at {current}").format(
|
||||
start=py.io.saferepr(start_obj), current=py.io.saferepr(obj)
|
||||
start=saferepr(start_obj), current=saferepr(obj)
|
||||
)
|
||||
)
|
||||
if isinstance(obj, functools.partial):
|
||||
@@ -375,7 +390,6 @@ else:
|
||||
COLLECT_FAKEMODULE_ATTRIBUTES = (
|
||||
"Collector",
|
||||
"Module",
|
||||
"Generator",
|
||||
"Function",
|
||||
"Instance",
|
||||
"Session",
|
||||
|
||||
@@ -26,11 +26,14 @@ from .exceptions import PrintHelp
|
||||
from .exceptions import UsageError
|
||||
from .findpaths import determine_setup
|
||||
from .findpaths import exists
|
||||
from _pytest import deprecated
|
||||
from _pytest._code import ExceptionInfo
|
||||
from _pytest._code import filter_traceback
|
||||
from _pytest.compat import lru_cache
|
||||
from _pytest.compat import safe_str
|
||||
from _pytest.outcomes import fail
|
||||
from _pytest.outcomes import Skipped
|
||||
from _pytest.warning_types import PytestWarning
|
||||
|
||||
hookimpl = HookimplMarker("pytest")
|
||||
hookspec = HookspecMarker("pytest")
|
||||
@@ -173,12 +176,9 @@ def _prepareconfig(args=None, plugins=None):
|
||||
elif isinstance(args, py.path.local):
|
||||
args = [str(args)]
|
||||
elif not isinstance(args, (tuple, list)):
|
||||
if not isinstance(args, str):
|
||||
raise ValueError("not a string or argument list: %r" % (args,))
|
||||
args = shlex.split(args, posix=sys.platform != "win32")
|
||||
from _pytest import deprecated
|
||||
msg = "`args` parameter expected to be a list or tuple of strings, got: {!r} (type: {})"
|
||||
raise TypeError(msg.format(args, type(args)))
|
||||
|
||||
warning = deprecated.MAIN_STR_ARGS
|
||||
config = get_config()
|
||||
pluginmanager = config.pluginmanager
|
||||
try:
|
||||
@@ -189,9 +189,9 @@ def _prepareconfig(args=None, plugins=None):
|
||||
else:
|
||||
pluginmanager.register(plugin)
|
||||
if warning:
|
||||
from _pytest.warnings import _issue_config_warning
|
||||
from _pytest.warnings import _issue_warning_captured
|
||||
|
||||
_issue_config_warning(warning, config=config, stacklevel=4)
|
||||
_issue_warning_captured(warning, hook=config.hook, stacklevel=4)
|
||||
return pluginmanager.hook.pytest_cmdline_parse(
|
||||
pluginmanager=pluginmanager, args=args
|
||||
)
|
||||
@@ -245,14 +245,7 @@ class PytestPluginManager(PluginManager):
|
||||
Use :py:meth:`pluggy.PluginManager.add_hookspecs <PluginManager.add_hookspecs>`
|
||||
instead.
|
||||
"""
|
||||
warning = dict(
|
||||
code="I2",
|
||||
fslocation=_pytest._code.getfslineno(sys._getframe(1)),
|
||||
nodeid=None,
|
||||
message="use pluginmanager.add_hookspecs instead of "
|
||||
"deprecated addhooks() method.",
|
||||
)
|
||||
self._warn(warning)
|
||||
warnings.warn(deprecated.PLUGIN_MANAGER_ADDHOOKS, stacklevel=2)
|
||||
return self.add_hookspecs(module_or_class)
|
||||
|
||||
def parse_hookimpl_opts(self, plugin, name):
|
||||
@@ -261,8 +254,8 @@ class PytestPluginManager(PluginManager):
|
||||
# (see issue #1073)
|
||||
if not name.startswith("pytest_"):
|
||||
return
|
||||
# ignore some historic special names which can not be hooks anyway
|
||||
if name == "pytest_plugins" or name.startswith("pytest_funcarg__"):
|
||||
# ignore names which can not be hooks
|
||||
if name == "pytest_plugins":
|
||||
return
|
||||
|
||||
method = getattr(plugin, name)
|
||||
@@ -275,10 +268,14 @@ class PytestPluginManager(PluginManager):
|
||||
# collect unmarked hooks as long as they have the `pytest_' prefix
|
||||
if opts is None and name.startswith("pytest_"):
|
||||
opts = {}
|
||||
|
||||
if opts is not None:
|
||||
# TODO: DeprecationWarning, people should use hookimpl
|
||||
# https://github.com/pytest-dev/pytest/issues/4562
|
||||
known_marks = {m.name for m in getattr(method, "pytestmark", [])}
|
||||
|
||||
for name in ("tryfirst", "trylast", "optionalhook", "hookwrapper"):
|
||||
opts.setdefault(name, hasattr(method, name))
|
||||
|
||||
opts.setdefault(name, hasattr(method, name) or name in known_marks)
|
||||
return opts
|
||||
|
||||
def parse_hookspec_opts(self, module_or_class, name):
|
||||
@@ -287,19 +284,27 @@ class PytestPluginManager(PluginManager):
|
||||
)
|
||||
if opts is None:
|
||||
method = getattr(module_or_class, name)
|
||||
|
||||
if name.startswith("pytest_"):
|
||||
# todo: deprecate hookspec hacks
|
||||
# https://github.com/pytest-dev/pytest/issues/4562
|
||||
known_marks = {m.name for m in getattr(method, "pytestmark", [])}
|
||||
opts = {
|
||||
"firstresult": hasattr(method, "firstresult"),
|
||||
"historic": hasattr(method, "historic"),
|
||||
"firstresult": hasattr(method, "firstresult")
|
||||
or "firstresult" in known_marks,
|
||||
"historic": hasattr(method, "historic")
|
||||
or "historic" in known_marks,
|
||||
}
|
||||
return opts
|
||||
|
||||
def register(self, plugin, name=None):
|
||||
if name in ["pytest_catchlog", "pytest_capturelog"]:
|
||||
self._warn(
|
||||
"{} plugin has been merged into the core, "
|
||||
"please remove it from your requirements.".format(
|
||||
name.replace("_", "-")
|
||||
warnings.warn(
|
||||
PytestWarning(
|
||||
"{} plugin has been merged into the core, "
|
||||
"please remove it from your requirements.".format(
|
||||
name.replace("_", "-")
|
||||
)
|
||||
)
|
||||
)
|
||||
return
|
||||
@@ -336,14 +341,6 @@ class PytestPluginManager(PluginManager):
|
||||
)
|
||||
self._configured = True
|
||||
|
||||
def _warn(self, message):
|
||||
kwargs = (
|
||||
message
|
||||
if isinstance(message, dict)
|
||||
else {"code": "I1", "message": message, "fslocation": None, "nodeid": None}
|
||||
)
|
||||
self.hook.pytest_logwarning.call_historic(kwargs=kwargs)
|
||||
|
||||
#
|
||||
# internal API for local conftest plugin handling
|
||||
#
|
||||
@@ -411,7 +408,10 @@ class PytestPluginManager(PluginManager):
|
||||
continue
|
||||
conftestpath = parent.join("conftest.py")
|
||||
if conftestpath.isfile():
|
||||
mod = self._importconftest(conftestpath)
|
||||
# Use realpath to avoid loading the same conftest twice
|
||||
# with build systems that create build directories containing
|
||||
# symlinks to actual files.
|
||||
mod = self._importconftest(conftestpath.realpath())
|
||||
clist.append(mod)
|
||||
self._dirpath2confmods[directory] = clist
|
||||
return clist
|
||||
@@ -440,14 +440,14 @@ class PytestPluginManager(PluginManager):
|
||||
and not self._using_pyargs
|
||||
):
|
||||
from _pytest.deprecated import (
|
||||
PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST
|
||||
PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST,
|
||||
)
|
||||
|
||||
warnings.warn_explicit(
|
||||
PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST,
|
||||
category=None,
|
||||
filename=str(conftestpath),
|
||||
lineno=0,
|
||||
fail(
|
||||
PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST.format(
|
||||
conftestpath, self._confcutdir
|
||||
),
|
||||
pytrace=False,
|
||||
)
|
||||
except Exception:
|
||||
raise ConftestImportFailure(conftestpath, sys.exc_info())
|
||||
@@ -470,9 +470,20 @@ class PytestPluginManager(PluginManager):
|
||||
#
|
||||
|
||||
def consider_preparse(self, args):
|
||||
for opt1, opt2 in zip(args, args[1:]):
|
||||
if opt1 == "-p":
|
||||
self.consider_pluginarg(opt2)
|
||||
i = 0
|
||||
n = len(args)
|
||||
while i < n:
|
||||
opt = args[i]
|
||||
i += 1
|
||||
if isinstance(opt, six.string_types):
|
||||
if opt == "-p":
|
||||
parg = args[i]
|
||||
i += 1
|
||||
elif opt.startswith("-p"):
|
||||
parg = opt[2:]
|
||||
else:
|
||||
continue
|
||||
self.consider_pluginarg(parg)
|
||||
|
||||
def consider_pluginarg(self, arg):
|
||||
if arg.startswith("no:"):
|
||||
@@ -507,7 +518,7 @@ class PytestPluginManager(PluginManager):
|
||||
# "terminal" or "capture". Those plugins are registered under their
|
||||
# basename for historic purposes but must be imported with the
|
||||
# _pytest prefix.
|
||||
assert isinstance(modname, (six.text_type, str)), (
|
||||
assert isinstance(modname, six.string_types), (
|
||||
"module name as text required, got %r" % modname
|
||||
)
|
||||
modname = str(modname)
|
||||
@@ -531,7 +542,13 @@ class PytestPluginManager(PluginManager):
|
||||
six.reraise(new_exc_type, new_exc, sys.exc_info()[2])
|
||||
|
||||
except Skipped as e:
|
||||
self._warn("skipped plugin %r: %s" % ((modname, e.msg)))
|
||||
from _pytest.warnings import _issue_warning_captured
|
||||
|
||||
_issue_warning_captured(
|
||||
PytestWarning("skipped plugin %r: %s" % (modname, e.msg)),
|
||||
self.hook,
|
||||
stacklevel=1,
|
||||
)
|
||||
else:
|
||||
mod = sys.modules[importspec]
|
||||
self.register(mod, modname)
|
||||
@@ -545,8 +562,8 @@ def _get_plugin_specs_as_list(specs):
|
||||
which case it is returned as a list. Specs can also be `None` in which case an
|
||||
empty list is returned.
|
||||
"""
|
||||
if specs is not None:
|
||||
if isinstance(specs, str):
|
||||
if specs is not None and not isinstance(specs, types.ModuleType):
|
||||
if isinstance(specs, six.string_types):
|
||||
specs = specs.split(",") if specs else []
|
||||
if not isinstance(specs, (list, tuple)):
|
||||
raise UsageError(
|
||||
@@ -606,16 +623,9 @@ class Config(object):
|
||||
self._override_ini = ()
|
||||
self._opt2dest = {}
|
||||
self._cleanup = []
|
||||
self._warn = self.pluginmanager._warn
|
||||
self.pluginmanager.register(self, "pytestconfig")
|
||||
self._configured = False
|
||||
|
||||
def do_setns(dic):
|
||||
import pytest
|
||||
|
||||
setns(pytest, dic)
|
||||
|
||||
self.hook.pytest_namespace.call_historic(do_setns, {})
|
||||
self.invocation_dir = py.path.local()
|
||||
self.hook.pytest_addoption.call_historic(kwargs=dict(parser=self._parser))
|
||||
|
||||
def add_cleanup(self, func):
|
||||
@@ -637,42 +647,31 @@ class Config(object):
|
||||
fin = self._cleanup.pop()
|
||||
fin()
|
||||
|
||||
def warn(self, code, message, fslocation=None, nodeid=None):
|
||||
"""
|
||||
.. deprecated:: 3.8
|
||||
|
||||
Use :py:func:`warnings.warn` or :py:func:`warnings.warn_explicit` directly instead.
|
||||
|
||||
Generate a warning for this test session.
|
||||
"""
|
||||
from _pytest.warning_types import RemovedInPytest4Warning
|
||||
|
||||
if isinstance(fslocation, (tuple, list)) and len(fslocation) > 2:
|
||||
filename, lineno = fslocation[:2]
|
||||
else:
|
||||
filename = "unknown file"
|
||||
lineno = 0
|
||||
msg = "config.warn has been deprecated, use warnings.warn instead"
|
||||
if nodeid:
|
||||
msg = "{}: {}".format(nodeid, msg)
|
||||
warnings.warn_explicit(
|
||||
RemovedInPytest4Warning(msg),
|
||||
category=None,
|
||||
filename=filename,
|
||||
lineno=lineno,
|
||||
)
|
||||
self.hook.pytest_logwarning.call_historic(
|
||||
kwargs=dict(
|
||||
code=code, message=message, fslocation=fslocation, nodeid=nodeid
|
||||
)
|
||||
)
|
||||
|
||||
def get_terminal_writer(self):
|
||||
return self.pluginmanager.get_plugin("terminalreporter")._tw
|
||||
|
||||
def pytest_cmdline_parse(self, pluginmanager, args):
|
||||
# REF1 assert self == pluginmanager.config, (self, pluginmanager.config)
|
||||
self.parse(args)
|
||||
try:
|
||||
self.parse(args)
|
||||
except UsageError:
|
||||
|
||||
# Handle --version and --help here in a minimal fashion.
|
||||
# This gets done via helpconfig normally, but its
|
||||
# pytest_cmdline_main is not called in case of errors.
|
||||
if getattr(self.option, "version", False) or "--version" in args:
|
||||
from _pytest.helpconfig import showversion
|
||||
|
||||
showversion(self)
|
||||
elif (
|
||||
getattr(self.option, "help", False) or "--help" in args or "-h" in args
|
||||
):
|
||||
self._parser._getparser().print_help()
|
||||
sys.stdout.write(
|
||||
"\nNOTE: displaying only minimal help due to UsageError.\n\n"
|
||||
)
|
||||
|
||||
raise
|
||||
|
||||
return self
|
||||
|
||||
def notify_exception(self, excinfo, option=None):
|
||||
@@ -731,7 +730,6 @@ class Config(object):
|
||||
self.rootdir, self.inifile, self.inicfg = r
|
||||
self._parser.extra_info["rootdir"] = self.rootdir
|
||||
self._parser.extra_info["inifile"] = self.inifile
|
||||
self.invocation_dir = py.path.local()
|
||||
self._parser.addini("addopts", "extra command line options", "args")
|
||||
self._parser.addini("minversion", "minimally required pytest version")
|
||||
self._override_ini = ns.override_ini or ()
|
||||
@@ -784,21 +782,32 @@ class Config(object):
|
||||
for name in _iter_rewritable_modules(package_files):
|
||||
hook.mark_rewrite(name)
|
||||
|
||||
def _validate_args(self, args):
|
||||
def _validate_args(self, args, via):
|
||||
"""Validate known args."""
|
||||
self._parser.parse_known_and_unknown_args(
|
||||
args, namespace=copy.copy(self.option)
|
||||
)
|
||||
self._parser._config_source_hint = via
|
||||
try:
|
||||
self._parser.parse_known_and_unknown_args(
|
||||
args, namespace=copy.copy(self.option)
|
||||
)
|
||||
finally:
|
||||
del self._parser._config_source_hint
|
||||
|
||||
return args
|
||||
|
||||
def _preparse(self, args, addopts=True):
|
||||
if addopts:
|
||||
env_addopts = os.environ.get("PYTEST_ADDOPTS", "")
|
||||
if len(env_addopts):
|
||||
args[:] = self._validate_args(shlex.split(env_addopts)) + args
|
||||
args[:] = (
|
||||
self._validate_args(shlex.split(env_addopts), "via PYTEST_ADDOPTS")
|
||||
+ args
|
||||
)
|
||||
self._initini(args)
|
||||
if addopts:
|
||||
args[:] = self._validate_args(self.getini("addopts")) + args
|
||||
args[:] = (
|
||||
self._validate_args(self.getini("addopts"), "via addopts config") + args
|
||||
)
|
||||
|
||||
self._checkversion()
|
||||
self._consider_importhook(args)
|
||||
self.pluginmanager.consider_preparse(args)
|
||||
@@ -822,7 +831,15 @@ class Config(object):
|
||||
if ns.help or ns.version:
|
||||
# we don't want to prevent --help/--version to work
|
||||
# so just let is pass and print a warning at the end
|
||||
self._warn("could not load initial conftests (%s)\n" % e.path)
|
||||
from _pytest.warnings import _issue_warning_captured
|
||||
|
||||
_issue_warning_captured(
|
||||
PytestWarning(
|
||||
"could not load initial conftests: {}".format(e.path)
|
||||
),
|
||||
self.hook,
|
||||
stacklevel=2,
|
||||
)
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import argparse
|
||||
import sys as _sys
|
||||
import warnings
|
||||
from gettext import gettext as _
|
||||
|
||||
import py
|
||||
import six
|
||||
|
||||
from ..main import EXIT_USAGEERROR
|
||||
from _pytest.config.exceptions import UsageError
|
||||
|
||||
FILE_OR_DIR = "file_or_dir"
|
||||
|
||||
@@ -18,6 +16,8 @@ class Parser(object):
|
||||
there's an error processing the command line arguments.
|
||||
"""
|
||||
|
||||
prog = None
|
||||
|
||||
def __init__(self, usage=None, processopt=None):
|
||||
self._anonymous = OptionGroup("custom options", parser=self)
|
||||
self._groups = []
|
||||
@@ -82,7 +82,7 @@ class Parser(object):
|
||||
def _getparser(self):
|
||||
from _pytest._argcomplete import filescompleter
|
||||
|
||||
optparser = MyOptionParser(self, self.extra_info)
|
||||
optparser = MyOptionParser(self, self.extra_info, prog=self.prog)
|
||||
groups = self._groups + [self._anonymous]
|
||||
for group in groups:
|
||||
if group.options:
|
||||
@@ -319,12 +319,13 @@ class OptionGroup(object):
|
||||
|
||||
|
||||
class MyOptionParser(argparse.ArgumentParser):
|
||||
def __init__(self, parser, extra_info=None):
|
||||
def __init__(self, parser, extra_info=None, prog=None):
|
||||
if not extra_info:
|
||||
extra_info = {}
|
||||
self._parser = parser
|
||||
argparse.ArgumentParser.__init__(
|
||||
self,
|
||||
prog=prog,
|
||||
usage=parser._usage,
|
||||
add_help=False,
|
||||
formatter_class=DropShorterLongHelpFormatter,
|
||||
@@ -334,14 +335,13 @@ class MyOptionParser(argparse.ArgumentParser):
|
||||
self.extra_info = extra_info
|
||||
|
||||
def error(self, message):
|
||||
"""error(message: string)
|
||||
"""Transform argparse error message into UsageError."""
|
||||
msg = "%s: error: %s" % (self.prog, message)
|
||||
|
||||
Prints a usage message incorporating the message to stderr and
|
||||
exits.
|
||||
Overrides the method in parent class to change exit code"""
|
||||
self.print_usage(_sys.stderr)
|
||||
args = {"prog": self.prog, "message": message}
|
||||
self.exit(EXIT_USAGEERROR, _("%(prog)s: error: %(message)s\n") % args)
|
||||
if hasattr(self._parser, "_config_source_hint"):
|
||||
msg = "%s (%s)" % (msg, self._parser._config_source_hint)
|
||||
|
||||
raise UsageError(self.format_usage() + msg)
|
||||
|
||||
def parse_args(self, args=None, namespace=None):
|
||||
"""allow splitting of positional arguments"""
|
||||
|
||||
@@ -3,6 +3,7 @@ import os
|
||||
import py
|
||||
|
||||
from .exceptions import UsageError
|
||||
from _pytest.outcomes import fail
|
||||
|
||||
|
||||
def exists(path, ignore=EnvironmentError):
|
||||
@@ -34,15 +35,10 @@ def getcfg(args, config=None):
|
||||
iniconfig = py.iniconfig.IniConfig(p)
|
||||
if "pytest" in iniconfig.sections:
|
||||
if inibasename == "setup.cfg" and config is not None:
|
||||
from _pytest.warnings import _issue_config_warning
|
||||
from _pytest.warning_types import RemovedInPytest4Warning
|
||||
|
||||
_issue_config_warning(
|
||||
RemovedInPytest4Warning(
|
||||
CFG_PYTEST_SECTION.format(filename=inibasename)
|
||||
),
|
||||
config=config,
|
||||
stacklevel=2,
|
||||
fail(
|
||||
CFG_PYTEST_SECTION.format(filename=inibasename),
|
||||
pytrace=False,
|
||||
)
|
||||
return base, p, iniconfig["pytest"]
|
||||
if (
|
||||
@@ -112,40 +108,41 @@ def determine_setup(inifile, args, rootdir_cmd_arg=None, config=None):
|
||||
inicfg = iniconfig[section]
|
||||
if is_cfg_file and section == "pytest" and config is not None:
|
||||
from _pytest.deprecated import CFG_PYTEST_SECTION
|
||||
from _pytest.warnings import _issue_config_warning
|
||||
|
||||
# TODO: [pytest] section in *.cfg files is deprecated. Need refactoring once
|
||||
# the deprecation expires.
|
||||
_issue_config_warning(
|
||||
CFG_PYTEST_SECTION.format(filename=str(inifile)),
|
||||
config,
|
||||
stacklevel=2,
|
||||
fail(
|
||||
CFG_PYTEST_SECTION.format(filename=str(inifile)), pytrace=False
|
||||
)
|
||||
break
|
||||
except KeyError:
|
||||
inicfg = None
|
||||
rootdir = get_common_ancestor(dirs)
|
||||
if rootdir_cmd_arg is None:
|
||||
rootdir = get_common_ancestor(dirs)
|
||||
else:
|
||||
ancestor = get_common_ancestor(dirs)
|
||||
rootdir, inifile, inicfg = getcfg([ancestor], config=config)
|
||||
if rootdir is None:
|
||||
for rootdir in ancestor.parts(reverse=True):
|
||||
if rootdir.join("setup.py").exists():
|
||||
if rootdir is None and rootdir_cmd_arg is None:
|
||||
for possible_rootdir in ancestor.parts(reverse=True):
|
||||
if possible_rootdir.join("setup.py").exists():
|
||||
rootdir = possible_rootdir
|
||||
break
|
||||
else:
|
||||
rootdir, inifile, inicfg = getcfg(dirs, config=config)
|
||||
if dirs != [ancestor]:
|
||||
rootdir, inifile, inicfg = getcfg(dirs, config=config)
|
||||
if rootdir is None:
|
||||
rootdir = get_common_ancestor([py.path.local(), ancestor])
|
||||
if config is not None:
|
||||
cwd = config.invocation_dir
|
||||
else:
|
||||
cwd = py.path.local()
|
||||
rootdir = get_common_ancestor([cwd, ancestor])
|
||||
is_fs_root = os.path.splitdrive(str(rootdir))[1] == "/"
|
||||
if is_fs_root:
|
||||
rootdir = ancestor
|
||||
if rootdir_cmd_arg:
|
||||
rootdir_abs_path = py.path.local(os.path.expandvars(rootdir_cmd_arg))
|
||||
if not os.path.isdir(str(rootdir_abs_path)):
|
||||
rootdir = py.path.local(os.path.expandvars(rootdir_cmd_arg))
|
||||
if not rootdir.isdir():
|
||||
raise UsageError(
|
||||
"Directory '{}' not found. Check your '--rootdir' option.".format(
|
||||
rootdir_abs_path
|
||||
rootdir
|
||||
)
|
||||
)
|
||||
rootdir = rootdir_abs_path
|
||||
return rootdir, inifile, inicfg or {}
|
||||
|
||||
@@ -75,38 +75,50 @@ class pytestPDB(object):
|
||||
_config = None
|
||||
_pdb_cls = pdb.Pdb
|
||||
_saved = []
|
||||
_recursive_debug = 0
|
||||
|
||||
@classmethod
|
||||
def set_trace(cls, set_break=True):
|
||||
""" invoke PDB set_trace debugging, dropping any IO capturing. """
|
||||
def _init_pdb(cls, *args, **kwargs):
|
||||
""" Initialize PDB debugging, dropping any IO capturing. """
|
||||
import _pytest.config
|
||||
|
||||
frame = sys._getframe().f_back
|
||||
if cls._pluginmanager is not None:
|
||||
capman = cls._pluginmanager.getplugin("capturemanager")
|
||||
if capman:
|
||||
capman.suspend_global_capture(in_=True)
|
||||
tw = _pytest.config.create_terminal_writer(cls._config)
|
||||
tw.line()
|
||||
if capman and capman.is_globally_capturing():
|
||||
tw.sep(">", "PDB set_trace (IO-capturing turned off)")
|
||||
else:
|
||||
tw.sep(">", "PDB set_trace")
|
||||
if cls._recursive_debug == 0:
|
||||
# Handle header similar to pdb.set_trace in py37+.
|
||||
header = kwargs.pop("header", None)
|
||||
if header is not None:
|
||||
tw.sep(">", header)
|
||||
elif capman and capman.is_globally_capturing():
|
||||
tw.sep(">", "PDB set_trace (IO-capturing turned off)")
|
||||
else:
|
||||
tw.sep(">", "PDB set_trace")
|
||||
|
||||
class _PdbWrapper(cls._pdb_cls, object):
|
||||
_pytest_capman = capman
|
||||
_continued = False
|
||||
|
||||
def do_debug(self, arg):
|
||||
cls._recursive_debug += 1
|
||||
ret = super(_PdbWrapper, self).do_debug(arg)
|
||||
cls._recursive_debug -= 1
|
||||
return ret
|
||||
|
||||
def do_continue(self, arg):
|
||||
ret = super(_PdbWrapper, self).do_continue(arg)
|
||||
if self._pytest_capman:
|
||||
tw = _pytest.config.create_terminal_writer(cls._config)
|
||||
tw.line()
|
||||
if self._pytest_capman.is_globally_capturing():
|
||||
tw.sep(">", "PDB continue (IO-capturing resumed)")
|
||||
else:
|
||||
tw.sep(">", "PDB continue")
|
||||
self._pytest_capman.resume_global_capture()
|
||||
if cls._recursive_debug == 0:
|
||||
if self._pytest_capman.is_globally_capturing():
|
||||
tw.sep(">", "PDB continue (IO-capturing resumed)")
|
||||
else:
|
||||
tw.sep(">", "PDB continue")
|
||||
self._pytest_capman.resume_global_capture()
|
||||
cls._pluginmanager.hook.pytest_leave_pdb(
|
||||
config=cls._config, pdb=self
|
||||
)
|
||||
@@ -115,6 +127,10 @@ class pytestPDB(object):
|
||||
|
||||
do_c = do_cont = do_continue
|
||||
|
||||
def set_quit(self):
|
||||
super(_PdbWrapper, self).set_quit()
|
||||
outcomes.exit("Quitting debugger")
|
||||
|
||||
def setup(self, f, tb):
|
||||
"""Suspend on setup().
|
||||
|
||||
@@ -129,13 +145,18 @@ class pytestPDB(object):
|
||||
self._pytest_capman.suspend_global_capture(in_=True)
|
||||
return ret
|
||||
|
||||
_pdb = _PdbWrapper()
|
||||
_pdb = _PdbWrapper(**kwargs)
|
||||
cls._pluginmanager.hook.pytest_enter_pdb(config=cls._config, pdb=_pdb)
|
||||
else:
|
||||
_pdb = cls._pdb_cls()
|
||||
_pdb = cls._pdb_cls(**kwargs)
|
||||
return _pdb
|
||||
|
||||
if set_break:
|
||||
_pdb.set_trace(frame)
|
||||
@classmethod
|
||||
def set_trace(cls, *args, **kwargs):
|
||||
"""Invoke debugging via ``Pdb.set_trace``, dropping any IO capturing."""
|
||||
frame = sys._getframe().f_back
|
||||
_pdb = cls._init_pdb(*args, **kwargs)
|
||||
_pdb.set_trace(frame)
|
||||
|
||||
|
||||
class PdbInvoke(object):
|
||||
@@ -161,9 +182,9 @@ class PdbTrace(object):
|
||||
|
||||
|
||||
def _test_pytest_function(pyfuncitem):
|
||||
pytestPDB.set_trace(set_break=False)
|
||||
_pdb = pytestPDB._init_pdb()
|
||||
testfunction = pyfuncitem.obj
|
||||
pyfuncitem.obj = pdb.runcall
|
||||
pyfuncitem.obj = _pdb.runcall
|
||||
if pyfuncitem._isyieldedfunction():
|
||||
arg_list = list(pyfuncitem._args)
|
||||
arg_list.insert(0, testfunction)
|
||||
@@ -202,8 +223,7 @@ def _enter_pdb(node, excinfo, rep):
|
||||
tw.sep(">", "entering PDB")
|
||||
tb = _postmortem_traceback(excinfo)
|
||||
rep._pdbshown = True
|
||||
if post_mortem(tb):
|
||||
outcomes.exit("Quitting debugger")
|
||||
post_mortem(tb)
|
||||
return rep
|
||||
|
||||
|
||||
@@ -234,4 +254,5 @@ def post_mortem(t):
|
||||
p = Pdb()
|
||||
p.reset()
|
||||
p.interaction(None, t)
|
||||
return p.quitting
|
||||
if p.quitting:
|
||||
outcomes.exit("Quitting debugger")
|
||||
|
||||
@@ -17,63 +17,36 @@ from _pytest.warning_types import RemovedInPytest4Warning
|
||||
from _pytest.warning_types import UnformattedWarning
|
||||
|
||||
|
||||
MAIN_STR_ARGS = RemovedInPytest4Warning(
|
||||
"passing a string to pytest.main() is deprecated, "
|
||||
"pass a list of arguments instead."
|
||||
)
|
||||
YIELD_TESTS = "yield tests were removed in pytest 4.0 - {name} will be ignored"
|
||||
|
||||
YIELD_TESTS = RemovedInPytest4Warning(
|
||||
"yield tests are deprecated, and scheduled to be removed in pytest 4.0"
|
||||
)
|
||||
|
||||
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.",
|
||||
)
|
||||
|
||||
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.",
|
||||
FIXTURE_FUNCTION_CALL = (
|
||||
'Fixture "{name}" called directly. Fixtures are not meant to be called directly,\n'
|
||||
"but are created automatically when test functions request them as parameters.\n"
|
||||
"See https://docs.pytest.org/en/latest/fixture.html for more information about fixtures, and\n"
|
||||
"https://docs.pytest.org/en/latest/deprecations.html#calling-fixtures-directly about how to update your code."
|
||||
)
|
||||
|
||||
FIXTURE_NAMED_REQUEST = PytestDeprecationWarning(
|
||||
"'request' is a reserved name for fixtures and will raise an error in future versions"
|
||||
)
|
||||
|
||||
CFG_PYTEST_SECTION = UnformattedWarning(
|
||||
RemovedInPytest4Warning,
|
||||
"[pytest] section in {filename} files is deprecated, use [tool:pytest] instead.",
|
||||
)
|
||||
CFG_PYTEST_SECTION = "[pytest] section in {filename} files is no longer supported, change to [tool:pytest] instead."
|
||||
|
||||
GETFUNCARGVALUE = RemovedInPytest4Warning(
|
||||
"getfuncargvalue is deprecated, use getfixturevalue"
|
||||
)
|
||||
|
||||
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."
|
||||
RAISES_MESSAGE_PARAMETER = PytestDeprecationWarning(
|
||||
"The 'message' parameter is deprecated.\n"
|
||||
"(did you mean to use `match='some regex'` to check the exception message?)\n"
|
||||
"Please comment on https://github.com/pytest-dev/pytest/issues/3974 "
|
||||
"if you have concerns about removal of this parameter."
|
||||
)
|
||||
|
||||
RESULT_LOG = PytestDeprecationWarning(
|
||||
"--result-log is deprecated and scheduled for removal in pytest 5.0.\n"
|
||||
"See https://docs.pytest.org/en/latest/deprecations.html#result-log-result-log for more information."
|
||||
)
|
||||
|
||||
MARK_INFO_ATTRIBUTE = RemovedInPytest4Warning(
|
||||
@@ -82,42 +55,42 @@ MARK_INFO_ATTRIBUTE = RemovedInPytest4Warning(
|
||||
"Docs: https://docs.pytest.org/en/latest/mark.html#updating-code"
|
||||
)
|
||||
|
||||
MARK_PARAMETERSET_UNPACKING = RemovedInPytest4Warning(
|
||||
"Applying marks directly to parameters is deprecated,"
|
||||
" please use pytest.param(..., marks=...) instead.\n"
|
||||
"For more details, see: https://docs.pytest.org/en/latest/parametrize.html"
|
||||
RAISES_EXEC = PytestDeprecationWarning(
|
||||
"raises(..., 'code(as_a_string)') is deprecated, use the context manager form or use `exec()` directly\n\n"
|
||||
"See https://docs.pytest.org/en/latest/deprecations.html#raises-warns-exec"
|
||||
)
|
||||
WARNS_EXEC = PytestDeprecationWarning(
|
||||
"warns(..., 'code(as_a_string)') is deprecated, use the context manager form or use `exec()` directly.\n\n"
|
||||
"See https://docs.pytest.org/en/latest/deprecations.html#raises-warns-exec"
|
||||
)
|
||||
|
||||
NODE_WARN = RemovedInPytest4Warning(
|
||||
"Node.warn(code, message) form has been deprecated, use Node.warn(warning_instance) instead."
|
||||
)
|
||||
|
||||
RECORD_XML_PROPERTY = RemovedInPytest4Warning(
|
||||
'Fixture renamed from "record_xml_property" to "record_property" as user '
|
||||
"properties are now available to all reporters.\n"
|
||||
'"record_xml_property" is now deprecated.'
|
||||
)
|
||||
|
||||
COLLECTOR_MAKEITEM = RemovedInPytest4Warning(
|
||||
"pycollector makeitem was removed as it is an accidentially leaked internal api"
|
||||
)
|
||||
|
||||
METAFUNC_ADD_CALL = RemovedInPytest4Warning(
|
||||
"Metafunc.addcall is deprecated and scheduled to be removed in pytest 4.0.\n"
|
||||
"Please use Metafunc.parametrize instead."
|
||||
)
|
||||
|
||||
PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST = RemovedInPytest4Warning(
|
||||
"Defining pytest_plugins in a non-top-level conftest is deprecated, "
|
||||
PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST = (
|
||||
"Defining 'pytest_plugins' in a non-top-level conftest is no longer supported "
|
||||
"because it affects the entire directory tree in a non-explicit way.\n"
|
||||
"Please move it to the top level conftest file instead."
|
||||
" {}\n"
|
||||
"Please move it to a top level conftest file at the rootdir:\n"
|
||||
" {}\n"
|
||||
"For more information, visit:\n"
|
||||
" https://docs.pytest.org/en/latest/deprecations.html#pytest-plugins-in-non-top-level-conftest-files"
|
||||
)
|
||||
|
||||
PYTEST_NAMESPACE = RemovedInPytest4Warning(
|
||||
"pytest_namespace is deprecated and will be removed soon"
|
||||
PYTEST_CONFIG_GLOBAL = PytestDeprecationWarning(
|
||||
"the `pytest.config` global is deprecated. Please use `request.config` "
|
||||
"or `pytest_configure` (if you're a pytest plugin) instead."
|
||||
)
|
||||
|
||||
PYTEST_ENSURETEMP = RemovedInPytest4Warning(
|
||||
"pytest/tmpdir_factory.ensuretemp is deprecated, \n"
|
||||
"please use the tmp_path fixture or tmp_path_factory.mktemp"
|
||||
)
|
||||
|
||||
PYTEST_LOGWARNING = PytestDeprecationWarning(
|
||||
"pytest_logwarning is deprecated, no longer being called, and will be removed soon\n"
|
||||
"please use pytest_warning_captured instead"
|
||||
)
|
||||
|
||||
PYTEST_WARNS_UNKNOWN_KWARGS = UnformattedWarning(
|
||||
PytestDeprecationWarning,
|
||||
"pytest.warns() got unexpected keyword arguments: {args!r}.\n"
|
||||
"This will be an error in future versions.",
|
||||
)
|
||||
|
||||
@@ -3,17 +3,19 @@ from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import inspect
|
||||
import platform
|
||||
import sys
|
||||
import traceback
|
||||
from contextlib import contextmanager
|
||||
|
||||
import pytest
|
||||
from _pytest._code.code import ExceptionInfo
|
||||
from _pytest._code.code import ReprFileLocation
|
||||
from _pytest._code.code import TerminalRepr
|
||||
from _pytest.compat import safe_getattr
|
||||
from _pytest.fixtures import FixtureRequest
|
||||
|
||||
|
||||
DOCTEST_REPORT_CHOICE_NONE = "none"
|
||||
DOCTEST_REPORT_CHOICE_CDIFF = "cdiff"
|
||||
DOCTEST_REPORT_CHOICE_NDIFF = "ndiff"
|
||||
@@ -346,10 +348,61 @@ def _check_all_skipped(test):
|
||||
pytest.skip("all tests skipped by +SKIP option")
|
||||
|
||||
|
||||
def _is_mocked(obj):
|
||||
"""
|
||||
returns if a object is possibly a mock object by checking the existence of a highly improbable attribute
|
||||
"""
|
||||
return (
|
||||
safe_getattr(obj, "pytest_mock_example_attribute_that_shouldnt_exist", None)
|
||||
is not None
|
||||
)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def _patch_unwrap_mock_aware():
|
||||
"""
|
||||
contextmanager which replaces ``inspect.unwrap`` with a version
|
||||
that's aware of mock objects and doesn't recurse on them
|
||||
"""
|
||||
real_unwrap = getattr(inspect, "unwrap", None)
|
||||
if real_unwrap is None:
|
||||
yield
|
||||
else:
|
||||
|
||||
def _mock_aware_unwrap(obj, stop=None):
|
||||
if stop is None:
|
||||
return real_unwrap(obj, stop=_is_mocked)
|
||||
else:
|
||||
return real_unwrap(obj, stop=lambda obj: _is_mocked(obj) or stop(obj))
|
||||
|
||||
inspect.unwrap = _mock_aware_unwrap
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
inspect.unwrap = real_unwrap
|
||||
|
||||
|
||||
class DoctestModule(pytest.Module):
|
||||
def collect(self):
|
||||
import doctest
|
||||
|
||||
class MockAwareDocTestFinder(doctest.DocTestFinder):
|
||||
"""
|
||||
a hackish doctest finder that overrides stdlib internals to fix a stdlib bug
|
||||
|
||||
https://github.com/pytest-dev/pytest/issues/3456
|
||||
https://bugs.python.org/issue25532
|
||||
"""
|
||||
|
||||
def _find(self, tests, obj, name, module, source_lines, globs, seen):
|
||||
if _is_mocked(obj):
|
||||
return
|
||||
with _patch_unwrap_mock_aware():
|
||||
|
||||
doctest.DocTestFinder._find(
|
||||
self, tests, obj, name, module, source_lines, globs, seen
|
||||
)
|
||||
|
||||
if self.fspath.basename == "conftest.py":
|
||||
module = self.config.pluginmanager._importconftest(self.fspath)
|
||||
else:
|
||||
@@ -361,7 +414,7 @@ class DoctestModule(pytest.Module):
|
||||
else:
|
||||
raise
|
||||
# uses internal doctest module parsing mechanism
|
||||
finder = doctest.DocTestFinder()
|
||||
finder = MockAwareDocTestFinder()
|
||||
optionflags = get_optionflags(self)
|
||||
runner = _get_runner(
|
||||
verbose=0,
|
||||
|
||||
@@ -4,6 +4,7 @@ from __future__ import print_function
|
||||
|
||||
import functools
|
||||
import inspect
|
||||
import itertools
|
||||
import sys
|
||||
import warnings
|
||||
from collections import defaultdict
|
||||
@@ -13,11 +14,10 @@ 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 FormattedExcinfo
|
||||
from _pytest._code.code import TerminalRepr
|
||||
from _pytest.compat import _format_args
|
||||
from _pytest.compat import _PytestWrapper
|
||||
@@ -38,8 +38,6 @@ from _pytest.deprecated import FIXTURE_NAMED_REQUEST
|
||||
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{}'
|
||||
|
||||
|
||||
@attr.s(frozen=True)
|
||||
class PseudoFixtureDef(object):
|
||||
@@ -309,8 +307,8 @@ class FuncFixtureInfo(object):
|
||||
# fixture names specified via usefixtures and via autouse=True in fixture
|
||||
# definitions.
|
||||
initialnames = attr.ib(type=tuple)
|
||||
names_closure = attr.ib() # type: List[str]
|
||||
name2fixturedefs = attr.ib() # type: List[str, List[FixtureDef]]
|
||||
names_closure = attr.ib() # List[str]
|
||||
name2fixturedefs = attr.ib() # List[str, List[FixtureDef]]
|
||||
|
||||
def prune_dependency_tree(self):
|
||||
"""Recompute names_closure from initialnames and name2fixturedefs
|
||||
@@ -469,43 +467,6 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||
if argname not in item.funcargs:
|
||||
item.funcargs[argname] = self.getfixturevalue(argname)
|
||||
|
||||
def cached_setup(self, setup, teardown=None, scope="module", extrakey=None):
|
||||
""" (deprecated) Return a testing resource managed by ``setup`` &
|
||||
``teardown`` calls. ``scope`` and ``extrakey`` determine when the
|
||||
``teardown`` function will be called so that subsequent calls to
|
||||
``setup`` would recreate the resource. With pytest-2.3 you often
|
||||
do not need ``cached_setup()`` as you can directly declare a scope
|
||||
on a fixture function and register a finalizer through
|
||||
``request.addfinalizer()``.
|
||||
|
||||
:arg teardown: function receiving a previously setup resource.
|
||||
:arg setup: a no-argument function creating a resource.
|
||||
:arg scope: a string value out of ``function``, ``class``, ``module``
|
||||
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)
|
||||
cache = self.config._setupcache
|
||||
try:
|
||||
val = cache[cachekey]
|
||||
except KeyError:
|
||||
self._check_scope(self.fixturename, self.scope, scope)
|
||||
val = setup()
|
||||
cache[cachekey] = val
|
||||
if teardown is not None:
|
||||
|
||||
def finalizer():
|
||||
del cache[cachekey]
|
||||
teardown(val)
|
||||
|
||||
self._addfinalizer(finalizer, scope=scope)
|
||||
return val
|
||||
|
||||
def getfixturevalue(self, argname):
|
||||
""" Dynamically run a named fixture function.
|
||||
|
||||
@@ -605,8 +566,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||
)
|
||||
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)
|
||||
param_index = funcitem.callspec.indices[argname]
|
||||
# if a parametrize invocation set a scope it will override
|
||||
# the static scope defined with the fixture function
|
||||
paramscopenum = funcitem.callspec._arg2scopenum.get(argname)
|
||||
@@ -700,12 +660,6 @@ class SubRequest(FixtureRequest):
|
||||
self._fixturedef.addfinalizer(finalizer)
|
||||
|
||||
|
||||
class ScopeMismatchError(Exception):
|
||||
""" A fixture function tries to use a different fixture function which
|
||||
which has a lower scope (e.g. a Session one calls a function one)
|
||||
"""
|
||||
|
||||
|
||||
scopes = "session package module class function".split()
|
||||
scopenum_function = scopes.index("function")
|
||||
|
||||
@@ -982,34 +936,17 @@ def _ensure_immutable_ids(ids):
|
||||
return tuple(ids)
|
||||
|
||||
|
||||
def wrap_function_to_warning_if_called_directly(function, fixture_marker):
|
||||
"""Wrap the given fixture function so we can issue warnings about it being called directly, instead of
|
||||
used as an argument in a test function.
|
||||
def wrap_function_to_error_out_if_called_directly(function, fixture_marker):
|
||||
"""Wrap the given fixture function so we can raise an error about it being called directly,
|
||||
instead of used as an argument in a test function.
|
||||
"""
|
||||
is_yield_function = is_generator(function)
|
||||
warning = FIXTURE_FUNCTION_CALL.format(
|
||||
message = FIXTURE_FUNCTION_CALL.format(
|
||||
name=fixture_marker.name or function.__name__
|
||||
)
|
||||
|
||||
if is_yield_function:
|
||||
|
||||
@functools.wraps(function)
|
||||
def result(*args, **kwargs):
|
||||
__tracebackhide__ = True
|
||||
warnings.warn(warning, stacklevel=3)
|
||||
for x in function(*args, **kwargs):
|
||||
yield x
|
||||
|
||||
else:
|
||||
|
||||
@functools.wraps(function)
|
||||
def result(*args, **kwargs):
|
||||
__tracebackhide__ = True
|
||||
warnings.warn(warning, stacklevel=3)
|
||||
return function(*args, **kwargs)
|
||||
|
||||
if six.PY2:
|
||||
result.__wrapped__ = function
|
||||
@six.wraps(function)
|
||||
def result(*args, **kwargs):
|
||||
fail(message, pytrace=False)
|
||||
|
||||
# keep reference to the original function in our own custom attribute so we don't unwrap
|
||||
# further than this point and lose useful wrappings like @mock.patch (#3774)
|
||||
@@ -1035,7 +972,7 @@ class FixtureFunctionMarker(object):
|
||||
"fixture is being applied more than once to the same function"
|
||||
)
|
||||
|
||||
function = wrap_function_to_warning_if_called_directly(function, self)
|
||||
function = wrap_function_to_error_out_if_called_directly(function, self)
|
||||
|
||||
name = self.name or function.__name__
|
||||
if name == "request":
|
||||
@@ -1155,7 +1092,6 @@ class FixtureManager(object):
|
||||
by a lookup of their FuncFixtureInfo.
|
||||
"""
|
||||
|
||||
_argprefix = "pytest_funcarg__"
|
||||
FixtureLookupError = FixtureLookupError
|
||||
FixtureLookupErrorRepr = FixtureLookupErrorRepr
|
||||
|
||||
@@ -1173,7 +1109,7 @@ class FixtureManager(object):
|
||||
argnames = getfuncargnames(func, cls=cls)
|
||||
else:
|
||||
argnames = ()
|
||||
usefixtures = flatten(
|
||||
usefixtures = itertools.chain.from_iterable(
|
||||
mark.args for mark in node.iter_markers(name="usefixtures")
|
||||
)
|
||||
initialnames = tuple(usefixtures) + argnames
|
||||
@@ -1265,19 +1201,20 @@ class FixtureManager(object):
|
||||
if faclist:
|
||||
fixturedef = faclist[-1]
|
||||
if fixturedef.params is not None:
|
||||
parametrize_func = getattr(metafunc.function, "parametrize", None)
|
||||
if parametrize_func is not None:
|
||||
parametrize_func = parametrize_func.combined
|
||||
func_params = getattr(parametrize_func, "args", [[None]])
|
||||
func_kwargs = getattr(parametrize_func, "kwargs", {})
|
||||
# skip directly parametrized arguments
|
||||
if "argnames" in func_kwargs:
|
||||
argnames = parametrize_func.kwargs["argnames"]
|
||||
markers = list(metafunc.definition.iter_markers("parametrize"))
|
||||
for parametrize_mark in markers:
|
||||
if "argnames" in parametrize_mark.kwargs:
|
||||
argnames = parametrize_mark.kwargs["argnames"]
|
||||
else:
|
||||
argnames = parametrize_mark.args[0]
|
||||
|
||||
if not isinstance(argnames, (tuple, list)):
|
||||
argnames = [
|
||||
x.strip() for x in argnames.split(",") if x.strip()
|
||||
]
|
||||
if argname in argnames:
|
||||
break
|
||||
else:
|
||||
argnames = func_params[0]
|
||||
if not isinstance(argnames, (tuple, list)):
|
||||
argnames = [x.strip() for x in argnames.split(",") if x.strip()]
|
||||
if argname not in func_params and argname not in argnames:
|
||||
metafunc.parametrize(
|
||||
argname,
|
||||
fixturedef.params,
|
||||
@@ -1293,8 +1230,6 @@ class FixtureManager(object):
|
||||
items[:] = reorder_items(items)
|
||||
|
||||
def parsefactories(self, node_or_obj, nodeid=NOTSET, unittest=False):
|
||||
from _pytest import deprecated
|
||||
|
||||
if nodeid is not NOTSET:
|
||||
holderobj = node_or_obj
|
||||
else:
|
||||
@@ -1303,44 +1238,20 @@ class FixtureManager(object):
|
||||
if holderobj in self._holderobjseen:
|
||||
return
|
||||
|
||||
from _pytest.nodes import _CompatProperty
|
||||
|
||||
self._holderobjseen.add(holderobj)
|
||||
autousenames = []
|
||||
for name in dir(holderobj):
|
||||
# The attribute can be an arbitrary descriptor, so the attribute
|
||||
# access below can raise. safe_getatt() ignores such exceptions.
|
||||
maybe_property = safe_getattr(type(holderobj), name, None)
|
||||
if isinstance(maybe_property, _CompatProperty):
|
||||
# deprecated
|
||||
continue
|
||||
obj = safe_getattr(holderobj, name, None)
|
||||
marker = getfixturemarker(obj)
|
||||
# fixture functions have a pytest_funcarg__ prefix (pre-2.3 style)
|
||||
# or are "@pytest.fixture" marked
|
||||
if marker is None:
|
||||
if not name.startswith(self._argprefix):
|
||||
continue
|
||||
if not callable(obj):
|
||||
continue
|
||||
marker = defaultfuncargprefixmarker
|
||||
|
||||
filename, lineno = getfslineno(obj)
|
||||
warnings.warn_explicit(
|
||||
deprecated.FUNCARG_PREFIX.format(name=name),
|
||||
category=None,
|
||||
filename=str(filename),
|
||||
lineno=lineno + 1,
|
||||
)
|
||||
name = name[len(self._argprefix) :]
|
||||
elif not isinstance(marker, FixtureFunctionMarker):
|
||||
if not isinstance(marker, FixtureFunctionMarker):
|
||||
# magic globals with __getattr__ might have got us a wrong
|
||||
# fixture attribute
|
||||
continue
|
||||
else:
|
||||
if marker.name:
|
||||
name = marker.name
|
||||
assert not name.startswith(self._argprefix), FIXTURE_MSG.format(name)
|
||||
|
||||
if marker.name:
|
||||
name = marker.name
|
||||
|
||||
# during fixture definition we wrap the original fixture function
|
||||
# to issue a warning if called directly, so here we unwrap it in order to not emit the warning
|
||||
|
||||
@@ -118,16 +118,20 @@ def pytest_cmdline_parse():
|
||||
config.add_cleanup(unset_tracing)
|
||||
|
||||
|
||||
def showversion(config):
|
||||
p = py.path.local(pytest.__file__)
|
||||
sys.stderr.write(
|
||||
"This is pytest version %s, imported from %s\n" % (pytest.__version__, p)
|
||||
)
|
||||
plugininfo = getpluginversioninfo(config)
|
||||
if plugininfo:
|
||||
for line in plugininfo:
|
||||
sys.stderr.write(line + "\n")
|
||||
|
||||
|
||||
def pytest_cmdline_main(config):
|
||||
if config.option.version:
|
||||
p = py.path.local(pytest.__file__)
|
||||
sys.stderr.write(
|
||||
"This is pytest version %s, imported from %s\n" % (pytest.__version__, p)
|
||||
)
|
||||
plugininfo = getpluginversioninfo(config)
|
||||
if plugininfo:
|
||||
for line in plugininfo:
|
||||
sys.stderr.write(line + "\n")
|
||||
showversion(config)
|
||||
return 0
|
||||
elif config.option.help:
|
||||
config._do_configure()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
""" hook specifications for pytest plugins, invoked from main.py and builtin plugins. """
|
||||
from pluggy import HookspecMarker
|
||||
|
||||
from .deprecated import PYTEST_NAMESPACE
|
||||
from _pytest.deprecated import PYTEST_LOGWARNING
|
||||
|
||||
|
||||
hookspec = HookspecMarker("pytest")
|
||||
@@ -24,32 +24,6 @@ def pytest_addhooks(pluginmanager):
|
||||
"""
|
||||
|
||||
|
||||
@hookspec(historic=True, warn_on_impl=PYTEST_NAMESPACE)
|
||||
def pytest_namespace():
|
||||
"""
|
||||
return dict of name->object to be made globally available in
|
||||
the pytest namespace.
|
||||
|
||||
This hook is called at plugin registration time.
|
||||
|
||||
.. note::
|
||||
This hook is incompatible with ``hookwrapper=True``.
|
||||
|
||||
.. warning::
|
||||
This hook has been **deprecated** and will be removed in pytest 4.0.
|
||||
|
||||
Plugins whose users depend on the current namespace functionality should prepare to migrate to a
|
||||
namespace they actually own.
|
||||
|
||||
To support the migration it's suggested to trigger ``DeprecationWarnings`` for objects they put into the
|
||||
pytest namespace.
|
||||
|
||||
A stopgap measure to avoid the warning is to monkeypatch the ``pytest`` module, but just as the
|
||||
``pytest_namespace`` hook this should be seen as a temporary measure to be removed in future versions after
|
||||
an appropriate transition period.
|
||||
"""
|
||||
|
||||
|
||||
@hookspec(historic=True)
|
||||
def pytest_plugin_registered(plugin, manager):
|
||||
""" a new pytest plugin got registered.
|
||||
@@ -507,24 +481,27 @@ def pytest_report_collectionfinish(config, startdir, items):
|
||||
|
||||
|
||||
@hookspec(firstresult=True)
|
||||
def pytest_report_teststatus(report):
|
||||
def pytest_report_teststatus(report, config):
|
||||
""" return result-category, shortletter and verbose word for reporting.
|
||||
|
||||
:param _pytest.config.Config config: pytest config object
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult` """
|
||||
|
||||
|
||||
def pytest_terminal_summary(terminalreporter, exitstatus):
|
||||
def pytest_terminal_summary(terminalreporter, exitstatus, config):
|
||||
"""Add a section to terminal summary reporting.
|
||||
|
||||
:param _pytest.terminal.TerminalReporter terminalreporter: the internal terminal reporter object
|
||||
:param int exitstatus: the exit status that will be reported back to the OS
|
||||
:param _pytest.config.Config config: pytest config object
|
||||
|
||||
.. versionadded:: 3.5
|
||||
.. versionadded:: 4.2
|
||||
The ``config`` parameter.
|
||||
"""
|
||||
|
||||
|
||||
@hookspec(historic=True)
|
||||
@hookspec(historic=True, warn_on_impl=PYTEST_LOGWARNING)
|
||||
def pytest_logwarning(message, code, nodeid, fslocation):
|
||||
"""
|
||||
.. deprecated:: 3.8
|
||||
|
||||
@@ -19,6 +19,7 @@ import sys
|
||||
import time
|
||||
|
||||
import py
|
||||
import six
|
||||
|
||||
import pytest
|
||||
from _pytest import nodes
|
||||
@@ -27,10 +28,6 @@ from _pytest.config import filename_arg
|
||||
# Python 2.X and 3.X compatibility
|
||||
if sys.version_info[0] < 3:
|
||||
from codecs import open
|
||||
else:
|
||||
unichr = chr
|
||||
unicode = str
|
||||
long = int
|
||||
|
||||
|
||||
class Junit(py.xml.Namespace):
|
||||
@@ -45,12 +42,12 @@ class Junit(py.xml.Namespace):
|
||||
_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))
|
||||
u"%s-%s" % (six.unichr(low), six.unichr(high))
|
||||
for (low, high) in _legal_ranges
|
||||
if low < sys.maxunicode
|
||||
]
|
||||
_legal_xml_re = [unichr(x) for x in _legal_chars] + _legal_xml_re
|
||||
illegal_xml_re = re.compile(unicode("[^%s]") % unicode("").join(_legal_xml_re))
|
||||
_legal_xml_re = [six.unichr(x) for x in _legal_chars] + _legal_xml_re
|
||||
illegal_xml_re = re.compile(u"[^%s]" % u"".join(_legal_xml_re))
|
||||
del _legal_chars
|
||||
del _legal_ranges
|
||||
del _legal_xml_re
|
||||
@@ -62,19 +59,41 @@ def bin_xml_escape(arg):
|
||||
def repl(matchobj):
|
||||
i = ord(matchobj.group())
|
||||
if i <= 0xFF:
|
||||
return unicode("#x%02X") % i
|
||||
return u"#x%02X" % i
|
||||
else:
|
||||
return unicode("#x%04X") % i
|
||||
return u"#x%04X" % i
|
||||
|
||||
return py.xml.raw(illegal_xml_re.sub(repl, py.xml.escape(arg)))
|
||||
|
||||
|
||||
def merge_family(left, right):
|
||||
result = {}
|
||||
for kl, vl in left.items():
|
||||
for kr, vr in right.items():
|
||||
if not isinstance(vl, list):
|
||||
raise TypeError(type(vl))
|
||||
result[kl] = vl + vr
|
||||
left.update(result)
|
||||
|
||||
|
||||
families = {}
|
||||
families["_base"] = {"testcase": ["classname", "name"]}
|
||||
families["_base_legacy"] = {"testcase": ["file", "line", "url"]}
|
||||
|
||||
# xUnit 1.x inherits legacy attributes
|
||||
families["xunit1"] = families["_base"].copy()
|
||||
merge_family(families["xunit1"], families["_base_legacy"])
|
||||
|
||||
# xUnit 2.x uses strict base attributes
|
||||
families["xunit2"] = families["_base"]
|
||||
|
||||
|
||||
class _NodeReporter(object):
|
||||
def __init__(self, nodeid, xml):
|
||||
|
||||
self.id = nodeid
|
||||
self.xml = xml
|
||||
self.add_stats = self.xml.add_stats
|
||||
self.family = self.xml.family
|
||||
self.duration = 0
|
||||
self.properties = []
|
||||
self.nodes = []
|
||||
@@ -122,8 +141,20 @@ class _NodeReporter(object):
|
||||
self.attrs = attrs
|
||||
self.attrs.update(existing_attrs) # restore any user-defined attributes
|
||||
|
||||
# Preserve legacy testcase behavior
|
||||
if self.family == "xunit1":
|
||||
return
|
||||
|
||||
# Filter out attributes not permitted by this test family.
|
||||
# Including custom attributes because they are not valid here.
|
||||
temp_attrs = {}
|
||||
for key in self.attrs.keys():
|
||||
if key in families[self.family]["testcase"]:
|
||||
temp_attrs[key] = self.attrs[key]
|
||||
self.attrs = temp_attrs
|
||||
|
||||
def to_xml(self):
|
||||
testcase = Junit.testcase(time=self.duration, **self.attrs)
|
||||
testcase = Junit.testcase(time="%.3f" % self.duration, **self.attrs)
|
||||
testcase.append(self.make_properties_node())
|
||||
for node in self.nodes:
|
||||
testcase.append(node)
|
||||
@@ -194,7 +225,7 @@ class _NodeReporter(object):
|
||||
else:
|
||||
if hasattr(report.longrepr, "reprcrash"):
|
||||
message = report.longrepr.reprcrash.message
|
||||
elif isinstance(report.longrepr, (unicode, str)):
|
||||
elif isinstance(report.longrepr, six.string_types):
|
||||
message = report.longrepr
|
||||
else:
|
||||
message = str(report.longrepr)
|
||||
@@ -213,7 +244,7 @@ class _NodeReporter(object):
|
||||
self._add_simple(Junit.skipped, "collection skipped", report.longrepr)
|
||||
|
||||
def append_error(self, report):
|
||||
if getattr(report, "when", None) == "teardown":
|
||||
if report.when == "teardown":
|
||||
msg = "test teardown failure"
|
||||
else:
|
||||
msg = "test setup failure"
|
||||
@@ -263,16 +294,6 @@ def record_property(request):
|
||||
return append_property
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def record_xml_property(record_property, request):
|
||||
"""(Deprecated) use record_property."""
|
||||
from _pytest import deprecated
|
||||
|
||||
request.node.warn(deprecated.RECORD_XML_PROPERTY)
|
||||
|
||||
return record_property
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def record_xml_attribute(request):
|
||||
"""Add extra xml attributes to the tag for the calling test.
|
||||
@@ -282,16 +303,26 @@ def record_xml_attribute(request):
|
||||
from _pytest.warning_types import PytestWarning
|
||||
|
||||
request.node.warn(PytestWarning("record_xml_attribute is an experimental feature"))
|
||||
|
||||
# Declare noop
|
||||
def add_attr_noop(name, value):
|
||||
pass
|
||||
|
||||
attr_func = add_attr_noop
|
||||
xml = getattr(request.config, "_xml", None)
|
||||
if xml is not None:
|
||||
|
||||
if xml is not None and xml.family != "xunit1":
|
||||
request.node.warn(
|
||||
PytestWarning(
|
||||
"record_xml_attribute is incompatible with junit_family: "
|
||||
"%s (use: legacy|xunit1)" % xml.family
|
||||
)
|
||||
)
|
||||
elif xml is not None:
|
||||
node_reporter = xml.node_reporter(request.node.nodeid)
|
||||
return node_reporter.add_attribute
|
||||
else:
|
||||
attr_func = node_reporter.add_attribute
|
||||
|
||||
def add_attr_noop(name, value):
|
||||
pass
|
||||
|
||||
return add_attr_noop
|
||||
return attr_func
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
@@ -323,6 +354,16 @@ def pytest_addoption(parser):
|
||||
"one of no|system-out|system-err",
|
||||
default="no",
|
||||
) # choices=['no', 'stdout', 'stderr'])
|
||||
parser.addini(
|
||||
"junit_duration_report",
|
||||
"Duration time to report: one of total|call",
|
||||
default="total",
|
||||
) # choices=['total', 'call'])
|
||||
parser.addini(
|
||||
"junit_family",
|
||||
"Emit XML for schema: one of legacy|xunit1|xunit2",
|
||||
default="xunit1",
|
||||
)
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
@@ -334,6 +375,8 @@ def pytest_configure(config):
|
||||
config.option.junitprefix,
|
||||
config.getini("junit_suite_name"),
|
||||
config.getini("junit_logging"),
|
||||
config.getini("junit_duration_report"),
|
||||
config.getini("junit_family"),
|
||||
)
|
||||
config.pluginmanager.register(config._xml)
|
||||
|
||||
@@ -361,12 +404,22 @@ def mangle_test_address(address):
|
||||
|
||||
|
||||
class LogXML(object):
|
||||
def __init__(self, logfile, prefix, suite_name="pytest", logging="no"):
|
||||
def __init__(
|
||||
self,
|
||||
logfile,
|
||||
prefix,
|
||||
suite_name="pytest",
|
||||
logging="no",
|
||||
report_duration="total",
|
||||
family="xunit1",
|
||||
):
|
||||
logfile = os.path.expanduser(os.path.expandvars(logfile))
|
||||
self.logfile = os.path.normpath(os.path.abspath(logfile))
|
||||
self.prefix = prefix
|
||||
self.suite_name = suite_name
|
||||
self.logging = logging
|
||||
self.report_duration = report_duration
|
||||
self.family = family
|
||||
self.stats = dict.fromkeys(["error", "passed", "failure", "skipped"], 0)
|
||||
self.node_reporters = {} # nodeid -> _NodeReporter
|
||||
self.node_reporters_ordered = []
|
||||
@@ -375,6 +428,10 @@ class LogXML(object):
|
||||
self.open_reports = []
|
||||
self.cnt_double_fail_tests = 0
|
||||
|
||||
# Replaces convenience family with real family
|
||||
if self.family == "legacy":
|
||||
self.family = "xunit1"
|
||||
|
||||
def finalize(self, report):
|
||||
nodeid = getattr(report, "nodeid", report)
|
||||
# local hack to handle xdist report order
|
||||
@@ -500,8 +557,9 @@ class LogXML(object):
|
||||
"""accumulates total duration for nodeid from given report and updates
|
||||
the Junit.testcase with the new total if already created.
|
||||
"""
|
||||
reporter = self.node_reporter(report)
|
||||
reporter.duration += getattr(report, "duration", 0.0)
|
||||
if self.report_duration == "total" or report.when == self.report_duration:
|
||||
reporter = self.node_reporter(report)
|
||||
reporter.duration += getattr(report, "duration", 0.0)
|
||||
|
||||
def pytest_collectreport(self, report):
|
||||
if not report.passed:
|
||||
@@ -543,7 +601,7 @@ class LogXML(object):
|
||||
name=self.suite_name,
|
||||
errors=self.stats["error"],
|
||||
failures=self.stats["failure"],
|
||||
skips=self.stats["skipped"],
|
||||
skipped=self.stats["skipped"],
|
||||
tests=numtests,
|
||||
time="%.3f" % suite_time_delta,
|
||||
).unicode(indent=0)
|
||||
|
||||
@@ -13,6 +13,7 @@ import six
|
||||
import pytest
|
||||
from _pytest.compat import dummy_context_manager
|
||||
from _pytest.config import create_terminal_writer
|
||||
from _pytest.pathlib import Path
|
||||
|
||||
|
||||
DEFAULT_LOG_FORMAT = "%(filename)-25s %(lineno)4d %(levelname)-8s %(message)s"
|
||||
@@ -217,7 +218,7 @@ class LogCaptureFixture(object):
|
||||
"""Creates a new funcarg."""
|
||||
self._item = item
|
||||
# dict of log name -> log level
|
||||
self._initial_log_levels = {} # type: Dict[str, int]
|
||||
self._initial_log_levels = {} # Dict[str, int]
|
||||
|
||||
def _finalize(self):
|
||||
"""Finalizes the fixture.
|
||||
@@ -370,6 +371,8 @@ def get_actual_log_level(config, *setting_names):
|
||||
)
|
||||
|
||||
|
||||
# run after terminalreporter/capturemanager are configured
|
||||
@pytest.hookimpl(trylast=True)
|
||||
def pytest_configure(config):
|
||||
config.pluginmanager.register(LoggingPlugin(config), "logging-plugin")
|
||||
|
||||
@@ -388,8 +391,6 @@ class LoggingPlugin(object):
|
||||
|
||||
# enable verbose output automatically if live logging is enabled
|
||||
if self._log_cli_enabled() and not config.getoption("verbose"):
|
||||
# sanity check: terminal reporter should not have been loaded at this point
|
||||
assert self._config.pluginmanager.get_plugin("terminalreporter") is None
|
||||
config.option.verbose = 1
|
||||
|
||||
self.print_logs = get_option_ini(config, "log_print")
|
||||
@@ -399,27 +400,88 @@ class LoggingPlugin(object):
|
||||
)
|
||||
self.log_level = get_actual_log_level(config, "log_level")
|
||||
|
||||
self.log_file_level = get_actual_log_level(config, "log_file_level")
|
||||
self.log_file_format = get_option_ini(config, "log_file_format", "log_format")
|
||||
self.log_file_date_format = get_option_ini(
|
||||
config, "log_file_date_format", "log_date_format"
|
||||
)
|
||||
self.log_file_formatter = logging.Formatter(
|
||||
self.log_file_format, datefmt=self.log_file_date_format
|
||||
)
|
||||
|
||||
log_file = get_option_ini(config, "log_file")
|
||||
if log_file:
|
||||
self.log_file_level = get_actual_log_level(config, "log_file_level")
|
||||
|
||||
log_file_format = get_option_ini(config, "log_file_format", "log_format")
|
||||
log_file_date_format = get_option_ini(
|
||||
config, "log_file_date_format", "log_date_format"
|
||||
)
|
||||
# Each pytest runtests session will write to a clean logfile
|
||||
self.log_file_handler = logging.FileHandler(
|
||||
log_file, mode="w", encoding="UTF-8"
|
||||
)
|
||||
log_file_formatter = logging.Formatter(
|
||||
log_file_format, datefmt=log_file_date_format
|
||||
)
|
||||
self.log_file_handler.setFormatter(log_file_formatter)
|
||||
self.log_file_handler.setFormatter(self.log_file_formatter)
|
||||
else:
|
||||
self.log_file_handler = None
|
||||
|
||||
self.log_cli_handler = None
|
||||
|
||||
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.
|
||||
|
||||
if self._log_cli_enabled():
|
||||
self._setup_cli_logging()
|
||||
|
||||
def _setup_cli_logging(self):
|
||||
config = self._config
|
||||
terminal_reporter = config.pluginmanager.get_plugin("terminalreporter")
|
||||
if terminal_reporter is None:
|
||||
# terminal reporter is disabled e.g. by pytest-xdist.
|
||||
return
|
||||
|
||||
capture_manager = config.pluginmanager.get_plugin("capturemanager")
|
||||
# if capturemanager plugin is disabled, live logging still works.
|
||||
log_cli_handler = _LiveLoggingStreamHandler(terminal_reporter, capture_manager)
|
||||
log_cli_format = get_option_ini(config, "log_cli_format", "log_format")
|
||||
log_cli_date_format = get_option_ini(
|
||||
config, "log_cli_date_format", "log_date_format"
|
||||
)
|
||||
if (
|
||||
config.option.color != "no"
|
||||
and ColoredLevelFormatter.LEVELNAME_FMT_REGEX.search(log_cli_format)
|
||||
):
|
||||
log_cli_formatter = ColoredLevelFormatter(
|
||||
create_terminal_writer(config),
|
||||
log_cli_format,
|
||||
datefmt=log_cli_date_format,
|
||||
)
|
||||
else:
|
||||
log_cli_formatter = logging.Formatter(
|
||||
log_cli_format, datefmt=log_cli_date_format
|
||||
)
|
||||
log_cli_level = get_actual_log_level(config, "log_cli_level", "log_level")
|
||||
self.log_cli_handler = log_cli_handler
|
||||
self.live_logs_context = lambda: catching_logs(
|
||||
log_cli_handler, formatter=log_cli_formatter, level=log_cli_level
|
||||
)
|
||||
|
||||
def set_log_path(self, fname):
|
||||
"""Public method, which can set filename parameter for
|
||||
Logging.FileHandler(). Also creates parent directory if
|
||||
it does not exist.
|
||||
|
||||
.. warning::
|
||||
Please considered as an experimental API.
|
||||
"""
|
||||
fname = Path(fname)
|
||||
|
||||
if not fname.is_absolute():
|
||||
fname = Path(self._config.rootdir, fname)
|
||||
|
||||
if not fname.parent.exists():
|
||||
fname.parent.mkdir(exist_ok=True, parents=True)
|
||||
|
||||
self.log_file_handler = logging.FileHandler(
|
||||
str(fname), mode="w", encoding="UTF-8"
|
||||
)
|
||||
self.log_file_handler.setFormatter(self.log_file_formatter)
|
||||
|
||||
def _log_cli_enabled(self):
|
||||
"""Return True if log_cli should be considered enabled, either explicitly
|
||||
or because --log-cli-level was given in the command-line.
|
||||
@@ -430,10 +492,6 @@ class LoggingPlugin(object):
|
||||
|
||||
@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")
|
||||
@@ -446,6 +504,15 @@ class LoggingPlugin(object):
|
||||
|
||||
@contextmanager
|
||||
def _runtest_for(self, item, when):
|
||||
with self._runtest_for_main(item, when):
|
||||
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_main(self, item, when):
|
||||
"""Implements the internals of pytest_runtest_xxx() hook."""
|
||||
with catching_logs(
|
||||
LogCaptureHandler(), formatter=self.formatter, level=self.log_level
|
||||
@@ -513,7 +580,6 @@ class LoggingPlugin(object):
|
||||
|
||||
@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")
|
||||
@@ -533,46 +599,6 @@ class LoggingPlugin(object):
|
||||
else:
|
||||
yield # run all the tests
|
||||
|
||||
def _setup_cli_logging(self):
|
||||
"""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")
|
||||
log_cli_handler = _LiveLoggingStreamHandler(
|
||||
terminal_reporter, capture_manager
|
||||
)
|
||||
log_cli_format = get_option_ini(
|
||||
self._config, "log_cli_format", "log_format"
|
||||
)
|
||||
log_cli_date_format = get_option_ini(
|
||||
self._config, "log_cli_date_format", "log_date_format"
|
||||
)
|
||||
if (
|
||||
self._config.option.color != "no"
|
||||
and ColoredLevelFormatter.LEVELNAME_FMT_REGEX.search(log_cli_format)
|
||||
):
|
||||
log_cli_formatter = ColoredLevelFormatter(
|
||||
create_terminal_writer(self._config),
|
||||
log_cli_format,
|
||||
datefmt=log_cli_date_format,
|
||||
)
|
||||
else:
|
||||
log_cli_formatter = logging.Formatter(
|
||||
log_cli_format, datefmt=log_cli_date_format
|
||||
)
|
||||
log_cli_level = get_actual_log_level(
|
||||
self._config, "log_cli_level", "log_level"
|
||||
)
|
||||
self.log_cli_handler = log_cli_handler
|
||||
self.live_logs_context = lambda: catching_logs(
|
||||
log_cli_handler, formatter=log_cli_formatter, level=log_cli_level
|
||||
)
|
||||
else:
|
||||
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):
|
||||
"""
|
||||
|
||||
@@ -4,10 +4,12 @@ from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import contextlib
|
||||
import fnmatch
|
||||
import functools
|
||||
import os
|
||||
import pkgutil
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
import attr
|
||||
import py
|
||||
@@ -18,6 +20,7 @@ from _pytest import nodes
|
||||
from _pytest.config import directory_arg
|
||||
from _pytest.config import hookimpl
|
||||
from _pytest.config import UsageError
|
||||
from _pytest.deprecated import PYTEST_CONFIG_GLOBAL
|
||||
from _pytest.outcomes import exit
|
||||
from _pytest.runner import collect_one_node
|
||||
|
||||
@@ -115,6 +118,12 @@ def pytest_addoption(parser):
|
||||
metavar="path",
|
||||
help="ignore path during collection (multi-allowed).",
|
||||
)
|
||||
group.addoption(
|
||||
"--ignore-glob",
|
||||
action="append",
|
||||
metavar="path",
|
||||
help="ignore path pattern during collection (multi-allowed).",
|
||||
)
|
||||
group.addoption(
|
||||
"--deselect",
|
||||
action="append",
|
||||
@@ -167,8 +176,24 @@ def pytest_addoption(parser):
|
||||
)
|
||||
|
||||
|
||||
class _ConfigDeprecated(object):
|
||||
def __init__(self, config):
|
||||
self.__dict__["_config"] = config
|
||||
|
||||
def __getattr__(self, attr):
|
||||
warnings.warn(PYTEST_CONFIG_GLOBAL, stacklevel=2)
|
||||
return getattr(self._config, attr)
|
||||
|
||||
def __setattr__(self, attr, val):
|
||||
warnings.warn(PYTEST_CONFIG_GLOBAL, stacklevel=2)
|
||||
return setattr(self._config, attr, val)
|
||||
|
||||
def __repr__(self):
|
||||
return "{}({!r})".format(type(self).__name__, self._config)
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
__import__("pytest").config = config # compatibility
|
||||
__import__("pytest").config = _ConfigDeprecated(config) # compatibility
|
||||
|
||||
|
||||
def wrap_session(config, doit):
|
||||
@@ -187,8 +212,8 @@ def wrap_session(config, doit):
|
||||
raise
|
||||
except Failed:
|
||||
session.exitstatus = EXIT_TESTSFAILED
|
||||
except KeyboardInterrupt:
|
||||
excinfo = _pytest._code.ExceptionInfo()
|
||||
except (KeyboardInterrupt, exit.Exception):
|
||||
excinfo = _pytest._code.ExceptionInfo.from_current()
|
||||
exitstatus = EXIT_INTERRUPTED
|
||||
if initstate <= 2 and isinstance(excinfo.value, exit.Exception):
|
||||
sys.stderr.write("{}: {}\n".format(excinfo.typename, excinfo.value.msg))
|
||||
@@ -197,7 +222,7 @@ def wrap_session(config, doit):
|
||||
config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
|
||||
session.exitstatus = exitstatus
|
||||
except: # noqa
|
||||
excinfo = _pytest._code.ExceptionInfo()
|
||||
excinfo = _pytest._code.ExceptionInfo.from_current()
|
||||
config.notify_exception(excinfo, config.option)
|
||||
session.exitstatus = EXIT_INTERNALERROR
|
||||
if excinfo.errisinstance(SystemExit):
|
||||
@@ -278,6 +303,20 @@ def pytest_ignore_collect(path, config):
|
||||
if py.path.local(path) in ignore_paths:
|
||||
return True
|
||||
|
||||
ignore_globs = config._getconftest_pathlist(
|
||||
"collect_ignore_glob", path=path.dirpath()
|
||||
)
|
||||
ignore_globs = ignore_globs or []
|
||||
excludeglobopt = config.getoption("ignore_glob")
|
||||
if excludeglobopt:
|
||||
ignore_globs.extend([py.path.local(x) for x in excludeglobopt])
|
||||
|
||||
if any(
|
||||
fnmatch.fnmatch(six.text_type(path), six.text_type(glob))
|
||||
for glob in ignore_globs
|
||||
):
|
||||
return True
|
||||
|
||||
allow_in_venv = config.getoption("collect_in_virtualenv")
|
||||
if not allow_in_venv and _in_venv(path):
|
||||
return True
|
||||
@@ -532,19 +571,9 @@ class Session(nodes.FSCollector):
|
||||
if argpath.check(dir=1):
|
||||
assert not names, "invalid arg %r" % (arg,)
|
||||
|
||||
if six.PY2:
|
||||
|
||||
def filter_(f):
|
||||
return f.check(file=1) and not f.strpath.endswith("*.pyc")
|
||||
|
||||
else:
|
||||
|
||||
def filter_(f):
|
||||
return f.check(file=1)
|
||||
|
||||
seen_dirs = set()
|
||||
for path in argpath.visit(
|
||||
fil=filter_, rec=self._recurse, bf=True, sort=True
|
||||
fil=self._visit_filter, rec=self._recurse, bf=True, sort=True
|
||||
):
|
||||
dirpath = path.dirpath()
|
||||
if dirpath not in seen_dirs:
|
||||
@@ -574,7 +603,7 @@ class Session(nodes.FSCollector):
|
||||
col = self._node_cache[argpath]
|
||||
else:
|
||||
collect_root = self._pkg_roots.get(argpath.dirname, self)
|
||||
col = collect_root._collectfile(argpath)
|
||||
col = collect_root._collectfile(argpath, handle_dupes=False)
|
||||
if col:
|
||||
self._node_cache[argpath] = col
|
||||
m = self.matchnodes(col, names)
|
||||
@@ -589,6 +618,12 @@ class Session(nodes.FSCollector):
|
||||
yield y
|
||||
|
||||
def _collectfile(self, path, handle_dupes=True):
|
||||
assert path.isfile(), "%r is not a file (isdir=%r, exists=%r, islink=%r)" % (
|
||||
path,
|
||||
path.isdir(),
|
||||
path.exists(),
|
||||
path.islink(),
|
||||
)
|
||||
ihook = self.gethookproxy(path)
|
||||
if not self.isinitpath(path):
|
||||
if ihook.pytest_ignore_collect(path=path, config=self.config):
|
||||
@@ -618,6 +653,18 @@ class Session(nodes.FSCollector):
|
||||
ihook.pytest_collect_directory(path=dirpath, parent=self)
|
||||
return True
|
||||
|
||||
if six.PY2:
|
||||
|
||||
@staticmethod
|
||||
def _visit_filter(f):
|
||||
return f.check(file=1) and not f.strpath.endswith("*.pyc")
|
||||
|
||||
else:
|
||||
|
||||
@staticmethod
|
||||
def _visit_filter(f):
|
||||
return f.check(file=1)
|
||||
|
||||
def _tryconvertpyarg(self, x):
|
||||
"""Convert a dotted module name to path."""
|
||||
try:
|
||||
|
||||
@@ -11,19 +11,10 @@ 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
|
||||
|
||||
__all__ = [
|
||||
"Mark",
|
||||
"MarkInfo",
|
||||
"MarkDecorator",
|
||||
"MarkGenerator",
|
||||
"transfer_markers",
|
||||
"get_empty_parameterset_mark",
|
||||
]
|
||||
__all__ = ["Mark", "MarkDecorator", "MarkGenerator", "get_empty_parameterset_mark"]
|
||||
|
||||
|
||||
def param(*values, **kw):
|
||||
|
||||
@@ -45,13 +45,14 @@ class KeywordMapping(object):
|
||||
mapped_names.add(item.name)
|
||||
|
||||
# Add the names added as extra keywords to current or parent items
|
||||
for name in item.listextrakeywords():
|
||||
mapped_names.add(name)
|
||||
mapped_names.update(item.listextrakeywords())
|
||||
|
||||
# Add the names attached to the current function through direct assignment
|
||||
if hasattr(item, "function"):
|
||||
for name in item.function.__dict__:
|
||||
mapped_names.add(name)
|
||||
mapped_names.update(item.function.__dict__)
|
||||
|
||||
# add the markers to the keywords as we no longer handle them correctly
|
||||
mapped_names.update(mark.name for mark in item.iter_markers())
|
||||
|
||||
return cls(mapped_names)
|
||||
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import inspect
|
||||
import warnings
|
||||
from collections import namedtuple
|
||||
from functools import reduce
|
||||
from operator import attrgetter
|
||||
|
||||
import attr
|
||||
from six.moves import map
|
||||
import six
|
||||
|
||||
from ..compat import ascii_escaped
|
||||
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
|
||||
|
||||
|
||||
@@ -70,46 +68,33 @@ class ParameterSet(namedtuple("ParameterSet", "values, marks, id")):
|
||||
else:
|
||||
assert isinstance(marks, (tuple, list, set))
|
||||
|
||||
def param_extract_id(id=None):
|
||||
return id
|
||||
|
||||
id_ = param_extract_id(**kw)
|
||||
id_ = kw.pop("id", None)
|
||||
if id_ is not None:
|
||||
if not isinstance(id_, six.string_types):
|
||||
raise TypeError(
|
||||
"Expected id to be a string, got {}: {!r}".format(type(id_), id_)
|
||||
)
|
||||
id_ = ascii_escaped(id_)
|
||||
return cls(values, marks, id_)
|
||||
|
||||
@classmethod
|
||||
def extract_from(cls, parameterset, belonging_definition, legacy_force_tuple=False):
|
||||
def extract_from(cls, parameterset, force_tuple=False):
|
||||
"""
|
||||
:param parameterset:
|
||||
a legacy style parameterset that may or may not be a tuple,
|
||||
and may or may not be wrapped into a mess of mark objects
|
||||
|
||||
:param legacy_force_tuple:
|
||||
:param force_tuple:
|
||||
enforce tuple wrapping so single argument tuple values
|
||||
don't get decomposed and break tests
|
||||
|
||||
:param belonging_definition: the item that we will be extracting the parameters from.
|
||||
"""
|
||||
|
||||
if isinstance(parameterset, cls):
|
||||
return parameterset
|
||||
if not isinstance(parameterset, MarkDecorator) and legacy_force_tuple:
|
||||
if force_tuple:
|
||||
return cls.param(parameterset)
|
||||
|
||||
newmarks = []
|
||||
argval = parameterset
|
||||
while isinstance(argval, MarkDecorator):
|
||||
newmarks.append(
|
||||
MarkDecorator(Mark(argval.markname, argval.args[:-1], argval.kwargs))
|
||||
)
|
||||
argval = argval.args[-1]
|
||||
assert not isinstance(argval, ParameterSet)
|
||||
if legacy_force_tuple:
|
||||
argval = (argval,)
|
||||
|
||||
if newmarks and belonging_definition is not None:
|
||||
belonging_definition.warn(MARK_PARAMETERSET_UNPACKING)
|
||||
|
||||
return cls(argval, marks=newmarks, id=None)
|
||||
else:
|
||||
return cls(parameterset, marks=[], id=None)
|
||||
|
||||
@classmethod
|
||||
def _for_parametrize(cls, argnames, argvalues, func, config, function_definition):
|
||||
@@ -119,12 +104,7 @@ class ParameterSet(namedtuple("ParameterSet", "values, marks, id")):
|
||||
else:
|
||||
force_tuple = False
|
||||
parameters = [
|
||||
ParameterSet.extract_from(
|
||||
x,
|
||||
legacy_force_tuple=force_tuple,
|
||||
belonging_definition=function_definition,
|
||||
)
|
||||
for x in argvalues
|
||||
ParameterSet.extract_from(x, force_tuple=force_tuple) for x in argvalues
|
||||
]
|
||||
del argvalues
|
||||
|
||||
@@ -132,11 +112,21 @@ class ParameterSet(namedtuple("ParameterSet", "values, marks, id")):
|
||||
# check all parameter sets have the correct number of values
|
||||
for param in parameters:
|
||||
if len(param.values) != len(argnames):
|
||||
raise ValueError(
|
||||
'In "parametrize" the number of values ({}) must be '
|
||||
"equal to the number of names ({})".format(
|
||||
param.values, argnames
|
||||
)
|
||||
msg = (
|
||||
'{nodeid}: in "parametrize" the number of names ({names_len}):\n'
|
||||
" {names}\n"
|
||||
"must be equal to the number of values ({values_len}):\n"
|
||||
" {values}"
|
||||
)
|
||||
fail(
|
||||
msg.format(
|
||||
nodeid=function_definition.nodeid,
|
||||
values=param.values,
|
||||
names=argnames,
|
||||
names_len=len(argnames),
|
||||
values_len=len(param.values),
|
||||
),
|
||||
pytrace=False,
|
||||
)
|
||||
else:
|
||||
# empty parameter set (likely computed at runtime): create a single
|
||||
@@ -153,9 +143,9 @@ class Mark(object):
|
||||
#: name of the mark
|
||||
name = attr.ib(type=str)
|
||||
#: positional arguments of the mark decorator
|
||||
args = attr.ib() # type: List[object]
|
||||
args = attr.ib() # List[object]
|
||||
#: keyword arguments of the mark decorator
|
||||
kwargs = attr.ib() # type: Dict[str, object]
|
||||
kwargs = attr.ib() # Dict[str, object]
|
||||
|
||||
def combined_with(self, other):
|
||||
"""
|
||||
@@ -240,11 +230,7 @@ class MarkDecorator(object):
|
||||
func = args[0]
|
||||
is_class = inspect.isclass(func)
|
||||
if len(args) == 1 and (istestfunc(func) or is_class):
|
||||
if is_class:
|
||||
store_mark(func, self.mark)
|
||||
else:
|
||||
store_legacy_markinfo(func, self.mark)
|
||||
store_mark(func, self.mark)
|
||||
store_mark(func, self.mark)
|
||||
return func
|
||||
return self.with_args(*args, **kwargs)
|
||||
|
||||
@@ -266,7 +252,13 @@ def normalize_mark_list(mark_list):
|
||||
:type mark_list: List[Union[Mark, Markdecorator]]
|
||||
:rtype: List[Mark]
|
||||
"""
|
||||
return [getattr(mark, "mark", mark) for mark in mark_list] # unpack MarkDecorator
|
||||
extracted = [
|
||||
getattr(mark, "mark", mark) for mark in mark_list
|
||||
] # unpack MarkDecorator
|
||||
for mark in extracted:
|
||||
if not isinstance(mark, Mark):
|
||||
raise TypeError("got {!r} instead of Mark".format(mark))
|
||||
return [x for x in extracted if isinstance(x, Mark)]
|
||||
|
||||
|
||||
def store_mark(obj, mark):
|
||||
@@ -279,90 +271,6 @@ def store_mark(obj, mark):
|
||||
obj.pytestmark = get_unpacked_marks(obj) + [mark]
|
||||
|
||||
|
||||
def store_legacy_markinfo(func, mark):
|
||||
"""create the legacy MarkInfo objects and put them onto the function
|
||||
"""
|
||||
if not isinstance(mark, Mark):
|
||||
raise TypeError("got {mark!r} instead of a Mark".format(mark=mark))
|
||||
holder = getattr(func, mark.name, None)
|
||||
if holder is None:
|
||||
holder = MarkInfo.for_mark(mark)
|
||||
setattr(func, mark.name, holder)
|
||||
elif isinstance(holder, MarkInfo):
|
||||
holder.add_mark(mark)
|
||||
|
||||
|
||||
def transfer_markers(funcobj, cls, mod):
|
||||
"""
|
||||
this function transfers class level markers and module level markers
|
||||
into function level markinfo objects
|
||||
|
||||
this is the main reason why marks are so broken
|
||||
the resolution will involve phasing out function level MarkInfo objects
|
||||
|
||||
"""
|
||||
for obj in (cls, mod):
|
||||
for mark in get_unpacked_marks(obj):
|
||||
if not _marked(funcobj, mark):
|
||||
store_legacy_markinfo(funcobj, mark)
|
||||
|
||||
|
||||
def _marked(func, mark):
|
||||
""" Returns True if :func: is already marked with :mark:, False otherwise.
|
||||
This can happen if marker is applied to class and the test file is
|
||||
invoked more than once.
|
||||
"""
|
||||
try:
|
||||
func_mark = getattr(func, getattr(mark, "combined", mark).name)
|
||||
except AttributeError:
|
||||
return False
|
||||
return any(mark == info.combined for info in func_mark)
|
||||
|
||||
|
||||
@attr.s(repr=False)
|
||||
class MarkInfo(object):
|
||||
""" Marking object created by :class:`MarkDecorator` instances. """
|
||||
|
||||
_marks = attr.ib(converter=list)
|
||||
|
||||
@_marks.validator
|
||||
def validate_marks(self, attribute, value):
|
||||
for item in value:
|
||||
if not isinstance(item, Mark):
|
||||
raise ValueError(
|
||||
"MarkInfo expects Mark instances, got {!r} ({!r})".format(
|
||||
item, type(item)
|
||||
)
|
||||
)
|
||||
|
||||
combined = attr.ib(
|
||||
repr=False,
|
||||
default=attr.Factory(
|
||||
lambda self: reduce(Mark.combined_with, self._marks), takes_self=True
|
||||
),
|
||||
)
|
||||
|
||||
name = alias("combined.name", warning=MARK_INFO_ATTRIBUTE)
|
||||
args = alias("combined.args", warning=MARK_INFO_ATTRIBUTE)
|
||||
kwargs = alias("combined.kwargs", warning=MARK_INFO_ATTRIBUTE)
|
||||
|
||||
@classmethod
|
||||
def for_mark(cls, mark):
|
||||
return cls([mark])
|
||||
|
||||
def __repr__(self):
|
||||
return "<MarkInfo {!r}>".format(self.combined)
|
||||
|
||||
def add_mark(self, mark):
|
||||
""" add a MarkInfo with the given args and kwargs. """
|
||||
self._marks.append(mark)
|
||||
self.combined = self.combined.combined_with(mark)
|
||||
|
||||
def __iter__(self):
|
||||
""" yield MarkInfo objects each relating to a marking-call. """
|
||||
return map(MarkInfo.for_mark, self._marks)
|
||||
|
||||
|
||||
class MarkGenerator(object):
|
||||
""" Factory for :class:`MarkDecorator` objects - exposed as
|
||||
a ``pytest.mark`` singleton instance. Example::
|
||||
|
||||
@@ -181,6 +181,8 @@ class MonkeyPatch(object):
|
||||
attribute is missing.
|
||||
"""
|
||||
__tracebackhide__ = True
|
||||
import inspect
|
||||
|
||||
if name is notset:
|
||||
if not isinstance(target, six.string_types):
|
||||
raise TypeError(
|
||||
@@ -194,7 +196,11 @@ class MonkeyPatch(object):
|
||||
if raising:
|
||||
raise AttributeError(name)
|
||||
else:
|
||||
self._setattr.append((target, name, getattr(target, name, notset)))
|
||||
oldval = getattr(target, name, notset)
|
||||
# Avoid class descriptors like staticmethod/classmethod.
|
||||
if inspect.isclass(target):
|
||||
oldval = target.__dict__.get(name, notset)
|
||||
self._setattr.append((target, name, oldval))
|
||||
delattr(target, name)
|
||||
|
||||
def setitem(self, dic, name, value):
|
||||
|
||||
@@ -5,13 +5,11 @@ from __future__ import print_function
|
||||
import os
|
||||
import warnings
|
||||
|
||||
import attr
|
||||
import py
|
||||
import six
|
||||
|
||||
import _pytest._code
|
||||
from _pytest.compat import getfslineno
|
||||
from _pytest.mark.structures import MarkInfo
|
||||
from _pytest.mark.structures import NodeKeywords
|
||||
from _pytest.outcomes import fail
|
||||
|
||||
@@ -56,22 +54,6 @@ def ischildnode(baseid, nodeid):
|
||||
return node_parts[: len(base_parts)] == base_parts
|
||||
|
||||
|
||||
@attr.s
|
||||
class _CompatProperty(object):
|
||||
name = attr.ib()
|
||||
|
||||
def __get__(self, obj, owner):
|
||||
if obj is None:
|
||||
return self
|
||||
|
||||
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)
|
||||
|
||||
|
||||
class Node(object):
|
||||
""" base class for Collector and Item the test collection tree.
|
||||
Collector subclasses have children, Items are terminal nodes."""
|
||||
@@ -119,95 +101,10 @@ class Node(object):
|
||||
""" fspath sensitive hook proxy used to call pytest hooks"""
|
||||
return self.session.gethookproxy(self.fspath)
|
||||
|
||||
Module = _CompatProperty("Module")
|
||||
Class = _CompatProperty("Class")
|
||||
Instance = _CompatProperty("Instance")
|
||||
Function = _CompatProperty("Function")
|
||||
File = _CompatProperty("File")
|
||||
Item = _CompatProperty("Item")
|
||||
|
||||
def _getcustomclass(self, name):
|
||||
maybe_compatprop = getattr(type(self), name)
|
||||
if isinstance(maybe_compatprop, _CompatProperty):
|
||||
return getattr(__import__("pytest"), name)
|
||||
else:
|
||||
from _pytest.deprecated import CUSTOM_CLASS
|
||||
|
||||
cls = getattr(self, name)
|
||||
self.warn(CUSTOM_CLASS.format(name=name, type_name=type(self).__name__))
|
||||
return cls
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s %r>" % (self.__class__.__name__, getattr(self, "name", None))
|
||||
return "<%s %s>" % (self.__class__.__name__, getattr(self, "name", None))
|
||||
|
||||
def warn(self, _code_or_warning=None, message=None, code=None):
|
||||
"""Issue a warning for this item.
|
||||
|
||||
Warnings will be displayed after the test session, unless explicitly suppressed.
|
||||
|
||||
This can be called in two forms:
|
||||
|
||||
**Warning instance**
|
||||
|
||||
This was introduced in pytest 3.8 and uses the standard warning mechanism to issue warnings.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
node.warn(PytestWarning("some message"))
|
||||
|
||||
The warning instance must be a subclass of :class:`pytest.PytestWarning`.
|
||||
|
||||
**code/message (deprecated)**
|
||||
|
||||
This form was used in pytest prior to 3.8 and is considered deprecated. Using this form will emit another
|
||||
warning about the deprecation:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
node.warn("CI", "some message")
|
||||
|
||||
:param Union[Warning,str] _code_or_warning:
|
||||
warning instance or warning code (legacy). This parameter receives an underscore for backward
|
||||
compatibility with the legacy code/message form, and will be replaced for something
|
||||
more usual when the legacy form is removed.
|
||||
|
||||
:param Union[str,None] message: message to display when called in the legacy form.
|
||||
:param str code: code for the warning, in legacy form when using keyword arguments.
|
||||
:return:
|
||||
"""
|
||||
if message is None:
|
||||
if _code_or_warning is None:
|
||||
raise ValueError("code_or_warning must be given")
|
||||
self._std_warn(_code_or_warning)
|
||||
else:
|
||||
if _code_or_warning and code:
|
||||
raise ValueError(
|
||||
"code_or_warning and code cannot both be passed to this function"
|
||||
)
|
||||
code = _code_or_warning or code
|
||||
self._legacy_warn(code, message)
|
||||
|
||||
def _legacy_warn(self, code, message):
|
||||
"""
|
||||
.. deprecated:: 3.8
|
||||
|
||||
Use :meth:`Node.std_warn <_pytest.nodes.Node.std_warn>` instead.
|
||||
|
||||
Generate a warning with the given code and message for this item.
|
||||
"""
|
||||
from _pytest.deprecated import NODE_WARN
|
||||
|
||||
self._std_warn(NODE_WARN)
|
||||
|
||||
assert isinstance(code, str)
|
||||
fslocation = get_fslocation_from_item(self)
|
||||
self.ihook.pytest_logwarning.call_historic(
|
||||
kwargs=dict(
|
||||
code=code, message=message, nodeid=self.nodeid, fslocation=fslocation
|
||||
)
|
||||
)
|
||||
|
||||
def _std_warn(self, warning):
|
||||
def warn(self, warning):
|
||||
"""Issue a warning for this item.
|
||||
|
||||
Warnings will be displayed after the test session, unless explicitly suppressed
|
||||
@@ -215,6 +112,13 @@ class Node(object):
|
||||
:param Warning warning: the warning instance to issue. Must be a subclass of PytestWarning.
|
||||
|
||||
:raise ValueError: if ``warning`` instance is not a subclass of PytestWarning.
|
||||
|
||||
Example usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
node.warn(PytestWarning("some message"))
|
||||
|
||||
"""
|
||||
from _pytest.warning_types import PytestWarning
|
||||
|
||||
@@ -307,20 +211,6 @@ class Node(object):
|
||||
"""
|
||||
return next(self.iter_markers(name=name), default)
|
||||
|
||||
def get_marker(self, name):
|
||||
""" get a marker object from this node or None if
|
||||
the node doesn't have a marker with that name.
|
||||
|
||||
.. deprecated:: 3.6
|
||||
This function has been deprecated in favor of
|
||||
:meth:`Node.get_closest_marker <_pytest.nodes.Node.get_closest_marker>` and
|
||||
:meth:`Node.iter_markers <_pytest.nodes.Node.iter_markers>`, see :ref:`update marker code`
|
||||
for more details.
|
||||
"""
|
||||
markers = list(self.iter_markers(name=name))
|
||||
if markers:
|
||||
return MarkInfo(markers)
|
||||
|
||||
def listextrakeywords(self):
|
||||
""" Return a set of all extra keywords in self and any parents."""
|
||||
extra_keywords = set()
|
||||
|
||||
@@ -5,6 +5,8 @@ from __future__ import print_function
|
||||
|
||||
import sys
|
||||
|
||||
import six
|
||||
|
||||
from _pytest import python
|
||||
from _pytest import runner
|
||||
from _pytest import unittest
|
||||
@@ -23,20 +25,15 @@ def get_skip_exceptions():
|
||||
def pytest_runtest_makereport(item, call):
|
||||
if call.excinfo and call.excinfo.errisinstance(get_skip_exceptions()):
|
||||
# let's substitute the excinfo with a pytest.skip one
|
||||
call2 = call.__class__(lambda: runner.skip(str(call.excinfo.value)), call.when)
|
||||
call2 = runner.CallInfo.from_call(
|
||||
lambda: runner.skip(six.text_type(call.excinfo.value)), call.when
|
||||
)
|
||||
call.excinfo = call2.excinfo
|
||||
|
||||
|
||||
@hookimpl(trylast=True)
|
||||
def pytest_runtest_setup(item):
|
||||
if is_potential_nosetest(item):
|
||||
if isinstance(item.parent, python.Generator):
|
||||
gen = item.parent
|
||||
if not hasattr(gen, "_nosegensetup"):
|
||||
call_optional(gen.obj, "setup")
|
||||
if isinstance(gen.parent, python.Instance):
|
||||
call_optional(gen.parent.obj, "setup")
|
||||
gen._nosegensetup = True
|
||||
if not call_optional(item.obj, "setup"):
|
||||
# call module level setup if there is no object level one
|
||||
call_optional(item.parent.obj, "setup")
|
||||
@@ -53,11 +50,6 @@ def teardown_nose(item):
|
||||
# del item.parent._nosegensetup
|
||||
|
||||
|
||||
def pytest_make_collect_report(collector):
|
||||
if isinstance(collector, python.Generator):
|
||||
call_optional(collector.obj, "setup")
|
||||
|
||||
|
||||
def is_potential_nosetest(item):
|
||||
# extra check needed since we do not do nose style setup/teardown
|
||||
# on direct unittest style classes
|
||||
|
||||
@@ -49,13 +49,13 @@ class Failed(OutcomeException):
|
||||
__module__ = "builtins"
|
||||
|
||||
|
||||
class Exit(KeyboardInterrupt):
|
||||
class Exit(Exception):
|
||||
""" raised for immediate program exits (no tracebacks/summaries)"""
|
||||
|
||||
def __init__(self, msg="unknown reason", returncode=None):
|
||||
self.msg = msg
|
||||
self.returncode = returncode
|
||||
KeyboardInterrupt.__init__(self, msg)
|
||||
super(Exit, self).__init__(msg)
|
||||
|
||||
|
||||
# exposed helper methods
|
||||
@@ -63,7 +63,7 @@ class Exit(KeyboardInterrupt):
|
||||
|
||||
def exit(msg, returncode=None):
|
||||
"""
|
||||
Exit testing process as if KeyboardInterrupt was triggered.
|
||||
Exit testing process.
|
||||
|
||||
:param str msg: message to display upon exit.
|
||||
:param int returncode: return code to be used when exiting pytest.
|
||||
@@ -137,10 +137,15 @@ def xfail(reason=""):
|
||||
xfail.Exception = XFailed
|
||||
|
||||
|
||||
def importorskip(modname, minversion=None):
|
||||
""" return imported module if it has at least "minversion" as its
|
||||
__version__ attribute. If no minversion is specified the a skip
|
||||
is only triggered if the module can not be imported.
|
||||
def importorskip(modname, minversion=None, reason=None):
|
||||
"""Imports and returns the requested module ``modname``, or skip the current test
|
||||
if the module cannot be imported.
|
||||
|
||||
:param str modname: the name of the module to import
|
||||
:param str minversion: if given, the imported module ``__version__`` attribute must be
|
||||
at least this minimal version, otherwise the test is still skipped.
|
||||
:param str reason: if given, this reason is shown as the message when the module
|
||||
cannot be imported.
|
||||
"""
|
||||
import warnings
|
||||
|
||||
@@ -159,7 +164,9 @@ def importorskip(modname, minversion=None):
|
||||
# Do not raise chained exception here(#1485)
|
||||
should_skip = True
|
||||
if should_skip:
|
||||
raise Skipped("could not import %r" % (modname,), allow_module_level=True)
|
||||
if reason is None:
|
||||
reason = "could not import %r" % (modname,)
|
||||
raise Skipped(reason, allow_module_level=True)
|
||||
mod = sys.modules[modname]
|
||||
if minversion is None:
|
||||
return mod
|
||||
|
||||
@@ -4,6 +4,7 @@ from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import codecs
|
||||
import distutils.spawn
|
||||
import gc
|
||||
import os
|
||||
import platform
|
||||
@@ -20,6 +21,7 @@ import six
|
||||
|
||||
import pytest
|
||||
from _pytest._code import Source
|
||||
from _pytest._io.saferepr import saferepr
|
||||
from _pytest.assertion.rewrite import AssertionRewritingHook
|
||||
from _pytest.capture import MultiCapture
|
||||
from _pytest.capture import SysCapture
|
||||
@@ -79,7 +81,11 @@ class LsofFdLeakChecker(object):
|
||||
|
||||
def _exec_lsof(self):
|
||||
pid = os.getpid()
|
||||
return py.process.cmdexec("lsof -Ffn0 -p %d" % pid)
|
||||
# py3: use subprocess.DEVNULL directly.
|
||||
with open(os.devnull, "wb") as devnull:
|
||||
return subprocess.check_output(
|
||||
("lsof", "-Ffn0", "-p", str(pid)), stderr=devnull
|
||||
).decode()
|
||||
|
||||
def _parse_lsof_output(self, out):
|
||||
def isopen(line):
|
||||
@@ -106,11 +112,8 @@ class LsofFdLeakChecker(object):
|
||||
|
||||
def matching_platform(self):
|
||||
try:
|
||||
py.process.cmdexec("lsof -v")
|
||||
except (py.process.cmdexec.Error, UnicodeDecodeError):
|
||||
# cmdexec may raise UnicodeDecodeError on Windows systems with
|
||||
# locale other than English:
|
||||
# https://bitbucket.org/pytest-dev/py/issues/66
|
||||
subprocess.check_output(("lsof", "-v"))
|
||||
except (OSError, subprocess.CalledProcessError):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
@@ -152,7 +155,7 @@ def getexecutable(name, cache={}):
|
||||
try:
|
||||
return cache[name]
|
||||
except KeyError:
|
||||
executable = py.path.local.sysfind(name)
|
||||
executable = distutils.spawn.find_executable(name)
|
||||
if executable:
|
||||
import subprocess
|
||||
|
||||
@@ -306,13 +309,10 @@ class HookRecorder(object):
|
||||
"""return a testreport whose dotted import path matches"""
|
||||
values = []
|
||||
for rep in self.getreports(names=names):
|
||||
try:
|
||||
if not when and rep.when != "call" and rep.passed:
|
||||
# setup/teardown passing reports - let's ignore those
|
||||
continue
|
||||
except AttributeError:
|
||||
pass
|
||||
if when and getattr(rep, "when", None) != when:
|
||||
if not when and rep.when != "call" and rep.passed:
|
||||
# setup/teardown passing reports - let's ignore those
|
||||
continue
|
||||
if when and rep.when != when:
|
||||
continue
|
||||
if not inamepart or inamepart in rep.nodeid.split("::"):
|
||||
values.append(rep)
|
||||
@@ -339,7 +339,7 @@ class HookRecorder(object):
|
||||
failed = []
|
||||
for rep in self.getreports("pytest_collectreport pytest_runtest_logreport"):
|
||||
if rep.passed:
|
||||
if getattr(rep, "when", None) == "call":
|
||||
if rep.when == "call":
|
||||
passed.append(rep)
|
||||
elif rep.skipped:
|
||||
skipped.append(rep)
|
||||
@@ -402,6 +402,12 @@ class RunResult(object):
|
||||
self.stderr = LineMatcher(errlines)
|
||||
self.duration = duration
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
"<RunResult ret=%r len(stdout.lines)=%d len(stderr.lines)=%d duration=%.2fs>"
|
||||
% (self.ret, len(self.stdout.lines), len(self.stderr.lines), self.duration)
|
||||
)
|
||||
|
||||
def parseoutcomes(self):
|
||||
"""Return a dictionary of outcomestring->num from parsing the terminal
|
||||
output that the test process produced.
|
||||
@@ -1225,9 +1231,7 @@ def getdecoded(out):
|
||||
try:
|
||||
return out.decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
return "INTERNAL not-utf8-decodeable, truncated string:\n%s" % (
|
||||
py.io.saferepr(out),
|
||||
)
|
||||
return "INTERNAL not-utf8-decodeable, truncated string:\n%s" % (saferepr(out),)
|
||||
|
||||
|
||||
class LineComp(object):
|
||||
|
||||
@@ -9,6 +9,7 @@ import inspect
|
||||
import os
|
||||
import sys
|
||||
import warnings
|
||||
from functools import partial
|
||||
from textwrap import dedent
|
||||
|
||||
import py
|
||||
@@ -38,13 +39,12 @@ 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 import MARK_GEN
|
||||
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.pathlib import parts
|
||||
from _pytest.warning_types import PytestWarning
|
||||
from _pytest.warning_types import RemovedInPytest4Warning
|
||||
|
||||
|
||||
def pyobj_property(name):
|
||||
@@ -125,10 +125,10 @@ def pytest_generate_tests(metafunc):
|
||||
# those alternative spellings are common - raise a specific error to alert
|
||||
# the user
|
||||
alt_spellings = ["parameterize", "parametrise", "parameterise"]
|
||||
for attr in alt_spellings:
|
||||
if hasattr(metafunc.function, attr):
|
||||
for mark_name in alt_spellings:
|
||||
if metafunc.definition.get_closest_marker(mark_name):
|
||||
msg = "{0} has '{1}' mark, spelling should be 'parametrize'"
|
||||
fail(msg.format(metafunc.function.__name__, attr), pytrace=False)
|
||||
fail(msg.format(metafunc.function.__name__, mark_name), pytrace=False)
|
||||
for marker in metafunc.definition.iter_markers(name="parametrize"):
|
||||
metafunc.parametrize(*marker.args, **marker.kwargs)
|
||||
|
||||
@@ -199,7 +199,6 @@ def pytest_pycollect_makeitem(collector, name, obj):
|
||||
# nothing was collected elsewhere, let's do it here
|
||||
if safe_isclass(obj):
|
||||
if collector.istestclass(obj, name):
|
||||
Class = collector._getcustomclass("Class")
|
||||
outcome.force_result(Class(name, parent=collector))
|
||||
elif collector.istestfunction(obj, name):
|
||||
# mock seems to store unbound methods (issue473), normalize it
|
||||
@@ -219,7 +218,10 @@ def pytest_pycollect_makeitem(collector, name, obj):
|
||||
)
|
||||
elif getattr(obj, "__test__", True):
|
||||
if is_generator(obj):
|
||||
res = Generator(name, parent=collector)
|
||||
res = Function(name, parent=collector)
|
||||
reason = deprecated.YIELD_TESTS.format(name=name)
|
||||
res.add_marker(MARK_GEN.xfail(run=False, reason=reason))
|
||||
res.warn(PytestWarning(reason))
|
||||
else:
|
||||
res = list(collector._genfunctions(name, obj))
|
||||
outcome.force_result(res)
|
||||
@@ -282,9 +284,6 @@ class PyobjMixin(PyobjContext):
|
||||
s = ".".join(parts)
|
||||
return s.replace(".[", "[")
|
||||
|
||||
def _getfslineno(self):
|
||||
return getfslineno(self.obj)
|
||||
|
||||
def reportinfo(self):
|
||||
# XXX caching?
|
||||
obj = self.obj
|
||||
@@ -375,10 +374,6 @@ class PyCollector(PyobjMixin, nodes.Collector):
|
||||
values.sort(key=lambda item: item.reportinfo()[:2])
|
||||
return values
|
||||
|
||||
def makeitem(self, name, obj):
|
||||
warnings.warn(deprecated.COLLECTOR_MAKEITEM, stacklevel=2)
|
||||
self._makeitem(name, obj)
|
||||
|
||||
def _makeitem(self, name, obj):
|
||||
# assert self.ihook.fspath == self.fspath, self
|
||||
return self.ihook.pytest_pycollect_makeitem(collector=self, name=name, obj=obj)
|
||||
@@ -387,7 +382,6 @@ class PyCollector(PyobjMixin, nodes.Collector):
|
||||
module = self.getparent(Module).obj
|
||||
clscol = self.getparent(Class)
|
||||
cls = clscol and clscol.obj or None
|
||||
transfer_markers(funcobj, cls, module)
|
||||
fm = self.session._fixturemanager
|
||||
|
||||
definition = FunctionDefinition(name=name, parent=self, callobj=funcobj)
|
||||
@@ -408,7 +402,6 @@ class PyCollector(PyobjMixin, nodes.Collector):
|
||||
else:
|
||||
self.ihook.pytest_generate_tests(metafunc=metafunc)
|
||||
|
||||
Function = self._getcustomclass("Function")
|
||||
if not metafunc._calls:
|
||||
yield Function(name, parent=self, fixtureinfo=fixtureinfo)
|
||||
else:
|
||||
@@ -440,9 +433,66 @@ class Module(nodes.File, PyCollector):
|
||||
return self._importtestmodule()
|
||||
|
||||
def collect(self):
|
||||
self._inject_setup_module_fixture()
|
||||
self._inject_setup_function_fixture()
|
||||
self.session._fixturemanager.parsefactories(self)
|
||||
return super(Module, self).collect()
|
||||
|
||||
def _inject_setup_module_fixture(self):
|
||||
"""Injects a hidden autouse, module scoped fixture into the collected module object
|
||||
that invokes setUpModule/tearDownModule if either or both are available.
|
||||
|
||||
Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with
|
||||
other fixtures (#517).
|
||||
"""
|
||||
setup_module = _get_non_fixture_func(self.obj, "setUpModule")
|
||||
if setup_module is None:
|
||||
setup_module = _get_non_fixture_func(self.obj, "setup_module")
|
||||
|
||||
teardown_module = _get_non_fixture_func(self.obj, "tearDownModule")
|
||||
if teardown_module is None:
|
||||
teardown_module = _get_non_fixture_func(self.obj, "teardown_module")
|
||||
|
||||
if setup_module is None and teardown_module is None:
|
||||
return
|
||||
|
||||
@fixtures.fixture(autouse=True, scope="module")
|
||||
def xunit_setup_module_fixture(request):
|
||||
if setup_module is not None:
|
||||
_call_with_optional_argument(setup_module, request.module)
|
||||
yield
|
||||
if teardown_module is not None:
|
||||
_call_with_optional_argument(teardown_module, request.module)
|
||||
|
||||
self.obj.__pytest_setup_module = xunit_setup_module_fixture
|
||||
|
||||
def _inject_setup_function_fixture(self):
|
||||
"""Injects a hidden autouse, function scoped fixture into the collected module object
|
||||
that invokes setup_function/teardown_function if either or both are available.
|
||||
|
||||
Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with
|
||||
other fixtures (#517).
|
||||
"""
|
||||
setup_function = _get_non_fixture_func(self.obj, "setup_function")
|
||||
teardown_function = _get_non_fixture_func(self.obj, "teardown_function")
|
||||
if setup_function is None and teardown_function is None:
|
||||
return
|
||||
|
||||
@fixtures.fixture(autouse=True, scope="function")
|
||||
def xunit_setup_function_fixture(request):
|
||||
if request.instance is not None:
|
||||
# in this case we are bound to an instance, so we need to let
|
||||
# setup_method handle this
|
||||
yield
|
||||
return
|
||||
if setup_function is not None:
|
||||
_call_with_optional_argument(setup_function, request.function)
|
||||
yield
|
||||
if teardown_function is not None:
|
||||
_call_with_optional_argument(teardown_function, request.function)
|
||||
|
||||
self.obj.__pytest_setup_function = xunit_setup_function_fixture
|
||||
|
||||
def _importtestmodule(self):
|
||||
# we assume we are only called once per module
|
||||
importmode = self.config.getoption("--import-mode")
|
||||
@@ -450,7 +500,7 @@ class Module(nodes.File, PyCollector):
|
||||
mod = self.fspath.pyimport(ensuresyspath=importmode)
|
||||
except SyntaxError:
|
||||
raise self.CollectError(
|
||||
_pytest._code.ExceptionInfo().getrepr(style="short")
|
||||
_pytest._code.ExceptionInfo.from_current().getrepr(style="short")
|
||||
)
|
||||
except self.fspath.ImportMismatchError:
|
||||
e = sys.exc_info()[1]
|
||||
@@ -466,7 +516,7 @@ class Module(nodes.File, PyCollector):
|
||||
except ImportError:
|
||||
from _pytest._code.code import ExceptionInfo
|
||||
|
||||
exc_info = ExceptionInfo()
|
||||
exc_info = ExceptionInfo.from_current()
|
||||
if self.config.getoption("verbose") < 2:
|
||||
exc_info.traceback = exc_info.traceback.filter(filter_traceback)
|
||||
exc_repr = (
|
||||
@@ -493,19 +543,6 @@ class Module(nodes.File, PyCollector):
|
||||
self.config.pluginmanager.consider_module(mod)
|
||||
return mod
|
||||
|
||||
def setup(self):
|
||||
setup_module = _get_xunit_setup_teardown(self.obj, "setUpModule")
|
||||
if setup_module is None:
|
||||
setup_module = _get_xunit_setup_teardown(self.obj, "setup_module")
|
||||
if setup_module is not None:
|
||||
setup_module()
|
||||
|
||||
teardown_module = _get_xunit_setup_teardown(self.obj, "tearDownModule")
|
||||
if teardown_module is None:
|
||||
teardown_module = _get_xunit_setup_teardown(self.obj, "teardown_module")
|
||||
if teardown_module is not None:
|
||||
self.addfinalizer(teardown_module)
|
||||
|
||||
|
||||
class Package(Module):
|
||||
def __init__(self, fspath, parent=None, config=None, session=None, nodeid=None):
|
||||
@@ -518,6 +555,22 @@ class Package(Module):
|
||||
self._norecursepatterns = session._norecursepatterns
|
||||
self.fspath = fspath
|
||||
|
||||
def setup(self):
|
||||
# not using fixtures to call setup_module here because autouse fixtures
|
||||
# from packages are not called automatically (#4085)
|
||||
setup_module = _get_non_fixture_func(self.obj, "setUpModule")
|
||||
if setup_module is None:
|
||||
setup_module = _get_non_fixture_func(self.obj, "setup_module")
|
||||
if setup_module is not None:
|
||||
_call_with_optional_argument(setup_module, self.obj)
|
||||
|
||||
teardown_module = _get_non_fixture_func(self.obj, "tearDownModule")
|
||||
if teardown_module is None:
|
||||
teardown_module = _get_non_fixture_func(self.obj, "teardown_module")
|
||||
if teardown_module is not None:
|
||||
func = partial(_call_with_optional_argument, teardown_module, self.obj)
|
||||
self.addfinalizer(func)
|
||||
|
||||
def _recurse(self, dirpath):
|
||||
if dirpath.basename == "__pycache__":
|
||||
return False
|
||||
@@ -546,6 +599,12 @@ class Package(Module):
|
||||
return proxy
|
||||
|
||||
def _collectfile(self, path, handle_dupes=True):
|
||||
assert path.isfile(), "%r is not a file (isdir=%r, exists=%r, islink=%r)" % (
|
||||
path,
|
||||
path.isdir(),
|
||||
path.exists(),
|
||||
path.islink(),
|
||||
)
|
||||
ihook = self.gethookproxy(path)
|
||||
if not self.isinitpath(path):
|
||||
if ihook.pytest_ignore_collect(path=path, config=self.config):
|
||||
@@ -578,7 +637,8 @@ class Package(Module):
|
||||
pkg_prefixes = set()
|
||||
for path in this_path.visit(rec=self._recurse, bf=True, sort=True):
|
||||
# We will visit our own __init__.py file, in which case we skip it.
|
||||
if path.isfile():
|
||||
is_file = path.isfile()
|
||||
if is_file:
|
||||
if path.basename == "__init__.py" and path.dirpath() == this_path:
|
||||
continue
|
||||
|
||||
@@ -589,12 +649,15 @@ class Package(Module):
|
||||
):
|
||||
continue
|
||||
|
||||
if path.isdir() and path.join("__init__.py").check(file=1):
|
||||
if is_file:
|
||||
for x in self._collectfile(path):
|
||||
yield x
|
||||
elif not path.isdir():
|
||||
# Broken symlink or invalid/missing file.
|
||||
continue
|
||||
elif path.join("__init__.py").check(file=1):
|
||||
pkg_prefixes.add(path)
|
||||
|
||||
for x in self._collectfile(path):
|
||||
yield x
|
||||
|
||||
|
||||
def _get_xunit_setup_teardown(holder, attr_name, param_obj=None):
|
||||
"""
|
||||
@@ -604,8 +667,9 @@ def _get_xunit_setup_teardown(holder, attr_name, param_obj=None):
|
||||
when the callable is called without arguments, defaults to the ``holder`` object.
|
||||
Return ``None`` if a suitable callable is not found.
|
||||
"""
|
||||
# TODO: only needed because of Package!
|
||||
param_obj = param_obj if param_obj is not None else holder
|
||||
result = _get_xunit_func(holder, attr_name)
|
||||
result = _get_non_fixture_func(holder, attr_name)
|
||||
if result is not None:
|
||||
arg_count = result.__code__.co_argcount
|
||||
if inspect.ismethod(result):
|
||||
@@ -616,7 +680,19 @@ def _get_xunit_setup_teardown(holder, attr_name, param_obj=None):
|
||||
return result
|
||||
|
||||
|
||||
def _get_xunit_func(obj, name):
|
||||
def _call_with_optional_argument(func, arg):
|
||||
"""Call the given function with the given argument if func accepts one argument, otherwise
|
||||
calls func without arguments"""
|
||||
arg_count = func.__code__.co_argcount
|
||||
if inspect.ismethod(func):
|
||||
arg_count -= 1
|
||||
if arg_count:
|
||||
func(arg)
|
||||
else:
|
||||
func()
|
||||
|
||||
|
||||
def _get_non_fixture_func(obj, name):
|
||||
"""Return the attribute from the given object to be used as a setup/teardown
|
||||
xunit-style function, but only if not marked as a fixture to
|
||||
avoid calling it twice.
|
||||
@@ -648,18 +724,60 @@ class Class(PyCollector):
|
||||
)
|
||||
)
|
||||
return []
|
||||
return [self._getcustomclass("Instance")(name="()", parent=self)]
|
||||
|
||||
def setup(self):
|
||||
setup_class = _get_xunit_func(self.obj, "setup_class")
|
||||
if setup_class is not None:
|
||||
setup_class = getimfunc(setup_class)
|
||||
setup_class(self.obj)
|
||||
self._inject_setup_class_fixture()
|
||||
self._inject_setup_method_fixture()
|
||||
|
||||
fin_class = getattr(self.obj, "teardown_class", None)
|
||||
if fin_class is not None:
|
||||
fin_class = getimfunc(fin_class)
|
||||
self.addfinalizer(lambda: fin_class(self.obj))
|
||||
return [Instance(name="()", parent=self)]
|
||||
|
||||
def _inject_setup_class_fixture(self):
|
||||
"""Injects a hidden autouse, class scoped fixture into the collected class object
|
||||
that invokes setup_class/teardown_class if either or both are available.
|
||||
|
||||
Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with
|
||||
other fixtures (#517).
|
||||
"""
|
||||
setup_class = _get_non_fixture_func(self.obj, "setup_class")
|
||||
teardown_class = getattr(self.obj, "teardown_class", None)
|
||||
if setup_class is None and teardown_class is None:
|
||||
return
|
||||
|
||||
@fixtures.fixture(autouse=True, scope="class")
|
||||
def xunit_setup_class_fixture(cls):
|
||||
if setup_class is not None:
|
||||
func = getimfunc(setup_class)
|
||||
_call_with_optional_argument(func, self.obj)
|
||||
yield
|
||||
if teardown_class is not None:
|
||||
func = getimfunc(teardown_class)
|
||||
_call_with_optional_argument(func, self.obj)
|
||||
|
||||
self.obj.__pytest_setup_class = xunit_setup_class_fixture
|
||||
|
||||
def _inject_setup_method_fixture(self):
|
||||
"""Injects a hidden autouse, function scoped fixture into the collected class object
|
||||
that invokes setup_method/teardown_method if either or both are available.
|
||||
|
||||
Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with
|
||||
other fixtures (#517).
|
||||
"""
|
||||
setup_method = _get_non_fixture_func(self.obj, "setup_method")
|
||||
teardown_method = getattr(self.obj, "teardown_method", None)
|
||||
if setup_method is None and teardown_method is None:
|
||||
return
|
||||
|
||||
@fixtures.fixture(autouse=True, scope="function")
|
||||
def xunit_setup_method_fixture(self, request):
|
||||
method = request.function
|
||||
if setup_method is not None:
|
||||
func = getattr(self, "setup_method")
|
||||
_call_with_optional_argument(func, method)
|
||||
yield
|
||||
if teardown_method is not None:
|
||||
func = getattr(self, "teardown_method")
|
||||
_call_with_optional_argument(func, method)
|
||||
|
||||
self.obj.__pytest_setup_method = xunit_setup_method_fixture
|
||||
|
||||
|
||||
class Instance(PyCollector):
|
||||
@@ -686,29 +804,9 @@ class FunctionMixin(PyobjMixin):
|
||||
|
||||
def setup(self):
|
||||
""" perform setup for this test function. """
|
||||
if hasattr(self, "_preservedparent"):
|
||||
obj = self._preservedparent
|
||||
elif isinstance(self.parent, Instance):
|
||||
obj = self.parent.newinstance()
|
||||
if isinstance(self.parent, Instance):
|
||||
self.parent.newinstance()
|
||||
self.obj = self._getobj()
|
||||
else:
|
||||
obj = self.parent.obj
|
||||
if inspect.ismethod(self.obj):
|
||||
setup_name = "setup_method"
|
||||
teardown_name = "teardown_method"
|
||||
else:
|
||||
setup_name = "setup_function"
|
||||
teardown_name = "teardown_function"
|
||||
setup_func_or_method = _get_xunit_setup_teardown(
|
||||
obj, setup_name, param_obj=self.obj
|
||||
)
|
||||
if setup_func_or_method is not None:
|
||||
setup_func_or_method()
|
||||
teardown_func_or_method = _get_xunit_setup_teardown(
|
||||
obj, teardown_name, param_obj=self.obj
|
||||
)
|
||||
if teardown_func_or_method is not None:
|
||||
self.addfinalizer(teardown_func_or_method)
|
||||
|
||||
def _prunetraceback(self, excinfo):
|
||||
if hasattr(self, "_obj") and not self.config.option.fulltrace:
|
||||
@@ -739,51 +837,6 @@ class FunctionMixin(PyobjMixin):
|
||||
return self._repr_failure_py(excinfo, style=style)
|
||||
|
||||
|
||||
class Generator(FunctionMixin, PyCollector):
|
||||
def collect(self):
|
||||
|
||||
# test generators are seen as collectors but they also
|
||||
# invoke setup/teardown on popular request
|
||||
# (induced by the common "test_*" naming shared with normal tests)
|
||||
from _pytest import deprecated
|
||||
|
||||
self.warn(deprecated.YIELD_TESTS)
|
||||
|
||||
self.session._setupstate.prepare(self)
|
||||
# see FunctionMixin.setup and test_setupstate_is_preserved_134
|
||||
self._preservedparent = self.parent.obj
|
||||
values = []
|
||||
seen = {}
|
||||
_Function = self._getcustomclass("Function")
|
||||
for i, x in enumerate(self.obj()):
|
||||
name, call, args = self.getcallargs(x)
|
||||
if not callable(call):
|
||||
raise TypeError("%r yielded non callable test %r" % (self.obj, call))
|
||||
if name is None:
|
||||
name = "[%d]" % i
|
||||
else:
|
||||
name = "['%s']" % name
|
||||
if name in seen:
|
||||
raise ValueError(
|
||||
"%r generated tests with non-unique name %r" % (self, name)
|
||||
)
|
||||
seen[name] = True
|
||||
values.append(_Function(name, self, args=args, callobj=call))
|
||||
return values
|
||||
|
||||
def getcallargs(self, obj):
|
||||
if not isinstance(obj, (tuple, list)):
|
||||
obj = (obj,)
|
||||
# explicit naming
|
||||
if isinstance(obj[0], six.string_types):
|
||||
name = obj[0]
|
||||
obj = obj[1:]
|
||||
else:
|
||||
name = None
|
||||
call, args = obj[0], obj[1:]
|
||||
return name, call, args
|
||||
|
||||
|
||||
def hasinit(obj):
|
||||
init = getattr(obj, "__init__", None)
|
||||
if init:
|
||||
@@ -986,7 +1039,7 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
|
||||
:rtype: List[str]
|
||||
:return: the list of ids for each argname given
|
||||
"""
|
||||
from py.io import saferepr
|
||||
from _pytest._io.saferepr import saferepr
|
||||
|
||||
idfn = None
|
||||
if callable(ids):
|
||||
@@ -1065,48 +1118,6 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
|
||||
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.
|
||||
|
||||
.. deprecated:: 3.3
|
||||
|
||||
Use :meth:`parametrize` instead.
|
||||
|
||||
Note that request.addcall() is called during the test collection phase prior and
|
||||
independently to actual test execution. You should only use addcall()
|
||||
if you need to specify multiple arguments of a test function.
|
||||
|
||||
:arg funcargs: argument keyword dictionary used when invoking
|
||||
the test function.
|
||||
|
||||
:arg id: used for reporting and identification purposes. If you
|
||||
don't supply an `id` an automatic unique id will be generated.
|
||||
|
||||
:arg param: a parameter which will be exposed to a later fixture function
|
||||
invocation through the ``request.param`` attribute.
|
||||
"""
|
||||
warnings.warn(deprecated.METAFUNC_ADD_CALL, stacklevel=2)
|
||||
|
||||
assert funcargs is None or isinstance(funcargs, dict)
|
||||
if funcargs is not None:
|
||||
for name in funcargs:
|
||||
if name not in self.fixturenames:
|
||||
fail("funcarg %r not used in this function." % name)
|
||||
else:
|
||||
funcargs = {}
|
||||
if id is None:
|
||||
raise ValueError("id=None not allowed")
|
||||
if id is NOTSET:
|
||||
id = len(self._calls)
|
||||
id = str(id)
|
||||
if id in self._ids:
|
||||
raise ValueError("duplicate id %r" % id)
|
||||
self._ids.add(id)
|
||||
|
||||
cs = CallSpec2(self)
|
||||
cs.setall(funcargs, id, param)
|
||||
self._calls.append(cs)
|
||||
|
||||
|
||||
def _find_parametrized_scope(argnames, arg2fixturedefs, indirect):
|
||||
"""Find the most appropriate scope for a parametrized call based on its arguments.
|
||||
@@ -1143,22 +1154,18 @@ def _find_parametrized_scope(argnames, arg2fixturedefs, indirect):
|
||||
|
||||
def _idval(val, argname, idx, idfn, item, config):
|
||||
if idfn:
|
||||
s = None
|
||||
try:
|
||||
s = idfn(val)
|
||||
generated_id = idfn(val)
|
||||
if generated_id is not None:
|
||||
val = generated_id
|
||||
except Exception as e:
|
||||
# See issue https://github.com/pytest-dev/pytest/issues/2169
|
||||
msg = (
|
||||
"While trying to determine id of parameter {} at position "
|
||||
"{} the following exception was raised:\n".format(argname, idx)
|
||||
)
|
||||
msg = "{}: error raised while trying to determine id of parameter '{}' at position {}\n"
|
||||
msg = msg.format(item.nodeid, argname, idx)
|
||||
# we only append the exception type and message because on Python 2 reraise does nothing
|
||||
msg += " {}: {}\n".format(type(e).__name__, e)
|
||||
msg += "This warning will be an error error in pytest-4.0."
|
||||
item.warn(RemovedInPytest4Warning(msg))
|
||||
if s:
|
||||
return ascii_escaped(s)
|
||||
|
||||
if config:
|
||||
six.raise_from(ValueError(msg), e)
|
||||
elif config:
|
||||
hook_id = config.hook.pytest_make_parametrize_id(
|
||||
config=config, val=val, argname=argname
|
||||
)
|
||||
@@ -1326,8 +1333,7 @@ def _showfixtures_main(config, session):
|
||||
tw.line(" %s: no docstring available" % (loc,), red=True)
|
||||
|
||||
|
||||
def write_docstring(tw, doc):
|
||||
INDENT = " "
|
||||
def write_docstring(tw, doc, indent=" "):
|
||||
doc = doc.rstrip()
|
||||
if "\n" in doc:
|
||||
firstline, rest = doc.split("\n", 1)
|
||||
@@ -1335,11 +1341,11 @@ def write_docstring(tw, doc):
|
||||
firstline, rest = doc, ""
|
||||
|
||||
if firstline.strip():
|
||||
tw.line(INDENT + firstline.strip())
|
||||
tw.line(indent + firstline.strip())
|
||||
|
||||
if rest:
|
||||
for line in dedent(rest).split("\n"):
|
||||
tw.write(INDENT + line + "\n")
|
||||
tw.write(indent + line + "\n")
|
||||
|
||||
|
||||
class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr):
|
||||
@@ -1347,7 +1353,6 @@ class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr):
|
||||
Python test function.
|
||||
"""
|
||||
|
||||
_genid = None
|
||||
# disable since functions handle it themselves
|
||||
_ALLOW_MARKERS = False
|
||||
|
||||
@@ -1384,6 +1389,20 @@ class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr):
|
||||
if keywords:
|
||||
self.keywords.update(keywords)
|
||||
|
||||
# todo: this is a hell of a hack
|
||||
# https://github.com/pytest-dev/pytest/issues/4569
|
||||
|
||||
self.keywords.update(
|
||||
dict.fromkeys(
|
||||
[
|
||||
mark.name
|
||||
for mark in self.iter_markers()
|
||||
if mark.name not in self.keywords
|
||||
],
|
||||
True,
|
||||
)
|
||||
)
|
||||
|
||||
if fixtureinfo is None:
|
||||
fixtureinfo = self.session._fixturemanager.getfixtureinfo(
|
||||
self, self.obj, self.cls, funcargs=not self._isyieldedfunction()
|
||||
@@ -1408,7 +1427,6 @@ class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr):
|
||||
if hasattr(self, "callspec"):
|
||||
callspec = self.callspec
|
||||
assert not callspec.funcargs
|
||||
self._genid = callspec.id
|
||||
if hasattr(callspec, "param"):
|
||||
self.param = callspec.param
|
||||
self._request = fixtures.FixtureRequest(self)
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import math
|
||||
import pprint
|
||||
import sys
|
||||
import warnings
|
||||
from decimal import Decimal
|
||||
from numbers import Number
|
||||
|
||||
@@ -10,9 +13,11 @@ from six.moves import filterfalse
|
||||
from six.moves import zip
|
||||
|
||||
import _pytest._code
|
||||
from _pytest import deprecated
|
||||
from _pytest.compat import isclass
|
||||
from _pytest.compat import Iterable
|
||||
from _pytest.compat import Mapping
|
||||
from _pytest.compat import Sequence
|
||||
from _pytest.compat import Sized
|
||||
from _pytest.compat import STRING_TYPES
|
||||
from _pytest.outcomes import fail
|
||||
|
||||
@@ -145,10 +150,10 @@ class ApproxNumpy(ApproxBase):
|
||||
|
||||
if np.isscalar(actual):
|
||||
for i in np.ndindex(self.expected.shape):
|
||||
yield actual, np.asscalar(self.expected[i])
|
||||
yield actual, self.expected[i].item()
|
||||
else:
|
||||
for i in np.ndindex(self.expected.shape):
|
||||
yield np.asscalar(actual[i]), np.asscalar(self.expected[i])
|
||||
yield actual[i].item(), self.expected[i].item()
|
||||
|
||||
|
||||
class ApproxMapping(ApproxBase):
|
||||
@@ -182,7 +187,7 @@ class ApproxMapping(ApproxBase):
|
||||
raise _non_numeric_type_error(self.expected, at="key={!r}".format(key))
|
||||
|
||||
|
||||
class ApproxSequence(ApproxBase):
|
||||
class ApproxSequencelike(ApproxBase):
|
||||
"""
|
||||
Perform approximate comparisons where the expected value is a sequence of
|
||||
numbers.
|
||||
@@ -518,10 +523,14 @@ def approx(expected, rel=None, abs=None, nan_ok=False):
|
||||
cls = ApproxScalar
|
||||
elif isinstance(expected, Mapping):
|
||||
cls = ApproxMapping
|
||||
elif isinstance(expected, Sequence) and not isinstance(expected, STRING_TYPES):
|
||||
cls = ApproxSequence
|
||||
elif _is_numpy_array(expected):
|
||||
cls = ApproxNumpy
|
||||
elif (
|
||||
isinstance(expected, Iterable)
|
||||
and isinstance(expected, Sized)
|
||||
and not isinstance(expected, STRING_TYPES)
|
||||
):
|
||||
cls = ApproxSequencelike
|
||||
else:
|
||||
raise _non_numeric_type_error(expected, at=None)
|
||||
|
||||
@@ -547,29 +556,47 @@ def _is_numpy_array(obj):
|
||||
def raises(expected_exception, *args, **kwargs):
|
||||
r"""
|
||||
Assert that a code block/function call raises ``expected_exception``
|
||||
and raise a failure exception otherwise.
|
||||
or raise a failure exception otherwise.
|
||||
|
||||
:arg message: if specified, provides a custom failure message if the
|
||||
exception is not raised
|
||||
:arg match: if specified, asserts that the exception matches a text or regex
|
||||
:kwparam match: if specified, asserts that the exception matches a text or regex
|
||||
|
||||
This helper produces a ``ExceptionInfo()`` object (see below).
|
||||
:kwparam message: **(deprecated since 4.1)** if specified, provides a custom failure message
|
||||
if the exception is not raised
|
||||
|
||||
You may use this function as a context manager::
|
||||
.. currentmodule:: _pytest._code
|
||||
|
||||
Use ``pytest.raises`` as a context manager, which will capture the exception of the given
|
||||
type::
|
||||
|
||||
>>> with raises(ZeroDivisionError):
|
||||
... 1/0
|
||||
|
||||
.. versionchanged:: 2.10
|
||||
If the code block does not raise the expected exception (``ZeroDivisionError`` in the example
|
||||
above), or no exception at all, the check will fail instead.
|
||||
|
||||
In the context manager form you may use the keyword argument
|
||||
``message`` to specify a custom failure message::
|
||||
You can also use the keyword argument ``match`` to assert that the
|
||||
exception matches a text or regex::
|
||||
|
||||
>>> with raises(ZeroDivisionError, message="Expecting ZeroDivisionError"):
|
||||
... pass
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
Failed: Expecting ZeroDivisionError
|
||||
>>> with raises(ValueError, match='must be 0 or None'):
|
||||
... raise ValueError("value must be 0 or None")
|
||||
|
||||
>>> with raises(ValueError, match=r'must be \d+$'):
|
||||
... raise ValueError("value must be 42")
|
||||
|
||||
The context manager produces an :class:`ExceptionInfo` object which can be used to inspect the
|
||||
details of the captured exception::
|
||||
|
||||
>>> with raises(ValueError) as exc_info:
|
||||
... raise ValueError("value must be 42")
|
||||
>>> assert exc_info.type is ValueError
|
||||
>>> assert exc_info.value.args[0] == "value must be 42"
|
||||
|
||||
.. deprecated:: 4.1
|
||||
|
||||
In the context manager form you may use the keyword argument
|
||||
``message`` to specify a custom failure message that will be displayed
|
||||
in case the ``pytest.raises`` check fails. This has been deprecated as it
|
||||
is considered error prone as users often mean to use ``match`` instead.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -583,7 +610,7 @@ def raises(expected_exception, *args, **kwargs):
|
||||
>>> with raises(ValueError) as exc_info:
|
||||
... if value > 10:
|
||||
... raise ValueError("value must be <= 10")
|
||||
... assert exc_info.type == ValueError # this will not execute
|
||||
... assert exc_info.type is ValueError # this will not execute
|
||||
|
||||
Instead, the following approach must be taken (note the difference in
|
||||
scope)::
|
||||
@@ -592,22 +619,17 @@ def raises(expected_exception, *args, **kwargs):
|
||||
... if value > 10:
|
||||
... raise ValueError("value must be <= 10")
|
||||
...
|
||||
>>> assert exc_info.type == ValueError
|
||||
>>> assert exc_info.type is ValueError
|
||||
|
||||
**Using with** ``pytest.mark.parametrize``
|
||||
|
||||
Since version ``3.1`` you can use the keyword argument ``match`` to assert that the
|
||||
exception matches a text or regex::
|
||||
When using :ref:`pytest.mark.parametrize ref`
|
||||
it is possible to parametrize tests such that
|
||||
some runs raise an exception and others do not.
|
||||
|
||||
>>> with raises(ValueError, match='must be 0 or None'):
|
||||
... raise ValueError("value must be 0 or None")
|
||||
See :ref:`parametrizing_conditional_raising` for an example.
|
||||
|
||||
>>> with raises(ValueError, match=r'must be \d+$'):
|
||||
... raise ValueError("value must be 42")
|
||||
|
||||
**Legacy forms**
|
||||
|
||||
The forms below are fully supported but are discouraged for new code because the
|
||||
context manager form is regarded as more readable and less error-prone.
|
||||
**Legacy form**
|
||||
|
||||
It is possible to specify a callable by passing a to-be-called lambda::
|
||||
|
||||
@@ -623,17 +645,8 @@ def raises(expected_exception, *args, **kwargs):
|
||||
>>> raises(ZeroDivisionError, f, x=0)
|
||||
<ExceptionInfo ...>
|
||||
|
||||
It is also possible to pass a string to be evaluated at runtime::
|
||||
|
||||
>>> raises(ZeroDivisionError, "f(0)")
|
||||
<ExceptionInfo ...>
|
||||
|
||||
The string will be evaluated using the same ``locals()`` and ``globals()``
|
||||
at the moment of the ``raises`` call.
|
||||
|
||||
.. currentmodule:: _pytest._code
|
||||
|
||||
Consult the API of ``excinfo`` objects: :class:`ExceptionInfo`.
|
||||
The form above is fully supported but discouraged for new code because the
|
||||
context manager form is regarded as more readable and less error-prone.
|
||||
|
||||
.. note::
|
||||
Similar to caught exception objects in Python, explicitly clearing
|
||||
@@ -664,6 +677,7 @@ def raises(expected_exception, *args, **kwargs):
|
||||
if not args:
|
||||
if "message" in kwargs:
|
||||
message = kwargs.pop("message")
|
||||
warnings.warn(deprecated.RAISES_MESSAGE_PARAMETER, stacklevel=2)
|
||||
if "match" in kwargs:
|
||||
match_expr = kwargs.pop("match")
|
||||
if kwargs:
|
||||
@@ -672,6 +686,7 @@ def raises(expected_exception, *args, **kwargs):
|
||||
raise TypeError(msg)
|
||||
return RaisesContext(expected_exception, message, match_expr)
|
||||
elif isinstance(args[0], str):
|
||||
warnings.warn(deprecated.RAISES_EXEC, stacklevel=2)
|
||||
code, = args
|
||||
assert isinstance(code, str)
|
||||
frame = sys._getframe(1)
|
||||
@@ -684,13 +699,13 @@ def raises(expected_exception, *args, **kwargs):
|
||||
# XXX didn't mean f_globals == f_locals something special?
|
||||
# this is destroyed here ...
|
||||
except expected_exception:
|
||||
return _pytest._code.ExceptionInfo()
|
||||
return _pytest._code.ExceptionInfo.from_current()
|
||||
else:
|
||||
func = args[0]
|
||||
try:
|
||||
func(*args[1:], **kwargs)
|
||||
except expected_exception:
|
||||
return _pytest._code.ExceptionInfo()
|
||||
return _pytest._code.ExceptionInfo.from_current()
|
||||
fail(message)
|
||||
|
||||
|
||||
@@ -705,7 +720,7 @@ class RaisesContext(object):
|
||||
self.excinfo = None
|
||||
|
||||
def __enter__(self):
|
||||
self.excinfo = object.__new__(_pytest._code.ExceptionInfo)
|
||||
self.excinfo = _pytest._code.ExceptionInfo.for_later()
|
||||
return self.excinfo
|
||||
|
||||
def __exit__(self, *tp):
|
||||
|
||||
@@ -11,6 +11,8 @@ import warnings
|
||||
import six
|
||||
|
||||
import _pytest._code
|
||||
from _pytest.deprecated import PYTEST_WARNS_UNKNOWN_KWARGS
|
||||
from _pytest.deprecated import WARNS_EXEC
|
||||
from _pytest.fixtures import yield_fixture
|
||||
from _pytest.outcomes import fail
|
||||
|
||||
@@ -83,24 +85,27 @@ def warns(expected_warning, *args, **kwargs):
|
||||
|
||||
"""
|
||||
__tracebackhide__ = True
|
||||
match_expr = None
|
||||
if not args:
|
||||
if "match" in kwargs:
|
||||
match_expr = kwargs.pop("match")
|
||||
match_expr = kwargs.pop("match", None)
|
||||
if kwargs:
|
||||
warnings.warn(
|
||||
PYTEST_WARNS_UNKNOWN_KWARGS.format(args=sorted(kwargs)), stacklevel=2
|
||||
)
|
||||
return WarningsChecker(expected_warning, match_expr=match_expr)
|
||||
elif isinstance(args[0], str):
|
||||
warnings.warn(WARNS_EXEC, stacklevel=2)
|
||||
code, = args
|
||||
assert isinstance(code, str)
|
||||
frame = sys._getframe(1)
|
||||
loc = frame.f_locals.copy()
|
||||
loc.update(kwargs)
|
||||
|
||||
with WarningsChecker(expected_warning, match_expr=match_expr):
|
||||
with WarningsChecker(expected_warning):
|
||||
code = _pytest._code.Source(code).compile()
|
||||
six.exec_(code, frame.f_globals, loc)
|
||||
else:
|
||||
func = args[0]
|
||||
with WarningsChecker(expected_warning, match_expr=match_expr):
|
||||
with WarningsChecker(expected_warning):
|
||||
return func(*args[1:], **kwargs)
|
||||
|
||||
|
||||
@@ -190,6 +195,10 @@ class WarningsRecorder(warnings.catch_warnings):
|
||||
warnings.warn = self._saved_warn
|
||||
super(WarningsRecorder, self).__exit__(*exc_info)
|
||||
|
||||
# Built-in catch_warnings does not reset entered state so we do it
|
||||
# manually here for this context manager to become reusable.
|
||||
self._entered = False
|
||||
|
||||
|
||||
class WarningsChecker(WarningsRecorder):
|
||||
def __init__(self, expected_warning=None, match_expr=None):
|
||||
|
||||
@@ -19,6 +19,8 @@ def getslaveinfoline(node):
|
||||
|
||||
|
||||
class BaseReport(object):
|
||||
when = None
|
||||
|
||||
def __init__(self, **kw):
|
||||
self.__dict__.update(kw)
|
||||
|
||||
@@ -158,17 +160,9 @@ class TestReport(BaseReport):
|
||||
)
|
||||
|
||||
|
||||
class TeardownErrorReport(BaseReport):
|
||||
outcome = "failed"
|
||||
when = "teardown"
|
||||
|
||||
def __init__(self, longrepr, **extra):
|
||||
self.longrepr = longrepr
|
||||
self.sections = []
|
||||
self.__dict__.update(extra)
|
||||
|
||||
|
||||
class CollectReport(BaseReport):
|
||||
when = "collect"
|
||||
|
||||
def __init__(self, nodeid, outcome, longrepr, result, sections=(), **extra):
|
||||
self.nodeid = nodeid
|
||||
self.outcome = outcome
|
||||
|
||||
@@ -34,9 +34,9 @@ def pytest_configure(config):
|
||||
config.pluginmanager.register(config._resultlog)
|
||||
|
||||
from _pytest.deprecated import RESULT_LOG
|
||||
from _pytest.warnings import _issue_config_warning
|
||||
from _pytest.warnings import _issue_warning_captured
|
||||
|
||||
_issue_config_warning(RESULT_LOG, config, stacklevel=2)
|
||||
_issue_warning_captured(RESULT_LOG, config.hook, stacklevel=2)
|
||||
|
||||
|
||||
def pytest_unconfigure(config):
|
||||
@@ -47,30 +47,6 @@ def pytest_unconfigure(config):
|
||||
config.pluginmanager.unregister(resultlog)
|
||||
|
||||
|
||||
def generic_path(item):
|
||||
chain = item.listchain()
|
||||
gpath = [chain[0].name]
|
||||
fspath = chain[0].fspath
|
||||
fspart = False
|
||||
for node in chain[1:]:
|
||||
newfspath = node.fspath
|
||||
if newfspath == fspath:
|
||||
if fspart:
|
||||
gpath.append(":")
|
||||
fspart = False
|
||||
else:
|
||||
gpath.append(".")
|
||||
else:
|
||||
gpath.append("/")
|
||||
fspart = True
|
||||
name = node.name
|
||||
if name[0] in "([":
|
||||
gpath.pop()
|
||||
gpath.append(name)
|
||||
fspath = newfspath
|
||||
return "".join(gpath)
|
||||
|
||||
|
||||
class ResultLog(object):
|
||||
def __init__(self, config, logfile):
|
||||
self.config = config
|
||||
@@ -90,7 +66,9 @@ class ResultLog(object):
|
||||
def pytest_runtest_logreport(self, report):
|
||||
if report.when != "call" and report.passed:
|
||||
return
|
||||
res = self.config.hook.pytest_report_teststatus(report=report)
|
||||
res = self.config.hook.pytest_report_teststatus(
|
||||
report=report, config=self.config
|
||||
)
|
||||
code = res[1]
|
||||
if code == "x":
|
||||
longrepr = str(report.longrepr)
|
||||
|
||||
@@ -8,12 +8,14 @@ import os
|
||||
import sys
|
||||
from time import time
|
||||
|
||||
import attr
|
||||
import six
|
||||
|
||||
from .reports import CollectErrorRepr
|
||||
from .reports import CollectReport
|
||||
from .reports import TestReport
|
||||
from _pytest._code.code import ExceptionInfo
|
||||
from _pytest.outcomes import Exit
|
||||
from _pytest.outcomes import skip
|
||||
from _pytest.outcomes import Skipped
|
||||
from _pytest.outcomes import TEST_OUTCOME
|
||||
@@ -189,43 +191,58 @@ def check_interactive_exception(call, report):
|
||||
def call_runtest_hook(item, when, **kwds):
|
||||
hookname = "pytest_runtest_" + when
|
||||
ihook = getattr(item.ihook, hookname)
|
||||
return CallInfo(
|
||||
lambda: ihook(item=item, **kwds),
|
||||
when=when,
|
||||
treat_keyboard_interrupt_as_exception=item.config.getvalue("usepdb"),
|
||||
reraise = (Exit,)
|
||||
if not item.config.getvalue("usepdb"):
|
||||
reraise += (KeyboardInterrupt,)
|
||||
return CallInfo.from_call(
|
||||
lambda: ihook(item=item, **kwds), when=when, reraise=reraise
|
||||
)
|
||||
|
||||
|
||||
@attr.s(repr=False)
|
||||
class CallInfo(object):
|
||||
""" Result/Exception info a function invocation. """
|
||||
|
||||
#: None or ExceptionInfo object.
|
||||
excinfo = None
|
||||
_result = attr.ib()
|
||||
# Optional[ExceptionInfo]
|
||||
excinfo = attr.ib()
|
||||
start = attr.ib()
|
||||
stop = attr.ib()
|
||||
when = attr.ib()
|
||||
|
||||
def __init__(self, func, when, treat_keyboard_interrupt_as_exception=False):
|
||||
@property
|
||||
def result(self):
|
||||
if self.excinfo is not None:
|
||||
raise AttributeError("{!r} has no valid result".format(self))
|
||||
return self._result
|
||||
|
||||
@classmethod
|
||||
def from_call(cls, func, when, reraise=None):
|
||||
#: context of invocation: one of "setup", "call",
|
||||
#: "teardown", "memocollect"
|
||||
self.when = when
|
||||
self.start = time()
|
||||
start = time()
|
||||
excinfo = None
|
||||
try:
|
||||
self.result = func()
|
||||
except KeyboardInterrupt:
|
||||
if treat_keyboard_interrupt_as_exception:
|
||||
self.excinfo = ExceptionInfo()
|
||||
else:
|
||||
self.stop = time()
|
||||
raise
|
||||
result = func()
|
||||
except: # noqa
|
||||
self.excinfo = ExceptionInfo()
|
||||
self.stop = time()
|
||||
excinfo = ExceptionInfo.from_current()
|
||||
if reraise is not None and excinfo.errisinstance(reraise):
|
||||
raise
|
||||
result = None
|
||||
stop = time()
|
||||
return cls(start=start, stop=stop, when=when, result=result, excinfo=excinfo)
|
||||
|
||||
def __repr__(self):
|
||||
if self.excinfo:
|
||||
status = "exception: %s" % str(self.excinfo.value)
|
||||
if self.excinfo is not None:
|
||||
status = "exception"
|
||||
value = self.excinfo.value
|
||||
else:
|
||||
result = getattr(self, "result", "<NOTSET>")
|
||||
status = "result: %r" % (result,)
|
||||
return "<CallInfo when=%r %s>" % (self.when, status)
|
||||
# TODO: investigate unification
|
||||
value = repr(self._result)
|
||||
status = "result"
|
||||
return "<CallInfo when={when!r} {status}: {value}>".format(
|
||||
when=self.when, value=value, status=status
|
||||
)
|
||||
|
||||
|
||||
def pytest_runtest_makereport(item, call):
|
||||
@@ -269,7 +286,7 @@ def pytest_runtest_makereport(item, call):
|
||||
|
||||
|
||||
def pytest_make_collect_report(collector):
|
||||
call = CallInfo(lambda: list(collector.collect()), "collect")
|
||||
call = CallInfo.from_call(lambda: list(collector.collect()), "collect")
|
||||
longrepr = None
|
||||
if not call.excinfo:
|
||||
outcome = "passed"
|
||||
|
||||
@@ -180,9 +180,9 @@ def pytest_runtest_makereport(item, call):
|
||||
def pytest_report_teststatus(report):
|
||||
if hasattr(report, "wasxfail"):
|
||||
if report.skipped:
|
||||
return "xfailed", "x", "xfail"
|
||||
return "xfailed", "x", "XFAIL"
|
||||
elif report.passed:
|
||||
return "xpassed", "X", ("XPASS", {"yellow": True})
|
||||
return "xpassed", "X", "XPASS"
|
||||
|
||||
|
||||
# called by the terminalreporter instance/plugin
|
||||
@@ -191,11 +191,6 @@ def pytest_report_teststatus(report):
|
||||
def pytest_terminal_summary(terminalreporter):
|
||||
tr = terminalreporter
|
||||
if not tr.reportchars:
|
||||
# for name in "xfailed skipped failed xpassed":
|
||||
# if not tr.stats.get(name, 0):
|
||||
# tr.write_line("HINT: use '-r' option to see extra "
|
||||
# "summary info about tests")
|
||||
# break
|
||||
return
|
||||
|
||||
lines = []
|
||||
@@ -209,21 +204,23 @@ def pytest_terminal_summary(terminalreporter):
|
||||
tr._tw.line(line)
|
||||
|
||||
|
||||
def show_simple(terminalreporter, lines, stat, format):
|
||||
def show_simple(terminalreporter, lines, stat):
|
||||
failed = terminalreporter.stats.get(stat)
|
||||
if failed:
|
||||
for rep in failed:
|
||||
verbose_word = _get_report_str(terminalreporter, rep)
|
||||
pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid)
|
||||
lines.append(format % (pos,))
|
||||
lines.append("%s %s" % (verbose_word, pos))
|
||||
|
||||
|
||||
def show_xfailed(terminalreporter, lines):
|
||||
xfailed = terminalreporter.stats.get("xfailed")
|
||||
if xfailed:
|
||||
for rep in xfailed:
|
||||
verbose_word = _get_report_str(terminalreporter, rep)
|
||||
pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid)
|
||||
reason = rep.wasxfail
|
||||
lines.append("XFAIL %s" % (pos,))
|
||||
lines.append("%s %s" % (verbose_word, pos))
|
||||
if reason:
|
||||
lines.append(" " + str(reason))
|
||||
|
||||
@@ -232,9 +229,10 @@ def show_xpassed(terminalreporter, lines):
|
||||
xpassed = terminalreporter.stats.get("xpassed")
|
||||
if xpassed:
|
||||
for rep in xpassed:
|
||||
verbose_word = _get_report_str(terminalreporter, rep)
|
||||
pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid)
|
||||
reason = rep.wasxfail
|
||||
lines.append("XPASS %s %s" % (pos, reason))
|
||||
lines.append("%s %s %s" % (verbose_word, pos, reason))
|
||||
|
||||
|
||||
def folded_skips(skipped):
|
||||
@@ -246,8 +244,11 @@ def folded_skips(skipped):
|
||||
# folding reports with global pytestmark variable
|
||||
# this is workaround, because for now we cannot identify the scope of a skip marker
|
||||
# TODO: revisit after marks scope would be fixed
|
||||
when = getattr(event, "when", None)
|
||||
if when == "setup" and "skip" in keywords and "pytestmark" not in keywords:
|
||||
if (
|
||||
event.when == "setup"
|
||||
and "skip" in keywords
|
||||
and "pytestmark" not in keywords
|
||||
):
|
||||
key = (key[0], None, key[2])
|
||||
d.setdefault(key, []).append(event)
|
||||
values = []
|
||||
@@ -260,39 +261,42 @@ def show_skipped(terminalreporter, lines):
|
||||
tr = terminalreporter
|
||||
skipped = tr.stats.get("skipped", [])
|
||||
if skipped:
|
||||
# if not tr.hasopt('skipped'):
|
||||
# tr.write_line(
|
||||
# "%d skipped tests, specify -rs for more info" %
|
||||
# len(skipped))
|
||||
# return
|
||||
verbose_word = _get_report_str(terminalreporter, report=skipped[0])
|
||||
fskips = folded_skips(skipped)
|
||||
if fskips:
|
||||
# tr.write_sep("_", "skipped test summary")
|
||||
for num, fspath, lineno, reason in fskips:
|
||||
if reason.startswith("Skipped: "):
|
||||
reason = reason[9:]
|
||||
if lineno is not None:
|
||||
lines.append(
|
||||
"SKIP [%d] %s:%d: %s" % (num, fspath, lineno + 1, reason)
|
||||
"%s [%d] %s:%d: %s"
|
||||
% (verbose_word, num, fspath, lineno + 1, reason)
|
||||
)
|
||||
else:
|
||||
lines.append("SKIP [%d] %s: %s" % (num, fspath, reason))
|
||||
lines.append("%s [%d] %s: %s" % (verbose_word, num, fspath, reason))
|
||||
|
||||
|
||||
def shower(stat, format):
|
||||
def shower(stat):
|
||||
def show_(terminalreporter, lines):
|
||||
return show_simple(terminalreporter, lines, stat, format)
|
||||
return show_simple(terminalreporter, lines, stat)
|
||||
|
||||
return show_
|
||||
|
||||
|
||||
def _get_report_str(terminalreporter, report):
|
||||
_category, _short, verbose = terminalreporter.config.hook.pytest_report_teststatus(
|
||||
report=report, config=terminalreporter.config
|
||||
)
|
||||
return verbose
|
||||
|
||||
|
||||
REPORTCHAR_ACTIONS = {
|
||||
"x": show_xfailed,
|
||||
"X": show_xpassed,
|
||||
"f": shower("failed", "FAIL %s"),
|
||||
"F": shower("failed", "FAIL %s"),
|
||||
"f": shower("failed"),
|
||||
"F": shower("failed"),
|
||||
"s": show_skipped,
|
||||
"S": show_skipped,
|
||||
"p": shower("passed", "PASSED %s"),
|
||||
"E": shower("error", "ERROR %s"),
|
||||
"p": shower("passed"),
|
||||
"E": shower("error"),
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import itertools
|
||||
import collections
|
||||
import platform
|
||||
import sys
|
||||
import time
|
||||
@@ -167,7 +167,7 @@ def getreportopt(config):
|
||||
if char not in reportopts and char != "a":
|
||||
reportopts += char
|
||||
elif char == "a":
|
||||
reportopts = "fEsxXw"
|
||||
reportopts = "sxXwEf"
|
||||
return reportopts
|
||||
|
||||
|
||||
@@ -186,20 +186,17 @@ def pytest_report_teststatus(report):
|
||||
@attr.s
|
||||
class WarningReport(object):
|
||||
"""
|
||||
Simple structure to hold warnings information captured by ``pytest_logwarning`` and ``pytest_warning_captured``.
|
||||
Simple structure to hold warnings information captured by ``pytest_warning_captured``.
|
||||
|
||||
:ivar str message: user friendly message about the warning
|
||||
:ivar str|None nodeid: node id that generated the warning (see ``get_location``).
|
||||
:ivar tuple|py.path.local fslocation:
|
||||
file system location of the source of the warning (see ``get_location``).
|
||||
|
||||
:ivar bool legacy: if this warning report was generated from the deprecated ``pytest_logwarning`` hook.
|
||||
"""
|
||||
|
||||
message = attr.ib()
|
||||
nodeid = attr.ib(default=None)
|
||||
fslocation = attr.ib(default=None)
|
||||
legacy = attr.ib(default=False)
|
||||
|
||||
def get_location(self, config):
|
||||
"""
|
||||
@@ -225,12 +222,9 @@ class TerminalReporter(object):
|
||||
import _pytest.config
|
||||
|
||||
self.config = config
|
||||
self.verbosity = self.config.option.verbose
|
||||
self.showheader = self.verbosity >= 0
|
||||
self.showfspath = self.verbosity >= 0
|
||||
self.showlongtestinfo = self.verbosity > 0
|
||||
self._numcollected = 0
|
||||
self._session = None
|
||||
self._showfspath = None
|
||||
|
||||
self.stats = {}
|
||||
self.startdir = py.path.local()
|
||||
@@ -258,13 +252,37 @@ class TerminalReporter(object):
|
||||
return False
|
||||
return self.config.getini("console_output_style") in ("progress", "count")
|
||||
|
||||
@property
|
||||
def verbosity(self):
|
||||
return self.config.option.verbose
|
||||
|
||||
@property
|
||||
def showheader(self):
|
||||
return self.verbosity >= 0
|
||||
|
||||
@property
|
||||
def showfspath(self):
|
||||
if self._showfspath is None:
|
||||
return self.verbosity >= 0
|
||||
return self._showfspath
|
||||
|
||||
@showfspath.setter
|
||||
def showfspath(self, value):
|
||||
self._showfspath = value
|
||||
|
||||
@property
|
||||
def showlongtestinfo(self):
|
||||
return self.verbosity > 0
|
||||
|
||||
def hasopt(self, char):
|
||||
char = {"xfailed": "x", "skipped": "s"}.get(char, char)
|
||||
return char in self.reportchars
|
||||
|
||||
def write_fspath_result(self, nodeid, res, **markup):
|
||||
fspath = self.config.rootdir.join(nodeid.split("::")[0])
|
||||
if fspath != self.currentfspath:
|
||||
# NOTE: explicitly check for None to work around py bug, and for less
|
||||
# overhead in general (https://github.com/pytest-dev/py/pull/207).
|
||||
if self.currentfspath is None or fspath != self.currentfspath:
|
||||
if self.currentfspath is not None and self._show_progress_info:
|
||||
self._write_progress_information_filling_space()
|
||||
self.currentfspath = fspath
|
||||
@@ -329,13 +347,6 @@ class TerminalReporter(object):
|
||||
self.write_line("INTERNALERROR> " + line)
|
||||
return 1
|
||||
|
||||
def pytest_logwarning(self, fslocation, message, nodeid):
|
||||
warnings = self.stats.setdefault("warnings", [])
|
||||
warning = WarningReport(
|
||||
fslocation=fslocation, message=message, nodeid=nodeid, legacy=True
|
||||
)
|
||||
warnings.append(warning)
|
||||
|
||||
def pytest_warning_captured(self, warning_message, item):
|
||||
# from _pytest.nodes import get_fslocation_from_item
|
||||
from _pytest.warnings import warning_record_to_str
|
||||
@@ -373,7 +384,7 @@ class TerminalReporter(object):
|
||||
|
||||
def pytest_runtest_logreport(self, report):
|
||||
rep = report
|
||||
res = self.config.hook.pytest_report_teststatus(report=rep)
|
||||
res = self.config.hook.pytest_report_teststatus(report=rep, config=self.config)
|
||||
category, letter, word = res
|
||||
if isinstance(word, tuple):
|
||||
word, markup = word
|
||||
@@ -386,8 +397,11 @@ class TerminalReporter(object):
|
||||
return
|
||||
running_xdist = hasattr(rep, "node")
|
||||
if markup is None:
|
||||
if rep.passed:
|
||||
was_xfail = hasattr(report, "wasxfail")
|
||||
if rep.passed and not was_xfail:
|
||||
markup = {"green": True}
|
||||
elif rep.passed and was_xfail:
|
||||
markup = {"yellow": True}
|
||||
elif rep.failed:
|
||||
markup = {"red": True}
|
||||
elif rep.skipped:
|
||||
@@ -507,6 +521,7 @@ class TerminalReporter(object):
|
||||
errors = len(self.stats.get("error", []))
|
||||
skipped = len(self.stats.get("skipped", []))
|
||||
deselected = len(self.stats.get("deselected", []))
|
||||
selected = self._numcollected - errors - skipped - deselected
|
||||
if final:
|
||||
line = "collected "
|
||||
else:
|
||||
@@ -520,6 +535,8 @@ class TerminalReporter(object):
|
||||
line += " / %d deselected" % deselected
|
||||
if skipped:
|
||||
line += " / %d skipped" % skipped
|
||||
if self._numcollected > selected > 0:
|
||||
line += " / %d selected" % selected
|
||||
if self.isatty:
|
||||
self.rewrite(line, bold=True, erase=True)
|
||||
if final:
|
||||
@@ -578,19 +595,20 @@ class TerminalReporter(object):
|
||||
return lines
|
||||
|
||||
def pytest_collection_finish(self, session):
|
||||
if self.config.option.collectonly:
|
||||
if self.config.getoption("collectonly"):
|
||||
self._printcollecteditems(session.items)
|
||||
if self.stats.get("failed"):
|
||||
self._tw.sep("!", "collection failures")
|
||||
for rep in self.stats.get("failed"):
|
||||
rep.toterminal(self._tw)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
lines = self.config.hook.pytest_report_collectionfinish(
|
||||
config=self.config, startdir=self.startdir, items=session.items
|
||||
)
|
||||
self._write_report_lines_from_hooks(lines)
|
||||
|
||||
if self.config.getoption("collectonly"):
|
||||
if self.stats.get("failed"):
|
||||
self._tw.sep("!", "collection failures")
|
||||
for rep in self.stats.get("failed"):
|
||||
rep.toterminal(self._tw)
|
||||
|
||||
def _printcollecteditems(self, items):
|
||||
# to print out items and their parent collectors
|
||||
# we take care to leave out Instances aka ()
|
||||
@@ -621,6 +639,10 @@ class TerminalReporter(object):
|
||||
continue
|
||||
indent = (len(stack) - 1) * " "
|
||||
self._tw.line("%s%s" % (indent, col))
|
||||
if self.config.option.verbose >= 1:
|
||||
if hasattr(col, "_obj") and col._obj.__doc__:
|
||||
for line in col._obj.__doc__.strip().splitlines():
|
||||
self._tw.line("%s%s" % (indent + " ", line.strip()))
|
||||
|
||||
@pytest.hookimpl(hookwrapper=True)
|
||||
def pytest_sessionfinish(self, exitstatus):
|
||||
@@ -636,7 +658,7 @@ class TerminalReporter(object):
|
||||
)
|
||||
if exitstatus in summary_exit_codes:
|
||||
self.config.hook.pytest_terminal_summary(
|
||||
terminalreporter=self, exitstatus=exitstatus
|
||||
terminalreporter=self, exitstatus=exitstatus, config=self.config
|
||||
)
|
||||
if exitstatus == EXIT_INTERRUPTED:
|
||||
self._report_keyboardinterrupt()
|
||||
@@ -730,33 +752,33 @@ class TerminalReporter(object):
|
||||
|
||||
final = hasattr(self, "_already_displayed_warnings")
|
||||
if final:
|
||||
warnings = all_warnings[self._already_displayed_warnings :]
|
||||
warning_reports = all_warnings[self._already_displayed_warnings :]
|
||||
else:
|
||||
warnings = all_warnings
|
||||
self._already_displayed_warnings = len(warnings)
|
||||
if not warnings:
|
||||
warning_reports = all_warnings
|
||||
self._already_displayed_warnings = len(warning_reports)
|
||||
if not warning_reports:
|
||||
return
|
||||
|
||||
grouped = itertools.groupby(
|
||||
warnings, key=lambda wr: wr.get_location(self.config)
|
||||
)
|
||||
reports_grouped_by_message = collections.OrderedDict()
|
||||
for wr in warning_reports:
|
||||
reports_grouped_by_message.setdefault(wr.message, []).append(wr)
|
||||
|
||||
title = "warnings summary (final)" if final else "warnings summary"
|
||||
self.write_sep("=", title, yellow=True, bold=False)
|
||||
for location, warning_records in grouped:
|
||||
# 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)
|
||||
if location:
|
||||
self._tw.line(str(location))
|
||||
for w in warning_records:
|
||||
for message, warning_reports in reports_grouped_by_message.items():
|
||||
has_any_location = False
|
||||
for w in warning_reports:
|
||||
location = w.get_location(self.config)
|
||||
if location:
|
||||
lines = w.message.splitlines()
|
||||
indented = "\n".join(" " + x for x in lines)
|
||||
message = indented.rstrip()
|
||||
else:
|
||||
message = w.message.rstrip()
|
||||
self._tw.line(message)
|
||||
self._tw.line(str(location))
|
||||
has_any_location = True
|
||||
if has_any_location:
|
||||
lines = message.splitlines()
|
||||
indented = "\n".join(" " + x for x in lines)
|
||||
message = indented.rstrip()
|
||||
else:
|
||||
message = message.rstrip()
|
||||
self._tw.line(message)
|
||||
self._tw.line()
|
||||
self._tw.line("-- Docs: https://docs.pytest.org/en/latest/warnings.html")
|
||||
|
||||
@@ -812,8 +834,7 @@ class TerminalReporter(object):
|
||||
self.write_sep("=", "ERRORS")
|
||||
for rep in self.stats["error"]:
|
||||
msg = self._getfailureheadline(rep)
|
||||
if not hasattr(rep, "when"):
|
||||
# collect
|
||||
if rep.when == "collect":
|
||||
msg = "ERROR collecting " + msg
|
||||
elif rep.when == "setup":
|
||||
msg = "ERROR at setup of " + msg
|
||||
@@ -847,15 +868,6 @@ class TerminalReporter(object):
|
||||
self.write_line(msg, **markup)
|
||||
|
||||
|
||||
def repr_pythonversion(v=None):
|
||||
if v is None:
|
||||
v = sys.version_info
|
||||
try:
|
||||
return "%s.%s.%s-%s-%s" % v
|
||||
except (TypeError, ValueError):
|
||||
return str(v)
|
||||
|
||||
|
||||
def build_summary_stats_line(stats):
|
||||
keys = ("failed passed skipped deselected xfailed xpassed warnings error").split()
|
||||
unknown_key_seen = False
|
||||
|
||||
@@ -31,7 +31,7 @@ class TempPathFactory(object):
|
||||
# using os.path.abspath() to get absolute path instead of resolve() as it
|
||||
# does not work the same in all platforms (see #4427)
|
||||
# Path.absolute() exists, but it is not public (see https://bugs.python.org/issue25012)
|
||||
convert=attr.converters.optional(
|
||||
converter=attr.converters.optional(
|
||||
lambda p: Path(os.path.abspath(six.text_type(p)))
|
||||
)
|
||||
)
|
||||
@@ -63,9 +63,10 @@ class TempPathFactory(object):
|
||||
if self._given_basetemp is not None:
|
||||
basetemp = self._given_basetemp
|
||||
ensure_reset_dir(basetemp)
|
||||
basetemp = basetemp.resolve()
|
||||
else:
|
||||
from_env = os.environ.get("PYTEST_DEBUG_TEMPROOT")
|
||||
temproot = Path(from_env or tempfile.gettempdir())
|
||||
temproot = Path(from_env or tempfile.gettempdir()).resolve()
|
||||
user = get_user() or "unknown"
|
||||
# use a sub-directory in the temproot to speed-up
|
||||
# make_numbered_dir() call
|
||||
@@ -167,7 +168,7 @@ def _mk_tmp(request, factory):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tmpdir(request, tmpdir_factory):
|
||||
def tmpdir(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
|
||||
@@ -176,7 +177,7 @@ def tmpdir(request, tmpdir_factory):
|
||||
|
||||
.. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html
|
||||
"""
|
||||
return _mk_tmp(request, tmpdir_factory)
|
||||
return py.path.local(tmp_path)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@@ -7,6 +7,7 @@ import sys
|
||||
import traceback
|
||||
|
||||
import _pytest._code
|
||||
import pytest
|
||||
from _pytest.compat import getimfunc
|
||||
from _pytest.config import hookimpl
|
||||
from _pytest.outcomes import fail
|
||||
@@ -14,8 +15,6 @@ from _pytest.outcomes import skip
|
||||
from _pytest.outcomes import xfail
|
||||
from _pytest.python import Class
|
||||
from _pytest.python import Function
|
||||
from _pytest.python import Module
|
||||
from _pytest.python import transfer_markers
|
||||
|
||||
|
||||
def pytest_pycollect_makeitem(collector, name, obj):
|
||||
@@ -34,34 +33,26 @@ class UnitTestCase(Class):
|
||||
# to declare that our children do not support funcargs
|
||||
nofuncargs = True
|
||||
|
||||
def setup(self):
|
||||
cls = self.obj
|
||||
if getattr(cls, "__unittest_skip__", False):
|
||||
return # skipped
|
||||
setup = getattr(cls, "setUpClass", None)
|
||||
if setup is not None:
|
||||
setup()
|
||||
teardown = getattr(cls, "tearDownClass", None)
|
||||
if teardown is not None:
|
||||
self.addfinalizer(teardown)
|
||||
super(UnitTestCase, self).setup()
|
||||
|
||||
def collect(self):
|
||||
from unittest import TestLoader
|
||||
|
||||
cls = self.obj
|
||||
if not getattr(cls, "__test__", True):
|
||||
return
|
||||
|
||||
skipped = getattr(cls, "__unittest_skip__", False)
|
||||
if not skipped:
|
||||
self._inject_setup_teardown_fixtures(cls)
|
||||
self._inject_setup_class_fixture()
|
||||
|
||||
self.session._fixturemanager.parsefactories(self, unittest=True)
|
||||
loader = TestLoader()
|
||||
module = self.getparent(Module).obj
|
||||
foundsomething = False
|
||||
for name in loader.getTestCaseNames(self.obj):
|
||||
x = getattr(self.obj, name)
|
||||
if not getattr(x, "__test__", True):
|
||||
continue
|
||||
funcobj = getimfunc(x)
|
||||
transfer_markers(funcobj, cls, module)
|
||||
yield TestCaseFunction(name, parent=self, callobj=funcobj)
|
||||
foundsomething = True
|
||||
|
||||
@@ -72,6 +63,47 @@ class UnitTestCase(Class):
|
||||
if ut is None or runtest != ut.TestCase.runTest:
|
||||
yield TestCaseFunction("runTest", parent=self)
|
||||
|
||||
def _inject_setup_teardown_fixtures(self, cls):
|
||||
"""Injects a hidden auto-use fixture to invoke setUpClass/setup_method and corresponding
|
||||
teardown functions (#517)"""
|
||||
class_fixture = _make_xunit_fixture(
|
||||
cls, "setUpClass", "tearDownClass", scope="class", pass_self=False
|
||||
)
|
||||
if class_fixture:
|
||||
cls.__pytest_class_setup = class_fixture
|
||||
|
||||
method_fixture = _make_xunit_fixture(
|
||||
cls, "setup_method", "teardown_method", scope="function", pass_self=True
|
||||
)
|
||||
if method_fixture:
|
||||
cls.__pytest_method_setup = method_fixture
|
||||
|
||||
|
||||
def _make_xunit_fixture(obj, setup_name, teardown_name, scope, pass_self):
|
||||
setup = getattr(obj, setup_name, None)
|
||||
teardown = getattr(obj, teardown_name, None)
|
||||
if setup is None and teardown is None:
|
||||
return None
|
||||
|
||||
@pytest.fixture(scope=scope, autouse=True)
|
||||
def fixture(self, request):
|
||||
if getattr(self, "__unittest_skip__", None):
|
||||
reason = self.__unittest_skip_why__
|
||||
pytest.skip(reason)
|
||||
if setup is not None:
|
||||
if pass_self:
|
||||
setup(self, request.function)
|
||||
else:
|
||||
setup()
|
||||
yield
|
||||
if teardown is not None:
|
||||
if pass_self:
|
||||
teardown(self, request.function)
|
||||
else:
|
||||
teardown()
|
||||
|
||||
return fixture
|
||||
|
||||
|
||||
class TestCaseFunction(Function):
|
||||
nofuncargs = True
|
||||
@@ -81,9 +113,6 @@ class TestCaseFunction(Function):
|
||||
def setup(self):
|
||||
self._testcase = self.parent.obj(self.name)
|
||||
self._fix_unittest_skip_decorator()
|
||||
self._obj = getattr(self._testcase, self.name)
|
||||
if hasattr(self._testcase, "setup_method"):
|
||||
self._testcase.setup_method(self._obj)
|
||||
if hasattr(self, "_request"):
|
||||
self._request._fillfixtures()
|
||||
|
||||
@@ -101,11 +130,7 @@ class TestCaseFunction(Function):
|
||||
setattr(self._testcase, "__name__", self.name)
|
||||
|
||||
def teardown(self):
|
||||
if hasattr(self._testcase, "teardown_method"):
|
||||
self._testcase.teardown_method(self._obj)
|
||||
# Allow garbage collection on TestCase instance attributes.
|
||||
self._testcase = None
|
||||
self._obj = None
|
||||
|
||||
def startTest(self, testcase):
|
||||
pass
|
||||
@@ -115,6 +140,10 @@ class TestCaseFunction(Function):
|
||||
rawexcinfo = getattr(rawexcinfo, "_rawexcinfo", rawexcinfo)
|
||||
try:
|
||||
excinfo = _pytest._code.ExceptionInfo(rawexcinfo)
|
||||
# invoke the attributes to trigger storing the traceback
|
||||
# trial causes some issue there
|
||||
excinfo.value
|
||||
excinfo.traceback
|
||||
except TypeError:
|
||||
try:
|
||||
try:
|
||||
@@ -136,7 +165,7 @@ class TestCaseFunction(Function):
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except fail.Exception:
|
||||
excinfo = _pytest._code.ExceptionInfo()
|
||||
excinfo = _pytest._code.ExceptionInfo.from_current()
|
||||
self.__dict__.setdefault("_excinfo", []).append(excinfo)
|
||||
|
||||
def addError(self, testcase, rawexcinfo):
|
||||
|
||||
@@ -160,19 +160,19 @@ def pytest_terminal_summary(terminalreporter):
|
||||
yield
|
||||
|
||||
|
||||
def _issue_config_warning(warning, config, stacklevel):
|
||||
def _issue_warning_captured(warning, hook, stacklevel):
|
||||
"""
|
||||
This function should be used instead of calling ``warnings.warn`` directly when we are in the "configure" stage:
|
||||
at this point the actual options might not have been set, so we manually trigger the pytest_warning_captured
|
||||
hook so we can display this warnings in the terminal. This is a hack until we can sort out #2891.
|
||||
|
||||
:param warning: the warning instance.
|
||||
:param config:
|
||||
:param hook: the hook caller
|
||||
:param stacklevel: stacklevel forwarded to warnings.warn
|
||||
"""
|
||||
with warnings.catch_warnings(record=True) as records:
|
||||
warnings.simplefilter("always", type(warning))
|
||||
warnings.warn(warning, stacklevel=stacklevel)
|
||||
config.hook.pytest_warning_captured.call_historic(
|
||||
hook.pytest_warning_captured.call_historic(
|
||||
kwargs=dict(warning_message=records[0], when="config", item=None)
|
||||
)
|
||||
|
||||
@@ -28,7 +28,6 @@ from _pytest.outcomes import skip
|
||||
from _pytest.outcomes import xfail
|
||||
from _pytest.python import Class
|
||||
from _pytest.python import Function
|
||||
from _pytest.python import Generator
|
||||
from _pytest.python import Instance
|
||||
from _pytest.python import Module
|
||||
from _pytest.python import Package
|
||||
@@ -57,7 +56,6 @@ __all__ = [
|
||||
"fixture",
|
||||
"freeze_includes",
|
||||
"Function",
|
||||
"Generator",
|
||||
"hookimpl",
|
||||
"hookspec",
|
||||
"importorskip",
|
||||
|
||||
@@ -146,6 +146,7 @@ class TestGeneralUsage(object):
|
||||
assert result.ret
|
||||
result.stderr.fnmatch_lines(["*ERROR: not found:*{}".format(p2.basename)])
|
||||
|
||||
@pytest.mark.filterwarnings("default")
|
||||
def test_better_reporting_on_conftest_load_failure(self, testdir, request):
|
||||
"""Show a user-friendly traceback on conftest import failures (#486, #3332)"""
|
||||
testdir.makepyfile("")
|
||||
@@ -299,7 +300,7 @@ class TestGeneralUsage(object):
|
||||
"""
|
||||
import pytest
|
||||
def pytest_generate_tests(metafunc):
|
||||
metafunc.addcall({'x': 3}, id='hello-123')
|
||||
metafunc.parametrize('x', [3], ids=['hello-123'])
|
||||
def pytest_runtest_setup(item):
|
||||
print(item.keywords)
|
||||
if 'hello-123' in item.keywords:
|
||||
@@ -316,8 +317,7 @@ class TestGeneralUsage(object):
|
||||
p = testdir.makepyfile(
|
||||
"""
|
||||
def pytest_generate_tests(metafunc):
|
||||
metafunc.addcall({'i': 1}, id="1")
|
||||
metafunc.addcall({'i': 2}, id="2")
|
||||
metafunc.parametrize('i', [1, 2], ids=["1", "2"])
|
||||
def test_func(i):
|
||||
pass
|
||||
"""
|
||||
@@ -560,12 +560,11 @@ class TestInvocationVariants(object):
|
||||
def test_equivalence_pytest_pytest(self):
|
||||
assert pytest.main == py.test.cmdline.main
|
||||
|
||||
def test_invoke_with_string(self, capsys):
|
||||
retcode = pytest.main("-h")
|
||||
assert not retcode
|
||||
out, err = capsys.readouterr()
|
||||
assert "--help" in out
|
||||
pytest.raises(ValueError, lambda: pytest.main(0))
|
||||
def test_invoke_with_invalid_type(self, capsys):
|
||||
with pytest.raises(
|
||||
TypeError, match="expected to be a list or tuple of strings, got: '-h'"
|
||||
):
|
||||
pytest.main("-h")
|
||||
|
||||
def test_invoke_with_path(self, tmpdir, capsys):
|
||||
retcode = pytest.main(tmpdir)
|
||||
@@ -805,8 +804,8 @@ class TestInvocationVariants(object):
|
||||
result = testdir.runpytest("-rf")
|
||||
lines = result.stdout.str().splitlines()
|
||||
for line in lines:
|
||||
if line.startswith("FAIL "):
|
||||
testid = line[5:].strip()
|
||||
if line.startswith(("FAIL ", "FAILED ")):
|
||||
_fail, _sep, testid = line.partition(" ")
|
||||
break
|
||||
result = testdir.runpytest(testid, "-rf")
|
||||
result.stdout.fnmatch_lines([line, "*1 failed*"])
|
||||
@@ -968,6 +967,20 @@ def test_import_plugin_unicode_name(testdir):
|
||||
assert r.ret == 0
|
||||
|
||||
|
||||
def test_pytest_plugins_as_module(testdir):
|
||||
"""Do not raise an error if pytest_plugins attribute is a module (#3899)"""
|
||||
testdir.makepyfile(
|
||||
**{
|
||||
"__init__.py": "",
|
||||
"pytest_plugins.py": "",
|
||||
"conftest.py": "from . import pytest_plugins",
|
||||
"test_foo.py": "def test(): pass",
|
||||
}
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines("* 1 passed in *")
|
||||
|
||||
|
||||
def test_deferred_hook_checking(testdir):
|
||||
"""
|
||||
Check hooks as late as possible (#1821).
|
||||
|
||||
@@ -37,7 +37,7 @@ def test_code_with_class():
|
||||
class A(object):
|
||||
pass
|
||||
|
||||
pytest.raises(TypeError, "_pytest._code.Code(A)")
|
||||
pytest.raises(TypeError, _pytest._code.Code, A)
|
||||
|
||||
|
||||
def x():
|
||||
@@ -169,7 +169,7 @@ class TestExceptionInfo(object):
|
||||
else:
|
||||
assert False
|
||||
except AssertionError:
|
||||
exci = _pytest._code.ExceptionInfo()
|
||||
exci = _pytest._code.ExceptionInfo.from_current()
|
||||
assert exci.getrepr()
|
||||
|
||||
|
||||
@@ -181,7 +181,7 @@ class TestTracebackEntry(object):
|
||||
else:
|
||||
assert False
|
||||
except AssertionError:
|
||||
exci = _pytest._code.ExceptionInfo()
|
||||
exci = _pytest._code.ExceptionInfo.from_current()
|
||||
entry = exci.traceback[0]
|
||||
source = entry.getsource()
|
||||
assert len(source) == 6
|
||||
|
||||
@@ -71,7 +71,7 @@ def test_excinfo_simple():
|
||||
try:
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
info = _pytest._code.ExceptionInfo()
|
||||
info = _pytest._code.ExceptionInfo.from_current()
|
||||
assert info.type == ValueError
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ def test_excinfo_getstatement():
|
||||
try:
|
||||
f()
|
||||
except ValueError:
|
||||
excinfo = _pytest._code.ExceptionInfo()
|
||||
excinfo = _pytest._code.ExceptionInfo.from_current()
|
||||
linenumbers = [
|
||||
_pytest._code.getrawcode(f).co_firstlineno - 1 + 4,
|
||||
_pytest._code.getrawcode(f).co_firstlineno - 1 + 1,
|
||||
@@ -126,7 +126,7 @@ class TestTraceback_f_g_h(object):
|
||||
try:
|
||||
h()
|
||||
except ValueError:
|
||||
self.excinfo = _pytest._code.ExceptionInfo()
|
||||
self.excinfo = _pytest._code.ExceptionInfo.from_current()
|
||||
|
||||
def test_traceback_entries(self):
|
||||
tb = self.excinfo.traceback
|
||||
@@ -163,7 +163,7 @@ class TestTraceback_f_g_h(object):
|
||||
try:
|
||||
exec(source.compile())
|
||||
except NameError:
|
||||
tb = _pytest._code.ExceptionInfo().traceback
|
||||
tb = _pytest._code.ExceptionInfo.from_current().traceback
|
||||
print(tb[-1].getsource())
|
||||
s = str(tb[-1].getsource())
|
||||
assert s.startswith("def xyz():\n try:")
|
||||
@@ -180,7 +180,8 @@ class TestTraceback_f_g_h(object):
|
||||
|
||||
def test_traceback_cut_excludepath(self, testdir):
|
||||
p = testdir.makepyfile("def f(): raise ValueError")
|
||||
excinfo = pytest.raises(ValueError, "p.pyimport().f()")
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
p.pyimport().f()
|
||||
basedir = py.path.local(pytest.__file__).dirpath()
|
||||
newtraceback = excinfo.traceback.cut(excludepath=basedir)
|
||||
for x in newtraceback:
|
||||
@@ -336,7 +337,8 @@ class TestTraceback_f_g_h(object):
|
||||
def test_excinfo_exconly():
|
||||
excinfo = pytest.raises(ValueError, h)
|
||||
assert excinfo.exconly().startswith("ValueError")
|
||||
excinfo = pytest.raises(ValueError, "raise ValueError('hello\\nworld')")
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
raise ValueError("hello\nworld")
|
||||
msg = excinfo.exconly(tryshort=True)
|
||||
assert msg.startswith("ValueError")
|
||||
assert msg.endswith("world")
|
||||
@@ -356,6 +358,12 @@ def test_excinfo_str():
|
||||
assert len(s.split(":")) >= 3 # on windows it's 4
|
||||
|
||||
|
||||
def test_excinfo_for_later():
|
||||
e = ExceptionInfo.for_later()
|
||||
assert "for raises" in repr(e)
|
||||
assert "for raises" in str(e)
|
||||
|
||||
|
||||
def test_excinfo_errisinstance():
|
||||
excinfo = pytest.raises(ValueError, h)
|
||||
assert excinfo.errisinstance(ValueError)
|
||||
@@ -365,7 +373,7 @@ def test_excinfo_no_sourcecode():
|
||||
try:
|
||||
exec("raise ValueError()")
|
||||
except ValueError:
|
||||
excinfo = _pytest._code.ExceptionInfo()
|
||||
excinfo = _pytest._code.ExceptionInfo.from_current()
|
||||
s = str(excinfo.traceback[-1])
|
||||
assert s == " File '<string>':1 in <module>\n ???\n"
|
||||
|
||||
@@ -390,7 +398,7 @@ def test_entrysource_Queue_example():
|
||||
try:
|
||||
queue.Queue().get(timeout=0.001)
|
||||
except queue.Empty:
|
||||
excinfo = _pytest._code.ExceptionInfo()
|
||||
excinfo = _pytest._code.ExceptionInfo.from_current()
|
||||
entry = excinfo.traceback[-1]
|
||||
source = entry.getsource()
|
||||
assert source is not None
|
||||
@@ -402,7 +410,7 @@ def test_codepath_Queue_example():
|
||||
try:
|
||||
queue.Queue().get(timeout=0.001)
|
||||
except queue.Empty:
|
||||
excinfo = _pytest._code.ExceptionInfo()
|
||||
excinfo = _pytest._code.ExceptionInfo.from_current()
|
||||
entry = excinfo.traceback[-1]
|
||||
path = entry.path
|
||||
assert isinstance(path, py.path.local)
|
||||
@@ -453,7 +461,7 @@ class TestFormattedExcinfo(object):
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except: # noqa
|
||||
return _pytest._code.ExceptionInfo()
|
||||
return _pytest._code.ExceptionInfo.from_current()
|
||||
assert 0, "did not raise"
|
||||
|
||||
def test_repr_source(self):
|
||||
@@ -491,7 +499,7 @@ class TestFormattedExcinfo(object):
|
||||
try:
|
||||
exec(co)
|
||||
except ValueError:
|
||||
excinfo = _pytest._code.ExceptionInfo()
|
||||
excinfo = _pytest._code.ExceptionInfo.from_current()
|
||||
repr = pr.repr_excinfo(excinfo)
|
||||
assert repr.reprtraceback.reprentries[1].lines[0] == "> ???"
|
||||
if sys.version_info[0] >= 3:
|
||||
@@ -510,7 +518,7 @@ raise ValueError()
|
||||
try:
|
||||
exec(co)
|
||||
except ValueError:
|
||||
excinfo = _pytest._code.ExceptionInfo()
|
||||
excinfo = _pytest._code.ExceptionInfo.from_current()
|
||||
repr = pr.repr_excinfo(excinfo)
|
||||
assert repr.reprtraceback.reprentries[1].lines[0] == "> ???"
|
||||
if sys.version_info[0] >= 3:
|
||||
@@ -1340,7 +1348,7 @@ def test_repr_traceback_with_unicode(style, encoding):
|
||||
try:
|
||||
raise RuntimeError(msg)
|
||||
except RuntimeError:
|
||||
e_info = ExceptionInfo()
|
||||
e_info = ExceptionInfo.from_current()
|
||||
formatter = FormattedExcinfo(style=style)
|
||||
repr_traceback = formatter.repr_traceback(e_info)
|
||||
assert repr_traceback is not None
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user