Compare commits
677 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82cc3d8cc2 | ||
|
|
e20e376881 | ||
|
|
8052d01a37 | ||
|
|
a8003286b5 | ||
|
|
b4be228330 | ||
|
|
67dd10de26 | ||
|
|
dc8af18a0e | ||
|
|
61b9246afe | ||
|
|
9feb4941f4 | ||
|
|
237f690f8b | ||
|
|
386e801a5a | ||
|
|
5cf05ce149 | ||
|
|
aee67bb1a7 | ||
|
|
5e2d740829 | ||
|
|
82b8ec37fc | ||
|
|
f73fa47b1f | ||
|
|
fd1684e70b | ||
|
|
19501028ca | ||
|
|
3a366f451a | ||
|
|
7f6108beb1 | ||
|
|
76b0660f47 | ||
|
|
75a12b9d2b | ||
|
|
32c6d4f603 | ||
|
|
b4b2f58eab | ||
|
|
8b92d10fb3 | ||
|
|
8e220f0e6f | ||
|
|
e191a65ebb | ||
|
|
5ca81596bb | ||
|
|
7bb504b807 | ||
|
|
9be069f899 | ||
|
|
913a2da6e5 | ||
|
|
ea732464aa | ||
|
|
ddbea29c12 | ||
|
|
4c7ddb8d9b | ||
|
|
a1fcd6e445 | ||
|
|
7b8fd0cc12 | ||
|
|
391dc549c0 | ||
|
|
526f4a95cc | ||
|
|
59e6fb94b5 | ||
|
|
2f083504ee | ||
|
|
3384ffc6eb | ||
|
|
7445b5345f | ||
|
|
429485e621 | ||
|
|
52d497570b | ||
|
|
0c5e717f43 | ||
|
|
54af0f4c65 | ||
|
|
678dfaa6eb | ||
|
|
19c93d16d1 | ||
|
|
0ce8b910ca | ||
|
|
c780d1fa7c | ||
|
|
584c052da4 | ||
|
|
315374008b | ||
|
|
a9457345ee | ||
|
|
726e165932 | ||
|
|
2264db7f4a | ||
|
|
4e93dc2c97 | ||
|
|
8003d8d279 | ||
|
|
f0ecb25acd | ||
|
|
7ec1a1407a | ||
|
|
7dbe40092d | ||
|
|
e53563ebbe | ||
|
|
c2c9b27771 | ||
|
|
c3d7340542 | ||
|
|
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 | ||
|
|
6c5a1150d4 | ||
|
|
26d202a7bd | ||
|
|
b390c66dc4 | ||
|
|
f96e1b6f3e | ||
|
|
e03c1f538f | ||
|
|
15b0a89fb1 | ||
|
|
5b83417afc | ||
|
|
a254ad0436 | ||
|
|
9b3be870dc | ||
|
|
7a600ea3eb | ||
|
|
110fe2473f | ||
|
|
f8d31d2400 | ||
|
|
6af674a3ac | ||
|
|
e3d30f8ebf | ||
|
|
5d79baf3f8 | ||
|
|
ec4507d12a | ||
|
|
b1e766c30e | ||
|
|
316cca204f | ||
|
|
0bccfc44a7 | ||
|
|
3cd11617ea | ||
|
|
9839ceffe0 | ||
|
|
a44776ed48 | ||
|
|
cfbd387a5d | ||
|
|
bb363c8ff2 | ||
|
|
ebe0a88226 | ||
|
|
3445eae737 | ||
|
|
8152b6837e | ||
|
|
0e4e8e00a9 | ||
|
|
7b1cb885c7 | ||
|
|
fc4aa27cae | ||
|
|
22345ef722 | ||
|
|
038f1f94c2 | ||
|
|
539d3dc34d | ||
|
|
5ecbb02ace | ||
|
|
0db5ccb0dd | ||
|
|
abb0dfcfa3 | ||
|
|
f3babf13ea | ||
|
|
64ee1ee81b | ||
|
|
76884c73bf | ||
|
|
5ebacc49c6 | ||
|
|
8a0ed7e2b3 | ||
|
|
62b8f2f731 | ||
|
|
8fd60483ef | ||
|
|
7a7ad0c120 | ||
|
|
41031fce2f | ||
|
|
818aa4d343 | ||
|
|
b7a64d6729 | ||
|
|
cb52d17d07 | ||
|
|
e1e4b226c6 | ||
|
|
26d27df6fc | ||
|
|
3e6f1fa2db | ||
|
|
aaf7f7fcca | ||
|
|
e0c2ab1901 | ||
|
|
59a11b6a5d | ||
|
|
9fc9b2926f | ||
|
|
1654b77ca0 | ||
|
|
60599b667e | ||
|
|
db32949c50 | ||
|
|
d237197de3 | ||
|
|
86ccfaec22 | ||
|
|
ae92b8c624 | ||
|
|
5db46d2087 | ||
|
|
99d3fe22dc | ||
|
|
b88c3f8f82 | ||
|
|
6da8befc74 | ||
|
|
c1bdb07b2f | ||
|
|
14024c7fc1 | ||
|
|
517b8bc69e | ||
|
|
ecc5c84c1e | ||
|
|
4a3c8e22d7 | ||
|
|
f792cc420c | ||
|
|
f4d3ec6370 | ||
|
|
a131f0acf6 | ||
|
|
4ffa13728d | ||
|
|
44b74c8c25 | ||
|
|
40b85d7ee8 | ||
|
|
090f7ff449 | ||
|
|
b05061dcd2 | ||
|
|
06dc6e3490 | ||
|
|
63f38de38e | ||
|
|
e0ba1cbf8d | ||
|
|
847eacea19 | ||
|
|
b531f7d585 | ||
|
|
7eb28f9eb7 | ||
|
|
1d86247b2c | ||
|
|
1bba0a9714 | ||
|
|
5cf69fae7d | ||
|
|
b73e083d9d | ||
|
|
d8e00c983e | ||
|
|
b00b715cb6 | ||
|
|
7d3ca68be6 | ||
|
|
e9b2475e29 | ||
|
|
d05b30ce11 | ||
|
|
2eac1bfcb8 | ||
|
|
91b25c4dec | ||
|
|
d219e033e9 | ||
|
|
59f65230b5 | ||
|
|
3d8d04c6f8 | ||
|
|
5bee396e4b | ||
|
|
8facf1db90 | ||
|
|
9e522c97fa | ||
|
|
7015801377 | ||
|
|
860bc50772 | ||
|
|
33b877cc01 | ||
|
|
f75f2117d0 | ||
|
|
0d5298475d | ||
|
|
be3b8fc9c1 | ||
|
|
16b15af624 | ||
|
|
23e4447922 | ||
|
|
0cf45ee18a | ||
|
|
63f90a2bcd | ||
|
|
0b73d6d4f5 | ||
|
|
e4ae33d592 | ||
|
|
c5c728c8bc | ||
|
|
f987b368e8 | ||
|
|
5d2e2377ff | ||
|
|
9ae8429a21 | ||
|
|
2eaf3db6ae | ||
|
|
88bf01a31e | ||
|
|
abaf496fe8 | ||
|
|
b71bd9b300 | ||
|
|
94d032a6de | ||
|
|
9424d88843 | ||
|
|
d471ecc4d8 | ||
|
|
aa765cf8c2 | ||
|
|
5f1d692072 | ||
|
|
2c45bc3019 | ||
|
|
664257c7a3 | ||
|
|
b3700f61ba | ||
|
|
d894bf4535 | ||
|
|
3eaa6d8835 | ||
|
|
f1fe9e41ac | ||
|
|
4f5c153d29 | ||
|
|
ee4f8c98a9 | ||
|
|
f180ab3e69 | ||
|
|
9ed63c607e | ||
|
|
fc61bdd907 | ||
|
|
d52ea4b6cf | ||
|
|
0ffb8ddd7f | ||
|
|
45c33c4e05 | ||
|
|
95c6d591f7 | ||
|
|
9a1e518cc3 | ||
|
|
9ca0ab6e2b | ||
|
|
8395b9e25d | ||
|
|
3d92d5a659 | ||
|
|
50e3783f07 | ||
|
|
1568e38997 | ||
|
|
6e85febf20 | ||
|
|
ba17363d75 | ||
|
|
92a2884b09 | ||
|
|
2754a13f86 | ||
|
|
a281d6620b | ||
|
|
4eddf634e7 | ||
|
|
1a8d9bf254 | ||
|
|
c56cc93cbd | ||
|
|
cdbe2299ec | ||
|
|
62967b3110 | ||
|
|
5c24430555 | ||
|
|
e46f995cc7 | ||
|
|
10cdae8e38 | ||
|
|
5fab0ca312 | ||
|
|
7f990e2b9a | ||
|
|
690a63b921 | ||
|
|
4c9cde74ab | ||
|
|
94e5bdd4e0 | ||
|
|
cb1d4044e6 | ||
|
|
a252e81ced | ||
|
|
3acfa3abdc | ||
|
|
084c36d538 | ||
|
|
950d30e6e0 | ||
|
|
5872e1c35a | ||
|
|
0385c27343 | ||
|
|
9dec146edf | ||
|
|
c2d6b3524c | ||
|
|
e025974cbd | ||
|
|
fea09cda6c | ||
|
|
ba457f5feb | ||
|
|
ed91d5f086 | ||
|
|
b7863a5f48 | ||
|
|
b83e97802e | ||
|
|
a72eff5a08 | ||
|
|
46c8363e12 | ||
|
|
557cb6cffe | ||
|
|
8198e7cd33 | ||
|
|
a6a96469ca | ||
|
|
27dab4e05f | ||
|
|
3e05848ab9 | ||
|
|
5902e5a2ce | ||
|
|
243d898b38 | ||
|
|
2bffd6829e | ||
|
|
f8f22d29ee | ||
|
|
dc20dedbc7 | ||
|
|
4e99c80425 | ||
|
|
3cec1482eb | ||
|
|
0df5ce4082 | ||
|
|
93a9836962 | ||
|
|
a663f60b05 | ||
|
|
e1e81e315e | ||
|
|
025d160dfc | ||
|
|
a3e388a73a | ||
|
|
1847cc7420 | ||
|
|
87b019d5f9 | ||
|
|
1184db8273 | ||
|
|
a0ba881c22 | ||
|
|
d42f1e87c3 | ||
|
|
9769bc05c6 | ||
|
|
935b106213 | ||
|
|
74366426b9 | ||
|
|
9628242423 | ||
|
|
9af613bef2 | ||
|
|
7b833ce5f9 | ||
|
|
11a9cbd50b | ||
|
|
2626bd9afa | ||
|
|
b6f16abfe0 | ||
|
|
e5a9f92dfb | ||
|
|
a6ff5e6bfc | ||
|
|
be15ad8d25 | ||
|
|
dc9ceda5d2 | ||
|
|
825085f699 | ||
|
|
b1312147e0 | ||
|
|
7ab3d818f0 | ||
|
|
f551cb9677 | ||
|
|
87254ca593 | ||
|
|
ac8b9c6e9d | ||
|
|
b51c1c3b8d | ||
|
|
e14ca19988 | ||
|
|
10d27f412b | ||
|
|
bee72a6622 | ||
|
|
1b260a844f | ||
|
|
e00f3a2fb7 | ||
|
|
b92530de78 | ||
|
|
afaad2f82b | ||
|
|
a507f44465 | ||
|
|
d4fdf793b5 | ||
|
|
feccf532d2 | ||
|
|
859b322773 | ||
|
|
4a74d455de | ||
|
|
c1bde8e0a2 | ||
|
|
560c055b09 | ||
|
|
a0890f98d8 | ||
|
|
401a3cd1bc | ||
|
|
b494d3d1c1 | ||
|
|
6c06057242 | ||
|
|
bb8a8495ff | ||
|
|
423e19909e | ||
|
|
f06fe43649 | ||
|
|
3137c89cf1 | ||
|
|
5dd509c963 | ||
|
|
f48a26f59c | ||
|
|
9d838fa861 | ||
|
|
91404db284 | ||
|
|
4c00552192 | ||
|
|
64762d2cfc | ||
|
|
17b8e2d45b | ||
|
|
9a884f1ccb | ||
|
|
827573c049 | ||
|
|
6fce1f0ac7 | ||
|
|
bbb9d72c13 | ||
|
|
f8b944dee0 | ||
|
|
f840521854 | ||
|
|
134b103605 | ||
|
|
fa35f650b5 | ||
|
|
cb57159e01 | ||
|
|
1752c7e710 | ||
|
|
176d27440c | ||
|
|
832b59b316 | ||
|
|
f521f5e012 | ||
|
|
d42c490bc1 | ||
|
|
5f61f0d2cb | ||
|
|
e253852e4b | ||
|
|
a4819844a4 | ||
|
|
db996335c8 | ||
|
|
85a3333824 | ||
|
|
4bf6a07fe2 | ||
|
|
db70c75807 | ||
|
|
1793ac38a9 | ||
|
|
4aa3c4fa2b | ||
|
|
a447dc86fb | ||
|
|
7cb271b46f | ||
|
|
c55ca155e9 | ||
|
|
5eb4016110 | ||
|
|
34152445cf | ||
|
|
5e0441d96e | ||
|
|
d1c9c54571 | ||
|
|
bb2ed2f898 | ||
|
|
3d88d1827b | ||
|
|
7b5f3fe83a | ||
|
|
f2b9bbdd4a | ||
|
|
c2e906ec97 | ||
|
|
1ec6805112 | ||
|
|
6befdf8b46 | ||
|
|
4cb838d978 | ||
|
|
e61e81a7b5 | ||
|
|
023e1c78df | ||
|
|
6ffa347c77 | ||
|
|
2b50911c9d | ||
|
|
a41820fbf0 | ||
|
|
21725e9304 | ||
|
|
48f52b1be0 | ||
|
|
a5b3ad2e45 | ||
|
|
e30f7094f3 | ||
|
|
5197354375 | ||
|
|
5ac4eff09b | ||
|
|
320e41b142 | ||
|
|
70976b04be | ||
|
|
d65f300988 | ||
|
|
948fd7b8b0 | ||
|
|
f2cebce2eb | ||
|
|
a192e6b430 | ||
|
|
f8a2452247 | ||
|
|
56e6bb0ff6 | ||
|
|
017e504a11 | ||
|
|
9871d5ec2d | ||
|
|
642521a9b3 | ||
|
|
0994829afe | ||
|
|
ce1cc3dddb | ||
|
|
65817dd797 | ||
|
|
c31abb1176 | ||
|
|
d4ca634ef6 | ||
|
|
af00367fed | ||
|
|
9b94313b44 | ||
|
|
5404246e64 | ||
|
|
e0038b82f7 | ||
|
|
0fea71a4f5 | ||
|
|
7571f079c8 | ||
|
|
b5d62cdb55 | ||
|
|
cc25256982 | ||
|
|
da04ff52e4 | ||
|
|
d5b5be6fbe | ||
|
|
3b65d190a4 | ||
|
|
ff04a1fb09 | ||
|
|
1f1d4aaf68 | ||
|
|
196a739f58 | ||
|
|
f20eeebde9 | ||
|
|
b17e6cea21 | ||
|
|
233c2a23de | ||
|
|
46ec0ec43a | ||
|
|
a035c89ea7 | ||
|
|
40228fce5a | ||
|
|
f258b75a24 | ||
|
|
22ab737243 | ||
|
|
0d1f142b1c | ||
|
|
8c475a45bb | ||
|
|
e6e40db9c7 | ||
|
|
cc531a1ca9 | ||
|
|
c3acf049bd | ||
|
|
bab1ef5d38 | ||
|
|
b0c0911ba3 | ||
|
|
3b7fbcd47f | ||
|
|
9ef23b686c | ||
|
|
96784c2052 | ||
|
|
9fe871016d | ||
|
|
e986d06ade | ||
|
|
ebba3ebe67 | ||
|
|
1d09e1d8ce | ||
|
|
d59786fcc4 | ||
|
|
af34164858 | ||
|
|
dcdf86ef5b | ||
|
|
167e9b954a | ||
|
|
9cde67c09c | ||
|
|
ae2fc27799 | ||
|
|
56989a8350 | ||
|
|
8d0e1a99e4 | ||
|
|
ce0b0518c7 | ||
|
|
bf19917537 | ||
|
|
777e9e1e17 | ||
|
|
bfa1993840 | ||
|
|
e041823643 | ||
|
|
63691f5656 | ||
|
|
6dc575f5ee | ||
|
|
0dc6cb298e | ||
|
|
041044eef0 | ||
|
|
1af31a09f4 | ||
|
|
f466105d66 | ||
|
|
ccdb248397 | ||
|
|
ede3a4e850 | ||
|
|
a4ea66cb1f | ||
|
|
2368fbb63c | ||
|
|
f6dfca7182 | ||
|
|
65b97c2f41 | ||
|
|
4a69104af3 | ||
|
|
1786ad16a7 | ||
|
|
2cf4c67e45 | ||
|
|
2ad43ee442 | ||
|
|
15278aacb9 | ||
|
|
57caa4e25e | ||
|
|
66ce952da6 | ||
|
|
b6fa4e248f | ||
|
|
eee8201e4f | ||
|
|
e690e191fd | ||
|
|
d40cd3ec6b | ||
|
|
9df1b037d6 | ||
|
|
28dbffdaf2 | ||
|
|
f8f4c16020 | ||
|
|
8bced7415c | ||
|
|
72d98a7c52 | ||
|
|
0dd85157ea | ||
|
|
2a45851c9e | ||
|
|
b51ee48f78 | ||
|
|
fadac0ffc0 | ||
|
|
799b72cf6f | ||
|
|
fc0f89ac14 | ||
|
|
cb39bd0651 | ||
|
|
f30911d3af | ||
|
|
7bb51b8ceb | ||
|
|
f947cb2613 | ||
|
|
b432f1207c | ||
|
|
43c3f59660 | ||
|
|
f694d8d6ad | ||
|
|
e478f66d8b | ||
|
|
c25310d34f | ||
|
|
d67d189d00 | ||
|
|
8187c148d9 | ||
|
|
e773c8ceda | ||
|
|
4f652c9045 | ||
|
|
126bb0760e | ||
|
|
8c059dbc48 | ||
|
|
fd66f69c19 | ||
|
|
63c01d1541 | ||
|
|
c56d7ac40e | ||
|
|
d9c428c1de | ||
|
|
bd9495486b | ||
|
|
33f1ff4e8c | ||
|
|
1d23bef3fb | ||
|
|
661055105c | ||
|
|
dbfb3ccc42 | ||
|
|
956b3aca97 |
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,15 +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.5
|
||||
hooks:
|
||||
- id: reorder-python-imports
|
||||
args: ['--application-directories=.:src']
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v1.8.0
|
||||
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
|
||||
@@ -46,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]
|
||||
|
||||
41
.travis.yml
41
.travis.yml
@@ -1,5 +1,6 @@
|
||||
sudo: false
|
||||
language: python
|
||||
dist: xenial
|
||||
stages:
|
||||
- baseline
|
||||
- name: test
|
||||
@@ -7,34 +8,41 @@ stages:
|
||||
- name: deploy
|
||||
if: repo = pytest-dev/pytest AND tag IS present
|
||||
python:
|
||||
- '3.6'
|
||||
- '3.7'
|
||||
install:
|
||||
- pip install --upgrade --pre tox
|
||||
env:
|
||||
matrix:
|
||||
- TOXENV=py27
|
||||
# Specialized factors for py27.
|
||||
- TOXENV=py27-pexpect,py27-trial,py27-numpy
|
||||
- TOXENV=py27-nobyte
|
||||
- TOXENV=py27-xdist
|
||||
- TOXENV=py27-pluggymaster PYTEST_NO_COVERAGE=1
|
||||
# Specialized factors for py36.
|
||||
- TOXENV=py36-pexpect,py36-trial,py36-numpy
|
||||
- TOXENV=py36-xdist
|
||||
- TOXENV=py36-pluggymaster PYTEST_NO_COVERAGE=1
|
||||
- TOXENV=py27-pluggymaster
|
||||
# Specialized factors for py37.
|
||||
- TOXENV=py37-pexpect,py37-trial,py37-numpy
|
||||
- TOXENV=py37-pluggymaster
|
||||
- TOXENV=py37-freeze PYTEST_NO_COVERAGE=1
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- python: '3.8-dev'
|
||||
env: TOXENV=py38
|
||||
|
||||
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
|
||||
python: '3.4'
|
||||
- env: TOXENV=py35
|
||||
python: '3.5'
|
||||
- env: TOXENV=py36-freeze PYTEST_NO_COVERAGE=1
|
||||
- env: TOXENV=py36
|
||||
python: '3.6'
|
||||
- env: TOXENV=py38
|
||||
python: '3.8-dev'
|
||||
- env: TOXENV=py37
|
||||
python: '3.7'
|
||||
sudo: required
|
||||
dist: xenial
|
||||
- &test-macos
|
||||
language: generic
|
||||
os: osx
|
||||
@@ -52,10 +60,10 @@ jobs:
|
||||
- brew link python
|
||||
|
||||
- stage: baseline
|
||||
env: TOXENV=py27
|
||||
- env: TOXENV=py34
|
||||
- env: TOXENV=py36
|
||||
- env: TOXENV=linting,docs,doctesting PYTEST_NO_COVERAGE=1
|
||||
env: TOXENV=py27-pexpect,py27-trial,py27-numpy
|
||||
- env: TOXENV=py37-xdist
|
||||
- env: TOXENV=linting,docs,doctesting
|
||||
python: '3.7'
|
||||
|
||||
- stage: deploy
|
||||
python: '3.6'
|
||||
@@ -88,7 +96,8 @@ after_success:
|
||||
- |
|
||||
if [[ "$PYTEST_NO_COVERAGE" != 1 ]]; then
|
||||
set -e
|
||||
pip install coverage
|
||||
# Add last TOXENV to $PATH.
|
||||
PATH="$PWD/.tox/${TOXENV##*,}/bin:$PATH"
|
||||
coverage combine
|
||||
coverage xml --ignore-errors
|
||||
coverage report -m --ignore-errors
|
||||
|
||||
17
AUTHORS
17
AUTHORS
@@ -6,22 +6,28 @@ 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 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
|
||||
@@ -48,6 +54,7 @@ Christian Boelsen
|
||||
Christian Theunert
|
||||
Christian Tismer
|
||||
Christopher Gilling
|
||||
Christopher Dignam
|
||||
CrazyMerlyn
|
||||
Cyrus Maden
|
||||
Dhiren Serai
|
||||
@@ -59,6 +66,7 @@ Danielle Jenkins
|
||||
Dave Hunt
|
||||
David Díaz-Barquero
|
||||
David Mohr
|
||||
David Szotten
|
||||
David Vierra
|
||||
Daw-Ran Liou
|
||||
Denis Kirisov
|
||||
@@ -75,6 +83,7 @@ Endre Galaczi
|
||||
Eric Hunsberger
|
||||
Eric Siegerman
|
||||
Erik M. Bray
|
||||
Fabien Zarifian
|
||||
Fabio Zadrozny
|
||||
Feng Ma
|
||||
Florian Bruhin
|
||||
@@ -112,6 +121,7 @@ Jonas Obrist
|
||||
Jordan Guymon
|
||||
Jordan Moldow
|
||||
Jordan Speicher
|
||||
Joseph Hunkeler
|
||||
Joshua Bronson
|
||||
Jurko Gospodnetić
|
||||
Justyna Janczyszyn
|
||||
@@ -121,6 +131,7 @@ Katerina Koukiou
|
||||
Kevin Cox
|
||||
Kodi B. Arfer
|
||||
Kostis Anagnostopoulos
|
||||
Kristoffer Nordström
|
||||
Kyle Altendorf
|
||||
Lawrence Mitchell
|
||||
Lee Kamentsky
|
||||
@@ -156,11 +167,15 @@ Michael Droettboom
|
||||
Michael Seifert
|
||||
Michal Wajszczuk
|
||||
Mihai Capotă
|
||||
Mike Hoyle (hoylemd)
|
||||
Mike Lundy
|
||||
Miro Hrončok
|
||||
Nathaniel Waisbrot
|
||||
Ned Batchelder
|
||||
Neven Mundar
|
||||
Nicholas Devenish
|
||||
Nicholas Murphy
|
||||
Niclas Olofsson
|
||||
Nicolas Delaby
|
||||
Oleg Pidsadnyi
|
||||
Oleg Sushchenko
|
||||
@@ -168,6 +183,7 @@ Oliver Bestwalter
|
||||
Omar Kohl
|
||||
Omer Hadari
|
||||
Ondřej Súkup
|
||||
Oscar Benjamin
|
||||
Patrick Hayes
|
||||
Paweł Adamczak
|
||||
Pedro Algarvio
|
||||
@@ -202,6 +218,7 @@ Stefan Zimmermann
|
||||
Stefano Taschini
|
||||
Steffen Allner
|
||||
Stephan Obermann
|
||||
Sven-Hendrik Haase
|
||||
Tadek Teleżyński
|
||||
Tarcisio Fischer
|
||||
Tareq Alayan
|
||||
|
||||
672
CHANGELOG.rst
672
CHANGELOG.rst
@@ -18,6 +18,670 @@ with advance notice in the **Deprecations** section of releases.
|
||||
|
||||
.. towncrier release notes start
|
||||
|
||||
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)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#4265 <https://github.com/pytest-dev/pytest/issues/4265>`_: Validate arguments from the ``PYTEST_ADDOPTS`` environment variable and the ``addopts`` ini option separately.
|
||||
|
||||
|
||||
- `#4435 <https://github.com/pytest-dev/pytest/issues/4435>`_: Fix ``raises(..., 'code(string)')`` frame filename.
|
||||
|
||||
|
||||
- `#4500 <https://github.com/pytest-dev/pytest/issues/4500>`_: When a fixture yields and a log call is made after the test runs, and, if the test is interrupted, capture attributes are ``None``.
|
||||
|
||||
|
||||
- `#4538 <https://github.com/pytest-dev/pytest/issues/4538>`_: Raise ``TypeError`` for ``with raises(..., match=<non-None falsey value>)``.
|
||||
|
||||
|
||||
|
||||
Improved Documentation
|
||||
----------------------
|
||||
|
||||
- `#1495 <https://github.com/pytest-dev/pytest/issues/1495>`_: Document common doctest fixture directory tree structure pitfalls
|
||||
|
||||
|
||||
pytest 4.0.1 (2018-11-23)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#3952 <https://github.com/pytest-dev/pytest/issues/3952>`_: Display warnings before "short test summary info" again, but still later warnings in the end.
|
||||
|
||||
|
||||
- `#4386 <https://github.com/pytest-dev/pytest/issues/4386>`_: Handle uninitialized exceptioninfo in repr/str.
|
||||
|
||||
|
||||
- `#4393 <https://github.com/pytest-dev/pytest/issues/4393>`_: Do not create ``.gitignore``/``README.md`` files in existing cache directories.
|
||||
|
||||
|
||||
- `#4400 <https://github.com/pytest-dev/pytest/issues/4400>`_: Rearrange warning handling for the yield test errors so the opt-out in 4.0.x correctly works.
|
||||
|
||||
|
||||
- `#4405 <https://github.com/pytest-dev/pytest/issues/4405>`_: Fix collection of testpaths with ``--pyargs``.
|
||||
|
||||
|
||||
- `#4412 <https://github.com/pytest-dev/pytest/issues/4412>`_: Fix assertion rewriting involving ``Starred`` + side-effects.
|
||||
|
||||
|
||||
- `#4425 <https://github.com/pytest-dev/pytest/issues/4425>`_: Ensure we resolve the absolute path when the given ``--basetemp`` is a relative path.
|
||||
|
||||
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- `#4315 <https://github.com/pytest-dev/pytest/issues/4315>`_: Use ``pkg_resources.parse_version`` instead of ``LooseVersion`` in minversion check.
|
||||
|
||||
|
||||
- `#4440 <https://github.com/pytest-dev/pytest/issues/4440>`_: Adjust the stack level of some internal pytest warnings.
|
||||
|
||||
|
||||
pytest 4.0.0 (2018-11-13)
|
||||
=========================
|
||||
|
||||
Removals
|
||||
--------
|
||||
|
||||
- `#3737 <https://github.com/pytest-dev/pytest/issues/3737>`_: **RemovedInPytest4Warnings are now errors by default.**
|
||||
|
||||
Following our plan to remove deprecated features with as little disruption as
|
||||
possible, all warnings of type ``RemovedInPytest4Warnings`` now generate errors
|
||||
instead of warning messages.
|
||||
|
||||
**The affected features will be effectively removed in pytest 4.1**, so please consult the
|
||||
`Deprecations and Removals <https://docs.pytest.org/en/latest/deprecations.html>`__
|
||||
section in the docs for directions on how to update existing code.
|
||||
|
||||
In the pytest ``4.0.X`` series, it is possible to change the errors back into warnings as a stop
|
||||
gap measure by adding this to your ``pytest.ini`` file:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[pytest]
|
||||
filterwarnings =
|
||||
ignore::pytest.RemovedInPytest4Warning
|
||||
|
||||
But this will stop working when pytest ``4.1`` is released.
|
||||
|
||||
**If you have concerns** about the removal of a specific feature, please add a
|
||||
comment to `#4348 <https://github.com/pytest-dev/pytest/issues/4348>`__.
|
||||
|
||||
|
||||
- `#4358 <https://github.com/pytest-dev/pytest/issues/4358>`_: Remove the ``::()`` notation to denote a test class instance in node ids.
|
||||
|
||||
Previously, node ids that contain test instances would use ``::()`` to denote the instance like this::
|
||||
|
||||
test_foo.py::Test::()::test_bar
|
||||
|
||||
The extra ``::()`` was puzzling to most users and has been removed, so that the test id becomes now::
|
||||
|
||||
test_foo.py::Test::test_bar
|
||||
|
||||
This change could not accompany a deprecation period as is usual when user-facing functionality changes because
|
||||
it was not really possible to detect when the functionality was being used explicitly.
|
||||
|
||||
The extra ``::()`` might have been removed in some places internally already,
|
||||
which then led to confusion in places where it was expected, e.g. with
|
||||
``--deselect`` (`#4127 <https://github.com/pytest-dev/pytest/issues/4127>`_).
|
||||
|
||||
Test class instances are also not listed with ``--collect-only`` anymore.
|
||||
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- `#4270 <https://github.com/pytest-dev/pytest/issues/4270>`_: The ``cache_dir`` option uses ``$TOX_ENV_DIR`` as prefix (if set in the environment).
|
||||
|
||||
This uses a different cache per tox environment by default.
|
||||
|
||||
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#3554 <https://github.com/pytest-dev/pytest/issues/3554>`_: Fix ``CallInfo.__repr__`` for when the call is not finished yet.
|
||||
|
||||
|
||||
pytest 3.10.1 (2018-11-11)
|
||||
==========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#4287 <https://github.com/pytest-dev/pytest/issues/4287>`_: Fix nested usage of debugging plugin (pdb), e.g. with pytester's ``testdir.runpytest``.
|
||||
|
||||
|
||||
- `#4304 <https://github.com/pytest-dev/pytest/issues/4304>`_: Block the ``stepwise`` plugin if ``cacheprovider`` is also blocked, as one depends on the other.
|
||||
|
||||
|
||||
- `#4306 <https://github.com/pytest-dev/pytest/issues/4306>`_: Parse ``minversion`` as an actual version and not as dot-separated strings.
|
||||
|
||||
|
||||
- `#4310 <https://github.com/pytest-dev/pytest/issues/4310>`_: Fix duplicate collection due to multiple args matching the same packages.
|
||||
|
||||
|
||||
- `#4321 <https://github.com/pytest-dev/pytest/issues/4321>`_: Fix ``item.nodeid`` with resolved symlinks.
|
||||
|
||||
|
||||
- `#4325 <https://github.com/pytest-dev/pytest/issues/4325>`_: Fix collection of direct symlinked files, where the target does not match ``python_files``.
|
||||
|
||||
|
||||
- `#4329 <https://github.com/pytest-dev/pytest/issues/4329>`_: Fix TypeError in report_collect with _collect_report_last_write.
|
||||
|
||||
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- `#4305 <https://github.com/pytest-dev/pytest/issues/4305>`_: Replace byte/unicode helpers in test_capture with python level syntax.
|
||||
|
||||
|
||||
pytest 3.10.0 (2018-11-03)
|
||||
==========================
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- `#2619 <https://github.com/pytest-dev/pytest/issues/2619>`_: Resume capturing output after ``continue`` with ``__import__("pdb").set_trace()``.
|
||||
|
||||
This also adds a new ``pytest_leave_pdb`` hook, and passes in ``pdb`` to the
|
||||
existing ``pytest_enter_pdb`` hook.
|
||||
|
||||
|
||||
- `#4147 <https://github.com/pytest-dev/pytest/issues/4147>`_: Add ``--sw``, ``--stepwise`` as an alternative to ``--lf -x`` for stopping at the first failure, but starting the next test invocation from that test. See `the documentation <https://docs.pytest.org/en/latest/cache.html#stepwise>`__ for more info.
|
||||
|
||||
|
||||
- `#4188 <https://github.com/pytest-dev/pytest/issues/4188>`_: Make ``--color`` emit colorful dots when not running in verbose mode. Earlier, it would only colorize the test-by-test output if ``--verbose`` was also passed.
|
||||
|
||||
|
||||
- `#4225 <https://github.com/pytest-dev/pytest/issues/4225>`_: Improve performance with collection reporting in non-quiet mode with terminals.
|
||||
|
||||
The "collecting …" message is only printed/updated every 0.5s.
|
||||
|
||||
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#2701 <https://github.com/pytest-dev/pytest/issues/2701>`_: Fix false ``RemovedInPytest4Warning: usage of Session... is deprecated, please use pytest`` warnings.
|
||||
|
||||
|
||||
- `#4046 <https://github.com/pytest-dev/pytest/issues/4046>`_: Fix problems with running tests in package ``__init__.py`` files.
|
||||
|
||||
|
||||
- `#4260 <https://github.com/pytest-dev/pytest/issues/4260>`_: Swallow warnings during anonymous compilation of source.
|
||||
|
||||
|
||||
- `#4262 <https://github.com/pytest-dev/pytest/issues/4262>`_: Fix access denied error when deleting stale directories created by ``tmpdir`` / ``tmp_path``.
|
||||
|
||||
|
||||
- `#611 <https://github.com/pytest-dev/pytest/issues/611>`_: Naming a fixture ``request`` will now raise a warning: the ``request`` fixture is internal and
|
||||
should not be overwritten as it will lead to internal errors.
|
||||
|
||||
- `#4266 <https://github.com/pytest-dev/pytest/issues/4266>`_: Handle (ignore) exceptions raised during collection, e.g. with Django's LazySettings proxy class.
|
||||
|
||||
|
||||
|
||||
Improved Documentation
|
||||
----------------------
|
||||
|
||||
- `#4255 <https://github.com/pytest-dev/pytest/issues/4255>`_: Added missing documentation about the fact that module names passed to filter warnings are not regex-escaped.
|
||||
|
||||
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- `#4272 <https://github.com/pytest-dev/pytest/issues/4272>`_: Display cachedir also in non-verbose mode if non-default.
|
||||
|
||||
|
||||
- `#4277 <https://github.com/pytest-dev/pytest/issues/4277>`_: pdb: improve message about output capturing with ``set_trace``.
|
||||
|
||||
Do not display "IO-capturing turned off/on" when ``-s`` is used to avoid
|
||||
confusion.
|
||||
|
||||
|
||||
- `#4279 <https://github.com/pytest-dev/pytest/issues/4279>`_: Improve message and stack level of warnings issued by ``monkeypatch.setenv`` when the value of the environment variable is not a ``str``.
|
||||
|
||||
|
||||
pytest 3.9.3 (2018-10-27)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#4174 <https://github.com/pytest-dev/pytest/issues/4174>`_: Fix "ValueError: Plugin already registered" with conftest plugins via symlink.
|
||||
|
||||
|
||||
- `#4181 <https://github.com/pytest-dev/pytest/issues/4181>`_: Handle race condition between creation and deletion of temporary folders.
|
||||
|
||||
|
||||
- `#4221 <https://github.com/pytest-dev/pytest/issues/4221>`_: Fix bug where the warning summary at the end of the test session was not showing the test where the warning was originated.
|
||||
|
||||
|
||||
- `#4243 <https://github.com/pytest-dev/pytest/issues/4243>`_: Fix regression when ``stacklevel`` for warnings was passed as positional argument on python2.
|
||||
|
||||
|
||||
|
||||
Improved Documentation
|
||||
----------------------
|
||||
|
||||
- `#3851 <https://github.com/pytest-dev/pytest/issues/3851>`_: Add reference to ``empty_parameter_set_mark`` ini option in documentation of ``@pytest.mark.parametrize``
|
||||
|
||||
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- `#4028 <https://github.com/pytest-dev/pytest/issues/4028>`_: Revert patching of ``sys.breakpointhook`` since it appears to do nothing.
|
||||
|
||||
|
||||
- `#4233 <https://github.com/pytest-dev/pytest/issues/4233>`_: Apply an import sorter (``reorder-python-imports``) to the codebase.
|
||||
|
||||
|
||||
- `#4248 <https://github.com/pytest-dev/pytest/issues/4248>`_: Remove use of unnecessary compat shim, six.binary_type
|
||||
|
||||
|
||||
pytest 3.9.2 (2018-10-22)
|
||||
=========================
|
||||
|
||||
@@ -328,7 +992,7 @@ Features
|
||||
the standard warnings filters to manage those warnings. This introduces ``PytestWarning``,
|
||||
``PytestDeprecationWarning`` and ``RemovedInPytest4Warning`` warning types as part of the public API.
|
||||
|
||||
Consult `the documentation <https://docs.pytest.org/en/latest/warnings.html#internal-pytest-warnings>`_ for more info.
|
||||
Consult `the documentation <https://docs.pytest.org/en/latest/warnings.html#internal-pytest-warnings>`__ for more info.
|
||||
|
||||
|
||||
- `#2908 <https://github.com/pytest-dev/pytest/issues/2908>`_: ``DeprecationWarning`` and ``PendingDeprecationWarning`` are now shown by default if no other warning filter is
|
||||
@@ -503,7 +1167,7 @@ Bug Fixes
|
||||
- `#3473 <https://github.com/pytest-dev/pytest/issues/3473>`_: Raise immediately if ``approx()`` is given an expected value of a type it doesn't understand (e.g. strings, nested dicts, etc.).
|
||||
|
||||
|
||||
- `#3712 <https://github.com/pytest-dev/pytest/issues/3712>`_: Correctly represent the dimensions of an numpy array when calling ``repr()`` on ``approx()``.
|
||||
- `#3712 <https://github.com/pytest-dev/pytest/issues/3712>`_: Correctly represent the dimensions of a numpy array when calling ``repr()`` on ``approx()``.
|
||||
|
||||
- `#3742 <https://github.com/pytest-dev/pytest/issues/3742>`_: Fix incompatibility with third party plugins during collection, which produced the error ``object has no attribute '_collectfile'``.
|
||||
|
||||
@@ -753,7 +1417,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
|
||||
@@ -1488,7 +2152,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>`_)
|
||||
|
||||
|
||||
@@ -169,7 +169,7 @@ Short version
|
||||
#. Follow **PEP-8** for naming and `black <https://github.com/ambv/black>`_ for formatting.
|
||||
#. Tests are run using ``tox``::
|
||||
|
||||
tox -e linting,py27,py36
|
||||
tox -e linting,py27,py37
|
||||
|
||||
The test environments above are usually enough to cover most cases locally.
|
||||
|
||||
@@ -237,12 +237,12 @@ Here is a simple overview, with pytest-specific bits:
|
||||
|
||||
#. Run all the tests
|
||||
|
||||
You need to have Python 2.7 and 3.6 available in your system. Now
|
||||
You need to have Python 2.7 and 3.7 available in your system. Now
|
||||
running tests is as simple as issuing this command::
|
||||
|
||||
$ tox -e linting,py27,py36
|
||||
$ tox -e linting,py27,py37
|
||||
|
||||
This command will run tests via the "tox" tool against Python 2.7 and 3.6
|
||||
This command will run tests via the "tox" tool against Python 2.7 and 3.7
|
||||
and also perform "lint" coding-style checks.
|
||||
|
||||
#. You can now edit your local working copy and run the tests again as necessary. Please follow PEP-8 for naming.
|
||||
@@ -252,9 +252,9 @@ Here is a simple overview, with pytest-specific bits:
|
||||
|
||||
$ tox -e py27 -- --pdb
|
||||
|
||||
Or to only run tests in a particular test module on Python 3.6::
|
||||
Or to only run tests in a particular test module on Python 3.7::
|
||||
|
||||
$ tox -e py36 -- testing/test_config.py
|
||||
$ tox -e py37 -- testing/test_config.py
|
||||
|
||||
|
||||
When committing, ``pre-commit`` will re-format the files if necessary.
|
||||
|
||||
@@ -46,5 +46,3 @@ taking a lot of time to make a new one.
|
||||
* testing-in-python@lists.idyll.org (only major/minor releases)
|
||||
|
||||
And announce it on `Twitter <https://twitter.com/>`_ with the ``#pytest`` hashtag.
|
||||
|
||||
#. After a minor/major release, merge ``release-X.Y.Z`` into ``master`` and push (or open a PR).
|
||||
|
||||
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.
|
||||
|
||||
|
||||
15
appveyor.yml
15
appveyor.yml
@@ -1,8 +1,9 @@
|
||||
environment:
|
||||
matrix:
|
||||
- TOXENV: "py37-xdist"
|
||||
- TOXENV: "py27-xdist"
|
||||
- TOXENV: "py27"
|
||||
- TOXENV: "py37"
|
||||
PYTEST_NO_COVERAGE: "1"
|
||||
- TOXENV: "linting,docs,doctesting"
|
||||
- TOXENV: "py36"
|
||||
- TOXENV: "py35"
|
||||
@@ -12,15 +13,11 @@ environment:
|
||||
# Specialized factors for py27.
|
||||
- TOXENV: "py27-trial,py27-numpy,py27-nobyte"
|
||||
- TOXENV: "py27-pluggymaster"
|
||||
# Specialized factors for py37.
|
||||
- TOXENV: "py37-trial,py37-numpy"
|
||||
- TOXENV: "py37-pluggymaster"
|
||||
- TOXENV: "py37-freeze"
|
||||
PYTEST_NO_COVERAGE: "1"
|
||||
- TOXENV: "py27-xdist"
|
||||
# Specialized factors for py36.
|
||||
- TOXENV: "py36-trial,py36-numpy"
|
||||
- TOXENV: "py36-pluggymaster"
|
||||
PYTEST_NO_COVERAGE: "1"
|
||||
- TOXENV: "py36-freeze"
|
||||
PYTEST_NO_COVERAGE: "1"
|
||||
- TOXENV: "py36-xdist"
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
# 2.7.5 3.3.2
|
||||
# FilesCompleter 75.1109 69.2116
|
||||
# FastFilesCompleter 0.7383 1.0760
|
||||
|
||||
import timeit
|
||||
|
||||
imports = [
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from six.moves import range
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
# flasky extensions. flasky pygments style based on tango style
|
||||
from pygments.style import Style
|
||||
from pygments.token import (
|
||||
Keyword,
|
||||
Name,
|
||||
Comment,
|
||||
String,
|
||||
Error,
|
||||
Number,
|
||||
Operator,
|
||||
Generic,
|
||||
Whitespace,
|
||||
Punctuation,
|
||||
Other,
|
||||
Literal,
|
||||
)
|
||||
from pygments.token import Comment
|
||||
from pygments.token import Error
|
||||
from pygments.token import Generic
|
||||
from pygments.token import Keyword
|
||||
from pygments.token import Literal
|
||||
from pygments.token import Name
|
||||
from pygments.token import Number
|
||||
from pygments.token import Operator
|
||||
from pygments.token import Other
|
||||
from pygments.token import Punctuation
|
||||
from pygments.token import String
|
||||
from pygments.token import Whitespace
|
||||
|
||||
|
||||
class FlaskyStyle(Style):
|
||||
|
||||
@@ -6,6 +6,16 @@ Release announcements
|
||||
:maxdepth: 2
|
||||
|
||||
|
||||
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
|
||||
release-3.10.1
|
||||
release-3.10.0
|
||||
release-3.9.3
|
||||
release-3.9.2
|
||||
release-3.9.1
|
||||
release-3.9.0
|
||||
|
||||
43
doc/en/announce/release-3.10.0.rst
Normal file
43
doc/en/announce/release-3.10.0.rst
Normal file
@@ -0,0 +1,43 @@
|
||||
pytest-3.10.0
|
||||
=======================================
|
||||
|
||||
The pytest team is proud to announce the 3.10.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:
|
||||
|
||||
* Anders Hovmöller
|
||||
* Andreu Vallbona Plazas
|
||||
* Ankit Goel
|
||||
* Anthony Sottile
|
||||
* Bernardo Gomes
|
||||
* Brianna Laugher
|
||||
* Bruno Oliveira
|
||||
* Daniel Hahler
|
||||
* David Szotten
|
||||
* Mick Koch
|
||||
* Niclas Olofsson
|
||||
* Palash Chatterjee
|
||||
* Ronny Pfannschmidt
|
||||
* Sven-Hendrik Haase
|
||||
* Ville Skyttä
|
||||
* William Jamir Silva
|
||||
|
||||
|
||||
Happy testing,
|
||||
The Pytest Development Team
|
||||
24
doc/en/announce/release-3.10.1.rst
Normal file
24
doc/en/announce/release-3.10.1.rst
Normal file
@@ -0,0 +1,24 @@
|
||||
pytest-3.10.1
|
||||
=======================================
|
||||
|
||||
pytest 3.10.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
|
||||
* Boris Feld
|
||||
* Bruno Oliveira
|
||||
* Daniel Hahler
|
||||
* Fabien ZARIFIAN
|
||||
* Jon Dufresne
|
||||
* Ronny Pfannschmidt
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
||||
24
doc/en/announce/release-3.9.3.rst
Normal file
24
doc/en/announce/release-3.9.3.rst
Normal file
@@ -0,0 +1,24 @@
|
||||
pytest-3.9.3
|
||||
=======================================
|
||||
|
||||
pytest 3.9.3 has just been released to PyPI.
|
||||
|
||||
This is a bug-fix release, being a drop-in replacement. To upgrade::
|
||||
|
||||
pip install --upgrade pytest
|
||||
|
||||
The full changelog is available at https://docs.pytest.org/en/latest/changelog.html.
|
||||
|
||||
Thanks to all who contributed to this release, among them:
|
||||
|
||||
* Andreas Profous
|
||||
* Ankit Goel
|
||||
* Anthony Sottile
|
||||
* Bruno Oliveira
|
||||
* Daniel Hahler
|
||||
* Jon Dufresne
|
||||
* Ronny Pfannschmidt
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
||||
30
doc/en/announce/release-4.0.0.rst
Normal file
30
doc/en/announce/release-4.0.0.rst
Normal file
@@ -0,0 +1,30 @@
|
||||
pytest-4.0.0
|
||||
=======================================
|
||||
|
||||
The pytest team is proud to announce the 4.0.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:
|
||||
|
||||
* Bruno Oliveira
|
||||
* Daniel Hahler
|
||||
* Ronny Pfannschmidt
|
||||
|
||||
|
||||
Happy testing,
|
||||
The Pytest Development Team
|
||||
23
doc/en/announce/release-4.0.1.rst
Normal file
23
doc/en/announce/release-4.0.1.rst
Normal file
@@ -0,0 +1,23 @@
|
||||
pytest-4.0.1
|
||||
=======================================
|
||||
|
||||
pytest 4.0.1 has just been released to PyPI.
|
||||
|
||||
This is a bug-fix release, being a drop-in replacement. To upgrade::
|
||||
|
||||
pip install --upgrade pytest
|
||||
|
||||
The full changelog is available at https://docs.pytest.org/en/latest/changelog.html.
|
||||
|
||||
Thanks to all who contributed to this release, among them:
|
||||
|
||||
* Anthony Sottile
|
||||
* Bruno Oliveira
|
||||
* Daniel Hahler
|
||||
* Michael D. Hoyle
|
||||
* Ronny Pfannschmidt
|
||||
* Slam
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
||||
24
doc/en/announce/release-4.0.2.rst
Normal file
24
doc/en/announce/release-4.0.2.rst
Normal file
@@ -0,0 +1,24 @@
|
||||
pytest-4.0.2
|
||||
=======================================
|
||||
|
||||
pytest 4.0.2 has just been released to PyPI.
|
||||
|
||||
This is a bug-fix release, being a drop-in replacement. To upgrade::
|
||||
|
||||
pip install --upgrade pytest
|
||||
|
||||
The full changelog is available at https://docs.pytest.org/en/latest/changelog.html.
|
||||
|
||||
Thanks to all who contributed to this release, among them:
|
||||
|
||||
* Anthony Sottile
|
||||
* Bruno Oliveira
|
||||
* Daniel Hahler
|
||||
* Pedro Algarvio
|
||||
* Ronny Pfannschmidt
|
||||
* Tomer Keren
|
||||
* Yash Todi
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
||||
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
|
||||
@@ -22,11 +22,14 @@ following::
|
||||
assert f() == 4
|
||||
|
||||
to assert that your function returns a certain value. If this assertion fails
|
||||
you will see the return value of the function call::
|
||||
you will see the return value of the function call:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest test_assert1.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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
|
||||
|
||||
@@ -98,10 +101,9 @@ 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::
|
||||
|
||||
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
|
||||
which will execute the specified function with args and kwargs and
|
||||
assert that the given ``ExpectedException`` is raised. The reporter will
|
||||
provide you with helpful output in case of failures such as *no
|
||||
exception* or *wrong exception*.
|
||||
|
||||
@@ -165,11 +167,14 @@ when it encounters comparisons. For example::
|
||||
set2 = set("8035")
|
||||
assert set1 == set2
|
||||
|
||||
if you run this module::
|
||||
if you run this module:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest test_assert2.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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
|
||||
|
||||
@@ -200,8 +205,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.
|
||||
@@ -235,7 +240,9 @@ now, given this test module::
|
||||
assert f1 == f2
|
||||
|
||||
you can run the test module and get the custom output defined in
|
||||
the conftest file::
|
||||
the conftest file:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -q test_foocompare.py
|
||||
F [100%]
|
||||
|
||||
@@ -12,7 +12,9 @@ For information on plugin hooks and objects, see :ref:`plugins`.
|
||||
|
||||
For information on the ``pytest.mark`` mechanism, see :ref:`mark`.
|
||||
|
||||
For information about fixtures, see :ref:`fixtures`. To see a complete list of available fixtures (add ``-v`` to also see fixtures with leading ``_``), type ::
|
||||
For information about fixtures, see :ref:`fixtures`. To see a complete list of available fixtures (add ``-v`` to also see fixtures with leading ``_``), type :
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -q --fixtures
|
||||
cache
|
||||
@@ -66,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
|
||||
@@ -75,7 +75,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||
caplog
|
||||
Access and control log capturing.
|
||||
|
||||
Captured logs are available through the following methods::
|
||||
Captured logs are available through the following properties/methods::
|
||||
|
||||
* caplog.text -> string containing formatted log output
|
||||
* caplog.records -> list of logging.LogRecord instances
|
||||
|
||||
@@ -43,7 +43,9 @@ First, let's create 50 test invocation of which only 2 fail::
|
||||
if i in (17, 25):
|
||||
pytest.fail("bad luck")
|
||||
|
||||
If you run this for the first time you will see two failures::
|
||||
If you run this for the first time you will see two failures:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -q
|
||||
.................F.......F........................ [100%]
|
||||
@@ -72,13 +74,16 @@ If you run this for the first time you will see two failures::
|
||||
test_50.py:6: Failed
|
||||
2 failed, 48 passed in 0.12 seconds
|
||||
|
||||
If you then run it with ``--lf``::
|
||||
If you then run it with ``--lf``:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest --lf
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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%]
|
||||
@@ -113,11 +118,14 @@ not been run ("deselected").
|
||||
|
||||
Now, if you run with the ``--ff`` option, all tests will be run but the first
|
||||
previous failures will be executed first (as can be seen from the series
|
||||
of ``FF`` and dots)::
|
||||
of ``FF`` and dots):
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest --ff
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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
|
||||
@@ -179,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
|
||||
@@ -191,8 +202,9 @@ 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
|
||||
|
||||
$ pytest -q
|
||||
F [100%]
|
||||
@@ -204,12 +216,18 @@ 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
|
||||
|
||||
$ pytest -q
|
||||
F [100%]
|
||||
@@ -221,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.
|
||||
@@ -232,18 +252,28 @@ Inspecting Cache content
|
||||
-------------------------------
|
||||
|
||||
You can always peek at the content of the cache using the
|
||||
``--cache-show`` command line option::
|
||||
``--cache-show`` command line option:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest --cache-show
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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:
|
||||
[]
|
||||
example/value contains:
|
||||
42
|
||||
|
||||
@@ -260,3 +290,9 @@ by adding the ``--cache-clear`` option like this::
|
||||
This is recommended for invocations from Continuous Integration
|
||||
servers where isolation and correctness is more important
|
||||
than speed.
|
||||
|
||||
|
||||
Stepwise
|
||||
--------
|
||||
|
||||
As an alternative to ``--lf -x``, especially for cases where you expect a large part of the test suite will fail, ``--sw``, ``--stepwise`` allows you to fix them one at a time. The test suite will run until the first failure and then stop. At the next invocation, tests will continue from the last failing test and then run until the next failing test. You may use the ``--stepwise-skip`` option to ignore one failing test and stop the test execution on the second failing test instead. This is useful if you get stuck on a failing test and just want to ignore it until later.
|
||||
|
||||
@@ -52,7 +52,7 @@ is that you can use print statements for debugging::
|
||||
# content of test_module.py
|
||||
|
||||
def setup_function(function):
|
||||
print ("setting up %s" % function)
|
||||
print("setting up %s" % function)
|
||||
|
||||
def test_func1():
|
||||
assert True
|
||||
@@ -61,11 +61,14 @@ is that you can use print statements for debugging::
|
||||
assert False
|
||||
|
||||
and running this module will show you precisely the output
|
||||
of the failing function and hide the other one::
|
||||
of the failing function and hide the other one:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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
|
||||
|
||||
|
||||
@@ -10,17 +10,15 @@
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
# The short X.Y version.
|
||||
|
||||
import datetime
|
||||
import os
|
||||
import sys
|
||||
import datetime
|
||||
|
||||
from _pytest import __version__ as version
|
||||
|
||||
@@ -42,11 +40,13 @@ todo_include_todos = 1
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
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",
|
||||
]
|
||||
|
||||
@@ -65,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
|
||||
|
||||
@@ -33,6 +33,7 @@ Full pytest documentation
|
||||
reference
|
||||
|
||||
goodpractices
|
||||
flaky
|
||||
pythonpath
|
||||
customize
|
||||
example/index
|
||||
@@ -40,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
|
||||
|
||||
@@ -58,11 +58,14 @@ and another like this::
|
||||
"""
|
||||
return 42
|
||||
|
||||
then you can just invoke ``pytest`` without command line options::
|
||||
then you can just invoke ``pytest`` without command line options:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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
|
||||
|
||||
@@ -152,6 +155,9 @@ which can then be used in your doctests directly::
|
||||
"""
|
||||
pass
|
||||
|
||||
Note that like the normal ``conftest.py``, the fixtures are discovered in the directory tree conftest is in.
|
||||
Meaning that if you put your doctest with your source code, the relevant conftest.py needs to be in the same directory tree.
|
||||
Fixtures will not be discovered in a sibling directory tree!
|
||||
|
||||
Output format
|
||||
-------------
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
from pytest import raises
|
||||
import _pytest._code
|
||||
import six
|
||||
|
||||
import _pytest._code
|
||||
import pytest
|
||||
from pytest import raises
|
||||
|
||||
|
||||
def otherfunc(a, b):
|
||||
assert a == b
|
||||
@@ -15,15 +17,11 @@ def otherfunc_multi(a, b):
|
||||
assert a == b
|
||||
|
||||
|
||||
@pytest.mark.parametrize("param1, param2", [(3, 6)])
|
||||
def test_generative(param1, param2):
|
||||
assert param1 * 2 < param2
|
||||
|
||||
|
||||
def pytest_generate_tests(metafunc):
|
||||
if "param1" in metafunc.fixturenames:
|
||||
metafunc.addcall(funcargs=dict(param1=3, param2=6))
|
||||
|
||||
|
||||
class TestFailing(object):
|
||||
def test_simple(self):
|
||||
def f():
|
||||
@@ -100,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):
|
||||
@@ -143,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")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import pytest
|
||||
import py
|
||||
|
||||
import pytest
|
||||
|
||||
mydir = py.path.local(__file__).dirpath()
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -27,27 +27,31 @@ You can "mark" a test function with custom metadata like this::
|
||||
|
||||
.. versionadded:: 2.2
|
||||
|
||||
You can then restrict a test run to only run tests marked with ``webtest``::
|
||||
You can then restrict a test run to only run tests marked with ``webtest``:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -v -m webtest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/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%]
|
||||
|
||||
================== 1 passed, 3 deselected in 0.12 seconds ==================
|
||||
|
||||
Or the inverse, running all tests except the webtest ones::
|
||||
Or the inverse, running all tests except the webtest ones:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -v -m "not webtest"
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/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%]
|
||||
@@ -60,12 +64,14 @@ Selecting tests based on their node ID
|
||||
|
||||
You can provide one or more :ref:`node IDs <node-id>` as positional
|
||||
arguments to select only specified tests. This makes it easy to select
|
||||
tests based on their module, class, method, or function name::
|
||||
tests based on their module, class, method, or function name:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -v test_server.py::TestClass::test_method
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/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
|
||||
|
||||
@@ -73,12 +79,14 @@ tests based on their module, class, method, or function name::
|
||||
|
||||
========================= 1 passed in 0.12 seconds =========================
|
||||
|
||||
You can also select on the class::
|
||||
You can also select on the class:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -v test_server.py::TestClass
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/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
|
||||
|
||||
@@ -86,19 +94,21 @@ You can also select on the class::
|
||||
|
||||
========================= 1 passed in 0.12 seconds =========================
|
||||
|
||||
Or select multiple nodes::
|
||||
Or select multiple nodes:
|
||||
|
||||
$ pytest -v test_server.py::TestClass test_server.py::test_send_http
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
|
||||
cachedir: .pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 2 items
|
||||
.. code-block:: pytest
|
||||
|
||||
test_server.py::TestClass::test_method PASSED [ 50%]
|
||||
test_server.py::test_send_http PASSED [100%]
|
||||
$ 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/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 2 items
|
||||
|
||||
========================= 2 passed in 0.12 seconds =========================
|
||||
test_server.py::TestClass::test_method PASSED [ 50%]
|
||||
test_server.py::test_send_http PASSED [100%]
|
||||
|
||||
========================= 2 passed in 0.12 seconds =========================
|
||||
|
||||
.. _node-id:
|
||||
|
||||
@@ -124,27 +134,31 @@ Using ``-k expr`` to select tests based on their name
|
||||
You can use the ``-k`` command line option to specify an expression
|
||||
which implements a substring match on the test names instead of the
|
||||
exact match on markers that ``-m`` provides. This makes it easy to
|
||||
select tests based on their names::
|
||||
select tests based on their names:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -v -k http # running with the above defined example module
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/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%]
|
||||
|
||||
================== 1 passed, 3 deselected in 0.12 seconds ==================
|
||||
|
||||
And you can also run all tests except the ones that match the keyword::
|
||||
And you can also run all tests except the ones that match the keyword:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -k "not send_http" -v
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/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%]
|
||||
@@ -152,14 +166,16 @@ And you can also run all tests except the ones that match the keyword::
|
||||
|
||||
================== 3 passed, 1 deselected in 0.12 seconds ==================
|
||||
|
||||
Or to select "http" and "quick" tests::
|
||||
Or to select "http" and "quick" tests:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -k "http or quick" -v
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/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%]
|
||||
@@ -271,8 +287,12 @@ You can also set a module level marker::
|
||||
import pytest
|
||||
pytestmark = pytest.mark.webtest
|
||||
|
||||
in which case it will be applied to all functions and
|
||||
methods defined in the module.
|
||||
or multiple markers::
|
||||
|
||||
pytestmark = [pytest.mark.webtest, pytest.mark.slowtest]
|
||||
|
||||
in which case markers will be applied (in left-to-right order) to
|
||||
all functions and methods defined in the module.
|
||||
|
||||
.. _`marking individual tests when using parametrize`:
|
||||
|
||||
@@ -288,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):
|
||||
@@ -298,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
|
||||
@@ -347,11 +358,14 @@ A test file using this local plugin::
|
||||
pass
|
||||
|
||||
and an example invocations specifying a different environment than what
|
||||
the test needs::
|
||||
the test needs:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -E stage2
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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
|
||||
|
||||
@@ -359,11 +373,14 @@ the test needs::
|
||||
|
||||
======================== 1 skipped in 0.12 seconds =========================
|
||||
|
||||
and here is one that specifies exactly the environment needed::
|
||||
and here is one that specifies exactly the environment needed:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -E stage1
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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
|
||||
|
||||
@@ -424,7 +441,9 @@ However, if there is a callable as the single positional argument with no keywor
|
||||
def test_with_args():
|
||||
pass
|
||||
|
||||
The output is as follows::
|
||||
The output is as follows:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -q -s
|
||||
Mark(name='my_marker', args=(<function hello_world at 0xdeadbeef>,), kwargs={})
|
||||
@@ -462,10 +481,12 @@ test function. From a conftest file we can read it like this::
|
||||
|
||||
def pytest_runtest_setup(item):
|
||||
for mark in item.iter_markers(name='glob'):
|
||||
print ("glob args=%s kwargs=%s" %(mark.args, mark.kwargs))
|
||||
print("glob args=%s kwargs=%s" % (mark.args, mark.kwargs))
|
||||
sys.stdout.flush()
|
||||
|
||||
Let's run this without capturing output and see what we get::
|
||||
Let's run this without capturing output and see what we get:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -q -s
|
||||
glob args=('function',) kwargs={'x': 3}
|
||||
@@ -520,27 +541,33 @@ Let's do a little test file to show how this looks like::
|
||||
def test_runs_everywhere():
|
||||
pass
|
||||
|
||||
then you will see two tests skipped and two executed tests as expected::
|
||||
then you will see two tests skipped and two executed tests as expected:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -rs # this option reports skip reasons
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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 ====================
|
||||
|
||||
Note that if you specify a platform via the marker-command line option like this::
|
||||
Note that if you specify a platform via the marker-command line option like this:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -m linux
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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%]
|
||||
|
||||
@@ -585,48 +612,54 @@ We want to dynamically define two markers and can do it in a
|
||||
elif "event" in item.nodeid:
|
||||
item.add_marker(pytest.mark.event)
|
||||
|
||||
We can now use the ``-m option`` to select one set::
|
||||
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-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 4 items / 2 deselected
|
||||
.. code-block:: pytest
|
||||
|
||||
test_module.py FF [100%]
|
||||
$ 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 / 2 selected
|
||||
|
||||
================================= FAILURES =================================
|
||||
__________________________ test_interface_simple ___________________________
|
||||
test_module.py:3: in test_interface_simple
|
||||
assert 0
|
||||
E assert 0
|
||||
__________________________ test_interface_complex __________________________
|
||||
test_module.py:6: in test_interface_complex
|
||||
assert 0
|
||||
E assert 0
|
||||
================== 2 failed, 2 deselected in 0.12 seconds ==================
|
||||
test_module.py FF [100%]
|
||||
|
||||
or to select both "event" and "interface" tests::
|
||||
================================= FAILURES =================================
|
||||
__________________________ test_interface_simple ___________________________
|
||||
test_module.py:3: in test_interface_simple
|
||||
assert 0
|
||||
E assert 0
|
||||
__________________________ test_interface_complex __________________________
|
||||
test_module.py:6: in test_interface_complex
|
||||
assert 0
|
||||
E assert 0
|
||||
================== 2 failed, 2 deselected in 0.12 seconds ==================
|
||||
|
||||
$ pytest -m "interface or event" --tb=short
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 4 items / 1 deselected
|
||||
or to select both "event" and "interface" tests:
|
||||
|
||||
test_module.py FFF [100%]
|
||||
.. code-block:: pytest
|
||||
|
||||
================================= FAILURES =================================
|
||||
__________________________ test_interface_simple ___________________________
|
||||
test_module.py:3: in test_interface_simple
|
||||
assert 0
|
||||
E assert 0
|
||||
__________________________ test_interface_complex __________________________
|
||||
test_module.py:6: in test_interface_complex
|
||||
assert 0
|
||||
E assert 0
|
||||
____________________________ test_event_simple _____________________________
|
||||
test_module.py:9: in test_event_simple
|
||||
assert 0
|
||||
E assert 0
|
||||
================== 3 failed, 1 deselected in 0.12 seconds ==================
|
||||
$ 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 / 3 selected
|
||||
|
||||
test_module.py FFF [100%]
|
||||
|
||||
================================= FAILURES =================================
|
||||
__________________________ test_interface_simple ___________________________
|
||||
test_module.py:3: in test_interface_simple
|
||||
assert 0
|
||||
E assert 0
|
||||
__________________________ test_interface_complex __________________________
|
||||
test_module.py:6: in test_interface_complex
|
||||
assert 0
|
||||
E assert 0
|
||||
____________________________ test_event_simple _____________________________
|
||||
test_module.py:9: in test_event_simple
|
||||
assert 0
|
||||
E assert 0
|
||||
================== 3 failed, 1 deselected in 0.12 seconds ==================
|
||||
|
||||
@@ -2,9 +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"]
|
||||
@@ -23,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
|
||||
@@ -32,7 +33,7 @@ class Python(object):
|
||||
dumpfile = self.picklefile.dirpath("dump.py")
|
||||
dumpfile.write(
|
||||
textwrap.dedent(
|
||||
"""\
|
||||
r"""
|
||||
import pickle
|
||||
f = open({!r}, 'wb')
|
||||
s = pickle.dump({!r}, f, protocol=2)
|
||||
@@ -42,13 +43,13 @@ 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")
|
||||
loadfile.write(
|
||||
textwrap.dedent(
|
||||
"""\
|
||||
r"""
|
||||
import pickle
|
||||
f = open({!r}, 'rb')
|
||||
obj = pickle.load(f)
|
||||
@@ -62,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}])
|
||||
|
||||
@@ -23,11 +23,14 @@ You can create a simple example file:
|
||||
:literal:
|
||||
|
||||
and if you installed `PyYAML`_ or a compatible YAML-parser you can
|
||||
now execute the test specification::
|
||||
now execute the test specification:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
nonpython $ pytest test_simple.yml
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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
|
||||
|
||||
@@ -55,12 +58,14 @@ your own domain specific testing language this way.
|
||||
will be reported as a (red) string.
|
||||
|
||||
``reportinfo()`` is used for representing the test location and is also
|
||||
consulted when reporting in ``verbose`` mode::
|
||||
consulted when reporting in ``verbose`` mode:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
nonpython $ pytest -v
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/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
|
||||
|
||||
@@ -77,16 +82,19 @@ consulted when reporting in ``verbose`` mode::
|
||||
.. regendoc:wipe
|
||||
|
||||
While developing your custom test collection and execution it's also
|
||||
interesting to just look at the collection tree::
|
||||
interesting to just look at the collection tree:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
nonpython $ pytest --collect-only
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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 =======================
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# content of conftest.py
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
||||
@@ -42,14 +42,18 @@ Now we add a test configuration like this::
|
||||
end = 2
|
||||
metafunc.parametrize("param1", range(end))
|
||||
|
||||
This means that we only run 2 tests if we do not pass ``--all``::
|
||||
This means that we only run 2 tests if we do not pass ``--all``:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -q test_compute.py
|
||||
.. [100%]
|
||||
2 passed in 0.12 seconds
|
||||
|
||||
We run only two computations, so we see two dots.
|
||||
let's run the full monty::
|
||||
let's run the full monty:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -q --all
|
||||
....F [100%]
|
||||
@@ -134,23 +138,25 @@ used as the test IDs. These are succinct, but can be a pain to maintain.
|
||||
In ``test_timedistance_v2``, we specified ``ids`` as a function that can generate a
|
||||
string representation to make part of the test ID. So our ``datetime`` values use the
|
||||
label generated by ``idfn``, but because we didn't generate a label for ``timedelta``
|
||||
objects, they are still using the default pytest representation::
|
||||
objects, they are still using the default pytest representation:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest test_time.py --collect-only
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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 =======================
|
||||
|
||||
@@ -191,11 +197,14 @@ only have to work a bit to construct the correct arguments for pytest's
|
||||
def test_demo2(self, attribute):
|
||||
assert isinstance(attribute, str)
|
||||
|
||||
this is a fully self-contained example which you can run with::
|
||||
this is a fully self-contained example which you can run with:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest test_scenarios.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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
|
||||
|
||||
@@ -203,21 +212,22 @@ this is a fully self-contained example which you can run with::
|
||||
|
||||
========================= 4 passed in 0.12 seconds =========================
|
||||
|
||||
If you just collect tests you'll also nicely see 'advanced' and 'basic' as variants for the test function::
|
||||
If you just collect tests you'll also nicely see 'advanced' and 'basic' as variants for the test function:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest --collect-only test_scenarios.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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'>
|
||||
<Instance '()'>
|
||||
<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 =======================
|
||||
|
||||
@@ -269,20 +279,25 @@ creates a database object for the actual test invocations::
|
||||
else:
|
||||
raise ValueError("invalid internal test config")
|
||||
|
||||
Let's first see how it looks like at collection time::
|
||||
Let's first see how it looks like at collection time:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest test_backends.py --collect-only
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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 =======================
|
||||
|
||||
And then when we run the test::
|
||||
And then when we run the test:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -q test_backends.py
|
||||
.F [100%]
|
||||
@@ -330,15 +345,18 @@ will be passed to respective fixture function::
|
||||
assert x == 'aaa'
|
||||
assert y == 'b'
|
||||
|
||||
The result of this test will be successful::
|
||||
The result of this test will be successful:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest test_indirect_list.py --collect-only
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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 =======================
|
||||
|
||||
@@ -375,10 +393,13 @@ 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::
|
||||
argument sets to use for each test function. Let's run it:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -q
|
||||
F.. [100%]
|
||||
@@ -390,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
|
||||
@@ -408,12 +431,14 @@ is to be run with different sets of arguments for its three arguments:
|
||||
|
||||
.. literalinclude:: multipython.py
|
||||
|
||||
Running it results in some skips if we don't have all the python interpreters installed and otherwise runs all combinations (5 interpreters times 5 interpreters times 3 objects to serialize/deserialize)::
|
||||
Running it results in some skips if we don't have all the python interpreters installed and otherwise runs all combinations (5 interpreters times 5 interpreters times 3 objects to serialize/deserialize):
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
. $ pytest -rs -q multipython.py
|
||||
...sss...sssssssss...sss... [100%]
|
||||
========================= short test summary info ==========================
|
||||
SKIP [15] $REGENDOC_TMPDIR/CWD/multipython.py:29: 'python3.4' not found
|
||||
SKIPPED [15] $REGENDOC_TMPDIR/CWD/multipython.py:30: 'python3.4' not found
|
||||
12 passed, 15 skipped in 0.12 seconds
|
||||
|
||||
Indirect parametrization of optional implementations/imports
|
||||
@@ -457,17 +482,20 @@ And finally a little test module::
|
||||
assert round(basemod.func1(), 3) == round(optmod.func1(), 3)
|
||||
|
||||
|
||||
If you run this with reporting for skips enabled::
|
||||
If you run this with reporting for skips enabled:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -rs test_module.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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 ====================
|
||||
|
||||
@@ -512,21 +540,22 @@ we mark the rest three parametrized tests with the custom marker ``basic``,
|
||||
and for the fourth test we also use the built-in mark ``xfail`` to indicate this
|
||||
test is expected to fail. For explicitness, we set test ids for some tests.
|
||||
|
||||
Then run ``pytest`` with verbose mode and with only the ``basic`` marker::
|
||||
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-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
.. code-block:: pytest
|
||||
|
||||
$ 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/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 4 items
|
||||
collecting ... collected 17 items / 14 deselected / 3 selected
|
||||
|
||||
test_pytest_param_example.py::test_eval[1+7-8] PASSED
|
||||
test_pytest_param_example.py::test_eval[basic_2+4] PASSED
|
||||
test_pytest_param_example.py::test_eval[basic_6*9] xfail
|
||||
========================================== short test summary info ===========================================
|
||||
XFAIL test_pytest_param_example.py::test_eval[basic_6*9]
|
||||
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%]
|
||||
|
||||
============================================= 1 tests deselected =============================================
|
||||
============ 2 passed, 14 deselected, 1 xfailed in 0.12 seconds ============
|
||||
|
||||
As the result:
|
||||
|
||||
@@ -536,3 +565,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
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
py3 = sys.version_info[0] >= 3
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
|
||||
def test_exception_syntax():
|
||||
try:
|
||||
0/0
|
||||
0 / 0
|
||||
except ZeroDivisionError, e:
|
||||
pass
|
||||
assert e
|
||||
|
||||
@@ -2,4 +2,4 @@ def test_exception_syntax():
|
||||
try:
|
||||
0 / 0
|
||||
except ZeroDivisionError as e:
|
||||
pass
|
||||
assert e
|
||||
|
||||
@@ -24,20 +24,22 @@ by passing the ``--ignore=path`` option on the cli. ``pytest`` allows multiple
|
||||
'-- test_world_03.py
|
||||
|
||||
Now if you invoke ``pytest`` with ``--ignore=tests/foobar/test_foobar_03.py --ignore=tests/hello/``,
|
||||
you will see that ``pytest`` only collects test-modules, which do not match the patterns specified::
|
||||
you will see that ``pytest`` only collects test-modules, which do not match the patterns specified:
|
||||
|
||||
========= test session starts ==========
|
||||
platform darwin -- Python 2.7.10, pytest-2.8.2, py-1.4.30, pluggy-0.3.1
|
||||
.. code-block:: pytest
|
||||
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 5 items
|
||||
|
||||
tests/example/test_example_01.py .
|
||||
tests/example/test_example_02.py .
|
||||
tests/example/test_example_03.py .
|
||||
tests/foobar/test_foobar_01.py .
|
||||
tests/foobar/test_foobar_02.py .
|
||||
tests/example/test_example_01.py . [ 20%]
|
||||
tests/example/test_example_02.py . [ 40%]
|
||||
tests/example/test_example_03.py . [ 60%]
|
||||
tests/foobar/test_foobar_01.py . [ 80%]
|
||||
tests/foobar/test_foobar_02.py . [100%]
|
||||
|
||||
======= 5 passed in 0.02 seconds =======
|
||||
========================= 5 passed in 0.02 seconds =========================
|
||||
|
||||
Deselect tests during test collection
|
||||
-------------------------------------
|
||||
@@ -123,18 +125,20 @@ that match ``*_check``. For example, if we have::
|
||||
def complex_check(self):
|
||||
pass
|
||||
|
||||
The test collection would look like this::
|
||||
The test collection would look like this:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest --collect-only
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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'>
|
||||
<Instance '()'>
|
||||
<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 =======================
|
||||
|
||||
@@ -177,19 +181,21 @@ treat it as a filesystem path.
|
||||
Finding out what is collected
|
||||
-----------------------------------------------
|
||||
|
||||
You can always peek at the collection tree without running tests like this::
|
||||
You can always peek at the collection tree without running tests like this:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
. $ pytest --collect-only pythoncollection.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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'>
|
||||
<Instance '()'>
|
||||
<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 =======================
|
||||
|
||||
@@ -233,7 +239,9 @@ and a ``setup.py`` dummy file like this::
|
||||
0/0 # will raise exception if imported
|
||||
|
||||
If you run with a Python 2 interpreter then you will find the one test and will
|
||||
leave out the ``setup.py`` file::
|
||||
leave out the ``setup.py`` file:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
#$ pytest --collect-only
|
||||
====== test session starts ======
|
||||
@@ -246,11 +254,14 @@ leave out the ``setup.py`` file::
|
||||
====== no tests ran in 0.04 seconds ======
|
||||
|
||||
If you run with a Python 3 interpreter both the one test and the ``setup.py``
|
||||
file will be left out::
|
||||
file will be left out:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest --collect-only
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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
|
||||
|
||||
|
||||
@@ -7,26 +7,30 @@ Demo of Python failure reports with pytest
|
||||
Here is a nice run of several tens of failures
|
||||
and how ``pytest`` presents things (unfortunately
|
||||
not showing the nice colors here in the HTML that you
|
||||
get on the terminal - we are working on that)::
|
||||
get on the terminal - we are working on that):
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
assertion $ pytest failure_demo.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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[0] ____________________________
|
||||
___________________________ test_generative[3-6] ___________________________
|
||||
|
||||
param1 = 3, param2 = 6
|
||||
|
||||
@pytest.mark.parametrize("param1, param2", [(3, 6)])
|
||||
def test_generative(param1, param2):
|
||||
> assert param1 * 2 < param2
|
||||
E assert (3 * 2) < 6
|
||||
|
||||
failure_demo.py:19: AssertionError
|
||||
failure_demo.py:22: AssertionError
|
||||
_________________________ TestFailing.test_simple __________________________
|
||||
|
||||
self = <failure_demo.TestFailing object at 0xdeadbeef>
|
||||
@@ -43,7 +47,7 @@ get on the terminal - we are working on that)::
|
||||
E + where 42 = <function TestFailing.test_simple.<locals>.f at 0xdeadbeef>()
|
||||
E + and 43 = <function TestFailing.test_simple.<locals>.g at 0xdeadbeef>()
|
||||
|
||||
failure_demo.py:35: AssertionError
|
||||
failure_demo.py:33: AssertionError
|
||||
____________________ TestFailing.test_simple_multiline _____________________
|
||||
|
||||
self = <failure_demo.TestFailing object at 0xdeadbeef>
|
||||
@@ -51,7 +55,7 @@ get on the terminal - we are working on that)::
|
||||
def test_simple_multiline(self):
|
||||
> otherfunc_multi(42, 6 * 9)
|
||||
|
||||
failure_demo.py:38:
|
||||
failure_demo.py:36:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
a = 42, b = 54
|
||||
@@ -60,7 +64,7 @@ get on the terminal - we are working on that)::
|
||||
> assert a == b
|
||||
E assert 42 == 54
|
||||
|
||||
failure_demo.py:15: AssertionError
|
||||
failure_demo.py:17: AssertionError
|
||||
___________________________ TestFailing.test_not ___________________________
|
||||
|
||||
self = <failure_demo.TestFailing object at 0xdeadbeef>
|
||||
@@ -73,7 +77,7 @@ get on the terminal - we are working on that)::
|
||||
E assert not 42
|
||||
E + where 42 = <function TestFailing.test_not.<locals>.f at 0xdeadbeef>()
|
||||
|
||||
failure_demo.py:44: AssertionError
|
||||
failure_demo.py:42: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_eq_text _________________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -84,7 +88,7 @@ get on the terminal - we are working on that)::
|
||||
E - spam
|
||||
E + eggs
|
||||
|
||||
failure_demo.py:49: AssertionError
|
||||
failure_demo.py:47: AssertionError
|
||||
_____________ TestSpecialisedExplanations.test_eq_similar_text _____________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -97,7 +101,7 @@ get on the terminal - we are working on that)::
|
||||
E + foo 2 bar
|
||||
E ? ^
|
||||
|
||||
failure_demo.py:52: AssertionError
|
||||
failure_demo.py:50: AssertionError
|
||||
____________ TestSpecialisedExplanations.test_eq_multiline_text ____________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -110,7 +114,7 @@ get on the terminal - we are working on that)::
|
||||
E + eggs
|
||||
E bar
|
||||
|
||||
failure_demo.py:55: AssertionError
|
||||
failure_demo.py:53: AssertionError
|
||||
______________ TestSpecialisedExplanations.test_eq_long_text _______________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -127,7 +131,7 @@ get on the terminal - we are working on that)::
|
||||
E + 1111111111b222222222
|
||||
E ? ^
|
||||
|
||||
failure_demo.py:60: AssertionError
|
||||
failure_demo.py:58: AssertionError
|
||||
_________ TestSpecialisedExplanations.test_eq_long_text_multiline __________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -147,7 +151,7 @@ get on the terminal - we are working on that)::
|
||||
E
|
||||
E ...Full output truncated (7 lines hidden), use '-vv' to show
|
||||
|
||||
failure_demo.py:65: AssertionError
|
||||
failure_demo.py:63: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_eq_list _________________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -158,7 +162,7 @@ get on the terminal - we are working on that)::
|
||||
E At index 2 diff: 2 != 3
|
||||
E Use -v to get the full diff
|
||||
|
||||
failure_demo.py:68: AssertionError
|
||||
failure_demo.py:66: AssertionError
|
||||
______________ TestSpecialisedExplanations.test_eq_list_long _______________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -171,7 +175,7 @@ get on the terminal - we are working on that)::
|
||||
E At index 100 diff: 1 != 2
|
||||
E Use -v to get the full diff
|
||||
|
||||
failure_demo.py:73: AssertionError
|
||||
failure_demo.py:71: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_eq_dict _________________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -189,7 +193,7 @@ get on the terminal - we are working on that)::
|
||||
E
|
||||
E ...Full output truncated (2 lines hidden), use '-vv' to show
|
||||
|
||||
failure_demo.py:76: AssertionError
|
||||
failure_demo.py:74: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_eq_set __________________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -207,7 +211,7 @@ get on the terminal - we are working on that)::
|
||||
E
|
||||
E ...Full output truncated (2 lines hidden), use '-vv' to show
|
||||
|
||||
failure_demo.py:79: AssertionError
|
||||
failure_demo.py:77: AssertionError
|
||||
_____________ TestSpecialisedExplanations.test_eq_longer_list ______________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -218,7 +222,7 @@ get on the terminal - we are working on that)::
|
||||
E Right contains more items, first extra item: 3
|
||||
E Use -v to get the full diff
|
||||
|
||||
failure_demo.py:82: AssertionError
|
||||
failure_demo.py:80: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_in_list _________________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -227,7 +231,7 @@ get on the terminal - we are working on that)::
|
||||
> assert 1 in [0, 2, 3, 4, 5]
|
||||
E assert 1 in [0, 2, 3, 4, 5]
|
||||
|
||||
failure_demo.py:85: AssertionError
|
||||
failure_demo.py:83: AssertionError
|
||||
__________ TestSpecialisedExplanations.test_not_in_text_multiline __________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -246,7 +250,7 @@ get on the terminal - we are working on that)::
|
||||
E
|
||||
E ...Full output truncated (2 lines hidden), use '-vv' to show
|
||||
|
||||
failure_demo.py:89: AssertionError
|
||||
failure_demo.py:87: AssertionError
|
||||
___________ TestSpecialisedExplanations.test_not_in_text_single ____________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -259,7 +263,7 @@ get on the terminal - we are working on that)::
|
||||
E single foo line
|
||||
E ? +++
|
||||
|
||||
failure_demo.py:93: AssertionError
|
||||
failure_demo.py:91: AssertionError
|
||||
_________ TestSpecialisedExplanations.test_not_in_text_single_long _________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -272,7 +276,7 @@ get on the terminal - we are working on that)::
|
||||
E head head foo tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail
|
||||
E ? +++
|
||||
|
||||
failure_demo.py:97: AssertionError
|
||||
failure_demo.py:95: AssertionError
|
||||
______ TestSpecialisedExplanations.test_not_in_text_single_long_term _______
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -285,7 +289,49 @@ get on the terminal - we are working on that)::
|
||||
E head head fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffftail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail
|
||||
E ? ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
failure_demo.py:101: AssertionError
|
||||
failure_demo.py: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():
|
||||
@@ -297,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:109: AssertionError
|
||||
failure_demo.py:131: AssertionError
|
||||
_________________________ test_attribute_instance __________________________
|
||||
|
||||
def test_attribute_instance():
|
||||
@@ -309,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:116: AssertionError
|
||||
failure_demo.py:138: AssertionError
|
||||
__________________________ test_attribute_failure __________________________
|
||||
|
||||
def test_attribute_failure():
|
||||
@@ -322,7 +368,7 @@ get on the terminal - we are working on that)::
|
||||
i = Foo()
|
||||
> assert i.b == 2
|
||||
|
||||
failure_demo.py:127:
|
||||
failure_demo.py:149:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
self = <failure_demo.test_attribute_failure.<locals>.Foo object at 0xdeadbeef>
|
||||
@@ -331,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:122: Exception
|
||||
failure_demo.py:144: Exception
|
||||
_________________________ test_attribute_multiple __________________________
|
||||
|
||||
def test_attribute_multiple():
|
||||
@@ -348,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:137: 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:147:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
> int(s)
|
||||
E ValueError: invalid literal for int() with base 10: 'qwe'
|
||||
|
||||
<0-codegen $PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/python_api.py:682>: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:150: Failed
|
||||
failure_demo.py:172: Failed
|
||||
__________________________ TestRaises.test_raise ___________________________
|
||||
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||
@@ -381,7 +422,7 @@ get on the terminal - we are working on that)::
|
||||
> raise ValueError("demo error")
|
||||
E ValueError: demo error
|
||||
|
||||
failure_demo.py:153: ValueError
|
||||
failure_demo.py:175: ValueError
|
||||
________________________ TestRaises.test_tupleerror ________________________
|
||||
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||
@@ -390,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:156: ValueError
|
||||
failure_demo.py:178: ValueError
|
||||
______ TestRaises.test_reinterpret_fails_with_print_for_the_fun_of_it ______
|
||||
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||
@@ -401,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:161: TypeError
|
||||
failure_demo.py:183: TypeError
|
||||
--------------------------- Captured stdout call ---------------------------
|
||||
items is [1, 2, 3]
|
||||
________________________ TestRaises.test_some_error ________________________
|
||||
@@ -412,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:164: NameError
|
||||
failure_demo.py:186: NameError
|
||||
____________________ test_dynamic_compile_shows_nicely _____________________
|
||||
|
||||
def test_dynamic_compile_shows_nicely():
|
||||
@@ -427,14 +468,14 @@ get on the terminal - we are working on that)::
|
||||
sys.modules[name] = module
|
||||
> module.foo()
|
||||
|
||||
failure_demo.py:182:
|
||||
failure_demo.py:204:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
def foo():
|
||||
> assert 1 == 0
|
||||
E AssertionError
|
||||
|
||||
<2-codegen 'abc-123' $REGENDOC_TMPDIR/assertion/failure_demo.py:179>: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>
|
||||
@@ -448,9 +489,9 @@ get on the terminal - we are working on that)::
|
||||
|
||||
> somefunc(f(), g())
|
||||
|
||||
failure_demo.py:193:
|
||||
failure_demo.py:215:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
failure_demo.py:11: in somefunc
|
||||
failure_demo.py:13: in somefunc
|
||||
otherfunc(x, y)
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
@@ -460,7 +501,7 @@ get on the terminal - we are working on that)::
|
||||
> assert a == b
|
||||
E assert 44 == 43
|
||||
|
||||
failure_demo.py:7: AssertionError
|
||||
failure_demo.py:9: AssertionError
|
||||
___________________ TestMoreErrors.test_z1_unpack_error ____________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -470,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:197: ValueError
|
||||
failure_demo.py:219: ValueError
|
||||
____________________ TestMoreErrors.test_z2_type_error _____________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -480,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:201: TypeError
|
||||
failure_demo.py:223: TypeError
|
||||
______________________ TestMoreErrors.test_startswith ______________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -493,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:206: AssertionError
|
||||
failure_demo.py:228: AssertionError
|
||||
__________________ TestMoreErrors.test_startswith_nested ___________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -512,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:215: AssertionError
|
||||
failure_demo.py:237: AssertionError
|
||||
_____________________ TestMoreErrors.test_global_func ______________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -523,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:218: AssertionError
|
||||
failure_demo.py:240: AssertionError
|
||||
_______________________ TestMoreErrors.test_instance _______________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -534,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:222: AssertionError
|
||||
failure_demo.py:244: AssertionError
|
||||
_______________________ TestMoreErrors.test_compare ________________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -544,7 +585,7 @@ get on the terminal - we are working on that)::
|
||||
E assert 11 < 5
|
||||
E + where 11 = globf(10)
|
||||
|
||||
failure_demo.py:225: AssertionError
|
||||
failure_demo.py:247: AssertionError
|
||||
_____________________ TestMoreErrors.test_try_finally ______________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -555,7 +596,7 @@ get on the terminal - we are working on that)::
|
||||
> assert x == 0
|
||||
E assert 1 == 0
|
||||
|
||||
failure_demo.py:230: AssertionError
|
||||
failure_demo.py:252: AssertionError
|
||||
___________________ TestCustomAssertMsg.test_single_line ___________________
|
||||
|
||||
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
|
||||
@@ -570,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:241: AssertionError
|
||||
failure_demo.py:263: AssertionError
|
||||
____________________ TestCustomAssertMsg.test_multiline ____________________
|
||||
|
||||
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
|
||||
@@ -589,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:248: AssertionError
|
||||
failure_demo.py:270: AssertionError
|
||||
___________________ TestCustomAssertMsg.test_custom_repr ___________________
|
||||
|
||||
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
|
||||
@@ -611,11 +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:261: AssertionError
|
||||
============================= warnings summary =============================
|
||||
$REGENDOC_TMPDIR/assertion/failure_demo.py:24: RemovedInPytest4Warning: Metafunc.addcall is deprecated and scheduled to be removed in pytest 4.0.
|
||||
Please use Metafunc.parametrize instead.
|
||||
metafunc.addcall(funcargs=dict(param1=3, param2=6))
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/latest/warnings.html
|
||||
================== 42 failed, 1 warnings in 0.12 seconds ===================
|
||||
failure_demo.py:283: AssertionError
|
||||
======================== 44 failed in 0.12 seconds =========================
|
||||
|
||||
@@ -43,7 +43,9 @@ provide the ``cmdopt`` through a :ref:`fixture function <fixture function>`:
|
||||
def cmdopt(request):
|
||||
return request.config.getoption("--cmdopt")
|
||||
|
||||
Let's run this without supplying our new option::
|
||||
Let's run this without supplying our new option:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -q test_sample.py
|
||||
F [100%]
|
||||
@@ -65,7 +67,9 @@ Let's run this without supplying our new option::
|
||||
first
|
||||
1 failed in 0.12 seconds
|
||||
|
||||
And now with supplying a command line option::
|
||||
And now with supplying a command line option:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -q --cmdopt=type2
|
||||
F [100%]
|
||||
@@ -117,11 +121,14 @@ the command line arguments before they get processed:
|
||||
If you have the `xdist plugin <https://pypi.org/project/pytest-xdist/>`_ installed
|
||||
you will now always perform test runs using a number
|
||||
of subprocesses close to your CPU. Running in an empty
|
||||
directory with the above conftest.py::
|
||||
directory with the above conftest.py:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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
|
||||
|
||||
@@ -175,25 +182,31 @@ We can now write a test module like this:
|
||||
def test_func_slow():
|
||||
pass
|
||||
|
||||
and when running it will see a skipped "slow" test::
|
||||
and when running it will see a skipped "slow" test:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -rs # "-rs" means report details on the little 's'
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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 ====================
|
||||
|
||||
Or run it including the ``slow`` marked test::
|
||||
Or run it including the ``slow`` marked test:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest --runslow
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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
|
||||
|
||||
@@ -230,7 +243,9 @@ Example:
|
||||
The ``__tracebackhide__`` setting influences ``pytest`` showing
|
||||
of tracebacks: the ``checkconfig`` function will not be shown
|
||||
unless the ``--full-trace`` command line option is specified.
|
||||
Let's run our little function::
|
||||
Let's run our little function:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -q test_checkconfig.py
|
||||
F [100%]
|
||||
@@ -327,11 +342,14 @@ It's easy to present extra information in a ``pytest`` run:
|
||||
def pytest_report_header(config):
|
||||
return "project deps: mylib-1.1"
|
||||
|
||||
which will add the string to the test header accordingly::
|
||||
which will add the string to the test header accordingly:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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
|
||||
@@ -353,12 +371,14 @@ display more information if applicable:
|
||||
if config.getoption("verbose") > 0:
|
||||
return ["info1: did you know that ...", "did you?"]
|
||||
|
||||
which will add info only when run with "--v"::
|
||||
which will add info only when run with "--v":
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -v
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/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:
|
||||
@@ -366,11 +386,14 @@ which will add info only when run with "--v"::
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
|
||||
and nothing when run plainly::
|
||||
and nothing when run plainly:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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
|
||||
|
||||
@@ -403,11 +426,14 @@ out which tests are the slowest. Let's make an artificial test suite:
|
||||
def test_funcslow2():
|
||||
time.sleep(0.3)
|
||||
|
||||
Now we can profile which test functions execute the slowest::
|
||||
Now we can profile which test functions execute the slowest:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest --durations=3
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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
|
||||
|
||||
@@ -475,11 +501,14 @@ tests in a class. Here is a test module example:
|
||||
def test_normal():
|
||||
pass
|
||||
|
||||
If we run this::
|
||||
If we run this:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -rx
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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
|
||||
|
||||
@@ -496,7 +525,7 @@ If we run this::
|
||||
|
||||
test_step.py:11: AssertionError
|
||||
========================= short test summary info ==========================
|
||||
XFAIL test_step.py::TestUserHandling::()::test_deletion
|
||||
XFAIL test_step.py::TestUserHandling::test_deletion
|
||||
reason: previous test failed (test_modification)
|
||||
============== 1 failed, 2 passed, 1 xfailed in 0.12 seconds ===============
|
||||
|
||||
@@ -556,11 +585,14 @@ the ``db`` fixture:
|
||||
def test_root(db): # no db here, will error out
|
||||
pass
|
||||
|
||||
We can run this::
|
||||
We can run this:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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
|
||||
|
||||
@@ -574,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
|
||||
@@ -667,11 +699,14 @@ if you then have failing tests:
|
||||
def test_fail2():
|
||||
assert 0
|
||||
|
||||
and run them::
|
||||
and run them:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest test_module.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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
|
||||
|
||||
@@ -766,11 +801,14 @@ if you then have failing tests:
|
||||
def test_fail2():
|
||||
assert 0
|
||||
|
||||
and run it::
|
||||
and run it:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -s test_module.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ calls it::
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def callattr_ahead_of_alltests(request):
|
||||
print ("callattr_ahead_of_alltests called")
|
||||
print("callattr_ahead_of_alltests called")
|
||||
seen = set([None])
|
||||
session = request.node
|
||||
for item in session.items:
|
||||
@@ -31,20 +31,20 @@ will be called ahead of running any tests::
|
||||
class TestHello(object):
|
||||
@classmethod
|
||||
def callme(cls):
|
||||
print ("callme called!")
|
||||
print("callme called!")
|
||||
|
||||
def test_method1(self):
|
||||
print ("test_method1 called")
|
||||
print("test_method1 called")
|
||||
|
||||
def test_method2(self):
|
||||
print ("test_method1 called")
|
||||
print("test_method1 called")
|
||||
|
||||
class TestOther(object):
|
||||
@classmethod
|
||||
def callme(cls):
|
||||
print ("callme other called")
|
||||
print("callme other called")
|
||||
def test_other(self):
|
||||
print ("test other")
|
||||
print("test other")
|
||||
|
||||
# works with unittest as well ...
|
||||
import unittest
|
||||
@@ -52,12 +52,14 @@ will be called ahead of running any tests::
|
||||
class SomeTest(unittest.TestCase):
|
||||
@classmethod
|
||||
def callme(self):
|
||||
print ("SomeTest callme called")
|
||||
print("SomeTest callme called")
|
||||
|
||||
def test_unit1(self):
|
||||
print ("test_unit1 method called")
|
||||
print("test_unit1 method called")
|
||||
|
||||
If you run this without output capturing::
|
||||
If you run this without output capturing:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -q -s test_module.py
|
||||
callattr_ahead_of_alltests called
|
||||
|
||||
@@ -66,11 +66,14 @@ using it::
|
||||
|
||||
Here, the ``test_ehlo`` needs the ``smtp_connection`` fixture value. pytest
|
||||
will discover and call the :py:func:`@pytest.fixture <_pytest.python.fixture>`
|
||||
marked ``smtp_connection`` fixture function. Running the test looks like this::
|
||||
marked ``smtp_connection`` fixture function. Running the test looks like this:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest test_smtpsimple.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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
|
||||
|
||||
@@ -153,7 +156,7 @@ This makes use of the automatic caching mechanisms of pytest.
|
||||
|
||||
Another good approach is by adding the data files in the ``tests`` folder.
|
||||
There are also community plugins available to help managing this aspect of
|
||||
testing, e.g. `pytest-datadir <https://github.com/gabrielcnr/pytest-datadir>`__
|
||||
testing, e.g. `pytest-datadir <https://pypi.org/project/pytest-datadir/>`__
|
||||
and `pytest-datafiles <https://pypi.org/project/pytest-datafiles/>`__.
|
||||
|
||||
.. _smtpshared:
|
||||
@@ -204,11 +207,14 @@ located)::
|
||||
assert 0 # for demo purposes
|
||||
|
||||
We deliberately insert failing ``assert 0`` statements in order to
|
||||
inspect what is going on and can now run the tests::
|
||||
inspect what is going on and can now run the tests:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest test_module.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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
|
||||
|
||||
@@ -460,7 +466,7 @@ read an optional server URL from the test module which uses our fixture::
|
||||
server = getattr(request.module, "smtpserver", "smtp.gmail.com")
|
||||
smtp_connection = smtplib.SMTP(server, 587, timeout=5)
|
||||
yield smtp_connection
|
||||
print ("finalizing %s (%s)" % (smtp_connection, server))
|
||||
print("finalizing %s (%s)" % (smtp_connection, server))
|
||||
smtp_connection.close()
|
||||
|
||||
We use the ``request.module`` attribute to optionally obtain an
|
||||
@@ -482,7 +488,9 @@ server URL in its module namespace::
|
||||
def test_showhelo(smtp_connection):
|
||||
assert 0, smtp_connection.helo()
|
||||
|
||||
Running it::
|
||||
Running it:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -qq --tb=short test_anothersmtp.py
|
||||
F [100%]
|
||||
@@ -584,7 +592,9 @@ The main change is the declaration of ``params`` with
|
||||
:py:func:`@pytest.fixture <_pytest.python.fixture>`, a list of values
|
||||
for each of which the fixture function will execute and can access
|
||||
a value via ``request.param``. No test function code needs to change.
|
||||
So let's just do another run::
|
||||
So let's just do another run:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -q test_module.py
|
||||
FFFF [100%]
|
||||
@@ -620,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 ---------------------------
|
||||
@@ -686,26 +696,29 @@ a function which will be called with the fixture value and then
|
||||
has to return a string to use. In the latter case if the function
|
||||
return ``None`` then pytest's auto-generated ID will be used.
|
||||
|
||||
Running the above tests results in the following test IDs being used::
|
||||
Running the above tests results in the following test IDs being used:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest --collect-only
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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 =======================
|
||||
|
||||
@@ -728,12 +741,14 @@ Example::
|
||||
def test_data(data_set):
|
||||
pass
|
||||
|
||||
Running this test will *skip* the invocation of ``data_set`` with value ``2``::
|
||||
Running this test will *skip* the invocation of ``data_set`` with value ``2``:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest test_fixture_marks.py -v
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/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
|
||||
|
||||
@@ -771,12 +786,14 @@ and instantiate an object ``app`` where we stick the already defined
|
||||
assert app.smtp_connection
|
||||
|
||||
Here we declare an ``app`` fixture which receives the previously defined
|
||||
``smtp_connection`` fixture and instantiates an ``App`` object with it. Let's run it::
|
||||
``smtp_connection`` fixture and instantiates an ``App`` object with it. Let's run it:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -v test_appsetup.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/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
|
||||
|
||||
@@ -790,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:
|
||||
@@ -821,31 +838,33 @@ to show the setup/teardown flow::
|
||||
@pytest.fixture(scope="module", params=["mod1", "mod2"])
|
||||
def modarg(request):
|
||||
param = request.param
|
||||
print (" SETUP modarg %s" % param)
|
||||
print(" SETUP modarg %s" % param)
|
||||
yield param
|
||||
print (" TEARDOWN modarg %s" % param)
|
||||
print(" TEARDOWN modarg %s" % param)
|
||||
|
||||
@pytest.fixture(scope="function", params=[1,2])
|
||||
def otherarg(request):
|
||||
param = request.param
|
||||
print (" SETUP otherarg %s" % param)
|
||||
print(" SETUP otherarg %s" % param)
|
||||
yield param
|
||||
print (" TEARDOWN otherarg %s" % param)
|
||||
print(" TEARDOWN otherarg %s" % param)
|
||||
|
||||
def test_0(otherarg):
|
||||
print (" RUN test0 with otherarg %s" % otherarg)
|
||||
print(" RUN test0 with otherarg %s" % otherarg)
|
||||
def test_1(modarg):
|
||||
print (" RUN test1 with modarg %s" % modarg)
|
||||
print(" RUN test1 with modarg %s" % modarg)
|
||||
def test_2(otherarg, modarg):
|
||||
print (" RUN test2 with otherarg %s and modarg %s" % (otherarg, modarg))
|
||||
print(" RUN test2 with otherarg %s and modarg %s" % (otherarg, modarg))
|
||||
|
||||
|
||||
Let's run the tests in verbose mode and with looking at the print-output::
|
||||
Let's run the tests in verbose mode and with looking at the print-output:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -v -s test_module.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/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
|
||||
|
||||
@@ -942,7 +961,9 @@ and declare its use in a test module via a ``usefixtures`` marker::
|
||||
Due to the ``usefixtures`` marker, the ``cleandir`` fixture
|
||||
will be required for the execution of each test method, just as if
|
||||
you specified a "cleandir" function argument to each of them. Let's run it
|
||||
to verify our fixture is activated and the tests pass::
|
||||
to verify our fixture is activated and the tests pass:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -q
|
||||
.. [100%]
|
||||
@@ -1041,7 +1062,9 @@ which implies that all test methods in the class will use this fixture
|
||||
without a need to state it in the test function signature or with a
|
||||
class-level ``usefixtures`` decorator.
|
||||
|
||||
If we run it, we get two passing tests::
|
||||
If we run it, we get two passing tests:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -q
|
||||
.. [100%]
|
||||
|
||||
125
doc/en/flaky.rst
Normal file
125
doc/en/flaky.rst
Normal file
@@ -0,0 +1,125 @@
|
||||
|
||||
Flaky tests
|
||||
-----------
|
||||
|
||||
A "flaky" test is one that exhibits intermittent or sporadic failure, that seems to have non-deterministic behaviour. Sometimes it passes, sometimes it fails, and it's not clear why. This page discusses pytest features that can help and other general strategies for identifying, fixing or mitigating them.
|
||||
|
||||
Why flaky tests are a problem
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Flaky tests are particularly troublesome when a continuous integration (CI) server is being used, so that all tests must pass before a new code change can be merged. If the test result is not a reliable signal -- that a test failure means the code change broke the test -- developers can become mistrustful of the test results, which can lead to overlooking genuine failures. It is also a source of wasted time as developers must re-run test suites and investigate spurious failures.
|
||||
|
||||
|
||||
Potential root causes
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
System state
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Broadly speaking, a flaky test indicates that the test relies on some system state that is not being appropriately controlled - the test environment is not sufficiently isolated. Higher level tests are more likely to be flaky as they rely on more state.
|
||||
|
||||
Flaky tests sometimes appear when a test suite is run in parallel (such as use of pytest-xdist). This can indicate a test is reliant on test ordering.
|
||||
|
||||
- Perhaps a different test is failing to clean up after itself and leaving behind data which causes the flaky test to fail.
|
||||
- The flaky test is reliant on data from a previous test that doesn't clean up after itself, and in parallel runs that previous test is not always present
|
||||
- Tests that modify global state typically cannot be run in parallel.
|
||||
|
||||
|
||||
Overly strict assertion
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Overly strict assertions can cause problems with floating point comparison as well as timing issues. `pytest.approx <https://docs.pytest.org/en/latest/reference.html#pytest-approx>`_ is useful here.
|
||||
|
||||
|
||||
Pytest features
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
Xfail strict
|
||||
~~~~~~~~~~~~
|
||||
|
||||
:ref:`pytest.mark.xfail ref` with ``strict=False`` can be used to mark a test so that its failure does not cause the whole build to break. This could be considered like a manual quarantine, and is rather dangerous to use permanently.
|
||||
|
||||
|
||||
PYTEST_CURRENT_TEST
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
:ref:`pytest current test env` may be useful for figuring out "which test got stuck".
|
||||
|
||||
|
||||
Plugins
|
||||
~~~~~~~
|
||||
|
||||
Rerunning any failed tests can mitigate the negative effects of flaky tests by giving them additional chances to pass, so that the overall build does not fail. Several pytest plugins support this:
|
||||
|
||||
* `flaky <https://github.com/box/flaky>`_
|
||||
* `pytest-flakefinder <https://github.com/dropbox/pytest-flakefinder>`_ - `blog post <https://blogs.dropbox.com/tech/2016/03/open-sourcing-pytest-tools/>`_
|
||||
* `pytest-rerunfailures <https://github.com/pytest-dev/pytest-rerunfailures>`_
|
||||
* `pytest-replay <https://github.com/ESSS/pytest-replay>`_: This plugin helps to reproduce locally crashes or flaky tests observed during CI runs.
|
||||
|
||||
Plugins to deliberately randomize tests can help expose tests with state problems:
|
||||
|
||||
* `pytest-random-order <https://github.com/jbasko/pytest-random-order>`_
|
||||
* `pytest-randomly <https://github.com/pytest-dev/pytest-randomly>`_
|
||||
|
||||
|
||||
Other general strategies
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Split up test suites
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
It can be common to split a single test suite into two, such as unit vs integration, and only use the unit test suite as a CI gate. This also helps keep build times manageable as high level tests tend to be slower. However, it means it does become possible for code that breaks the build to be merged, so extra vigilance is needed for monitoring the integration test results.
|
||||
|
||||
|
||||
Video/screenshot on failure
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
For UI tests these are important for understanding what the state of the UI was when the test failed. pytest-splinter can be used with plugins like pytest-bdd and can `save a screenshot on test failure <https://pytest-splinter.readthedocs.io/en/latest/#automatic-screenshots-on-test-failure>`_, which can help to isolate the cause.
|
||||
|
||||
|
||||
Delete or rewrite the test
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If the functionality is covered by other tests, perhaps the test can be removed. If not, perhaps it can be rewritten at a lower level which will remove the flakiness or make its source more apparent.
|
||||
|
||||
|
||||
Quarantine
|
||||
~~~~~~~~~~
|
||||
|
||||
Mark Lapierre discusses the `Pros and Cons of Quarantined Tests <https://dev.to/mlapierre/pros-and-cons-of-quarantined-tests-2emj>`_ in a post from 2018.
|
||||
|
||||
|
||||
|
||||
CI tools that rerun on failure
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Azure Pipelines (the Azure cloud CI/CD tool, formerly Visual Studio Team Services or VSTS) has a feature to `identify flaky tests <https://docs.microsoft.com/en-us/azure/devops/release-notes/2017/dec-11-vsts#identify-flaky-tests>`_ and rerun failed tests.
|
||||
|
||||
|
||||
|
||||
Research
|
||||
^^^^^^^^
|
||||
|
||||
This is a limited list, please submit an issue or pull request to expand it!
|
||||
|
||||
* Gao, Zebao, Yalan Liang, Myra B. Cohen, Atif M. Memon, and Zhen Wang. "Making system user interactive tests repeatable: When and what should we control?." In *Software Engineering (ICSE), 2015 IEEE/ACM 37th IEEE International Conference on*, vol. 1, pp. 55-65. IEEE, 2015. `PDF <http://www.cs.umd.edu/~atif/pubs/gao-icse15.pdf>`__
|
||||
* Palomba, Fabio, and Andy Zaidman. "Does refactoring of test smells induce fixing flaky tests?." In *Software Maintenance and Evolution (ICSME), 2017 IEEE International Conference on*, pp. 1-12. IEEE, 2017. `PDF in Google Drive <https://drive.google.com/file/d/10HdcCQiuQVgW3yYUJD-TSTq1NbYEprl0/view>`__
|
||||
* Bell, Jonathan, Owolabi Legunsen, Michael Hilton, Lamyaa Eloussi, Tifany Yung, and Darko Marinov. "DeFlaker: Automatically detecting flaky tests." In *Proceedings of the 2018 International Conference on Software Engineering*. 2018. `PDF <https://www.jonbell.net/icse18-deflaker.pdf>`__
|
||||
|
||||
|
||||
Resources
|
||||
^^^^^^^^^
|
||||
|
||||
* `Eradicating Non-Determinism in Tests <https://martinfowler.com/articles/nonDeterminism.html>`_ by Martin Fowler, 2011
|
||||
* `No more flaky tests on the Go team <https://www.thoughtworks.com/insights/blog/no-more-flaky-tests-go-team>`_ by Pavan Sudarshan, 2012
|
||||
* `The Build That Cried Broken: Building Trust in your Continuous Integration Tests <https://www.youtube.com/embed/VotJqV4n8ig>`_ talk (video) by `Angie Jones <http://angiejones.tech/>`_ at SeleniumConf Austin 2017
|
||||
* `Test and Code Podcast: Flaky Tests and How to Deal with Them <https://testandcode.com/50>`_ by Brian Okken and Anthony Shaw, 2018
|
||||
* Microsoft:
|
||||
|
||||
* `How we approach testing VSTS to enable continuous delivery <https://blogs.msdn.microsoft.com/bharry/2017/06/28/testing-in-a-cloud-delivery-cadence/>`_ by Brian Harry MS, 2017
|
||||
* `Eliminating Flaky Tests <https://docs.microsoft.com/en-us/azure/devops/learn/devops-at-microsoft/eliminating-flaky-tests>`_ blog and talk (video) by Munil Shah, 2017
|
||||
|
||||
* Google:
|
||||
|
||||
* `Flaky Tests at Google and How We Mitigate Them <https://testing.googleblog.com/2016/05/flaky-tests-at-google-and-how-we.html>`_ by John Micco, 2016
|
||||
* `Where do Google's flaky tests come from? <https://docs.google.com/document/d/1mZ0-Kc97DI_F3tf_GBW_NB_aqka-P1jVOsFfufxqUUM/edit#heading=h.ec0r4fypsleh>`_ by Jeff Listfield, 2017
|
||||
@@ -26,9 +26,9 @@ a per-session Database object::
|
||||
# content of conftest.py
|
||||
class Database(object):
|
||||
def __init__(self):
|
||||
print ("database instance created")
|
||||
print("database instance created")
|
||||
def destroy(self):
|
||||
print ("database instance destroyed")
|
||||
print("database instance destroyed")
|
||||
|
||||
def pytest_funcarg__db(request):
|
||||
return request.cached_setup(setup=DataBase,
|
||||
|
||||
@@ -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.
|
||||
@@ -27,7 +24,7 @@ Install ``pytest``
|
||||
2. Check that you installed the correct version::
|
||||
|
||||
$ pytest --version
|
||||
This is pytest version 3.x.y, imported from $PYTHON_PREFIX/lib/python3.6/site-packages/pytest.py
|
||||
This is pytest version 4.x.y, imported from $PYTHON_PREFIX/lib/python3.6/site-packages/pytest.py
|
||||
|
||||
.. _`simpletest`:
|
||||
|
||||
@@ -43,11 +40,14 @@ Create a simple test function with just four lines of code::
|
||||
def test_answer():
|
||||
assert func(3) == 5
|
||||
|
||||
That’s it. You can now execute the test function::
|
||||
That’s it. You can now execute the test function:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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
|
||||
|
||||
@@ -90,7 +90,9 @@ Use the ``raises`` helper to assert that some code raises an exception::
|
||||
with pytest.raises(SystemExit):
|
||||
f()
|
||||
|
||||
Execute the test function with “quiet” reporting mode::
|
||||
Execute the test function with “quiet” reporting mode:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -q test_sysexit.py
|
||||
. [100%]
|
||||
@@ -111,7 +113,9 @@ Once you develop multiple tests, you may want to group them into a class. pytest
|
||||
x = "hello"
|
||||
assert hasattr(x, 'check')
|
||||
|
||||
``pytest`` discovers all tests following its :ref:`Conventions for Python test discovery <test discovery>`, so it finds both ``test_`` prefixed functions. There is no need to subclass anything. We can simply run the module by passing its filename::
|
||||
``pytest`` discovers all tests following its :ref:`Conventions for Python test discovery <test discovery>`, so it finds both ``test_`` prefixed functions. There is no need to subclass anything. We can simply run the module by passing its filename:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -q test_class.py
|
||||
.F [100%]
|
||||
@@ -138,10 +142,12 @@ Request a unique temporary directory for functional tests
|
||||
|
||||
# content of test_tmpdir.py
|
||||
def test_needsfiles(tmpdir):
|
||||
print (tmpdir)
|
||||
print(tmpdir)
|
||||
assert 0
|
||||
|
||||
List the name ``tmpdir`` in the test function signature and ``pytest`` will lookup and call a fixture factory to create the resource before performing the test function call. Before the test runs, ``pytest`` creates a unique-per-test-invocation temporary directory::
|
||||
List the name ``tmpdir`` in the test function signature and ``pytest`` will lookup and call a fixture factory to create the resource before performing the test function call. Before the test runs, ``pytest`` creates a unique-per-test-invocation temporary directory:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -q test_tmpdir.py
|
||||
F [100%]
|
||||
@@ -151,7 +157,7 @@ List the name ``tmpdir`` in the test function signature and ``pytest`` will look
|
||||
tmpdir = local('PYTEST_TMPDIR/test_needsfiles0')
|
||||
|
||||
def test_needsfiles(tmpdir):
|
||||
print (tmpdir)
|
||||
print(tmpdir)
|
||||
> assert 0
|
||||
E assert 0
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -22,11 +22,14 @@ An example of a simple test:
|
||||
assert inc(3) == 5
|
||||
|
||||
|
||||
To execute it::
|
||||
To execute it:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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
|
||||
|
||||
|
||||
@@ -156,4 +156,4 @@ More details can be found in the `original PR <https://github.com/pytest-dev/pyt
|
||||
.. note::
|
||||
|
||||
in a future major relase of pytest we will introduce class based markers,
|
||||
at which points markers will no longer be limited to instances of :py:class:`Mark`
|
||||
at which point markers will no longer be limited to instances of :py:class:`Mark`
|
||||
|
||||
@@ -50,11 +50,14 @@ to an expected output::
|
||||
|
||||
Here, the ``@parametrize`` decorator defines three different ``(test_input,expected)``
|
||||
tuples so that the ``test_eval`` function will run three times using
|
||||
them in turn::
|
||||
them in turn:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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
|
||||
|
||||
@@ -99,11 +102,14 @@ for example with the builtin ``mark.xfail``::
|
||||
def test_eval(test_input, expected):
|
||||
assert eval(test_input) == expected
|
||||
|
||||
Let's run this::
|
||||
Let's run this:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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
|
||||
|
||||
@@ -114,6 +120,10 @@ Let's run this::
|
||||
The one parameter set which caused a failure previously now
|
||||
shows up as an "xfailed (expected to fail)" test.
|
||||
|
||||
In case the values provided to ``parametrize`` result in an empty list - for
|
||||
example, if they're dynamically generated by some function - the behaviour of
|
||||
pytest is defined by the :confval:`empty_parameter_set_mark` option.
|
||||
|
||||
To get all combinations of multiple parametrized arguments you can stack
|
||||
``parametrize`` decorators::
|
||||
|
||||
@@ -168,7 +178,9 @@ If we now pass two stringinput values, our test will run twice::
|
||||
.. [100%]
|
||||
2 passed in 0.12 seconds
|
||||
|
||||
Let's also run with a stringinput that will lead to a failing test::
|
||||
Let's also run with a stringinput that will lead to a failing test:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -q --stringinput="!" test_strings.py
|
||||
F [100%]
|
||||
@@ -190,12 +202,14 @@ As expected our test function fails.
|
||||
|
||||
If you don't specify a stringinput it will be skipped because
|
||||
``metafunc.parametrize()`` will be called with an empty parameter
|
||||
list::
|
||||
list:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ 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
|
||||
|
||||
@@ -59,9 +59,9 @@ To see a complete list of all plugins with their latest testing
|
||||
status against different pytest and Python versions, please visit
|
||||
`plugincompat <http://plugincompat.herokuapp.com/>`_.
|
||||
|
||||
You may also discover more plugins through a `pytest- pypi.python.org search`_.
|
||||
You may also discover more plugins through a `pytest- pypi.org search`_.
|
||||
|
||||
.. _`pytest- pypi.python.org search`: https://pypi.org/search/?q=pytest-
|
||||
.. _`pytest- pypi.org search`: https://pypi.org/search/?q=pytest-
|
||||
|
||||
|
||||
.. _`available installable plugins`:
|
||||
@@ -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
|
||||
|
||||
@@ -75,7 +75,7 @@ Issues
|
||||
------
|
||||
|
||||
* By using ``request.getfuncargvalue()`` we rely on actual fixture function
|
||||
execution to know what fixtures are involved, due to it's dynamic nature
|
||||
execution to know what fixtures are involved, due to its dynamic nature
|
||||
* More importantly, ``request.getfuncargvalue()`` cannot be combined with
|
||||
parametrized fixtures, such as ``extra_context``
|
||||
* This is very inconvenient if you wish to extend an existing test suite by
|
||||
|
||||
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**.
|
||||
@@ -117,6 +117,7 @@ Add warning filters to marked test items.
|
||||
A *warning specification string*, which is composed of contents of the tuple ``(action, message, category, module, lineno)``
|
||||
as specified in `The Warnings filter <https://docs.python.org/3/library/warnings.html#warning-filter>`_ section of
|
||||
the Python documentation, separated by ``":"``. Optional fields can be omitted.
|
||||
Module names passed for filtering are not regex-escaped.
|
||||
|
||||
For example:
|
||||
|
||||
@@ -617,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
|
||||
@@ -724,13 +724,6 @@ MarkGenerator
|
||||
:members:
|
||||
|
||||
|
||||
MarkInfo
|
||||
~~~~~~~~
|
||||
|
||||
.. autoclass:: _pytest.mark.MarkInfo
|
||||
:members:
|
||||
|
||||
|
||||
Mark
|
||||
~~~~
|
||||
|
||||
@@ -896,6 +889,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::
|
||||
|
||||
@@ -1016,6 +1015,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,4 +1,4 @@
|
||||
# pinning sphinx to 1.4.* due to search issues with rtd:
|
||||
# https://github.com/rtfd/readthedocs-sphinx-ext/issues/25
|
||||
sphinx ==1.4.*
|
||||
pygments-pytest>=1.1.0
|
||||
sphinx>=1.8.2
|
||||
sphinxcontrib-trio
|
||||
sphinx-removed-in>=0.1.3
|
||||
|
||||
@@ -323,11 +323,14 @@ Here is a simple test file with the several usages:
|
||||
|
||||
.. literalinclude:: example/xfail_demo.py
|
||||
|
||||
Running it with the report-on-xfail option gives this output::
|
||||
Running it with the report-on-xfail option gives this output:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
example $ pytest -rx xfail_demo.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ Books
|
||||
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>`_)
|
||||
|
||||
- `Pythonic testing, Igor Starikov (Russian, PyNsk, November 2016)
|
||||
<https://www.youtube.com/watch?v=_92nfdd5nK8>`_.
|
||||
|
||||
|
||||
@@ -35,11 +35,14 @@ created in the `base temporary directory`_.
|
||||
assert 0
|
||||
|
||||
Running this would result in a passed test except for the last
|
||||
``assert 0`` line which we use to look at values::
|
||||
``assert 0`` line which we use to look at values:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest test_tmp_path.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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
|
||||
|
||||
@@ -95,11 +98,14 @@ and more. Here is an example test usage::
|
||||
assert 0
|
||||
|
||||
Running this would result in a passed test except for the last
|
||||
``assert 0`` line which we use to look at values::
|
||||
``assert 0`` line which we use to look at values:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest test_tmpdir.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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
|
||||
|
||||
|
||||
@@ -122,11 +122,14 @@ fixture definition::
|
||||
The ``@pytest.mark.usefixtures("db_class")`` class-decorator makes sure that
|
||||
the pytest fixture function ``db_class`` is called once per class.
|
||||
Due to the deliberately failing assert statements, we can take a look at
|
||||
the ``self.db`` values in the traceback::
|
||||
the ``self.db`` values in the traceback:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest test_unittest_db.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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
|
||||
|
||||
@@ -199,7 +202,9 @@ used for all methods of the class where it is defined. This is a
|
||||
shortcut for using a ``@pytest.mark.usefixtures("initdir")`` marker
|
||||
on the class like in the previous example.
|
||||
|
||||
Running this test module ...::
|
||||
Running this test module ...:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -q test_unittest_cleandir.py
|
||||
. [100%]
|
||||
|
||||
194
doc/en/usage.rst
194
doc/en/usage.rst
@@ -147,18 +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::
|
||||
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-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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".
|
||||
|
||||
@@ -173,15 +238,79 @@ Here is the full list of available characters that can be used:
|
||||
- ``P`` - passed with output
|
||||
- ``a`` - all except ``pP``
|
||||
|
||||
More than one character can be used, so for example to only see failed and skipped tests, you can execute::
|
||||
More than one character can be used, so for example to only see failed and skipped tests, you can execute:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -rfs
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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:
|
||||
|
||||
@@ -255,8 +384,8 @@ Pytest supports the use of ``breakpoint()`` with the following behaviours:
|
||||
|
||||
- When ``breakpoint()`` is called and ``PYTHONBREAKPOINT`` is set to the default value, pytest will use the custom internal PDB trace UI instead of the system default ``Pdb``.
|
||||
- When tests are complete, the system will default back to the system ``Pdb`` trace UI.
|
||||
- If ``--pdb`` is called on execution of pytest, the custom internal Pdb trace UI is used on both ``breakpoint()`` and failed tests/unhandled exceptions.
|
||||
- If ``--pdbcls`` is used, the custom class debugger will be executed when a test fails (as expected within existing behaviour), but also when ``breakpoint()`` is called from within a test, the custom class debugger will be instantiated.
|
||||
- With ``--pdb`` passed to pytest, the custom internal Pdb trace UI is used with both ``breakpoint()`` and failed tests/unhandled exceptions.
|
||||
- ``--pdbcls`` can be used to specify a custom debugger class.
|
||||
|
||||
.. _durations:
|
||||
|
||||
@@ -290,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
|
||||
@@ -479,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::
|
||||
|
||||
@@ -557,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::
|
||||
|
||||
|
||||
@@ -18,25 +18,31 @@ and displays them at the end of the session::
|
||||
def test_one():
|
||||
assert api_v1() == 1
|
||||
|
||||
Running pytest now produces this output::
|
||||
Running pytest now produces this output:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest test_show_warnings.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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
|
||||
|
||||
test_show_warnings.py . [100%]
|
||||
|
||||
============================= warnings summary =============================
|
||||
$REGENDOC_TMPDIR/test_show_warnings.py:4: UserWarning: api v1, should use functions from v2
|
||||
warnings.warn(UserWarning("api v1, should use functions from v2"))
|
||||
test_show_warnings.py::test_one
|
||||
$REGENDOC_TMPDIR/test_show_warnings.py:4: UserWarning: api v1, should use functions from v2
|
||||
warnings.warn(UserWarning("api v1, should use functions from v2"))
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/latest/warnings.html
|
||||
=================== 1 passed, 1 warnings in 0.12 seconds ===================
|
||||
|
||||
The ``-W`` flag can be passed to control which warnings will be displayed or even turn
|
||||
them into errors::
|
||||
them into errors:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -q test_show_warnings.py -W error::UserWarning
|
||||
F [100%]
|
||||
@@ -150,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
|
||||
@@ -227,7 +233,7 @@ You can also use it as a contextmanager::
|
||||
.. _warns:
|
||||
|
||||
Asserting warnings with the warns function
|
||||
-----------------------------------------------
|
||||
------------------------------------------
|
||||
|
||||
.. versionadded:: 2.8
|
||||
|
||||
@@ -285,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.
|
||||
@@ -323,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:
|
||||
|
||||
@@ -346,13 +372,14 @@ defines an ``__init__`` constructor, as this prevents the class from being insta
|
||||
def test_foo(self):
|
||||
assert 1 == 1
|
||||
|
||||
::
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest test_pytest_warnings.py -q
|
||||
|
||||
============================= warnings summary =============================
|
||||
$REGENDOC_TMPDIR/test_pytest_warnings.py:1: PytestWarning: cannot collect test class 'Test' because it has a __init__ constructor
|
||||
class Test:
|
||||
test_pytest_warnings.py:1
|
||||
$REGENDOC_TMPDIR/test_pytest_warnings.py:1: PytestWarning: cannot collect test class 'Test' because it has a __init__ constructor
|
||||
class Test:
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/latest/warnings.html
|
||||
1 warnings in 0.12 seconds
|
||||
|
||||
@@ -73,7 +73,7 @@ sub directory but not for other directories::
|
||||
a/conftest.py:
|
||||
def pytest_runtest_setup(item):
|
||||
# called for running each test in 'a' directory
|
||||
print ("setting up", item)
|
||||
print("setting up", item)
|
||||
|
||||
a/test_sub.py:
|
||||
def test_sub():
|
||||
@@ -386,55 +386,46 @@ return a result object, with which we can assert the tests' outcomes.
|
||||
result.assert_outcomes(passed=4)
|
||||
|
||||
|
||||
additionally it is possible to copy examples for a example folder before running pytest on it
|
||||
additionally it is possible to copy examples for an example folder before running pytest on it
|
||||
|
||||
.. code:: ini
|
||||
.. code-block:: ini
|
||||
|
||||
# content of pytest.ini
|
||||
[pytest]
|
||||
pytester_example_dir = .
|
||||
|
||||
|
||||
.. code:: python
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_example.py
|
||||
|
||||
|
||||
def test_plugin(testdir):
|
||||
testdir.copy_example("test_example.py")
|
||||
testdir.runpytest("-k", "test_example")
|
||||
testdir.copy_example("test_example.py")
|
||||
testdir.runpytest("-k", "test_example")
|
||||
|
||||
|
||||
def test_example():
|
||||
pass
|
||||
pass
|
||||
|
||||
.. code::
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
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
|
||||
|
||||
test_example.py .. [100%]
|
||||
|
||||
============================= warnings summary =============================
|
||||
$REGENDOC_TMPDIR/test_example.py:4: PytestExperimentalApiWarning: testdir.copy_example is an experimental api that may change over time
|
||||
testdir.copy_example("test_example.py")
|
||||
$PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:329: RemovedInPytest4Warning: usage of Session.Class is deprecated, please use pytest.Class instead
|
||||
return getattr(object, name, default)
|
||||
$PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:329: RemovedInPytest4Warning: usage of Session.File is deprecated, please use pytest.File instead
|
||||
return getattr(object, name, default)
|
||||
$PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:329: RemovedInPytest4Warning: usage of Session.Function is deprecated, please use pytest.Function instead
|
||||
return getattr(object, name, default)
|
||||
$PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:329: RemovedInPytest4Warning: usage of Session.Instance is deprecated, please use pytest.Instance instead
|
||||
return getattr(object, name, default)
|
||||
$PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:329: RemovedInPytest4Warning: usage of Session.Item is deprecated, please use pytest.Item instead
|
||||
return getattr(object, name, default)
|
||||
$PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:329: RemovedInPytest4Warning: usage of Session.Module is deprecated, please use pytest.Module instead
|
||||
return getattr(object, name, default)
|
||||
test_example.py::test_plugin
|
||||
$REGENDOC_TMPDIR/test_example.py:4: PytestExperimentalApiWarning: testdir.copy_example is an experimental api that may change over time
|
||||
testdir.copy_example("test_example.py")
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/latest/warnings.html
|
||||
=================== 2 passed, 7 warnings in 0.12 seconds ===================
|
||||
=================== 2 passed, 1 warnings in 0.12 seconds ===================
|
||||
|
||||
For more information about the result object that ``runpytest()`` returns, and
|
||||
the methods that it provides please check out the :py:class:`RunResult
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import json
|
||||
|
||||
import py
|
||||
import requests
|
||||
|
||||
@@ -64,9 +65,9 @@ def report(issues):
|
||||
print(title)
|
||||
# print()
|
||||
# lines = body.split("\n")
|
||||
# print ("\n".join(lines[:3]))
|
||||
# print("\n".join(lines[:3]))
|
||||
# if len(lines) > 3 or len(body) > 240:
|
||||
# print ("...")
|
||||
# print("...")
|
||||
print("\n\nFound %s open issues" % len(issues))
|
||||
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ requires = [
|
||||
"setuptools-scm",
|
||||
"wheel",
|
||||
]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.towncrier]
|
||||
package = "pytest"
|
||||
|
||||
21
scripts/appveyor-retry.cmd
Normal file
21
scripts/appveyor-retry.cmd
Normal file
@@ -0,0 +1,21 @@
|
||||
@echo off
|
||||
rem Source: https://github.com/appveyor/ci/blob/master/scripts/appveyor-retry.cmd
|
||||
rem initiate the retry number
|
||||
set retryNumber=0
|
||||
set maxRetries=3
|
||||
|
||||
:RUN
|
||||
%*
|
||||
set LastErrorLevel=%ERRORLEVEL%
|
||||
IF %LastErrorLevel% == 0 GOTO :EOF
|
||||
set /a retryNumber=%retryNumber%+1
|
||||
IF %reTryNumber% == %maxRetries% (GOTO :FAILED)
|
||||
|
||||
:RETRY
|
||||
set /a retryNumberDisp=%retryNumber%+1
|
||||
@echo Command "%*" failed with exit code %LastErrorLevel%. Retrying %retryNumberDisp% of %maxRetries%
|
||||
GOTO :RUN
|
||||
|
||||
: FAILED
|
||||
@echo Sorry, we tried running command for %maxRetries% times and all attempts were unsuccessful!
|
||||
EXIT /B %LastErrorLevel%
|
||||
@@ -2,9 +2,13 @@
|
||||
Invoke development tasks.
|
||||
"""
|
||||
import argparse
|
||||
from colorama import init, Fore
|
||||
from pathlib import Path
|
||||
from subprocess import check_output, check_call, call
|
||||
from subprocess import call
|
||||
from subprocess import check_call
|
||||
from subprocess import check_output
|
||||
|
||||
from colorama import Fore
|
||||
from colorama import init
|
||||
|
||||
|
||||
def announce(version):
|
||||
|
||||
@@ -5,7 +5,7 @@ if not defined PYTEST_NO_COVERAGE (
|
||||
C:\Python36\Scripts\coverage combine
|
||||
C:\Python36\Scripts\coverage xml --ignore-errors
|
||||
C:\Python36\Scripts\coverage report -m --ignore-errors
|
||||
C:\Python36\Scripts\codecov --required -X gcov pycov search -f coverage.xml --flags %TOXENV:-= % windows
|
||||
scripts\appveyor-retry C:\Python36\Scripts\codecov --required -X gcov pycov search -f coverage.xml --flags %TOXENV:-= % windows
|
||||
) else (
|
||||
echo Skipping coverage upload, PYTEST_NO_COVERAGE is set
|
||||
)
|
||||
|
||||
@@ -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.*
|
||||
|
||||
14
setup.py
14
setup.py
@@ -1,4 +1,5 @@
|
||||
import os
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
|
||||
@@ -9,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"',
|
||||
@@ -28,6 +30,16 @@ def main():
|
||||
use_scm_version={"write_to": "src/_pytest/_version.py"},
|
||||
setup_requires=["setuptools-scm", "setuptools>=40.0"],
|
||||
package_dir={"": "src"},
|
||||
# fmt: off
|
||||
extras_require={
|
||||
"testing": [
|
||||
"hypothesis>=3.56",
|
||||
"nose",
|
||||
"requests",
|
||||
"mock;python_version=='2.7'",
|
||||
],
|
||||
},
|
||||
# fmt: on
|
||||
install_requires=INSTALL_REQUIRES,
|
||||
)
|
||||
|
||||
|
||||
@@ -53,9 +53,12 @@ If things do not work right away:
|
||||
which should throw a KeyError: 'COMPLINE' (which is properly set by the
|
||||
global argcomplete script).
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
import sys
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import sys
|
||||
from glob import glob
|
||||
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
""" python inspection/code generation API """
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
from .code import Code # noqa
|
||||
from .code import ExceptionInfo # noqa
|
||||
from .code import Frame # noqa
|
||||
from .code import Traceback # noqa
|
||||
from .code import filter_traceback # noqa
|
||||
from .code import Frame # noqa
|
||||
from .code import getrawcode # noqa
|
||||
from .source import Source # noqa
|
||||
from .code import Traceback # noqa
|
||||
from .source import compile_ as compile # noqa
|
||||
from .source import getfslineno # noqa
|
||||
from .source import Source # noqa
|
||||
|
||||
@@ -2,8 +2,13 @@
|
||||
# CHANGES:
|
||||
# - some_str is replaced, trying to create unicode strings
|
||||
#
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import types
|
||||
|
||||
from six import text_type
|
||||
|
||||
|
||||
|
||||
@@ -1,21 +1,28 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import inspect
|
||||
import pprint
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
from inspect import CO_VARARGS, CO_VARKEYWORDS
|
||||
from inspect import CO_VARARGS
|
||||
from inspect import CO_VARKEYWORDS
|
||||
from weakref import ref
|
||||
|
||||
import attr
|
||||
import pluggy
|
||||
import re
|
||||
from weakref import ref
|
||||
import _pytest
|
||||
from _pytest.compat import _PY2, _PY3, PY35, safe_str
|
||||
from six import text_type
|
||||
import py
|
||||
import six
|
||||
from six import text_type
|
||||
|
||||
builtin_repr = repr
|
||||
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
|
||||
|
||||
if _PY3:
|
||||
from traceback import format_exception_only
|
||||
@@ -136,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
|
||||
@@ -383,40 +390,84 @@ 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):
|
||||
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):
|
||||
@@ -505,6 +556,8 @@ class ExceptionInfo(object):
|
||||
return fmt.repr_excinfo(self)
|
||||
|
||||
def __str__(self):
|
||||
if self._excinfo is None:
|
||||
return repr(self)
|
||||
entry = self.traceback[-1]
|
||||
loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
|
||||
return str(loc)
|
||||
@@ -566,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:
|
||||
@@ -893,8 +946,6 @@ class ReprEntryNative(TerminalRepr):
|
||||
|
||||
|
||||
class ReprEntry(TerminalRepr):
|
||||
localssep = "_ "
|
||||
|
||||
def __init__(self, lines, reprfuncargs, reprlocals, filelocrepr, style):
|
||||
self.lines = lines
|
||||
self.reprfuncargs = reprfuncargs
|
||||
@@ -916,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:
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import ast
|
||||
from ast import PyCF_ONLY_AST as _AST_FLAG
|
||||
from bisect import bisect_right
|
||||
import inspect
|
||||
import linecache
|
||||
import sys
|
||||
import six
|
||||
import inspect
|
||||
import textwrap
|
||||
import tokenize
|
||||
import py
|
||||
import warnings
|
||||
from ast import PyCF_ONLY_AST as _AST_FLAG
|
||||
from bisect import bisect_right
|
||||
|
||||
cpy_compile = compile
|
||||
import py
|
||||
import six
|
||||
|
||||
|
||||
class Source(object):
|
||||
@@ -158,7 +160,7 @@ class Source(object):
|
||||
filename = base + "%r %s:%d>" % (filename, fn, lineno)
|
||||
source = "\n".join(self.lines) + "\n"
|
||||
try:
|
||||
co = cpy_compile(source, filename, mode, flag)
|
||||
co = compile(source, filename, mode, flag)
|
||||
except SyntaxError:
|
||||
ex = sys.exc_info()[1]
|
||||
# re-represent syntax errors from parsing python strings
|
||||
@@ -192,7 +194,7 @@ def compile_(source, filename=None, mode="exec", flags=0, dont_inherit=0):
|
||||
"""
|
||||
if isinstance(source, ast.AST):
|
||||
# XXX should Source support having AST?
|
||||
return cpy_compile(source, filename, mode, flags, dont_inherit)
|
||||
return compile(source, filename, mode, flags, dont_inherit)
|
||||
_genframe = sys._getframe(1) # the caller
|
||||
s = Source(source)
|
||||
co = s.compile(filename, mode, flags, _genframe=_genframe)
|
||||
@@ -235,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]
|
||||
@@ -287,7 +287,11 @@ def get_statement_startend2(lineno, node):
|
||||
def getstatementrange_ast(lineno, source, assertion=False, astnode=None):
|
||||
if astnode is None:
|
||||
content = str(source)
|
||||
astnode = compile(content, "source", "exec", 1024) # 1024 for AST
|
||||
# See #4260:
|
||||
# don't produce duplicate warnings when compiling source to find ast
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
astnode = compile(content, "source", "exec", _AST_FLAG)
|
||||
|
||||
start, end = get_statement_startend2(lineno, astnode)
|
||||
# we need to correct the end:
|
||||
|
||||
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)
|
||||
@@ -1,13 +1,17 @@
|
||||
"""
|
||||
support for presenting detailed information in failing assertions.
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import sys
|
||||
|
||||
import six
|
||||
|
||||
from _pytest.assertion import util
|
||||
from _pytest.assertion import rewrite
|
||||
from _pytest.assertion import truncate
|
||||
from _pytest.assertion import util
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
"""Rewrite assertion AST to produce nice error messages"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import ast
|
||||
import errno
|
||||
import itertools
|
||||
import imp
|
||||
import itertools
|
||||
import marshal
|
||||
import os
|
||||
import re
|
||||
import six
|
||||
import string
|
||||
import struct
|
||||
import sys
|
||||
@@ -15,11 +17,13 @@ import types
|
||||
|
||||
import atomicwrites
|
||||
import py
|
||||
import six
|
||||
|
||||
from _pytest._io.saferepr import saferepr
|
||||
from _pytest.assertion import util
|
||||
from _pytest.pathlib import PurePath
|
||||
from _pytest.compat import spec_from_file_location
|
||||
from _pytest.pathlib import fnmatch_ex
|
||||
from _pytest.pathlib import PurePath
|
||||
|
||||
# pytest caches rewritten pycs in __pycache__.
|
||||
if hasattr(imp, "get_tag"):
|
||||
@@ -262,11 +266,12 @@ 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,
|
||||
)
|
||||
|
||||
def load_module(self, name):
|
||||
@@ -467,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##`
|
||||
@@ -486,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
|
||||
@@ -495,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):
|
||||
@@ -508,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):
|
||||
@@ -655,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)
|
||||
@@ -706,10 +717,9 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||
setattr(node, name, new)
|
||||
elif (
|
||||
isinstance(field, ast.AST)
|
||||
and
|
||||
# Don't recurse into expressions as they can't contain
|
||||
# asserts.
|
||||
not isinstance(field, ast.expr)
|
||||
and not isinstance(field, ast.expr)
|
||||
):
|
||||
nodes.append(field)
|
||||
|
||||
@@ -731,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):
|
||||
@@ -825,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)
|
||||
@@ -855,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.
|
||||
@@ -944,7 +988,8 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||
def visit_Starred(self, starred):
|
||||
# From Python 3.5, a Starred node can appear in a function call
|
||||
res, expl = self.visit(starred.value)
|
||||
return starred, "*" + expl
|
||||
new_starred = ast.Starred(res, starred.ctx)
|
||||
return new_starred, "*" + expl
|
||||
|
||||
def visit_Call_legacy(self, call):
|
||||
"""
|
||||
|
||||
@@ -4,7 +4,10 @@ Utilities for truncating assertion output.
|
||||
Current default behaviour is to truncate assertion explanations at
|
||||
~8 terminal lines, unless running in "-vv" mode or running on CI.
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
|
||||
import six
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
"""Utilities for assertion debugging"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import pprint
|
||||
|
||||
import _pytest._code
|
||||
import py
|
||||
import six
|
||||
from ..compat import Sequence
|
||||
|
||||
u = six.text_type
|
||||
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
|
||||
@@ -19,9 +21,9 @@ _reprcompare = None
|
||||
# the re-encoding is needed for python2 repr
|
||||
# with non-ascii characters (see issue 877 and 1379)
|
||||
def ecu(s):
|
||||
try:
|
||||
return u(s, "utf-8", "replace")
|
||||
except TypeError:
|
||||
if isinstance(s, bytes):
|
||||
return s.decode("UTF-8", "replace")
|
||||
else:
|
||||
return s
|
||||
|
||||
|
||||
@@ -38,7 +40,7 @@ def format_explanation(explanation):
|
||||
explanation = ecu(explanation)
|
||||
lines = _split_explanation(explanation)
|
||||
result = _format_lines(lines)
|
||||
return u("\n").join(result)
|
||||
return u"\n".join(result)
|
||||
|
||||
|
||||
def _split_explanation(explanation):
|
||||
@@ -48,7 +50,7 @@ def _split_explanation(explanation):
|
||||
Any other newlines will be escaped and appear in the line as the
|
||||
literal '\n' characters.
|
||||
"""
|
||||
raw_lines = (explanation or u("")).split("\n")
|
||||
raw_lines = (explanation or u"").split("\n")
|
||||
lines = [raw_lines[0]]
|
||||
for values in raw_lines[1:]:
|
||||
if values and values[0] in ["{", "}", "~", ">"]:
|
||||
@@ -73,13 +75,13 @@ def _format_lines(lines):
|
||||
for line in lines[1:]:
|
||||
if line.startswith("{"):
|
||||
if stackcnt[-1]:
|
||||
s = u("and ")
|
||||
s = u"and "
|
||||
else:
|
||||
s = u("where ")
|
||||
s = u"where "
|
||||
stack.append(len(result))
|
||||
stackcnt[-1] += 1
|
||||
stackcnt.append(0)
|
||||
result.append(u(" +") + u(" ") * (len(stack) - 1) + s + line[1:])
|
||||
result.append(u" +" + u" " * (len(stack) - 1) + s + line[1:])
|
||||
elif line.startswith("}"):
|
||||
stack.pop()
|
||||
stackcnt.pop()
|
||||
@@ -88,7 +90,7 @@ def _format_lines(lines):
|
||||
assert line[0] in ["~", ">"]
|
||||
stack[-1] += 1
|
||||
indent = len(stack) if line.startswith("~") else len(stack) - 1
|
||||
result.append(u(" ") * indent + line[1:])
|
||||
result.append(u" " * indent + line[1:])
|
||||
assert len(stack) == 1
|
||||
return result
|
||||
|
||||
@@ -103,10 +105,10 @@ 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))
|
||||
summary = u"%s %s %s" % (ecu(left_repr), op, ecu(right_repr))
|
||||
|
||||
def issequence(x):
|
||||
return isinstance(x, Sequence) and not isinstance(x, basestring)
|
||||
@@ -120,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)
|
||||
@@ -140,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:
|
||||
@@ -151,11 +164,9 @@ def assertrepr_compare(config, op, left, right):
|
||||
explanation = _notin_text(left, right, verbose)
|
||||
except Exception:
|
||||
explanation = [
|
||||
u(
|
||||
"(pytest_assertion plugin: representation of details failed. "
|
||||
"Probably an object has a faulty __repr__.)"
|
||||
),
|
||||
u(_pytest._code.ExceptionInfo()),
|
||||
u"(pytest_assertion plugin: representation of details failed. "
|
||||
u"Probably an object has a faulty __repr__.)",
|
||||
six.text_type(_pytest._code.ExceptionInfo.from_current()),
|
||||
]
|
||||
|
||||
if not explanation:
|
||||
@@ -199,8 +210,7 @@ def _diff_text(left, right, verbose=False):
|
||||
if i > 42:
|
||||
i -= 10 # Provide some context
|
||||
explanation = [
|
||||
u("Skipping %s identical leading characters in diff, use -v to show")
|
||||
% i
|
||||
u"Skipping %s identical leading characters in diff, use -v to show" % i
|
||||
]
|
||||
left = left[i:]
|
||||
right = right[i:]
|
||||
@@ -211,11 +221,8 @@ def _diff_text(left, right, verbose=False):
|
||||
if i > 42:
|
||||
i -= 10 # Provide some context
|
||||
explanation += [
|
||||
u(
|
||||
"Skipping %s identical trailing "
|
||||
"characters in diff, use -v to show"
|
||||
)
|
||||
% i
|
||||
u"Skipping {} identical trailing "
|
||||
u"characters in diff, use -v to show".format(i)
|
||||
]
|
||||
left = left[:-i]
|
||||
right = right[:-i]
|
||||
@@ -231,23 +238,35 @@ 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")]
|
||||
return [u"Use -v to get the full diff"]
|
||||
# dynamic import to speedup pytest
|
||||
import difflib
|
||||
|
||||
try:
|
||||
left_formatting = pprint.pformat(left).splitlines()
|
||||
right_formatting = pprint.pformat(right).splitlines()
|
||||
explanation = [u("Full diff:")]
|
||||
explanation = [u"Full diff:"]
|
||||
except Exception:
|
||||
# hack: PrettyPrinter.pformat() in python 2 fails when formatting items that can't be sorted(), ie, calling
|
||||
# sorted() on a list would raise. See issue #718.
|
||||
# As a workaround, the full diff is generated by using the repr() string of each item of each container.
|
||||
left_formatting = sorted(repr(x) for x in left)
|
||||
right_formatting = sorted(repr(x) for x in right)
|
||||
explanation = [u("Full diff (fallback to calling repr on each item):")]
|
||||
explanation = [u"Full diff (fallback to calling repr on each item):"]
|
||||
explanation.extend(
|
||||
line.strip() for line in difflib.ndiff(left_formatting, right_formatting)
|
||||
)
|
||||
@@ -258,17 +277,17 @@ def _compare_eq_sequence(left, right, verbose=False):
|
||||
explanation = []
|
||||
for i in range(min(len(left), len(right))):
|
||||
if left[i] != right[i]:
|
||||
explanation += [u("At index %s diff: %r != %r") % (i, left[i], right[i])]
|
||||
explanation += [u"At index %s diff: %r != %r" % (i, left[i], right[i])]
|
||||
break
|
||||
if len(left) > len(right):
|
||||
explanation += [
|
||||
u("Left contains more items, first extra item: %s")
|
||||
% py.io.saferepr(left[len(right)])
|
||||
u"Left contains more items, first extra item: %s"
|
||||
% 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)])
|
||||
u"Right contains more items, first extra item: %s"
|
||||
% saferepr(right[len(left)])
|
||||
]
|
||||
return explanation
|
||||
|
||||
@@ -278,13 +297,13 @@ def _compare_eq_set(left, right, verbose=False):
|
||||
diff_left = left - right
|
||||
diff_right = right - left
|
||||
if diff_left:
|
||||
explanation.append(u("Extra items in the left set:"))
|
||||
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:"))
|
||||
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
|
||||
|
||||
|
||||
@@ -293,46 +312,76 @@ def _compare_eq_dict(left, right, verbose=False):
|
||||
common = set(left).intersection(set(right))
|
||||
same = {k: left[k] for k in common if left[k] == right[k]}
|
||||
if same and verbose < 2:
|
||||
explanation += [u("Omitting %s identical items, use -vv to show") % len(same)]
|
||||
explanation += [u"Omitting %s identical items, use -vv to show" % len(same)]
|
||||
elif same:
|
||||
explanation += [u("Common items:")]
|
||||
explanation += [u"Common items:"]
|
||||
explanation += pprint.pformat(same).splitlines()
|
||||
diff = {k for k in common if left[k] != right[k]}
|
||||
if diff:
|
||||
explanation += [u("Differing items:")]
|
||||
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:"))
|
||||
explanation.append(u"Left contains more items:")
|
||||
explanation.extend(
|
||||
pprint.pformat({k: left[k] for k in extra_left}).splitlines()
|
||||
)
|
||||
extra_right = set(right) - set(left)
|
||||
if extra_right:
|
||||
explanation.append(u("Right contains more items:"))
|
||||
explanation.append(u"Right contains more items:")
|
||||
explanation.extend(
|
||||
pprint.pformat({k: right[k] for k in extra_right}).splitlines()
|
||||
)
|
||||
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")):
|
||||
if line.startswith(u"Skipping"):
|
||||
continue
|
||||
if line.startswith(u("- ")):
|
||||
if line.startswith(u"- "):
|
||||
continue
|
||||
if line.startswith(u("+ ")):
|
||||
newdiff.append(u(" ") + line[2:])
|
||||
if line.startswith(u"+ "):
|
||||
newdiff.append(u" " + line[2:])
|
||||
else:
|
||||
newdiff.append(line)
|
||||
return newdiff
|
||||
|
||||
@@ -4,18 +4,23 @@ merged implementation of the cache provider
|
||||
the name cache was not chosen to ensure pluggy automatically
|
||||
ignores the external pytest-cache
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import json
|
||||
import os
|
||||
from collections import OrderedDict
|
||||
|
||||
import attr
|
||||
import py
|
||||
import six
|
||||
import attr
|
||||
|
||||
import pytest
|
||||
import json
|
||||
|
||||
from .compat import _PY2 as PY2
|
||||
from .pathlib import Path, resolve_from_str, rmtree
|
||||
from .pathlib import Path
|
||||
from .pathlib import resolve_from_str
|
||||
from .pathlib import rmtree
|
||||
|
||||
README_CONTENT = u"""\
|
||||
# pytest cache directory #
|
||||
@@ -28,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):
|
||||
@@ -47,11 +59,13 @@ 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(
|
||||
PytestWarning(fmt.format(**args) if args else fmt), self._config
|
||||
_issue_warning_captured(
|
||||
PytestWarning(fmt.format(**args) if args else fmt),
|
||||
self._config.hook,
|
||||
stacklevel=3,
|
||||
)
|
||||
|
||||
def makedir(self, name):
|
||||
@@ -103,6 +117,10 @@ class Cache(object):
|
||||
"""
|
||||
path = self._getvaluepath(key)
|
||||
try:
|
||||
if path.parent.is_dir():
|
||||
cache_dir_exists_already = True
|
||||
else:
|
||||
cache_dir_exists_already = self._cachedir.exists()
|
||||
path.parent.mkdir(exist_ok=True, parents=True)
|
||||
except (IOError, OSError):
|
||||
self.warn("could not create cache path {path}", path=path)
|
||||
@@ -114,6 +132,7 @@ class Cache(object):
|
||||
else:
|
||||
with f:
|
||||
json.dump(value, f, indent=2, sort_keys=True)
|
||||
if not cache_dir_exists_already:
|
||||
self._ensure_supporting_files()
|
||||
|
||||
def _ensure_supporting_files(self):
|
||||
@@ -123,8 +142,14 @@ class Cache(object):
|
||||
if not readme_path.is_file():
|
||||
readme_path.write_text(README_CONTENT)
|
||||
|
||||
msg = u"# created by pytest automatically, do not change\n*"
|
||||
self._cachedir.joinpath(".gitignore").write_text(msg, encoding="UTF-8")
|
||||
gitignore_path = self._cachedir.joinpath(".gitignore")
|
||||
if not gitignore_path.is_file():
|
||||
msg = u"# Created by pytest automatically.\n*"
|
||||
gitignore_path.write_text(msg, encoding="UTF-8")
|
||||
|
||||
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):
|
||||
@@ -271,7 +296,10 @@ def pytest_addoption(parser):
|
||||
dest="cacheclear",
|
||||
help="remove all cache contents at start of test run.",
|
||||
)
|
||||
parser.addini("cache_dir", default=".pytest_cache", help="cache directory path.")
|
||||
cache_dir_default = ".pytest_cache"
|
||||
if "TOX_ENV_DIR" in os.environ:
|
||||
cache_dir_default = os.path.join(os.environ["TOX_ENV_DIR"], cache_dir_default)
|
||||
parser.addini("cache_dir", default=cache_dir_default, help="cache directory path.")
|
||||
group.addoption(
|
||||
"--lfnf",
|
||||
"--last-failed-no-failures",
|
||||
@@ -315,7 +343,8 @@ def cache(request):
|
||||
|
||||
|
||||
def pytest_report_header(config):
|
||||
if config.option.verbose:
|
||||
"""Display cachedir with --cache-show and if non-default."""
|
||||
if config.option.verbose or config.getini("cache_dir") != ".pytest_cache":
|
||||
cachedir = config.cache._cachedir
|
||||
# TODO: evaluate generating upward relative paths
|
||||
# starting with .., ../.. if sensible
|
||||
|
||||
@@ -2,17 +2,20 @@
|
||||
per-test stdout/stderr capturing mechanism.
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import collections
|
||||
import contextlib
|
||||
import sys
|
||||
import os
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
from io import UnsupportedOperation
|
||||
from tempfile import TemporaryFile
|
||||
|
||||
import six
|
||||
|
||||
import pytest
|
||||
from _pytest.compat import CaptureIO
|
||||
|
||||
@@ -99,6 +102,9 @@ class CaptureManager(object):
|
||||
|
||||
# Global capturing control
|
||||
|
||||
def is_globally_capturing(self):
|
||||
return self._method != "no"
|
||||
|
||||
def start_global_capturing(self):
|
||||
assert self._global_capturing is None
|
||||
self._global_capturing = self._getcapture(self._method)
|
||||
@@ -111,7 +117,10 @@ class CaptureManager(object):
|
||||
self._global_capturing = None
|
||||
|
||||
def resume_global_capture(self):
|
||||
self._global_capturing.resume_capturing()
|
||||
# During teardown of the python process, and on rare occasions, capture
|
||||
# attributes can be `None` while trying to resume global capture.
|
||||
if self._global_capturing is not None:
|
||||
self._global_capturing.resume_capturing()
|
||||
|
||||
def suspend_global_capture(self, in_=False):
|
||||
cap = getattr(self, "_global_capturing", None)
|
||||
@@ -121,7 +130,7 @@ class CaptureManager(object):
|
||||
def read_global_capture(self):
|
||||
return self._global_capturing.readouterr()
|
||||
|
||||
# Fixture Control (its just forwarding, think about removing this later)
|
||||
# Fixture Control (it's just forwarding, think about removing this later)
|
||||
|
||||
def activate_fixture(self, item):
|
||||
"""If the current item is using ``capsys`` or ``capfd``, activate them so they take precedence over
|
||||
@@ -498,7 +507,7 @@ class FDCaptureBinary(object):
|
||||
snap() produces `bytes`
|
||||
"""
|
||||
|
||||
EMPTY_BUFFER = bytes()
|
||||
EMPTY_BUFFER = b""
|
||||
|
||||
def __init__(self, targetfd, tmpfile=None):
|
||||
self.targetfd = targetfd
|
||||
@@ -624,7 +633,7 @@ class SysCapture(object):
|
||||
|
||||
|
||||
class SysCaptureBinary(SysCapture):
|
||||
EMPTY_BUFFER = bytes()
|
||||
EMPTY_BUFFER = b""
|
||||
|
||||
def snap(self):
|
||||
res = self.tmpfile.buffer.getvalue()
|
||||
@@ -764,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):
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
"""
|
||||
python version compatibility code
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import codecs
|
||||
import functools
|
||||
@@ -11,11 +13,13 @@ import sys
|
||||
from contextlib import contextmanager
|
||||
|
||||
import py
|
||||
import six
|
||||
from six import text_type
|
||||
|
||||
import _pytest
|
||||
from _pytest.outcomes import TEST_OUTCOME, fail
|
||||
from six import text_type
|
||||
import six
|
||||
from _pytest._io.saferepr import saferepr
|
||||
from _pytest.outcomes import fail
|
||||
from _pytest.outcomes import TEST_OUTCOME
|
||||
|
||||
try:
|
||||
import enum
|
||||
@@ -42,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):
|
||||
@@ -179,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
|
||||
@@ -218,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:
|
||||
@@ -238,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):
|
||||
@@ -277,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):
|
||||
@@ -331,6 +349,14 @@ def safe_getattr(object, name, default):
|
||||
return default
|
||||
|
||||
|
||||
def safe_isclass(obj):
|
||||
"""Ignore any exception via isinstance on Python 3."""
|
||||
try:
|
||||
return isclass(obj)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _is_unittest_unexpected_success_a_failure():
|
||||
"""Return if the test suite should fail if an @expectedFailure unittest test PASSES.
|
||||
|
||||
@@ -364,7 +390,6 @@ else:
|
||||
COLLECT_FAKEMODULE_ATTRIBUTES = (
|
||||
"Collector",
|
||||
"Module",
|
||||
"Generator",
|
||||
"Function",
|
||||
"Instance",
|
||||
"Session",
|
||||
@@ -417,3 +442,16 @@ class FuncargnamesCompatAttr(object):
|
||||
def funcargnames(self):
|
||||
""" alias attribute for ``fixturenames`` for pre-2.3 compatibility"""
|
||||
return self.fixturenames
|
||||
|
||||
|
||||
if six.PY2:
|
||||
|
||||
def lru_cache(*_, **__):
|
||||
def dec(fn):
|
||||
return fn
|
||||
|
||||
return dec
|
||||
|
||||
|
||||
else:
|
||||
from functools import lru_cache # noqa: F401
|
||||
|
||||
@@ -1,27 +1,39 @@
|
||||
""" command line options, ini-file and conftest.py processing. """
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import copy
|
||||
import inspect
|
||||
import os
|
||||
import shlex
|
||||
import sys
|
||||
import types
|
||||
import warnings
|
||||
import copy
|
||||
import six
|
||||
import py
|
||||
|
||||
# DON't import pytest here because it causes import cycle troubles
|
||||
import sys
|
||||
import os
|
||||
from _pytest.outcomes import Skipped
|
||||
import py
|
||||
import six
|
||||
from pkg_resources import parse_version
|
||||
from pluggy import HookimplMarker
|
||||
from pluggy import HookspecMarker
|
||||
from pluggy import PluginManager
|
||||
|
||||
import _pytest._code
|
||||
import _pytest.hookspec # the extension point definitions
|
||||
import _pytest.assertion
|
||||
from pluggy import PluginManager, HookimplMarker, HookspecMarker
|
||||
from _pytest._code import ExceptionInfo, filter_traceback
|
||||
import _pytest.hookspec # the extension point definitions
|
||||
from .exceptions import PrintHelp
|
||||
from .exceptions import UsageError
|
||||
from .findpaths import determine_setup
|
||||
from .findpaths import exists
|
||||
from _pytest 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 .exceptions import UsageError, PrintHelp
|
||||
from .findpaths import determine_setup, exists
|
||||
from _pytest.outcomes import fail
|
||||
from _pytest.outcomes import Skipped
|
||||
from _pytest.warning_types import PytestWarning
|
||||
|
||||
hookimpl = HookimplMarker("pytest")
|
||||
hookspec = HookspecMarker("pytest")
|
||||
@@ -126,6 +138,7 @@ default_plugins = (
|
||||
"freeze_support",
|
||||
"setuponly",
|
||||
"setupplan",
|
||||
"stepwise",
|
||||
"warnings",
|
||||
"logging",
|
||||
)
|
||||
@@ -163,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:
|
||||
@@ -179,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)
|
||||
_issue_warning_captured(warning, hook=config.hook, stacklevel=4)
|
||||
return pluginmanager.hook.pytest_cmdline_parse(
|
||||
pluginmanager=pluginmanager, args=args
|
||||
)
|
||||
@@ -205,7 +215,7 @@ class PytestPluginManager(PluginManager):
|
||||
self._conftest_plugins = set()
|
||||
|
||||
# state related to local conftest plugins
|
||||
self._path2confmods = {}
|
||||
self._dirpath2confmods = {}
|
||||
self._conftestpath2mod = {}
|
||||
self._confcutdir = None
|
||||
self._noconftest = False
|
||||
@@ -235,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):
|
||||
@@ -251,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)
|
||||
@@ -265,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):
|
||||
@@ -277,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
|
||||
@@ -326,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
|
||||
#
|
||||
@@ -376,6 +383,7 @@ class PytestPluginManager(PluginManager):
|
||||
if x.check(dir=1):
|
||||
self._getconftestmodules(x)
|
||||
|
||||
@lru_cache(maxsize=128)
|
||||
def _getconftestmodules(self, path):
|
||||
if self._noconftest:
|
||||
return []
|
||||
@@ -384,23 +392,29 @@ class PytestPluginManager(PluginManager):
|
||||
directory = path.dirpath()
|
||||
else:
|
||||
directory = path
|
||||
try:
|
||||
return self._path2confmods[directory]
|
||||
except KeyError:
|
||||
# XXX these days we may rather want to use config.rootdir
|
||||
# and allow users to opt into looking into the rootdir parent
|
||||
# directories instead of requiring to specify confcutdir
|
||||
clist = []
|
||||
for parent in directory.parts():
|
||||
if self._confcutdir and self._confcutdir.relto(parent):
|
||||
continue
|
||||
conftestpath = parent.join("conftest.py")
|
||||
if conftestpath.isfile():
|
||||
mod = self._importconftest(conftestpath)
|
||||
clist.append(mod)
|
||||
|
||||
self._path2confmods[directory] = clist
|
||||
return clist
|
||||
if six.PY2: # py2 is not using lru_cache.
|
||||
try:
|
||||
return self._dirpath2confmods[directory]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# XXX these days we may rather want to use config.rootdir
|
||||
# and allow users to opt into looking into the rootdir parent
|
||||
# directories instead of requiring to specify confcutdir
|
||||
clist = []
|
||||
for parent in directory.realpath().parts():
|
||||
if self._confcutdir and self._confcutdir.relto(parent):
|
||||
continue
|
||||
conftestpath = parent.join("conftest.py")
|
||||
if conftestpath.isfile():
|
||||
# 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
|
||||
|
||||
def _rget_with_confmod(self, name, path):
|
||||
modules = self._getconftestmodules(path)
|
||||
@@ -426,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())
|
||||
@@ -441,8 +455,8 @@ class PytestPluginManager(PluginManager):
|
||||
self._conftest_plugins.add(mod)
|
||||
self._conftestpath2mod[conftestpath] = mod
|
||||
dirpath = conftestpath.dirpath()
|
||||
if dirpath in self._path2confmods:
|
||||
for path, mods in self._path2confmods.items():
|
||||
if dirpath in self._dirpath2confmods:
|
||||
for path, mods in self._dirpath2confmods.items():
|
||||
if path and path.relto(dirpath) or path == dirpath:
|
||||
assert mod not in mods
|
||||
mods.append(mod)
|
||||
@@ -456,13 +470,29 @@ 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:"):
|
||||
name = arg[3:]
|
||||
# PR #4304 : remove stepwise if cacheprovider is blocked
|
||||
if name == "cacheprovider":
|
||||
self.set_blocked("stepwise")
|
||||
self.set_blocked("pytest_stepwise")
|
||||
|
||||
self.set_blocked(name)
|
||||
if not name.startswith("pytest_"):
|
||||
self.set_blocked("pytest_" + name)
|
||||
@@ -488,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)
|
||||
@@ -512,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)
|
||||
@@ -526,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(
|
||||
@@ -587,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):
|
||||
@@ -618,36 +647,6 @@ 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
|
||||
|
||||
@@ -712,7 +711,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 ()
|
||||
@@ -765,12 +763,21 @@ class Config(object):
|
||||
for name in _iter_rewritable_modules(package_files):
|
||||
hook.mark_rewrite(name)
|
||||
|
||||
def _validate_args(self, args):
|
||||
"""Validate known args."""
|
||||
self._parser.parse_known_and_unknown_args(
|
||||
args, namespace=copy.copy(self.option)
|
||||
)
|
||||
return args
|
||||
|
||||
def _preparse(self, args, addopts=True):
|
||||
if addopts:
|
||||
args[:] = shlex.split(os.environ.get("PYTEST_ADDOPTS", "")) + args
|
||||
env_addopts = os.environ.get("PYTEST_ADDOPTS", "")
|
||||
if len(env_addopts):
|
||||
args[:] = self._validate_args(shlex.split(env_addopts)) + args
|
||||
self._initini(args)
|
||||
if addopts:
|
||||
args[:] = self.getini("addopts") + args
|
||||
args[:] = self._validate_args(self.getini("addopts")) + args
|
||||
self._checkversion()
|
||||
self._consider_importhook(args)
|
||||
self.pluginmanager.consider_preparse(args)
|
||||
@@ -794,7 +801,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
|
||||
|
||||
@@ -803,9 +818,7 @@ class Config(object):
|
||||
|
||||
minver = self.inicfg.get("minversion", None)
|
||||
if minver:
|
||||
ver = minver.split(".")
|
||||
myver = pytest.__version__.split(".")
|
||||
if myver < ver:
|
||||
if parse_version(minver) > parse_version(pytest.__version__):
|
||||
raise pytest.UsageError(
|
||||
"%s:%d: requires pytest-%s, actual pytest-%s'"
|
||||
% (
|
||||
@@ -834,11 +847,10 @@ class Config(object):
|
||||
args, self.option, namespace=self.option
|
||||
)
|
||||
if not args:
|
||||
cwd = os.getcwd()
|
||||
if cwd == self.rootdir:
|
||||
if self.invocation_dir == self.rootdir:
|
||||
args = self.getini("testpaths")
|
||||
if not args:
|
||||
args = [cwd]
|
||||
args = [str(self.invocation_dir)]
|
||||
self.args = args
|
||||
except PrintHelp:
|
||||
pass
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import six
|
||||
import warnings
|
||||
import argparse
|
||||
|
||||
from gettext import gettext as _
|
||||
import sys as _sys
|
||||
import warnings
|
||||
from gettext import gettext as _
|
||||
|
||||
import py
|
||||
import six
|
||||
|
||||
from ..main import EXIT_USAGEERROR
|
||||
|
||||
@@ -19,6 +18,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 = []
|
||||
@@ -83,7 +84,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:
|
||||
@@ -153,7 +154,7 @@ class ArgumentError(Exception):
|
||||
class Argument(object):
|
||||
"""class that mimics the necessary behaviour of optparse.Option
|
||||
|
||||
its currently a least effort implementation
|
||||
it's currently a least effort implementation
|
||||
and ignoring choices and integer prefixes
|
||||
https://docs.python.org/3/library/optparse.html#optparse-standard-option-types
|
||||
"""
|
||||
@@ -320,12 +321,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,
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import py
|
||||
import os
|
||||
|
||||
import py
|
||||
|
||||
from .exceptions import UsageError
|
||||
from _pytest.outcomes import fail
|
||||
|
||||
|
||||
def exists(path, ignore=EnvironmentError):
|
||||
@@ -32,14 +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,
|
||||
fail(
|
||||
CFG_PYTEST_SECTION.format(filename=inibasename),
|
||||
pytrace=False,
|
||||
)
|
||||
return base, p, iniconfig["pytest"]
|
||||
if (
|
||||
@@ -109,38 +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
|
||||
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 {}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
""" interactive debugging with PDB, the Python Debugger. """
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import pdb
|
||||
import sys
|
||||
from doctest import UnexpectedException
|
||||
@@ -9,13 +10,6 @@ from doctest import UnexpectedException
|
||||
from _pytest import outcomes
|
||||
from _pytest.config import hookimpl
|
||||
|
||||
try:
|
||||
from builtins import breakpoint # noqa
|
||||
|
||||
SUPPORTS_BREAKPOINT_BUILTIN = True
|
||||
except ImportError:
|
||||
SUPPORTS_BREAKPOINT_BUILTIN = False
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("general")
|
||||
@@ -53,25 +47,24 @@ def pytest_configure(config):
|
||||
if config.getvalue("usepdb"):
|
||||
config.pluginmanager.register(PdbInvoke(), "pdbinvoke")
|
||||
|
||||
# Use custom Pdb class set_trace instead of default Pdb on breakpoint() call
|
||||
if SUPPORTS_BREAKPOINT_BUILTIN:
|
||||
_environ_pythonbreakpoint = os.environ.get("PYTHONBREAKPOINT", "")
|
||||
if _environ_pythonbreakpoint == "":
|
||||
sys.breakpointhook = pytestPDB.set_trace
|
||||
|
||||
old = (pdb.set_trace, pytestPDB._pluginmanager)
|
||||
|
||||
def fin():
|
||||
pdb.set_trace, pytestPDB._pluginmanager = old
|
||||
pytestPDB._config = None
|
||||
pytestPDB._pdb_cls = pdb.Pdb
|
||||
if SUPPORTS_BREAKPOINT_BUILTIN:
|
||||
sys.breakpointhook = sys.__breakpointhook__
|
||||
|
||||
pytestPDB._saved.append(
|
||||
(pdb.set_trace, pytestPDB._pluginmanager, pytestPDB._config, pytestPDB._pdb_cls)
|
||||
)
|
||||
pdb.set_trace = pytestPDB.set_trace
|
||||
pytestPDB._pluginmanager = config.pluginmanager
|
||||
pytestPDB._config = config
|
||||
pytestPDB._pdb_cls = pdb_cls
|
||||
|
||||
# NOTE: not using pytest_unconfigure, since it might get called although
|
||||
# pytest_configure was not (if another plugin raises UsageError).
|
||||
def fin():
|
||||
(
|
||||
pdb.set_trace,
|
||||
pytestPDB._pluginmanager,
|
||||
pytestPDB._config,
|
||||
pytestPDB._pdb_cls,
|
||||
) = pytestPDB._saved.pop()
|
||||
|
||||
config._cleanup.append(fin)
|
||||
|
||||
|
||||
@@ -81,23 +74,89 @@ class pytestPDB(object):
|
||||
_pluginmanager = None
|
||||
_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()
|
||||
tw.sep(">", "PDB set_trace (IO-capturing turned off)")
|
||||
cls._pluginmanager.hook.pytest_enter_pdb(config=cls._config)
|
||||
if set_break:
|
||||
cls._pdb_cls().set_trace(frame)
|
||||
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 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
|
||||
)
|
||||
self._continued = True
|
||||
return ret
|
||||
|
||||
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().
|
||||
|
||||
Needed after do_continue resumed, and entering another
|
||||
breakpoint again.
|
||||
"""
|
||||
ret = super(_PdbWrapper, self).setup(f, tb)
|
||||
if not ret and self._continued:
|
||||
# pdb.setup() returns True if the command wants to exit
|
||||
# from the interaction: do not suspend capturing then.
|
||||
if self._pytest_capman:
|
||||
self._pytest_capman.suspend_global_capture(in_=True)
|
||||
return ret
|
||||
|
||||
_pdb = _PdbWrapper(**kwargs)
|
||||
cls._pluginmanager.hook.pytest_enter_pdb(config=cls._config, pdb=_pdb)
|
||||
else:
|
||||
_pdb = cls._pdb_cls(**kwargs)
|
||||
return _pdb
|
||||
|
||||
@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):
|
||||
@@ -123,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)
|
||||
@@ -164,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
|
||||
|
||||
|
||||
@@ -196,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")
|
||||
|
||||
@@ -8,65 +8,44 @@ be removed when the time comes.
|
||||
All constants defined in this module should be either PytestWarning instances or UnformattedWarning
|
||||
in case of warnings which need to format their messages.
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
from _pytest.warning_types import PytestDeprecationWarning
|
||||
from _pytest.warning_types import RemovedInPytest4Warning
|
||||
|
||||
|
||||
from _pytest.warning_types import UnformattedWarning, RemovedInPytest4Warning
|
||||
YIELD_TESTS = "yield tests were removed in pytest 4.0 - {name} will be ignored"
|
||||
|
||||
|
||||
MAIN_STR_ARGS = RemovedInPytest4Warning(
|
||||
"passing a string to pytest.main() is deprecated, "
|
||||
"pass a list of arguments instead."
|
||||
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."
|
||||
)
|
||||
|
||||
YIELD_TESTS = RemovedInPytest4Warning(
|
||||
"yield tests are deprecated, and scheduled to be removed in pytest 4.0"
|
||||
FIXTURE_NAMED_REQUEST = PytestDeprecationWarning(
|
||||
"'request' is a reserved name for fixtures and will raise an error in future versions"
|
||||
)
|
||||
|
||||
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.",
|
||||
)
|
||||
|
||||
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(
|
||||
@@ -75,42 +54,36 @@ 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"
|
||||
)
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
""" discover and run doctests in modules and test files."""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import traceback
|
||||
import sys
|
||||
import inspect
|
||||
import platform
|
||||
import sys
|
||||
import traceback
|
||||
from contextlib import contextmanager
|
||||
|
||||
import pytest
|
||||
from _pytest._code.code import ExceptionInfo, ReprFileLocation, TerminalRepr
|
||||
from _pytest._code.code import ExceptionInfo
|
||||
from _pytest._code.code import ReprFileLocation
|
||||
from _pytest._code.code import TerminalRepr
|
||||
from _pytest.compat import safe_getattr
|
||||
from _pytest.fixtures import FixtureRequest
|
||||
|
||||
|
||||
DOCTEST_REPORT_CHOICE_NONE = "none"
|
||||
DOCTEST_REPORT_CHOICE_CDIFF = "cdiff"
|
||||
DOCTEST_REPORT_CHOICE_NDIFF = "ndiff"
|
||||
@@ -342,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:
|
||||
@@ -357,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,
|
||||
|
||||
@@ -1,41 +1,42 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import functools
|
||||
import inspect
|
||||
import sys
|
||||
import warnings
|
||||
from collections import OrderedDict, deque, defaultdict
|
||||
|
||||
import six
|
||||
from more_itertools import flatten
|
||||
from collections import defaultdict
|
||||
from collections import deque
|
||||
from collections import OrderedDict
|
||||
|
||||
import attr
|
||||
import py
|
||||
from py._code.code import FormattedExcinfo
|
||||
import six
|
||||
from more_itertools import flatten
|
||||
|
||||
import _pytest
|
||||
from _pytest import nodes
|
||||
from _pytest._code.code import FormattedExcinfo
|
||||
from _pytest._code.code import TerminalRepr
|
||||
from _pytest.compat import (
|
||||
NOTSET,
|
||||
exc_clear,
|
||||
_format_args,
|
||||
getfslineno,
|
||||
get_real_func,
|
||||
is_generator,
|
||||
isclass,
|
||||
getimfunc,
|
||||
getlocation,
|
||||
getfuncargnames,
|
||||
safe_getattr,
|
||||
FuncargnamesCompatAttr,
|
||||
get_real_method,
|
||||
_PytestWrapper,
|
||||
)
|
||||
from _pytest.compat import _format_args
|
||||
from _pytest.compat import _PytestWrapper
|
||||
from _pytest.compat import exc_clear
|
||||
from _pytest.compat import FuncargnamesCompatAttr
|
||||
from _pytest.compat import get_real_func
|
||||
from _pytest.compat import get_real_method
|
||||
from _pytest.compat import getfslineno
|
||||
from _pytest.compat import getfuncargnames
|
||||
from _pytest.compat import getimfunc
|
||||
from _pytest.compat import getlocation
|
||||
from _pytest.compat import is_generator
|
||||
from _pytest.compat import isclass
|
||||
from _pytest.compat import NOTSET
|
||||
from _pytest.compat import safe_getattr
|
||||
from _pytest.deprecated import FIXTURE_FUNCTION_CALL
|
||||
from _pytest.outcomes import fail, TEST_OUTCOME
|
||||
|
||||
FIXTURE_MSG = 'fixtures cannot have "pytest_funcarg__" prefix and be decorated with @pytest.fixture:\n{}'
|
||||
from _pytest.deprecated import FIXTURE_NAMED_REQUEST
|
||||
from _pytest.outcomes import fail
|
||||
from _pytest.outcomes import TEST_OUTCOME
|
||||
|
||||
|
||||
@attr.s(frozen=True)
|
||||
@@ -306,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
|
||||
@@ -466,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.
|
||||
|
||||
@@ -602,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)
|
||||
@@ -616,7 +579,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||
subrequest._check_scope(argname, self.scope, scope)
|
||||
|
||||
# clear sys.exc_info before invoking the fixture (python bug?)
|
||||
# if its not explicitly cleared it will leak into the call
|
||||
# if it's not explicitly cleared it will leak into the call
|
||||
exc_clear()
|
||||
try:
|
||||
# call the fixture function
|
||||
@@ -697,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")
|
||||
|
||||
@@ -924,7 +881,7 @@ class FixtureDef(object):
|
||||
return hook.pytest_fixture_setup(fixturedef=self, request=request)
|
||||
|
||||
def __repr__(self):
|
||||
return "<FixtureDef name=%r scope=%r baseid=%r>" % (
|
||||
return "<FixtureDef argname=%r scope=%r baseid=%r>" % (
|
||||
self.argname,
|
||||
self.scope,
|
||||
self.baseid,
|
||||
@@ -979,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)
|
||||
@@ -1032,8 +972,11 @@ 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":
|
||||
warnings.warn(FIXTURE_NAMED_REQUEST)
|
||||
function._pytestfixturefunction = self
|
||||
return function
|
||||
|
||||
@@ -1149,7 +1092,6 @@ class FixtureManager(object):
|
||||
by a lookup of their FuncFixtureInfo.
|
||||
"""
|
||||
|
||||
_argprefix = "pytest_funcarg__"
|
||||
FixtureLookupError = FixtureLookupError
|
||||
FixtureLookupErrorRepr = FixtureLookupErrorRepr
|
||||
|
||||
@@ -1191,6 +1133,7 @@ class FixtureManager(object):
|
||||
nodeid = p.dirpath().relto(self.config.rootdir)
|
||||
if p.sep != nodes.SEP:
|
||||
nodeid = nodeid.replace(p.sep, nodes.SEP)
|
||||
|
||||
self.parsefactories(plugin, nodeid)
|
||||
|
||||
def _getautousenames(self, nodeid):
|
||||
@@ -1258,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,
|
||||
@@ -1286,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:
|
||||
@@ -1295,6 +1237,7 @@ class FixtureManager(object):
|
||||
nodeid = node_or_obj.nodeid
|
||||
if holderobj in self._holderobjseen:
|
||||
return
|
||||
|
||||
self._holderobjseen.add(holderobj)
|
||||
autousenames = []
|
||||
for name in dir(holderobj):
|
||||
@@ -1302,31 +1245,13 @@ class FixtureManager(object):
|
||||
# access below can raise. safe_getatt() ignores such exceptions.
|
||||
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
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
Provides a function to report all internal modules for using freezing tools
|
||||
pytest
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
|
||||
def freeze_includes():
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user