Compare commits
569 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
978bb190a1 | ||
|
|
a2a904466c | ||
|
|
77c28825df | ||
|
|
d3dcc2b8f1 | ||
|
|
85541113eb | ||
|
|
7ef06822cb | ||
|
|
289e0091de | ||
|
|
28efdebfcd | ||
|
|
fb2e7cc727 | ||
|
|
fb8ad714b1 | ||
|
|
945072b89a | ||
|
|
0d80a9c729 | ||
|
|
357a7c79ef | ||
|
|
158f3cfaea | ||
|
|
82c74fe7e6 | ||
|
|
afc5c7e4f6 | ||
|
|
03eb9203fd | ||
|
|
ae4dff0e0a | ||
|
|
d217b52508 | ||
|
|
436e13ac25 | ||
|
|
9fb5ddf778 | ||
|
|
6f98cd6faa | ||
|
|
561a5fb558 | ||
|
|
47739291cf | ||
|
|
8a39869347 | ||
|
|
8a94c66e68 | ||
|
|
0d07b64571 | ||
|
|
60212e8831 | ||
|
|
75abfbe8d4 | ||
|
|
6d661ace0a | ||
|
|
f51c34ef31 | ||
|
|
a986b8f945 | ||
|
|
c24e8e01b4 | ||
|
|
1a37035d71 | ||
|
|
99c4b6fdc3 | ||
|
|
dd2425675b | ||
|
|
6a3c943ce2 | ||
|
|
98430a17f2 | ||
|
|
fe6e1b2059 | ||
|
|
7ce5873da2 | ||
|
|
0eb80bcb5a | ||
|
|
fb45f82840 | ||
|
|
5af5ba11d3 | ||
|
|
053c052190 | ||
|
|
56156bb119 | ||
|
|
e048315d9b | ||
|
|
3155d0ca9c | ||
|
|
53d319144d | ||
|
|
6ddfd60ce7 | ||
|
|
653a53226a | ||
|
|
1fbd19b8cb | ||
|
|
890c2fa555 | ||
|
|
725290a8ab | ||
|
|
98c707561c | ||
|
|
a341dddc74 | ||
|
|
16e49d96d1 | ||
|
|
ec62a3c9e4 | ||
|
|
f70ed83479 | ||
|
|
dff914cadd | ||
|
|
3135463573 | ||
|
|
266b53dfc2 | ||
|
|
bdb3581a52 | ||
|
|
27b62740e3 | ||
|
|
a70e92777f | ||
|
|
3c011c05db | ||
|
|
168daaa71f | ||
|
|
43fc1b47c0 | ||
|
|
699f094b0c | ||
|
|
9bdf51fcc5 | ||
|
|
1d23999033 | ||
|
|
1d35a03812 | ||
|
|
d70596da91 | ||
|
|
a1277aaf0e | ||
|
|
19cec79363 | ||
|
|
981fcb2798 | ||
|
|
1ee3d40dbe | ||
|
|
bdddc9c38b | ||
|
|
b46c7dddaa | ||
|
|
4636bf6160 | ||
|
|
52a5acda92 | ||
|
|
2b0ad4630d | ||
|
|
3ea987ef9d | ||
|
|
7a186df271 | ||
|
|
6d4b14d7ee | ||
|
|
fd0010e6e9 | ||
|
|
8ce32b0795 | ||
|
|
5d4703852e | ||
|
|
9b51536a18 | ||
|
|
d8403d793f | ||
|
|
24d3e01548 | ||
|
|
3884398055 | ||
|
|
819e0ead44 | ||
|
|
c8ca1d12d7 | ||
|
|
3d2b7aeea5 | ||
|
|
7aa7c6bbfd | ||
|
|
28530836c9 | ||
|
|
055f1dc9ea | ||
|
|
0cbd58e16a | ||
|
|
b7b863b7bf | ||
|
|
4bcc06362d | ||
|
|
316e39872a | ||
|
|
81af1c024a | ||
|
|
e656dbb602 | ||
|
|
63b69326b8 | ||
|
|
19d05814d2 | ||
|
|
7d2b65813e | ||
|
|
f82c03833f | ||
|
|
486421fca2 | ||
|
|
2d7cfcd686 | ||
|
|
623e786524 | ||
|
|
5c09b33150 | ||
|
|
7e758a9dc6 | ||
|
|
3445bdaca2 | ||
|
|
89151b8c63 | ||
|
|
4194ddd5b4 | ||
|
|
ab90043adc | ||
|
|
a95fe3693b | ||
|
|
b64aaac7ec | ||
|
|
424b46de1b | ||
|
|
c9927bb66f | ||
|
|
cbb5d48fdd | ||
|
|
d98d655094 | ||
|
|
cf9a09e988 | ||
|
|
d9ede1bac2 | ||
|
|
5f90907509 | ||
|
|
310bada6f5 | ||
|
|
08b40396c9 | ||
|
|
a50209b29e | ||
|
|
a7b907d325 | ||
|
|
c78a8b28dc | ||
|
|
3a6a0f1220 | ||
|
|
fc4e240596 | ||
|
|
5507a4d239 | ||
|
|
01479189a5 | ||
|
|
ddb7060535 | ||
|
|
96a331e32f | ||
|
|
688622f5cf | ||
|
|
fa7b0086a2 | ||
|
|
3874d53ee1 | ||
|
|
b76de91474 | ||
|
|
6940f82235 | ||
|
|
f6a2f779ae | ||
|
|
19536c9f05 | ||
|
|
e4c1b9c1c4 | ||
|
|
25aed0dca8 | ||
|
|
b2f837acd8 | ||
|
|
3ef003f0ff | ||
|
|
baabde76ae | ||
|
|
c805412d47 | ||
|
|
e4d361b093 | ||
|
|
bed56e504a | ||
|
|
3dd50d039d | ||
|
|
0eeb466f11 | ||
|
|
ee88679c54 | ||
|
|
9af1f63ab6 | ||
|
|
fb7bbf816c | ||
|
|
9aae4b782b | ||
|
|
1d190dc618 | ||
|
|
7823838e69 | ||
|
|
a965386b9e | ||
|
|
6f0d90cd5a | ||
|
|
48424a6bf6 | ||
|
|
2a8d58814a | ||
|
|
fa601de5c4 | ||
|
|
8284d14ec4 | ||
|
|
238dcd8bae | ||
|
|
b95ff7104c | ||
|
|
03f8b50c8a | ||
|
|
dc7f76c276 | ||
|
|
20bd56f4b2 | ||
|
|
89c75b2c91 | ||
|
|
341bc33ff3 | ||
|
|
fe10057c15 | ||
|
|
48b62e4d89 | ||
|
|
1b431d6644 | ||
|
|
b1955c7f84 | ||
|
|
bfa2fadac1 | ||
|
|
48109b0e60 | ||
|
|
fdce2306a7 | ||
|
|
f00577f7c4 | ||
|
|
569dbeb087 | ||
|
|
c6938221ab | ||
|
|
2b764a0e73 | ||
|
|
51694b8295 | ||
|
|
2e04771893 | ||
|
|
7d107018e8 | ||
|
|
05aad5c381 | ||
|
|
1e0088a949 | ||
|
|
ba3b29e831 | ||
|
|
6218e20e88 | ||
|
|
190a52badb | ||
|
|
7b2956e10b | ||
|
|
de1a9f574c | ||
|
|
05a26d995b | ||
|
|
79722ae89b | ||
|
|
b5dc7d9be1 | ||
|
|
30e61f2777 | ||
|
|
58af604f82 | ||
|
|
c0024a723d | ||
|
|
545bf0d5a1 | ||
|
|
70b5d5aee9 | ||
|
|
fde44f4f30 | ||
|
|
fa4d832507 | ||
|
|
74a68b5ec6 | ||
|
|
e35ce98f89 | ||
|
|
dd0062c177 | ||
|
|
6c37a51f95 | ||
|
|
52ac6cd7a9 | ||
|
|
4825678e1a | ||
|
|
e43eaffd93 | ||
|
|
9f85d4c952 | ||
|
|
7a6f902f6f | ||
|
|
a912d3745b | ||
|
|
f23307b06d | ||
|
|
6c3e6401d4 | ||
|
|
3315b3a12f | ||
|
|
64d7d00218 | ||
|
|
7c747c97ec | ||
|
|
56c5db6e12 | ||
|
|
2d05f831fe | ||
|
|
cb6181255e | ||
|
|
cd9e30b221 | ||
|
|
d028fe1e66 | ||
|
|
b825af2e66 | ||
|
|
60e9698530 | ||
|
|
9e6bb74d71 | ||
|
|
ed3c96ee58 | ||
|
|
199fcf93d4 | ||
|
|
c8caa87759 | ||
|
|
b7de0401b8 | ||
|
|
01793ed8bc | ||
|
|
82d00efa8d | ||
|
|
61c569f960 | ||
|
|
dd56d7b7fc | ||
|
|
4de3d595c9 | ||
|
|
b28b3cc271 | ||
|
|
99072ea8c9 | ||
|
|
11a7bcaaa5 | ||
|
|
0caee1a673 | ||
|
|
4c87a6aa09 | ||
|
|
7b13c4bec0 | ||
|
|
aa8c352c10 | ||
|
|
ee75ecbda0 | ||
|
|
bc32e45bb6 | ||
|
|
3e5c9038ec | ||
|
|
b2c0864fbf | ||
|
|
a80efb038a | ||
|
|
5b29f579c5 | ||
|
|
808cb8e3ad | ||
|
|
8727503dd4 | ||
|
|
f46de68804 | ||
|
|
3c19cfcd9a | ||
|
|
b8784c28c9 | ||
|
|
29b05c8391 | ||
|
|
26c835eea5 | ||
|
|
eebf5c1d2c | ||
|
|
3daa0756eb | ||
|
|
3e34db50fb | ||
|
|
e2603d7050 | ||
|
|
369d9ecaa5 | ||
|
|
02dd6df6e6 | ||
|
|
6c170201d6 | ||
|
|
bf4de4bd68 | ||
|
|
80d6d94635 | ||
|
|
63cba1ed0d | ||
|
|
71ab6b8b05 | ||
|
|
c367180ab2 | ||
|
|
1bdf71730a | ||
|
|
0ea8dc0d40 | ||
|
|
638b3f5e39 | ||
|
|
309ecf7ab3 | ||
|
|
719d63085d | ||
|
|
5a5b732fe1 | ||
|
|
a0edbb75a4 | ||
|
|
0ef73ed3e0 | ||
|
|
7cfb750d7f | ||
|
|
70f72229c6 | ||
|
|
2e02579437 | ||
|
|
8d49abb0d1 | ||
|
|
c5631b6567 | ||
|
|
522224ee7c | ||
|
|
015e8e574a | ||
|
|
87ff7ee232 | ||
|
|
b5490b289d | ||
|
|
46039f8687 | ||
|
|
6b25fb4d64 | ||
|
|
99a5067edb | ||
|
|
5afb61ad26 | ||
|
|
fbfab6778c | ||
|
|
57bc14caa0 | ||
|
|
df3f21afb6 | ||
|
|
1a87bb2416 | ||
|
|
6e170a4a1c | ||
|
|
924a9667e1 | ||
|
|
319f6310f8 | ||
|
|
cd3a441304 | ||
|
|
8180165229 | ||
|
|
ec5a429c77 | ||
|
|
713069ebd4 | ||
|
|
f7af08d309 | ||
|
|
943099ddd1 | ||
|
|
379562107e | ||
|
|
25c392196f | ||
|
|
ec597e81a4 | ||
|
|
81588d7f63 | ||
|
|
af893aab26 | ||
|
|
8bf7e7cc4b | ||
|
|
7b20288c2b | ||
|
|
2b2bec6b97 | ||
|
|
fcc20d4181 | ||
|
|
6ac31088c5 | ||
|
|
7eea6b3b02 | ||
|
|
e87facfb22 | ||
|
|
855b115dab | ||
|
|
4263b8b407 | ||
|
|
7d150c20cf | ||
|
|
a124163425 | ||
|
|
2382546112 | ||
|
|
946bb08da5 | ||
|
|
85d1f0404a | ||
|
|
1d60f61ba8 | ||
|
|
ad05cbe6da | ||
|
|
1216a27b44 | ||
|
|
2b2240e904 | ||
|
|
74f7efd2a3 | ||
|
|
34db8aed34 | ||
|
|
926c6028bb | ||
|
|
af54e09759 | ||
|
|
dfaeefd692 | ||
|
|
86b6ce5042 | ||
|
|
8f880e1625 | ||
|
|
46c85bc352 | ||
|
|
8b61a332ba | ||
|
|
139c97930b | ||
|
|
9cfee82f9b | ||
|
|
c0a5f3df10 | ||
|
|
8220c05b01 | ||
|
|
d0f5f6676b | ||
|
|
ec02f694ef | ||
|
|
6351e2846c | ||
|
|
1c70827f33 | ||
|
|
ccfd962170 | ||
|
|
b417d7cb79 | ||
|
|
1c46462991 | ||
|
|
5ccb7b1ced | ||
|
|
eabf2f9091 | ||
|
|
1db4cbcc9f | ||
|
|
fbac936596 | ||
|
|
ffa572531a | ||
|
|
fde2a6f5fd | ||
|
|
7b7737bf96 | ||
|
|
04e9ae75c8 | ||
|
|
9ea7826427 | ||
|
|
09cc45b0c5 | ||
|
|
0aa54101c9 | ||
|
|
5eef6a2821 | ||
|
|
518c88f149 | ||
|
|
5f5a7995b9 | ||
|
|
0528e5b45f | ||
|
|
9b04958303 | ||
|
|
faed54d6c7 | ||
|
|
1f609f96e6 | ||
|
|
0664ae137c | ||
|
|
d0107c898e | ||
|
|
9128fec4c4 | ||
|
|
80bcf8d624 | ||
|
|
b8df5446c0 | ||
|
|
2a31df072b | ||
|
|
02f5defd89 | ||
|
|
efb5332023 | ||
|
|
b9908cc036 | ||
|
|
c727860241 | ||
|
|
8c17c7cd12 | ||
|
|
b7459b8a64 | ||
|
|
b920f09a95 | ||
|
|
a3353c49fd | ||
|
|
a4a12b8356 | ||
|
|
13ae2fe28b | ||
|
|
141a463fed | ||
|
|
f508a52ca9 | ||
|
|
b48a02fdb1 | ||
|
|
382efc6363 | ||
|
|
1bed514eb6 | ||
|
|
41f19796e8 | ||
|
|
427e6c3b4d | ||
|
|
14bc3c4009 | ||
|
|
7e063eec08 | ||
|
|
61934ae82d | ||
|
|
8c74bb0d25 | ||
|
|
bb4771cedf | ||
|
|
9475cd3fb8 | ||
|
|
464e16deca | ||
|
|
d9b78f2a95 | ||
|
|
7232b45f25 | ||
|
|
a54e4e64cd | ||
|
|
edfb567091 | ||
|
|
6a2ebddc7c | ||
|
|
5040dde0c5 | ||
|
|
095abfd035 | ||
|
|
69ef0ab189 | ||
|
|
d851a8fd07 | ||
|
|
0704fcacd7 | ||
|
|
4f17d56ecb | ||
|
|
b1f6dc23da | ||
|
|
c6f90c25e3 | ||
|
|
f0e5cb362e | ||
|
|
c7cf4adfd0 | ||
|
|
def543924b | ||
|
|
6be6798cdf | ||
|
|
ce4eb51ee0 | ||
|
|
0d2668017d | ||
|
|
e7e4860ded | ||
|
|
aba55a0fb2 | ||
|
|
b5d65e5139 | ||
|
|
3a3f0f5c56 | ||
|
|
ba9146c131 | ||
|
|
c790f7475e | ||
|
|
f9b1e39b8a | ||
|
|
81ad1689b9 | ||
|
|
44f60ba141 | ||
|
|
bced5a3f81 | ||
|
|
a8d7e513f4 | ||
|
|
604a021a2a | ||
|
|
603d81ef2f | ||
|
|
6378cdf7a9 | ||
|
|
84eacf3e3c | ||
|
|
320c95ca43 | ||
|
|
b20803f0a6 | ||
|
|
df767cca8f | ||
|
|
b3166a538c | ||
|
|
1f148a93ec | ||
|
|
af46ffe021 | ||
|
|
e4a000bb00 | ||
|
|
d04505553e | ||
|
|
2b5c46b2ab | ||
|
|
063f90f0d2 | ||
|
|
6096cae3dd | ||
|
|
3dc57d99f2 | ||
|
|
3cc5a4ecb0 | ||
|
|
ac0b7b9803 | ||
|
|
a1d226b751 | ||
|
|
d667259e31 | ||
|
|
98668c943d | ||
|
|
a7a470b56f | ||
|
|
7d6edb9ca5 | ||
|
|
313050b15b | ||
|
|
1833547936 | ||
|
|
8de2c035e2 | ||
|
|
a2d07bfa93 | ||
|
|
586fdbcbbd | ||
|
|
04079f9ae5 | ||
|
|
af2d391903 | ||
|
|
8abf85e96c | ||
|
|
9013cb1b4c | ||
|
|
2eb345f9b7 | ||
|
|
1376d75fbe | ||
|
|
4fe7cca44e | ||
|
|
a83d5c0f08 | ||
|
|
f3f61fb5d1 | ||
|
|
8e54d07c40 | ||
|
|
bd36e00174 | ||
|
|
da6b1557f0 | ||
|
|
f174026f53 | ||
|
|
390e1c93ed | ||
|
|
8f31a1a64b | ||
|
|
9a04879129 | ||
|
|
7469b5591e | ||
|
|
75932a92d0 | ||
|
|
cf56f59f58 | ||
|
|
4fba20e544 | ||
|
|
2956627b8b | ||
|
|
3404d2a99b | ||
|
|
311b0a9683 | ||
|
|
3b11995dbe | ||
|
|
a3bda59a30 | ||
|
|
0c21533cc5 | ||
|
|
dadf03baea | ||
|
|
1525cc78f6 | ||
|
|
db077555f6 | ||
|
|
37e96c9335 | ||
|
|
b052becba9 | ||
|
|
88c8dd96f9 | ||
|
|
cee828130c | ||
|
|
67236d6de3 | ||
|
|
470e4f9e91 | ||
|
|
0e55a8793f | ||
|
|
49d46a0059 | ||
|
|
616d8251f3 | ||
|
|
707226298a | ||
|
|
1f6988bdec | ||
|
|
ec74a8deb8 | ||
|
|
b5b53b6aec | ||
|
|
7c529e0afe | ||
|
|
b955473533 | ||
|
|
2026ce0ed2 | ||
|
|
42937d4bb6 | ||
|
|
cfd259ae6f | ||
|
|
493530ec6d | ||
|
|
4dea0892cb | ||
|
|
a14c77aeba | ||
|
|
a24126effb | ||
|
|
8984177448 | ||
|
|
750442909c | ||
|
|
df874db817 | ||
|
|
00d0c74657 | ||
|
|
122980ecad | ||
|
|
fc0bd9412c | ||
|
|
5ff9a0ff54 | ||
|
|
25d74a5919 | ||
|
|
213dbe7a5f | ||
|
|
9e57954b03 | ||
|
|
1b5aa2868d | ||
|
|
eee24138b0 | ||
|
|
04545f8a54 | ||
|
|
d1628944a6 | ||
|
|
abc27f56fc | ||
|
|
771aef9ddb | ||
|
|
dc7153e33c | ||
|
|
5ec08d3081 | ||
|
|
61b8443723 | ||
|
|
ad0b8e31b8 | ||
|
|
f144666f8b | ||
|
|
4e94135d36 | ||
|
|
b71add27da | ||
|
|
9deab6c0fd | ||
|
|
f9e9413f52 | ||
|
|
5171d167ce | ||
|
|
2981bece55 | ||
|
|
fd211bf490 | ||
|
|
9263b3a051 | ||
|
|
88209c6ac6 | ||
|
|
688f955f5e | ||
|
|
f3cee8f0b5 | ||
|
|
b7fd3f0031 | ||
|
|
d1e00f6e19 | ||
|
|
c9480c5b8b | ||
|
|
4829eac1e1 | ||
|
|
a10da0e540 | ||
|
|
7ac8a88a05 | ||
|
|
639ae0cfe0 | ||
|
|
c8f5a40fd9 | ||
|
|
f4f23e8e09 | ||
|
|
dc9ad12182 | ||
|
|
a808e09204 | ||
|
|
6ae16eba36 | ||
|
|
11f100813e | ||
|
|
b64470443f | ||
|
|
8633c4cefd | ||
|
|
e9240f7eee | ||
|
|
04deea3c6d | ||
|
|
6ca8923ec7 | ||
|
|
e3562be530 | ||
|
|
cb58eaa611 | ||
|
|
c5fd42b699 | ||
|
|
b184f391c6 | ||
|
|
5f860181b6 | ||
|
|
6caa7083db | ||
|
|
0c05ca1fd5 | ||
|
|
b09d3724a0 | ||
|
|
4867554eec | ||
|
|
36924b59bd | ||
|
|
7c088d1104 | ||
|
|
48df2f6842 | ||
|
|
79587d4b70 | ||
|
|
8a4517fd17 | ||
|
|
97f7815feb | ||
|
|
d8fbb0b8e3 | ||
|
|
9f77a8507e | ||
|
|
beaa8e55bd |
8
.github/ISSUE_TEMPLATE.md
vendored
Normal file
8
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
Thanks for submitting an issue!
|
||||
|
||||
Here's a quick checklist in what to include:
|
||||
|
||||
- [ ] Include a detailed description of the bug or suggestion
|
||||
- [ ] `pip list` of the virtual environment you are using
|
||||
- [ ] py.test and operating system versions
|
||||
- [ ] Minimal example if possible
|
||||
8
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
8
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
Thanks for submitting a PR, your contribution is really appreciated!
|
||||
|
||||
Here's a quick checklist that should be present in PRs:
|
||||
|
||||
- [ ] Target: for bug or doc fixes, target `master`; for new features, target `features`
|
||||
- [ ] Make sure to include one or more tests for your change
|
||||
- [ ] Add yourself to `AUTHORS`
|
||||
- [ ] Add a new entry to the `CHANGELOG` (choose any open position to avoid merge conflicts with other PRs)
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -16,6 +16,7 @@ include/
|
||||
*.class
|
||||
*.orig
|
||||
*~
|
||||
.hypothesis/
|
||||
|
||||
.eggs/
|
||||
|
||||
|
||||
24
.travis.yml
24
.travis.yml
@@ -7,25 +7,25 @@ install: "pip install -U tox"
|
||||
# # command to run tests
|
||||
env:
|
||||
matrix:
|
||||
# coveralls is not listed in tox's envlist, but should run in travis
|
||||
- TESTENV=coveralls
|
||||
- TESTENV=doctesting
|
||||
- TESTENV=flakes
|
||||
# note: please use "tox --listenvs" to populate the build matrix below
|
||||
- TESTENV=linting
|
||||
- TESTENV=py26
|
||||
- TESTENV=py27
|
||||
- TESTENV=py27-cxfreeze
|
||||
- TESTENV=py27-nobyte
|
||||
- TESTENV=py27-pexpect
|
||||
- TESTENV=py27-subprocess
|
||||
- TESTENV=py27-trial
|
||||
- TESTENV=py27-xdist
|
||||
- TESTENV=py33
|
||||
- TESTENV=py33
|
||||
- TESTENV=py34
|
||||
- TESTENV=py35-pexpect
|
||||
- TESTENV=py35-trial
|
||||
- TESTENV=py35-xdist
|
||||
- TESTENV=py35
|
||||
- TESTENV=pypy
|
||||
- TESTENV=py27-pexpect
|
||||
- TESTENV=py27-xdist
|
||||
- TESTENV=py27-trial
|
||||
- TESTENV=py35-pexpect
|
||||
- TESTENV=py35-xdist
|
||||
- TESTENV=py35-trial
|
||||
- TESTENV=py27-nobyte
|
||||
- TESTENV=doctesting
|
||||
- TESTENV=py27-cxfreeze
|
||||
|
||||
script: tox --recreate -e $TESTENV
|
||||
|
||||
|
||||
21
AUTHORS
21
AUTHORS
@@ -10,6 +10,7 @@ Andy Freeland
|
||||
Anthon van der Neut
|
||||
Armin Rigo
|
||||
Aron Curzon
|
||||
Aviv Palivoda
|
||||
Benjamin Peterson
|
||||
Bob Ippolito
|
||||
Brian Dorsey
|
||||
@@ -23,16 +24,22 @@ Christian Theunert
|
||||
Christian Tismer
|
||||
Christopher Gilling
|
||||
Daniel Grana
|
||||
Daniel Hahler
|
||||
Daniel Nuri
|
||||
Dave Hunt
|
||||
David Mohr
|
||||
David Vierra
|
||||
Edison Gustavo Muenz
|
||||
Eduardo Schettino
|
||||
Endre Galaczi
|
||||
Elizaveta Shashkova
|
||||
Eric Hunsberger
|
||||
Eric Siegerman
|
||||
Erik M. Bray
|
||||
Florian Bruhin
|
||||
Floris Bruynooghe
|
||||
Gabriel Reis
|
||||
Georgy Dyuldin
|
||||
Graham Horler
|
||||
Grig Gheorghiu
|
||||
Guido Wesdorp
|
||||
@@ -42,23 +49,34 @@ Jaap Broekhuizen
|
||||
Jan Balster
|
||||
Janne Vanhala
|
||||
Jason R. Coombs
|
||||
John Towler
|
||||
Joshua Bronson
|
||||
Jurko Gospodnetić
|
||||
Katarzyna Jachim
|
||||
Kevin Cox
|
||||
Lee Kamentsky
|
||||
Lukas Bednar
|
||||
Maciek Fijalkowski
|
||||
Maho
|
||||
Marc Schlaich
|
||||
Mark Abramowitz
|
||||
Markus Unterwaditzer
|
||||
Martijn Faassen
|
||||
Martin Prusse
|
||||
Matt Bachmann
|
||||
Michael Aquilina
|
||||
Michael Birtwell
|
||||
Michael Droettboom
|
||||
Nicolas Delaby
|
||||
Pieter Mulder
|
||||
Piotr Banaszkiewicz
|
||||
Punyashloka Biswal
|
||||
Quentin Pradet
|
||||
Ralf Schmitt
|
||||
Raphael Pierzina
|
||||
Ronny Pfannschmidt
|
||||
Ross Lawley
|
||||
Ryan Wooden
|
||||
Samuele Pedroni
|
||||
Tom Viner
|
||||
Trevor Bekolay
|
||||
@@ -68,3 +86,6 @@ Eric Hunsberger
|
||||
Simon Gomizelj
|
||||
Russel Winder
|
||||
Ben Webb
|
||||
Alexei Kozlenok
|
||||
Cal Leeming
|
||||
Feng Ma
|
||||
|
||||
@@ -1,5 +1,321 @@
|
||||
2.9.2
|
||||
=====
|
||||
|
||||
**Bug Fixes**
|
||||
|
||||
* fix `#510`_: skip tests where one parameterize dimension was empty
|
||||
thanks Alex Stapleton for the Report and `@RonnyPfannschmidt`_ for the PR
|
||||
|
||||
* Fix Xfail does not work with condition keyword argument.
|
||||
Thanks `@astraw38`_ for reporting the issue (`#1496`_) and `@tomviner`_
|
||||
for PR the (`#1524`_).
|
||||
|
||||
* Fix win32 path issue when puttinging custom config file with absolute path
|
||||
in ``pytest.main("-c your_absolute_path")``.
|
||||
|
||||
* Fix maximum recursion depth detection when raised error class is not aware
|
||||
of unicode/encoded bytes.
|
||||
Thanks `@prusse-martin`_ for the PR (`#1506`_).
|
||||
|
||||
* Fix ``pytest.mark.skip`` mark when used in strict mode.
|
||||
Thanks `@pquentin`_ for the PR and `@RonnyPfannschmidt`_ for
|
||||
showing how to fix the bug.
|
||||
|
||||
* Minor improvements and fixes to the documentation.
|
||||
Thanks `@omarkohl`_ for the PR.
|
||||
|
||||
* Fix ``--fixtures`` to show all fixture definitions as opposed to just
|
||||
one per fixture name.
|
||||
Thanks to `@hackebrot`_ for the PR.
|
||||
|
||||
.. _#510: https://github.com/pytest-dev/pytest/issues/510
|
||||
.. _#1506: https://github.com/pytest-dev/pytest/pull/1506
|
||||
.. _#1496: https://github.com/pytest-dev/pytest/issue/1496
|
||||
.. _#1524: https://github.com/pytest-dev/pytest/issue/1524
|
||||
|
||||
.. _@prusse-martin: https://github.com/prusse-martin
|
||||
.. _@astraw38: https://github.com/astraw38
|
||||
|
||||
|
||||
2.9.1
|
||||
=====
|
||||
|
||||
**Bug Fixes**
|
||||
|
||||
* Improve error message when a plugin fails to load.
|
||||
Thanks `@nicoddemus`_ for the PR.
|
||||
|
||||
* Fix (`#1178 <https://github.com/pytest-dev/pytest/issues/1178>`_):
|
||||
``pytest.fail`` with non-ascii characters raises an internal pytest error.
|
||||
Thanks `@nicoddemus`_ for the PR.
|
||||
|
||||
* Fix (`#469`_): junit parses report.nodeid incorrectly, when params IDs
|
||||
contain ``::``. Thanks `@tomviner`_ for the PR (`#1431`_).
|
||||
|
||||
* Fix (`#578 <https://github.com/pytest-dev/pytest/issues/578>`_): SyntaxErrors
|
||||
containing non-ascii lines at the point of failure generated an internal
|
||||
py.test error.
|
||||
Thanks `@asottile`_ for the report and `@nicoddemus`_ for the PR.
|
||||
|
||||
* Fix (`#1437`_): When passing in a bytestring regex pattern to parameterize
|
||||
attempt to decode it as utf-8 ignoring errors.
|
||||
|
||||
* Fix (`#649`_): parametrized test nodes cannot be specified to run on the command line.
|
||||
|
||||
|
||||
.. _#1437: https://github.com/pytest-dev/pytest/issues/1437
|
||||
.. _#469: https://github.com/pytest-dev/pytest/issues/469
|
||||
.. _#1431: https://github.com/pytest-dev/pytest/pull/1431
|
||||
.. _#649: https://github.com/pytest-dev/pytest/issues/649
|
||||
|
||||
.. _@asottile: https://github.com/asottile
|
||||
|
||||
|
||||
2.9.0
|
||||
=====
|
||||
|
||||
**New Features**
|
||||
|
||||
* New ``pytest.mark.skip`` mark, which unconditionally skips marked tests.
|
||||
Thanks `@MichaelAquilina`_ for the complete PR (`#1040`_).
|
||||
|
||||
* ``--doctest-glob`` may now be passed multiple times in the command-line.
|
||||
Thanks `@jab`_ and `@nicoddemus`_ for the PR.
|
||||
|
||||
* New ``-rp`` and ``-rP`` reporting options give the summary and full output
|
||||
of passing tests, respectively. Thanks to `@codewarrior0`_ for the PR.
|
||||
|
||||
* ``pytest.mark.xfail`` now has a ``strict`` option, which makes ``XPASS``
|
||||
tests to fail the test suite (defaulting to ``False``). There's also a
|
||||
``xfail_strict`` ini option that can be used to configure it project-wise.
|
||||
Thanks `@rabbbit`_ for the request and `@nicoddemus`_ for the PR (`#1355`_).
|
||||
|
||||
* ``Parser.addini`` now supports options of type ``bool``.
|
||||
Thanks `@nicoddemus`_ for the PR.
|
||||
|
||||
* New ``ALLOW_BYTES`` doctest option. This strips ``b`` prefixes from byte strings
|
||||
in doctest output (similar to ``ALLOW_UNICODE``).
|
||||
Thanks `@jaraco`_ for the request and `@nicoddemus`_ for the PR (`#1287`_).
|
||||
|
||||
* Give a hint on ``KeyboardInterrupt`` to use the ``--fulltrace`` option to show the errors.
|
||||
Fixes `#1366`_.
|
||||
Thanks to `@hpk42`_ for the report and `@RonnyPfannschmidt`_ for the PR.
|
||||
|
||||
* Catch ``IndexError`` exceptions when getting exception source location.
|
||||
Fixes a pytest internal error for dynamically generated code (fixtures and tests)
|
||||
where source lines are fake by intention.
|
||||
|
||||
**Changes**
|
||||
|
||||
* **Important**: `py.code <https://pylib.readthedocs.io/en/latest/code.html>`_ has been
|
||||
merged into the ``pytest`` repository as ``pytest._code``. This decision
|
||||
was made because ``py.code`` had very few uses outside ``pytest`` and the
|
||||
fact that it was in a different repository made it difficult to fix bugs on
|
||||
its code in a timely manner. The team hopes with this to be able to better
|
||||
refactor out and improve that code.
|
||||
This change shouldn't affect users, but it is useful to let users aware
|
||||
if they encounter any strange behavior.
|
||||
|
||||
Keep in mind that the code for ``pytest._code`` is **private** and
|
||||
**experimental**, so you definitely should not import it explicitly!
|
||||
|
||||
Please note that the original ``py.code`` is still available in
|
||||
`pylib <https://pylib.readthedocs.io>`_.
|
||||
|
||||
* ``pytest_enter_pdb`` now optionally receives the pytest config object.
|
||||
Thanks `@nicoddemus`_ for the PR.
|
||||
|
||||
* Removed code and documentation for Python 2.5 or lower versions,
|
||||
including removal of the obsolete ``_pytest.assertion.oldinterpret`` module.
|
||||
Thanks `@nicoddemus`_ for the PR (`#1226`_).
|
||||
|
||||
* Comparisons now always show up in full when ``CI`` or ``BUILD_NUMBER`` is
|
||||
found in the environment, even when ``-vv`` isn't used.
|
||||
Thanks `@The-Compiler`_ for the PR.
|
||||
|
||||
* ``--lf`` and ``--ff`` now support long names: ``--last-failed`` and
|
||||
``--failed-first`` respectively.
|
||||
Thanks `@MichaelAquilina`_ for the PR.
|
||||
|
||||
* Added expected exceptions to ``pytest.raises`` fail message.
|
||||
|
||||
* Collection only displays progress ("collecting X items") when in a terminal.
|
||||
This avoids cluttering the output when using ``--color=yes`` to obtain
|
||||
colors in CI integrations systems (`#1397`_).
|
||||
|
||||
**Bug Fixes**
|
||||
|
||||
* The ``-s`` and ``-c`` options should now work under ``xdist``;
|
||||
``Config.fromdictargs`` now represents its input much more faithfully.
|
||||
Thanks to `@bukzor`_ for the complete PR (`#680`_).
|
||||
|
||||
* Fix (`#1290`_): support Python 3.5's ``@`` operator in assertion rewriting.
|
||||
Thanks `@Shinkenjoe`_ for report with test case and `@tomviner`_ for the PR.
|
||||
|
||||
* Fix formatting utf-8 explanation messages (`#1379`_).
|
||||
Thanks `@biern`_ for the PR.
|
||||
|
||||
* Fix `traceback style docs`_ to describe all of the available options
|
||||
(auto/long/short/line/native/no), with `auto` being the default since v2.6.
|
||||
Thanks `@hackebrot`_ for the PR.
|
||||
|
||||
* Fix (`#1422`_): junit record_xml_property doesn't allow multiple records
|
||||
with same name.
|
||||
|
||||
.. _`traceback style docs`: https://pytest.org/latest/usage.html#modifying-python-traceback-printing
|
||||
|
||||
.. _#1422: https://github.com/pytest-dev/pytest/issues/1422
|
||||
.. _#1379: https://github.com/pytest-dev/pytest/issues/1379
|
||||
.. _#1366: https://github.com/pytest-dev/pytest/issues/1366
|
||||
.. _#1040: https://github.com/pytest-dev/pytest/pull/1040
|
||||
.. _#680: https://github.com/pytest-dev/pytest/issues/680
|
||||
.. _#1287: https://github.com/pytest-dev/pytest/pull/1287
|
||||
.. _#1226: https://github.com/pytest-dev/pytest/pull/1226
|
||||
.. _#1290: https://github.com/pytest-dev/pytest/pull/1290
|
||||
.. _#1355: https://github.com/pytest-dev/pytest/pull/1355
|
||||
.. _#1397: https://github.com/pytest-dev/pytest/issues/1397
|
||||
.. _@biern: https://github.com/biern
|
||||
.. _@MichaelAquilina: https://github.com/MichaelAquilina
|
||||
.. _@bukzor: https://github.com/bukzor
|
||||
.. _@hpk42: https://github.com/hpk42
|
||||
.. _@nicoddemus: https://github.com/nicoddemus
|
||||
.. _@jab: https://github.com/jab
|
||||
.. _@codewarrior0: https://github.com/codewarrior0
|
||||
.. _@jaraco: https://github.com/jaraco
|
||||
.. _@The-Compiler: https://github.com/The-Compiler
|
||||
.. _@Shinkenjoe: https://github.com/Shinkenjoe
|
||||
.. _@tomviner: https://github.com/tomviner
|
||||
.. _@RonnyPfannschmidt: https://github.com/RonnyPfannschmidt
|
||||
.. _@rabbbit: https://github.com/rabbbit
|
||||
.. _@hackebrot: https://github.com/hackebrot
|
||||
.. _@omarkohl: https://github.com/omarkohl
|
||||
.. _@pquentin: https://github.com/pquentin
|
||||
|
||||
2.8.7
|
||||
=====
|
||||
|
||||
- fix #1338: use predictable object resolution for monkeypatch
|
||||
|
||||
2.8.6
|
||||
=====
|
||||
|
||||
- fix #1259: allow for double nodeids in junitxml,
|
||||
this was a regression failing plugins combinations
|
||||
like pytest-pep8 + pytest-flakes
|
||||
|
||||
- Workaround for exception that occurs in pyreadline when using
|
||||
``--pdb`` with standard I/O capture enabled.
|
||||
Thanks Erik M. Bray for the PR.
|
||||
|
||||
- fix #900: Better error message in case the target of a ``monkeypatch`` call
|
||||
raises an ``ImportError``.
|
||||
|
||||
- fix #1292: monkeypatch calls (setattr, setenv, etc.) are now O(1).
|
||||
Thanks David R. MacIver for the report and Bruno Oliveira for the PR.
|
||||
|
||||
- fix #1223: captured stdout and stderr are now properly displayed before
|
||||
entering pdb when ``--pdb`` is used instead of being thrown away.
|
||||
Thanks Cal Leeming for the PR.
|
||||
|
||||
- fix #1305: pytest warnings emitted during ``pytest_terminal_summary`` are now
|
||||
properly displayed.
|
||||
Thanks Ionel Maries Cristian for the report and Bruno Oliveira for the PR.
|
||||
|
||||
- fix #628: fixed internal UnicodeDecodeError when doctests contain unicode.
|
||||
Thanks Jason R. Coombs for the report and Bruno Oliveira for the PR.
|
||||
|
||||
- fix #1334: Add captured stdout to jUnit XML report on setup error.
|
||||
Thanks Georgy Dyuldin for the PR.
|
||||
|
||||
|
||||
2.8.5
|
||||
=====
|
||||
|
||||
- fix #1243: fixed issue where class attributes injected during collection could break pytest.
|
||||
PR by Alexei Kozlenok, thanks Ronny Pfannschmidt and Bruno Oliveira for the review and help.
|
||||
|
||||
- fix #1074: precompute junitxml chunks instead of storing the whole tree in objects
|
||||
Thanks Bruno Oliveira for the report and Ronny Pfannschmidt for the PR
|
||||
|
||||
- fix #1238: fix ``pytest.deprecated_call()`` receiving multiple arguments
|
||||
(Regression introduced in 2.8.4). Thanks Alex Gaynor for the report and
|
||||
Bruno Oliveira for the PR.
|
||||
|
||||
|
||||
2.8.4
|
||||
=====
|
||||
|
||||
- fix #1190: ``deprecated_call()`` now works when the deprecated
|
||||
function has been already called by another test in the same
|
||||
module. Thanks Mikhail Chernykh for the report and Bruno Oliveira for the
|
||||
PR.
|
||||
|
||||
- fix #1198: ``--pastebin`` option now works on Python 3. Thanks
|
||||
Mehdy Khoshnoody for the PR.
|
||||
|
||||
- fix #1219: ``--pastebin`` now works correctly when captured output contains
|
||||
non-ascii characters. Thanks Bruno Oliveira for the PR.
|
||||
|
||||
- fix #1204: another error when collecting with a nasty __getattr__().
|
||||
Thanks Florian Bruhin for the PR.
|
||||
|
||||
- fix the summary printed when no tests did run.
|
||||
Thanks Florian Bruhin for the PR.
|
||||
- fix #1185 - ensure MANIFEST.in exactly matches what should go to a sdist
|
||||
|
||||
- a number of documentation modernizations wrt good practices.
|
||||
Thanks Bruno Oliveira for the PR.
|
||||
|
||||
2.8.3
|
||||
=====
|
||||
|
||||
- fix #1169: add __name__ attribute to testcases in TestCaseFunction to
|
||||
support the @unittest.skip decorator on functions and methods.
|
||||
Thanks Lee Kamentsky for the PR.
|
||||
|
||||
- fix #1035: collecting tests if test module level obj has __getattr__().
|
||||
Thanks Suor for the report and Bruno Oliveira / Tom Viner for the PR.
|
||||
|
||||
- fix #331: don't collect tests if their failure cannot be reported correctly
|
||||
e.g. they are a callable instance of a class.
|
||||
|
||||
- fix #1133: fixed internal error when filtering tracebacks where one entry
|
||||
belongs to a file which is no longer available.
|
||||
Thanks Bruno Oliveira for the PR.
|
||||
|
||||
- enhancement made to highlight in red the name of the failing tests so
|
||||
they stand out in the output.
|
||||
Thanks Gabriel Reis for the PR.
|
||||
|
||||
- add more talks to the documentation
|
||||
- extend documentation on the --ignore cli option
|
||||
- use pytest-runner for setuptools integration
|
||||
- minor fixes for interaction with OS X El Capitan
|
||||
system integrity protection (thanks Florian)
|
||||
|
||||
|
||||
2.8.2
|
||||
=====
|
||||
|
||||
- fix #1085: proper handling of encoding errors when passing encoded byte
|
||||
strings to pytest.parametrize in Python 2.
|
||||
Thanks Themanwithoutaplan for the report and Bruno Oliveira for the PR.
|
||||
|
||||
- fix #1087: handling SystemError when passing empty byte strings to
|
||||
pytest.parametrize in Python 3.
|
||||
Thanks Paul Kehrer for the report and Bruno Oliveira for the PR.
|
||||
|
||||
- fix #995: fixed internal error when filtering tracebacks where one entry
|
||||
was generated by an exec() statement.
|
||||
Thanks Daniel Hahler, Ashley C Straw, Philippe Gauthier and Pavel Savchenko
|
||||
for contributing and Bruno Oliveira for the PR.
|
||||
|
||||
- fix #1100 and #1057: errors when using autouse fixtures and doctest modules.
|
||||
Thanks Sergey B Kirpichev and Vital Kudzelka for contributing and Bruno
|
||||
Oliveira for the PR.
|
||||
|
||||
2.8.1
|
||||
-----
|
||||
=====
|
||||
|
||||
- fix #1034: Add missing nodeid on pytest_logwarning call in
|
||||
addhook. Thanks Simon Gomizelj for the PR.
|
||||
@@ -21,9 +337,9 @@
|
||||
"pytest-xdist" plugin, with test reports being assigned to the wrong tests.
|
||||
Thanks Daniel Grunwald for the report and Bruno Oliveira for the PR.
|
||||
|
||||
- (experimental) adapt more SEMVER style versioning and change meaning of
|
||||
master branch in git repo: "master" branch now keeps the bugfixes, changes
|
||||
aimed for micro releases. "features" branch will only be be released
|
||||
- (experimental) adapt more SEMVER style versioning and change meaning of
|
||||
master branch in git repo: "master" branch now keeps the bugfixes, changes
|
||||
aimed for micro releases. "features" branch will only be be released
|
||||
with minor or major pytest releases.
|
||||
|
||||
- Fix issue #766 by removing documentation references to distutils.
|
||||
@@ -38,6 +354,7 @@
|
||||
|
||||
- Fix issue #411: Add __eq__ method to assertion comparison example.
|
||||
Thanks Ben Webb.
|
||||
- Fix issue #653: deprecated_call can be used as context manager.
|
||||
|
||||
- fix issue 877: properly handle assertion explanations with non-ascii repr
|
||||
Thanks Mathieu Agopian for the report and Ronny Pfannschmidt for the PR.
|
||||
@@ -45,10 +362,10 @@
|
||||
- fix issue 1029: transform errors when writing cache values into pytest-warnings
|
||||
|
||||
2.8.0
|
||||
-----------------------------
|
||||
=====
|
||||
|
||||
- new ``--lf`` and ``-ff`` options to run only the last failing tests or
|
||||
"failing tests first" from the last run. This functionality is provided
|
||||
"failing tests first" from the last run. This functionality is provided
|
||||
through porting the formerly external pytest-cache plugin into pytest core.
|
||||
BACKWARD INCOMPAT: if you used pytest-cache's functionality to persist
|
||||
data between test runs be aware that we don't serialize sets anymore.
|
||||
@@ -102,10 +419,10 @@
|
||||
- fix issue768: docstrings found in python modules were not setting up session
|
||||
fixtures. Thanks Jason R. Coombs for reporting and Bruno Oliveira for the PR.
|
||||
|
||||
- added `tmpdir_factory`, a session-scoped fixture that can be used to create
|
||||
- added ``tmpdir_factory``, a session-scoped fixture that can be used to create
|
||||
directories under the base temporary directory. Previously this object was
|
||||
installed as a `_tmpdirhandler` attribute of the `config` object, but now it
|
||||
is part of the official API and using `config._tmpdirhandler` is
|
||||
installed as a ``_tmpdirhandler`` attribute of the ``config`` object, but now it
|
||||
is part of the official API and using ``config._tmpdirhandler`` is
|
||||
deprecated.
|
||||
Thanks Bruno Oliveira for the PR.
|
||||
|
||||
@@ -134,7 +451,7 @@
|
||||
or no tests were run at all (related to issue500).
|
||||
Thanks Eric Siegerman.
|
||||
|
||||
- New `testpaths` ini option: list of directories to search for tests
|
||||
- New ``testpaths`` ini option: list of directories to search for tests
|
||||
when executing pytest from the root directory. This can be used
|
||||
to speed up test collection when a project has well specified directories
|
||||
for tests, being usually more practical than configuring norecursedirs for
|
||||
@@ -154,9 +471,9 @@
|
||||
|
||||
- fix issue735: assertion failures on debug versions of Python 3.4+
|
||||
|
||||
- new option ``--import-mode`` to allow to change test module importing
|
||||
behaviour to append to sys.path instead of prepending. This better allows
|
||||
to run test modules against installated versions of a package even if the
|
||||
- new option ``--import-mode`` to allow to change test module importing
|
||||
behaviour to append to sys.path instead of prepending. This better allows
|
||||
to run test modules against installated versions of a package even if the
|
||||
package under test has the same import root. In this example::
|
||||
|
||||
testing/__init__.py
|
||||
@@ -164,7 +481,7 @@
|
||||
pkg_under_test/
|
||||
|
||||
the tests will run against the installed version
|
||||
of pkg_under_test when ``--import-mode=append`` is used whereas
|
||||
of pkg_under_test when ``--import-mode=append`` is used whereas
|
||||
by default they would always pick up the local version. Thanks Holger Krekel.
|
||||
|
||||
- pytester: add method ``TmpTestdir.delete_loaded_modules()``, and call it
|
||||
@@ -230,20 +547,20 @@
|
||||
- issue951: add new record_xml_property fixture, that supports logging
|
||||
additional information on xml output. Thanks David Diaz for the PR.
|
||||
|
||||
- issue949: paths after normal options (for example `-s`, `-v`, etc) are now
|
||||
properly used to discover `rootdir` and `ini` files.
|
||||
- issue949: paths after normal options (for example ``-s``, ``-v``, etc) are now
|
||||
properly used to discover ``rootdir`` and ``ini`` files.
|
||||
Thanks Peter Lauri for the report and Bruno Oliveira for the PR.
|
||||
|
||||
2.7.3 (compared to 2.7.2)
|
||||
-----------------------------
|
||||
=============================
|
||||
|
||||
- Allow 'dev', 'rc', or other non-integer version strings in `importorskip`.
|
||||
- Allow 'dev', 'rc', or other non-integer version strings in ``importorskip``.
|
||||
Thanks to Eric Hunsberger for the PR.
|
||||
|
||||
- fix issue856: consider --color parameter in all outputs (for example
|
||||
--fixtures). Thanks Barney Gale for the report and Bruno Oliveira for the PR.
|
||||
|
||||
- fix issue855: passing str objects as `plugins` argument to pytest.main
|
||||
- fix issue855: passing str objects as ``plugins`` argument to pytest.main
|
||||
is now interpreted as a module name to be imported and registered as a
|
||||
plugin, instead of silently having no effect.
|
||||
Thanks xmo-odoo for the report and Bruno Oliveira for the PR.
|
||||
@@ -278,7 +595,7 @@
|
||||
Thanks Bruno Oliveira for the PR.
|
||||
|
||||
2.7.2 (compared to 2.7.1)
|
||||
-----------------------------
|
||||
=============================
|
||||
|
||||
- fix issue767: pytest.raises value attribute does not contain the exception
|
||||
instance on Python 2.6. Thanks Eric Siegerman for providing the test
|
||||
@@ -307,7 +624,7 @@
|
||||
|
||||
|
||||
2.7.1 (compared to 2.7.0)
|
||||
-----------------------------
|
||||
=============================
|
||||
|
||||
- fix issue731: do not get confused by the braces which may be present
|
||||
and unbalanced in an object's repr while collapsing False
|
||||
@@ -340,7 +657,7 @@
|
||||
at least by pytest-xdist.
|
||||
|
||||
2.7.0 (compared to 2.6.4)
|
||||
-----------------------------
|
||||
=============================
|
||||
|
||||
- fix issue435: make reload() work when assert rewriting is active.
|
||||
Thanks Daniel Hahler.
|
||||
@@ -399,7 +716,7 @@
|
||||
it from the "decorator" case. Thanks Tom Viner.
|
||||
|
||||
- "python_classes" and "python_functions" options now support glob-patterns
|
||||
for test discovery, as discussed in issue600. Thanks Ldiary Translations.
|
||||
for test discovery, as discussed in issue600. Thanks Ldiary Translations.
|
||||
|
||||
- allow to override parametrized fixtures with non-parametrized ones and vice versa (bubenkoff).
|
||||
|
||||
@@ -410,7 +727,7 @@
|
||||
via postmortem debugging (almarklein).
|
||||
|
||||
2.6.4
|
||||
----------
|
||||
=====
|
||||
|
||||
- Improve assertion failure reporting on iterables, by using ndiff and
|
||||
pprint.
|
||||
@@ -439,7 +756,7 @@
|
||||
- fix issue614: fixed pastebin support.
|
||||
|
||||
2.6.3
|
||||
-----------
|
||||
=====
|
||||
|
||||
- fix issue575: xunit-xml was reporting collection errors as failures
|
||||
instead of errors, thanks Oleg Sinyavskiy.
|
||||
@@ -466,7 +783,7 @@
|
||||
Floris Bruynooghe.
|
||||
|
||||
2.6.2
|
||||
-----------
|
||||
=====
|
||||
|
||||
- Added function pytest.freeze_includes(), which makes it easy to embed
|
||||
pytest into executables using tools like cx_freeze.
|
||||
@@ -495,7 +812,7 @@
|
||||
to them.
|
||||
|
||||
2.6.1
|
||||
-----------------------------------
|
||||
=====
|
||||
|
||||
- No longer show line numbers in the --verbose output, the output is now
|
||||
purely the nodeid. The line number is still shown in failure reports.
|
||||
@@ -527,7 +844,7 @@
|
||||
Thanks Bruno Oliveira.
|
||||
|
||||
2.6
|
||||
-----------------------------------
|
||||
===
|
||||
|
||||
- Cache exceptions from fixtures according to their scope (issue 467).
|
||||
|
||||
@@ -632,7 +949,7 @@
|
||||
|
||||
|
||||
2.5.2
|
||||
-----------------------------------
|
||||
=====
|
||||
|
||||
- fix issue409 -- better interoperate with cx_freeze by not
|
||||
trying to import from collections.abc which causes problems
|
||||
@@ -660,7 +977,7 @@
|
||||
|
||||
|
||||
2.5.1
|
||||
-----------------------------------
|
||||
=====
|
||||
|
||||
- merge new documentation styling PR from Tobias Bieniek.
|
||||
|
||||
@@ -681,7 +998,7 @@
|
||||
|
||||
|
||||
2.5.0
|
||||
-----------------------------------
|
||||
=====
|
||||
|
||||
- dropped python2.5 from automated release testing of pytest itself
|
||||
which means it's probably going to break soon (but still works
|
||||
@@ -816,8 +1133,8 @@
|
||||
|
||||
- fix verbose reporting for @mock'd test functions
|
||||
|
||||
v2.4.2
|
||||
-----------------------------------
|
||||
2.4.2
|
||||
=====
|
||||
|
||||
- on Windows require colorama and a newer py lib so that py.io.TerminalWriter()
|
||||
now uses colorama instead of its own ctypes hacks. (fixes issue365)
|
||||
@@ -847,8 +1164,8 @@ v2.4.2
|
||||
- add pluginmanager.do_configure(config) as a link to
|
||||
config.do_configure() for plugin-compatibility
|
||||
|
||||
v2.4.1
|
||||
-----------------------------------
|
||||
2.4.1
|
||||
=====
|
||||
|
||||
- When using parser.addoption() unicode arguments to the
|
||||
"type" keyword should also be converted to the respective types.
|
||||
@@ -863,8 +1180,8 @@ v2.4.1
|
||||
|
||||
- merge doc typo fixes, thanks Andy Dirnberger
|
||||
|
||||
v2.4
|
||||
-----------------------------------
|
||||
2.4
|
||||
===
|
||||
|
||||
known incompatibilities:
|
||||
|
||||
@@ -1032,8 +1349,8 @@ Bug fixes:
|
||||
".section(title)" and ".line(msg)" methods to print extra
|
||||
information at the end of a test run.
|
||||
|
||||
v2.3.5
|
||||
-----------------------------------
|
||||
2.3.5
|
||||
=====
|
||||
|
||||
- fix issue169: respect --tb=style with setup/teardown errors as well.
|
||||
|
||||
@@ -1097,8 +1414,8 @@ v2.3.5
|
||||
|
||||
- fix issue266 - accept unicode in MarkEvaluator expressions
|
||||
|
||||
v2.3.4
|
||||
-----------------------------------
|
||||
2.3.4
|
||||
=====
|
||||
|
||||
- yielded test functions will now have autouse-fixtures active but
|
||||
cannot accept fixtures as funcargs - it's anyway recommended to
|
||||
@@ -1117,8 +1434,8 @@ v2.3.4
|
||||
need to write as -k "TestClass and test_method" to match a certain
|
||||
method in a certain test class.
|
||||
|
||||
v2.3.3
|
||||
-----------------------------------
|
||||
2.3.3
|
||||
=====
|
||||
|
||||
- fix issue214 - parse modules that contain special objects like e. g.
|
||||
flask's request object which blows up on getattr access if no request
|
||||
@@ -1149,8 +1466,8 @@ v2.3.3
|
||||
- fix issue127 - improve documentation for pytest_addoption() and
|
||||
add a ``config.getoption(name)`` helper function for consistency.
|
||||
|
||||
v2.3.2
|
||||
-----------------------------------
|
||||
2.3.2
|
||||
=====
|
||||
|
||||
- fix issue208 and fix issue29 use new py version to avoid long pauses
|
||||
when printing tracebacks in long modules
|
||||
@@ -1182,8 +1499,8 @@ v2.3.2
|
||||
- add tox.ini to pytest distribution so that ignore-dirs and others config
|
||||
bits are properly distributed for maintainers who run pytest-own tests
|
||||
|
||||
v2.3.1
|
||||
-----------------------------------
|
||||
2.3.1
|
||||
=====
|
||||
|
||||
- fix issue202 - fix regression: using "self" from fixture functions now
|
||||
works as expected (it's the same "self" instance that a test method
|
||||
@@ -1195,8 +1512,8 @@ v2.3.1
|
||||
- link to web pages from --markers output which provides help for
|
||||
pytest.mark.* usage.
|
||||
|
||||
v2.3.0
|
||||
-----------------------------------
|
||||
2.3.0
|
||||
=====
|
||||
|
||||
- fix issue202 - better automatic names for parametrized test functions
|
||||
- fix issue139 - introduce @pytest.fixture which allows direct scoping
|
||||
@@ -1274,8 +1591,8 @@ v2.3.0
|
||||
|
||||
- py.test -vv will show all of assert comparisations instead of truncating
|
||||
|
||||
v2.2.4
|
||||
-----------------------------------
|
||||
2.2.4
|
||||
=====
|
||||
|
||||
- fix error message for rewritten assertions involving the % operator
|
||||
- fix issue 126: correctly match all invalid xml characters for junitxml
|
||||
@@ -1291,13 +1608,13 @@ v2.2.4
|
||||
- fix issue #144: better mangle test ids to junitxml classnames
|
||||
- upgrade distribute_setup.py to 0.6.27
|
||||
|
||||
v2.2.3
|
||||
----------------------------------------
|
||||
2.2.3
|
||||
=====
|
||||
|
||||
- fix uploaded package to only include neccesary files
|
||||
|
||||
v2.2.2
|
||||
----------------------------------------
|
||||
2.2.2
|
||||
=====
|
||||
|
||||
- fix issue101: wrong args to unittest.TestCase test function now
|
||||
produce better output
|
||||
@@ -1316,8 +1633,8 @@ v2.2.2
|
||||
- allow adding of attributes to test reports such that it also works
|
||||
with distributed testing (no upgrade of pytest-xdist needed)
|
||||
|
||||
v2.2.1
|
||||
----------------------------------------
|
||||
2.2.1
|
||||
=====
|
||||
|
||||
- fix issue99 (in pytest and py) internallerrors with resultlog now
|
||||
produce better output - fixed by normalizing pytest_internalerror
|
||||
@@ -1333,8 +1650,8 @@ v2.2.1
|
||||
- fix collection crash due to unknown-source collected items, thanks
|
||||
to Ralf Schmitt (fixed by depending on a more recent pylib)
|
||||
|
||||
v2.2.0
|
||||
----------------------------------------
|
||||
2.2.0
|
||||
=====
|
||||
|
||||
- fix issue90: introduce eager tearing down of test items so that
|
||||
teardown function are called earlier.
|
||||
@@ -1368,8 +1685,8 @@ v2.2.0
|
||||
- simplify junitxml output code by relying on py.xml
|
||||
- add support for skip properties on unittest classes and functions
|
||||
|
||||
v2.1.3
|
||||
----------------------------------------
|
||||
2.1.3
|
||||
=====
|
||||
|
||||
- fix issue79: assertion rewriting failed on some comparisons in boolops
|
||||
- correctly handle zero length arguments (a la pytest '')
|
||||
@@ -1377,8 +1694,8 @@ v2.1.3
|
||||
- fix issue75 / skipping test failure on jython
|
||||
- fix issue77 / Allow assertrepr_compare hook to apply to a subset of tests
|
||||
|
||||
v2.1.2
|
||||
----------------------------------------
|
||||
2.1.2
|
||||
=====
|
||||
|
||||
- fix assertion rewriting on files with windows newlines on some Python versions
|
||||
- refine test discovery by package/module name (--pyargs), thanks Florian Mayer
|
||||
@@ -1387,8 +1704,8 @@ v2.1.2
|
||||
- fix issue66: use different assertion rewriting caches when the -O option is passed
|
||||
- don't try assertion rewriting on Jython, use reinterp
|
||||
|
||||
v2.1.1
|
||||
----------------------------------------------
|
||||
2.1.1
|
||||
=====
|
||||
|
||||
- fix issue64 / pytest.set_trace now works within pytest_generate_tests hooks
|
||||
- fix issue60 / fix error conditions involving the creation of __pycache__
|
||||
@@ -1400,8 +1717,8 @@ v2.1.1
|
||||
- fix issue61: assertion rewriting on boolean operations with 3 or more operands
|
||||
- you can now build a man page with "cd doc ; make man"
|
||||
|
||||
v2.1.0
|
||||
----------------------------------------------
|
||||
2.1.0
|
||||
=====
|
||||
|
||||
- fix issue53 call nosestyle setup functions with correct ordering
|
||||
- fix issue58 and issue59: new assertion code fixes
|
||||
@@ -1420,8 +1737,8 @@ v2.1.0
|
||||
- report KeyboardInterrupt even if interrupted during session startup
|
||||
- fix issue 35 - provide PDF doc version and download link from index page
|
||||
|
||||
v2.0.3
|
||||
----------------------------------------------
|
||||
2.0.3
|
||||
=====
|
||||
|
||||
- fix issue38: nicer tracebacks on calls to hooks, particularly early
|
||||
configure/sessionstart ones
|
||||
@@ -1440,8 +1757,8 @@ v2.0.3
|
||||
|
||||
- fix issue37: avoid invalid characters in junitxml's output
|
||||
|
||||
v2.0.2
|
||||
----------------------------------------------
|
||||
2.0.2
|
||||
=====
|
||||
|
||||
- tackle issue32 - speed up test runs of very quick test functions
|
||||
by reducing the relative overhead
|
||||
@@ -1492,8 +1809,8 @@ v2.0.2
|
||||
|
||||
- avoid std unittest assertion helper code in tracebacks (thanks Ronny)
|
||||
|
||||
v2.0.1
|
||||
----------------------------------------------
|
||||
2.0.1
|
||||
=====
|
||||
|
||||
- refine and unify initial capturing so that it works nicely
|
||||
even if the logging module is used on an early-loaded conftest.py
|
||||
@@ -1541,8 +1858,8 @@ v2.0.1
|
||||
parametraization remains the "pytest_generate_tests"
|
||||
mechanism, see the docs.
|
||||
|
||||
v2.0.0
|
||||
----------------------------------------------
|
||||
2.0.0
|
||||
=====
|
||||
|
||||
- pytest-2.0 is now its own package and depends on pylib-2.0
|
||||
- new ability: python -m pytest / python -m pytest.main ability
|
||||
@@ -1586,8 +1903,8 @@ v2.0.0
|
||||
- add ability to use "class" level for cached_setup helper
|
||||
- fix strangeness: mark.* objects are now immutable, create new instances
|
||||
|
||||
v1.3.4
|
||||
----------------------------------------------
|
||||
1.3.4
|
||||
=====
|
||||
|
||||
- fix issue111: improve install documentation for windows
|
||||
- fix issue119: fix custom collectability of __init__.py as a module
|
||||
@@ -1595,8 +1912,8 @@ v1.3.4
|
||||
- fix issue115: unify internal exception passthrough/catching/GeneratorExit
|
||||
- fix issue118: new --tb=native for presenting cpython-standard exceptions
|
||||
|
||||
v1.3.3
|
||||
----------------------------------------------
|
||||
1.3.3
|
||||
=====
|
||||
|
||||
- fix issue113: assertion representation problem with triple-quoted strings
|
||||
(and possibly other cases)
|
||||
@@ -1610,11 +1927,10 @@ v1.3.3
|
||||
(thanks Armin Ronacher for reporting)
|
||||
- remove trailing whitespace in all py/text distribution files
|
||||
|
||||
v1.3.2
|
||||
----------------------------------------------
|
||||
1.3.2
|
||||
=====
|
||||
|
||||
New features
|
||||
++++++++++++++++++
|
||||
**New features**
|
||||
|
||||
- fix issue103: introduce py.test.raises as context manager, examples::
|
||||
|
||||
@@ -1649,8 +1965,7 @@ New features
|
||||
- introduce '--junitprefix=STR' option to prepend a prefix
|
||||
to all reports in the junitxml file.
|
||||
|
||||
Bug fixes / Maintenance
|
||||
++++++++++++++++++++++++++
|
||||
**Bug fixes**
|
||||
|
||||
- make tests and the ``pytest_recwarn`` plugin in particular fully compatible
|
||||
to Python2.7 (if you use the ``recwarn`` funcarg warnings will be enabled so that
|
||||
@@ -1685,11 +2000,10 @@ Bug fixes / Maintenance
|
||||
- fix homedir detection on Windows
|
||||
- ship distribute_setup.py version 0.6.13
|
||||
|
||||
v1.3.1
|
||||
---------------------------------------------
|
||||
1.3.1
|
||||
=====
|
||||
|
||||
New features
|
||||
++++++++++++++++++
|
||||
**New features**
|
||||
|
||||
- issue91: introduce new py.test.xfail(reason) helper
|
||||
to imperatively mark a test as expected to fail. Can
|
||||
@@ -1727,8 +2041,7 @@ New features
|
||||
course requires that your application and tests are properly teared
|
||||
down and don't have global state.
|
||||
|
||||
Fixes / Maintenance
|
||||
++++++++++++++++++++++
|
||||
**Bug Fixes**
|
||||
|
||||
- improved traceback presentation:
|
||||
- improved and unified reporting for "--tb=short" option
|
||||
@@ -1757,8 +2070,8 @@ Fixes / Maintenance
|
||||
(and internally be more careful when presenting unexpected byte sequences)
|
||||
|
||||
|
||||
v1.3.0
|
||||
---------------------------------------------
|
||||
1.3.0
|
||||
=====
|
||||
|
||||
- deprecate --report option in favour of a new shorter and easier to
|
||||
remember -r option: it takes a string argument consisting of any
|
||||
@@ -1822,8 +2135,8 @@ v1.3.0
|
||||
- added links to the new capturelog and coverage plugins
|
||||
|
||||
|
||||
v1.2.0
|
||||
---------------------------------------------
|
||||
1.2.0
|
||||
=====
|
||||
|
||||
- refined usage and options for "py.cleanup"::
|
||||
|
||||
@@ -1861,8 +2174,8 @@ v1.2.0
|
||||
|
||||
- fix plugin links
|
||||
|
||||
v1.1.1
|
||||
---------------------------------------------
|
||||
1.1.1
|
||||
=====
|
||||
|
||||
- moved dist/looponfailing from py.test core into a new
|
||||
separately released pytest-xdist plugin.
|
||||
@@ -1882,7 +2195,7 @@ v1.1.1
|
||||
- new funcarg: "pytestconfig" is the pytest config object for access
|
||||
to command line args and can now be easily used in a test.
|
||||
|
||||
- install 'py.test' and `py.which` with a ``-$VERSION`` suffix to
|
||||
- install ``py.test`` and ``py.which`` with a ``-$VERSION`` suffix to
|
||||
disambiguate between Python3, python2.X, Jython and PyPy installed versions.
|
||||
|
||||
- new "pytestconfig" funcarg allows access to test config object
|
||||
@@ -1945,8 +2258,8 @@ v1.1.1
|
||||
- fix docs, fix internal bin/ script generation
|
||||
|
||||
|
||||
v1.1.0
|
||||
---------------------------------------------
|
||||
1.1.0
|
||||
=====
|
||||
|
||||
- introduce automatic plugin registration via 'pytest11'
|
||||
entrypoints via setuptools' pkg_resources.iter_entry_points
|
||||
@@ -1964,8 +2277,8 @@ v1.1.0
|
||||
- try harder to have deprecation warnings for py.compat.* accesses
|
||||
report a correct location
|
||||
|
||||
v1.0.2
|
||||
---------------------------------------------
|
||||
1.0.3
|
||||
=====
|
||||
|
||||
* adjust and improve docs
|
||||
|
||||
@@ -2049,16 +2362,16 @@ v1.0.2
|
||||
|
||||
* simplified internal localpath implementation
|
||||
|
||||
v1.0.2
|
||||
-------------------------------------------
|
||||
1.0.2
|
||||
=====
|
||||
|
||||
* fixing packaging issues, triggered by fedora redhat packaging,
|
||||
also added doc, examples and contrib dirs to the tarball.
|
||||
|
||||
* added a documentation link to the new django plugin.
|
||||
|
||||
v1.0.1
|
||||
-------------------------------------------
|
||||
1.0.1
|
||||
=====
|
||||
|
||||
* added a 'pytest_nose' plugin which handles nose.SkipTest,
|
||||
nose-style function/method/generator setup/teardown and
|
||||
@@ -2091,14 +2404,14 @@ v1.0.1
|
||||
* simplified multicall mechanism and plugin architecture,
|
||||
renamed some internal methods and argnames
|
||||
|
||||
v1.0.0
|
||||
-------------------------------------------
|
||||
1.0.0
|
||||
=====
|
||||
|
||||
* more terse reporting try to show filesystem path relatively to current dir
|
||||
* improve xfail output a bit
|
||||
|
||||
v1.0.0b9
|
||||
-------------------------------------------
|
||||
1.0.0b9
|
||||
=======
|
||||
|
||||
* cleanly handle and report final teardown of test setup
|
||||
|
||||
@@ -2131,8 +2444,8 @@ v1.0.0b9
|
||||
* item.repr_failure(excinfo) instead of item.repr_failure(excinfo, outerr)
|
||||
|
||||
|
||||
v1.0.0b8
|
||||
-------------------------------------------
|
||||
1.0.0b8
|
||||
=======
|
||||
|
||||
* pytest_unittest-plugin is now enabled by default
|
||||
|
||||
@@ -2160,8 +2473,8 @@ v1.0.0b8
|
||||
* tweaked doctest output for docstrings in py modules,
|
||||
thanks Radomir.
|
||||
|
||||
v1.0.0b7
|
||||
-------------------------------------------
|
||||
1.0.0b7
|
||||
=======
|
||||
|
||||
* renamed py.test.xfail back to py.test.mark.xfail to avoid
|
||||
two ways to decorate for xfail
|
||||
@@ -2185,8 +2498,8 @@ v1.0.0b7
|
||||
|
||||
* make __name__ == "__channelexec__" for remote_exec code
|
||||
|
||||
v1.0.0b3
|
||||
-------------------------------------------
|
||||
1.0.0b3
|
||||
=======
|
||||
|
||||
* plugin classes are removed: one now defines
|
||||
hooks directly in conftest.py or global pytest_*.py
|
||||
@@ -2202,8 +2515,8 @@ v1.0.0b3
|
||||
well with function arguments.
|
||||
|
||||
|
||||
v1.0.0b1
|
||||
-------------------------------------------
|
||||
1.0.0b1
|
||||
=======
|
||||
|
||||
* introduced new "funcarg" setup method,
|
||||
see doc/test/funcarg.txt
|
||||
@@ -2226,8 +2539,8 @@ v1.0.0b1
|
||||
|
||||
XXX lots of things missing here XXX
|
||||
|
||||
v0.9.2
|
||||
-------------------------------------------
|
||||
0.9.2
|
||||
=====
|
||||
|
||||
* refined installation and metadata, created new setup.py,
|
||||
now based on setuptools/ez_setup (thanks to Ralf Schmitt
|
||||
@@ -2259,8 +2572,8 @@ v0.9.2
|
||||
|
||||
* there now is a py.__version__ attribute
|
||||
|
||||
v0.9.1
|
||||
-------------------------------------------
|
||||
0.9.1
|
||||
=====
|
||||
|
||||
This is a fairly complete list of v0.9.1, which can
|
||||
serve as a reference for developers.
|
||||
144
CONTRIBUTING.rst
144
CONTRIBUTING.rst
@@ -9,46 +9,18 @@ so do not hesitate!
|
||||
:depth: 2
|
||||
|
||||
|
||||
.. _submitplugin:
|
||||
.. _submitfeedback:
|
||||
|
||||
Submit a plugin, co-develop pytest
|
||||
----------------------------------
|
||||
Feature requests and feedback
|
||||
-----------------------------
|
||||
|
||||
Pytest development of the core, some plugins and support code happens
|
||||
in repositories living under:
|
||||
Do you like pytest? Share some love on Twitter or in your blog posts!
|
||||
|
||||
- `the pytest-dev github organisation <https://github.com/pytest-dev>`_
|
||||
We'd also like to hear about your propositions and suggestions. Feel free to
|
||||
`submit them as issues <https://github.com/pytest-dev/pytest/issues>`_ and:
|
||||
|
||||
- `the pytest-dev bitbucket team <https://bitbucket.org/pytest-dev>`_
|
||||
|
||||
All pytest-dev Contributors team members have write access to all contained
|
||||
repositories. pytest core and plugins are generally developed
|
||||
using `pull requests`_ to respective repositories.
|
||||
|
||||
You can submit your plugin by subscribing to the `pytest-dev mail list
|
||||
<https://mail.python.org/mailman/listinfo/pytest-dev>`_ and writing a
|
||||
mail pointing to your existing pytest plugin repository which must have
|
||||
the following:
|
||||
|
||||
- PyPI presence with a ``setup.py`` that contains a license, ``pytest-``
|
||||
prefixed, version number, authors, short and long description.
|
||||
|
||||
- a ``tox.ini`` for running tests using `tox <http://tox.testrun.org>`_.
|
||||
|
||||
- a ``README.txt`` describing how to use the plugin and on which
|
||||
platforms it runs.
|
||||
|
||||
- a ``LICENSE.txt`` file or equivalent containing the licensing
|
||||
information, with matching info in ``setup.py``.
|
||||
|
||||
- an issue tracker unless you rather want to use the core ``pytest``
|
||||
issue tracker.
|
||||
|
||||
If no contributor strongly objects and two agree, the repo will be
|
||||
transferred to the ``pytest-dev`` organisation and you'll become a
|
||||
member of the ``pytest-dev Contributors`` team, with commit rights
|
||||
to all projects. We recommend that each plugin has at least three
|
||||
people who have the right to release to pypi.
|
||||
* Explain in detail how they should work.
|
||||
* Keep the scope as narrow as possible. This will make it easier to implement.
|
||||
|
||||
|
||||
.. _reportbugs:
|
||||
@@ -56,7 +28,7 @@ people who have the right to release to pypi.
|
||||
Report bugs
|
||||
-----------
|
||||
|
||||
Report bugs for pytest at https://github.com/pytest-dev/pytest/issues
|
||||
Report bugs for pytest in the `issue tracker <https://github.com/pytest-dev/pytest/issues>`_.
|
||||
|
||||
If you are reporting a bug, please include:
|
||||
|
||||
@@ -70,22 +42,6 @@ If you can write a demonstration test that currently fails but should pass (xfai
|
||||
that is a very useful commit to make as well, even if you can't find how
|
||||
to fix the bug yet.
|
||||
|
||||
.. _submitfeedback:
|
||||
|
||||
Submit feedback for developers
|
||||
------------------------------
|
||||
|
||||
Do you like pytest? Share some love on Twitter or in your blog posts!
|
||||
|
||||
We'd also like to hear about your propositions and suggestions. Feel free to
|
||||
`submit them as issues <https://github.com/pytest-dev/pytest/issues>`__ and:
|
||||
|
||||
* Set the "kind" to "enhancement" or "proposal" so that we can quickly find
|
||||
about them.
|
||||
* Explain in detail how they should work.
|
||||
* Keep the scope as narrow as possible. This will make it easier to implement.
|
||||
* If you have required skills and/or knowledge, we are very happy for
|
||||
:ref:`pull requests <pull-requests>`.
|
||||
|
||||
.. _fixbugs:
|
||||
|
||||
@@ -118,19 +74,95 @@ pytest could always use more documentation. What exactly is needed?
|
||||
|
||||
* More complementary documentation. Have you perhaps found something unclear?
|
||||
* Documentation translations. We currently have only English.
|
||||
* Docstrings. There's never too much of them.
|
||||
* Docstrings. There can never be too many of them.
|
||||
* Blog posts, articles and such -- they're all very appreciated.
|
||||
|
||||
You can also edit documentation files directly in the Github web interface
|
||||
without needing to make a fork and local copy. This can be convenient for
|
||||
small fixes.
|
||||
|
||||
|
||||
.. _submitplugin:
|
||||
|
||||
Submitting Plugins to pytest-dev
|
||||
--------------------------------
|
||||
|
||||
Pytest development of the core, some plugins and support code happens
|
||||
in repositories living under the ``pytest-dev`` organisations:
|
||||
|
||||
- `pytest-dev on GitHub <https://github.com/pytest-dev>`_
|
||||
|
||||
- `pytest-dev on Bitbucket <https://bitbucket.org/pytest-dev>`_
|
||||
|
||||
All pytest-dev Contributors team members have write access to all contained
|
||||
repositories. pytest core and plugins are generally developed
|
||||
using `pull requests`_ to respective repositories.
|
||||
|
||||
The objectives of the ``pytest-dev`` organisation are:
|
||||
|
||||
* Having a central location for popular pytest plugins
|
||||
* Sharing some of the maintenance responsibility (in case a maintainer no longer whishes to maintain a plugin)
|
||||
|
||||
You can submit your plugin by subscribing to the `pytest-dev mail list
|
||||
<https://mail.python.org/mailman/listinfo/pytest-dev>`_ and writing a
|
||||
mail pointing to your existing pytest plugin repository which must have
|
||||
the following:
|
||||
|
||||
- PyPI presence with a ``setup.py`` that contains a license, ``pytest-``
|
||||
prefixed name, version number, authors, short and long description.
|
||||
|
||||
- a ``tox.ini`` for running tests using `tox <http://tox.testrun.org>`_.
|
||||
|
||||
- a ``README.txt`` describing how to use the plugin and on which
|
||||
platforms it runs.
|
||||
|
||||
- a ``LICENSE.txt`` file or equivalent containing the licensing
|
||||
information, with matching info in ``setup.py``.
|
||||
|
||||
- an issue tracker for bug reports and enhancement requests.
|
||||
|
||||
If no contributor strongly objects and two agree, the repository can then be
|
||||
transferred to the ``pytest-dev`` organisation.
|
||||
|
||||
Here's a rundown of how a repository transfer usually proceeds
|
||||
(using a repository named ``joedoe/pytest-xyz`` as example):
|
||||
|
||||
* One of the ``pytest-dev`` administrators creates:
|
||||
|
||||
- ``pytest-xyz-admin`` team, with full administration rights to
|
||||
``pytest-dev/pytest-xyz``.
|
||||
- ``pytest-xyz-developers`` team, with write access to
|
||||
``pytest-dev/pytest-xyz``.
|
||||
|
||||
* ``joedoe`` is invited to the ``pytest-xyz-admin`` team;
|
||||
|
||||
* After accepting the invitation, ``joedoe`` transfers the repository from its
|
||||
original location to ``pytest-dev/pytest-xyz`` (A nice feature is that GitHub handles URL redirection from
|
||||
the old to the new location automatically).
|
||||
|
||||
* ``joedoe`` is free to add any other collaborators to the
|
||||
``pytest-xyz-admin`` or ``pytest-xyz-developers`` team as desired.
|
||||
|
||||
The ``pytest-dev/Contributors`` team has write access to all projects, and
|
||||
every project administrator is in it. We recommend that each plugin has at least three
|
||||
people who have the right to release to PyPI.
|
||||
|
||||
Repository owners can be assured that no ``pytest-dev`` administrator will ever make
|
||||
releases of your repository or take ownership in any way, except in rare cases
|
||||
where someone becomes unresponsive after months of contact attempts.
|
||||
As stated, the objective is to share maintenance and avoid "plugin-abandon".
|
||||
|
||||
|
||||
.. _`pull requests`:
|
||||
.. _pull-requests:
|
||||
|
||||
Preparing Pull Requests on GitHub
|
||||
---------------------------------
|
||||
|
||||
There's an excellent tutorial on how Pull Requests work in the
|
||||
`GitHub Help Center <https://help.github.com/articles/using-pull-requests/>`_
|
||||
|
||||
|
||||
.. note::
|
||||
What is a "pull request"? It informs project's core developers about the
|
||||
changes you want to review and merge. Pull requests are stored on
|
||||
@@ -179,10 +211,10 @@ but here is a simple overview:
|
||||
You need to have Python 2.7 and 3.5 available in your system. Now
|
||||
running tests is as simple as issuing this command::
|
||||
|
||||
$ python runtox.py -e py27,py35,flakes
|
||||
$ python runtox.py -e linting,py27,py35
|
||||
|
||||
This command will run tests via the "tox" tool against Python 2.7 and 3.5
|
||||
and also perform "flakes" coding-style checks. ``runtox.py`` is
|
||||
and also perform "lint" coding-style checks. ``runtox.py`` is
|
||||
a thin wrapper around ``tox`` which installs from a development package
|
||||
index where newer (not yet released to pypi) versions of dependencies
|
||||
(especially ``py``) might be present.
|
||||
|
||||
@@ -3,12 +3,18 @@ How to release pytest
|
||||
|
||||
Note: this assumes you have already registered on pypi.
|
||||
|
||||
0. create the branch release-VERSION
|
||||
use features as base for minor/major releases
|
||||
and master as base for bugfix releases
|
||||
|
||||
1. Bump version numbers in _pytest/__init__.py (setup.py reads it)
|
||||
|
||||
2. Check and finalize CHANGELOG
|
||||
|
||||
3. Write doc/en/announce/release-VERSION.txt and include
|
||||
it in doc/en/announce/index.txt
|
||||
it in doc/en/announce/index.txt::
|
||||
|
||||
git log 2.8.2..HEAD --format='%aN' | sort -u # lists the names of authors involved
|
||||
|
||||
4. Use devpi for uploading a release tarball to a staging area::
|
||||
|
||||
@@ -25,10 +31,6 @@ Note: this assumes you have already registered on pypi.
|
||||
devpi list pytest
|
||||
|
||||
or look at failures with "devpi list -f pytest".
|
||||
There will be some failed environments like e.g. the py33-trial
|
||||
or py27-pexpect tox environments on Win32 platforms
|
||||
which is ok (tox does not support skipping on
|
||||
per-platform basis yet).
|
||||
|
||||
7. Regenerate the docs examples using tox, and check for regressions::
|
||||
|
||||
@@ -39,7 +41,7 @@ Note: this assumes you have already registered on pypi.
|
||||
8. Build the docs, you need a virtualenv with py and sphinx
|
||||
installed::
|
||||
|
||||
cd doc/en
|
||||
cd doc/en
|
||||
make html
|
||||
|
||||
Commit any changes before tagging the release.
|
||||
@@ -54,25 +56,25 @@ Note: this assumes you have already registered on pypi.
|
||||
cd doc/en
|
||||
make install # or "installall" if you have LaTeX installed for PDF
|
||||
|
||||
This requires ssh-login permission on pytest.org because it uses
|
||||
rsync.
|
||||
Note that the ``install`` target of ``doc/en/Makefile`` defines where the
|
||||
rsync goes to, typically to the "latest" section of pytest.org.
|
||||
This requires ssh-login permission on pytest.org because it uses
|
||||
rsync.
|
||||
Note that the ``install`` target of ``doc/en/Makefile`` defines where the
|
||||
rsync goes to, typically to the "latest" section of pytest.org.
|
||||
|
||||
If you are making a minor release (e.g. 5.4), you also need to manually
|
||||
create a symlink for "latest"::
|
||||
If you are making a minor release (e.g. 5.4), you also need to manually
|
||||
create a symlink for "latest"::
|
||||
|
||||
ssh pytest-dev@pytest.org
|
||||
ln -s 5.4 latest
|
||||
ssh pytest-dev@pytest.org
|
||||
ln -s 5.4 latest
|
||||
|
||||
Browse to pytest.org to verify.
|
||||
Browse to pytest.org to verify.
|
||||
|
||||
11. Publish to pypi::
|
||||
|
||||
devpi push pytest-VERSION pypi:NAME
|
||||
|
||||
where NAME is the name of pypi.python.org as configured in your ``~/.pypirc``
|
||||
file `for devpi <http://doc.devpi.net/latest/quickstart-releaseprocess.html?highlight=pypirc#devpi-push-releasing-to-an-external-index>`_.
|
||||
where NAME is the name of pypi.python.org as configured in your ``~/.pypirc``
|
||||
file `for devpi <http://doc.devpi.net/latest/quickstart-releaseprocess.html?highlight=pypirc#devpi-push-releasing-to-an-external-index>`_.
|
||||
|
||||
|
||||
12. Send release announcement to mailing lists:
|
||||
@@ -83,5 +85,8 @@ Note: this assumes you have already registered on pypi.
|
||||
|
||||
|
||||
13. **after the release** Bump the version number in ``_pytest/__init__.py``,
|
||||
to the next Minor release version (i.e. if you released ``pytest-2.8.0``,
|
||||
set it to ``pytest-2.9.0.dev1``).
|
||||
to the next Minor release version (i.e. if you released ``pytest-2.8.0``,
|
||||
set it to ``pytest-2.9.0.dev1``).
|
||||
|
||||
14. merge the actual release into the master branch and do a pull request against it
|
||||
15. merge from master to features
|
||||
|
||||
@@ -18,6 +18,7 @@ Unlike mock, "args.path" acts on the parsed auto-spec'ed ``os.path.abspath``
|
||||
so it's independent from if the client side called "os.path.abspath(path=...)"
|
||||
or "os.path.abspath('positional')".
|
||||
|
||||
|
||||
refine parametrize API
|
||||
-------------------------------------------------------------
|
||||
tags: critical feature
|
||||
@@ -115,7 +116,7 @@ tags: feature
|
||||
|
||||
- introduce pytest.mark.nocollect for not considering a function for
|
||||
test collection at all. maybe also introduce a pytest.mark.test to
|
||||
explicitely mark a function to become a tested one. Lookup JUnit ways
|
||||
explicitly mark a function to become a tested one. Lookup JUnit ways
|
||||
of tagging tests.
|
||||
|
||||
introduce pytest.mark.importorskip
|
||||
|
||||
36
LICENSE
36
LICENSE
@@ -1,19 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
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 the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
Copyright (c) 2004-2016 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
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
37
MANIFEST.in
37
MANIFEST.in
@@ -1,7 +1,34 @@
|
||||
include CHANGELOG
|
||||
include README.rst
|
||||
include setup.py
|
||||
include tox.ini
|
||||
include CHANGELOG.rst
|
||||
include LICENSE
|
||||
graft doc
|
||||
include AUTHORS
|
||||
|
||||
include README.rst
|
||||
include CONTRIBUTING.rst
|
||||
|
||||
include tox.ini
|
||||
include setup.py
|
||||
|
||||
include .coveragerc
|
||||
|
||||
include plugin-test.sh
|
||||
include requirements-docs.txt
|
||||
include runtox.py
|
||||
|
||||
recursive-include bench *.py
|
||||
recursive-include extra *.py
|
||||
|
||||
graft testing
|
||||
graft doc
|
||||
|
||||
exclude _pytest/impl
|
||||
|
||||
graft _pytest/vendored_packages
|
||||
|
||||
recursive-exclude * *.pyc *.pyo
|
||||
|
||||
exclude appveyor/install.ps1
|
||||
exclude appveyor.yml
|
||||
exclude appveyor
|
||||
|
||||
exclude ISSUES.txt
|
||||
exclude HOWTORELEASE.rst
|
||||
|
||||
128
README.rst
128
README.rst
@@ -1,66 +1,102 @@
|
||||
======
|
||||
pytest
|
||||
======
|
||||
.. image:: http://pytest.org/latest/_static/pytest1.png
|
||||
:target: http://pytest.org
|
||||
:align: center
|
||||
:alt: pytest
|
||||
|
||||
The ``pytest`` testing tool makes it easy to write small tests, yet
|
||||
scales to support complex functional testing.
|
||||
------
|
||||
|
||||
.. image:: http://img.shields.io/pypi/v/pytest.svg
|
||||
.. image:: https://img.shields.io/pypi/v/pytest.svg
|
||||
:target: https://pypi.python.org/pypi/pytest
|
||||
.. image:: http://img.shields.io/coveralls/pytest-dev/pytest/master.svg
|
||||
.. image:: https://img.shields.io/pypi/pyversions/pytest.svg
|
||||
:target: https://pypi.python.org/pypi/pytest
|
||||
.. image:: https://img.shields.io/coveralls/pytest-dev/pytest/master.svg
|
||||
:target: https://coveralls.io/r/pytest-dev/pytest
|
||||
.. image:: https://travis-ci.org/pytest-dev/pytest.svg?branch=master
|
||||
:target: https://travis-ci.org/pytest-dev/pytest
|
||||
.. image:: https://ci.appveyor.com/api/projects/status/mrgbjaua7t33pg6b?svg=true
|
||||
:target: https://ci.appveyor.com/project/pytestbot/pytest
|
||||
|
||||
Documentation: http://pytest.org/latest/
|
||||
The ``pytest`` framework makes it easy to write small tests, yet
|
||||
scales to support complex functional testing for applications and libraries.
|
||||
|
||||
Changelog: http://pytest.org/latest/changelog.html
|
||||
An example of a simple test:
|
||||
|
||||
Issues: https://github.com/pytest-dev/pytest/issues
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_sample.py
|
||||
def func(x):
|
||||
return x + 1
|
||||
|
||||
def test_answer():
|
||||
assert func(3) == 5
|
||||
|
||||
|
||||
To execute it::
|
||||
|
||||
$ py.test
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.5, py-1.4.31, pluggy-0.3.1
|
||||
collected 1 items
|
||||
|
||||
test_sample.py F
|
||||
|
||||
======= FAILURES ========
|
||||
_______ test_answer ________
|
||||
|
||||
def test_answer():
|
||||
> assert func(3) == 5
|
||||
E assert 4 == 5
|
||||
E + where 4 = func(3)
|
||||
|
||||
test_sample.py:5: AssertionError
|
||||
======= 1 failed in 0.12 seconds ========
|
||||
|
||||
Due to ``py.test``'s detailed assertion introspection, only plain ``assert`` statements are used. See `getting-started <http://pytest.org/latest/getting-started.html#our-first-test-run>`_ for more examples.
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- `auto-discovery
|
||||
<http://pytest.org/latest/goodpractises.html#python-test-discovery>`_
|
||||
of test modules and functions,
|
||||
- detailed info on failing `assert statements <http://pytest.org/latest/assert.html>`_ (no need to remember ``self.assert*`` names)
|
||||
- `modular fixtures <http://pytest.org/latest/fixture.html>`_ for
|
||||
managing small or parametrized long-lived test resources.
|
||||
- multi-paradigm support: you can use ``pytest`` to run test suites based
|
||||
on `unittest <http://pytest.org/latest/unittest.html>`_ (or trial),
|
||||
`nose <http://pytest.org/latest/nose.html>`_
|
||||
- single-source compatibility from Python2.6 all the way up to
|
||||
Python3.5, PyPy-2.3, (jython-2.5 untested)
|
||||
- Detailed info on failing `assert statements <http://pytest.org/latest/assert.html>`_ (no need to remember ``self.assert*`` names);
|
||||
|
||||
- `Auto-discovery
|
||||
<http://pytest.org/latest/goodpractices.html#python-test-discovery>`_
|
||||
of test modules and functions;
|
||||
|
||||
- `Modular fixtures <http://pytest.org/latest/fixture.html>`_ for
|
||||
managing small or parametrized long-lived test resources;
|
||||
|
||||
- Can run `unittest <http://pytest.org/latest/unittest.html>`_ (or trial),
|
||||
`nose <http://pytest.org/latest/nose.html>`_ test suites out of the box;
|
||||
|
||||
- Python2.6+, Python3.2+, PyPy-2.3, Jython-2.5 (untested);
|
||||
|
||||
- Rich plugin architecture, with over 150+ `external plugins <http://pytest.org/latest/plugins.html#installing-external-plugins-searching>`_ and thriving community;
|
||||
|
||||
|
||||
- many `external plugins <http://pytest.org/latest/plugins.html#installing-external-plugins-searching>`_.
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
A simple example for a test:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_module.py
|
||||
def test_function():
|
||||
i = 4
|
||||
assert i == 3
|
||||
|
||||
which can be run with ``py.test test_module.py``. See `getting-started <http://pytest.org/latest/getting-started.html#our-first-test-run>`_ for more examples.
|
||||
|
||||
For much more info, including PDF docs, see
|
||||
|
||||
http://pytest.org
|
||||
|
||||
and report bugs at:
|
||||
|
||||
https://github.com/pytest-dev/pytest/issues
|
||||
|
||||
and checkout or fork repo at:
|
||||
|
||||
https://github.com/pytest-dev/pytest
|
||||
For full documentation, including installation, tutorials and PDF documents, please see http://pytest.org.
|
||||
|
||||
|
||||
Copyright Holger Krekel and others, 2004-2015
|
||||
Licensed under the MIT license.
|
||||
Bugs/Requests
|
||||
-------------
|
||||
|
||||
Please use the `GitHub issue tracker <https://github.com/pytest-dev/pytest/issues>`_ to submit bugs or request features.
|
||||
|
||||
|
||||
Changelog
|
||||
---------
|
||||
|
||||
Consult the `Changelog <http://pytest.org/latest/changelog.html>`_ page for fixes and enhancements of each version.
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
Copyright Holger Krekel and others, 2004-2016.
|
||||
|
||||
Distributed under the terms of the `MIT`_ license, pytest is free and open source software.
|
||||
|
||||
.. _`MIT`: https://github.com/pytest-dev/pytest/blob/master/LICENSE
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
#
|
||||
__version__ = '2.8.1'
|
||||
__version__ = '2.9.2'
|
||||
|
||||
@@ -88,9 +88,6 @@ class FastFilesCompleter:
|
||||
return completion
|
||||
|
||||
if os.environ.get('_ARGCOMPLETE'):
|
||||
# argcomplete 0.5.6 is not compatible with python 2.5.6: print/with/format
|
||||
if sys.version_info[:2] < (2, 6):
|
||||
sys.exit(1)
|
||||
try:
|
||||
import argcomplete.completers
|
||||
except ImportError:
|
||||
|
||||
12
_pytest/_code/__init__.py
Normal file
12
_pytest/_code/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
||||
""" python inspection/code generation API """
|
||||
from .code import Code # noqa
|
||||
from .code import ExceptionInfo # noqa
|
||||
from .code import Frame # noqa
|
||||
from .code import Traceback # noqa
|
||||
from .code import getrawcode # noqa
|
||||
from .code import patch_builtins # noqa
|
||||
from .code import unpatch_builtins # noqa
|
||||
from .source import Source # noqa
|
||||
from .source import compile_ as compile # noqa
|
||||
from .source import getfslineno # noqa
|
||||
|
||||
81
_pytest/_code/_py2traceback.py
Normal file
81
_pytest/_code/_py2traceback.py
Normal file
@@ -0,0 +1,81 @@
|
||||
# copied from python-2.7.3's traceback.py
|
||||
# CHANGES:
|
||||
# - some_str is replaced, trying to create unicode strings
|
||||
#
|
||||
import types
|
||||
|
||||
def format_exception_only(etype, value):
|
||||
"""Format the exception part of a traceback.
|
||||
|
||||
The arguments are the exception type and value such as given by
|
||||
sys.last_type and sys.last_value. The return value is a list of
|
||||
strings, each ending in a newline.
|
||||
|
||||
Normally, the list contains a single string; however, for
|
||||
SyntaxError exceptions, it contains several lines that (when
|
||||
printed) display detailed information about where the syntax
|
||||
error occurred.
|
||||
|
||||
The message indicating which exception occurred is always the last
|
||||
string in the list.
|
||||
|
||||
"""
|
||||
|
||||
# An instance should not have a meaningful value parameter, but
|
||||
# sometimes does, particularly for string exceptions, such as
|
||||
# >>> raise string1, string2 # deprecated
|
||||
#
|
||||
# Clear these out first because issubtype(string1, SyntaxError)
|
||||
# would throw another exception and mask the original problem.
|
||||
if (isinstance(etype, BaseException) or
|
||||
isinstance(etype, types.InstanceType) or
|
||||
etype is None or type(etype) is str):
|
||||
return [_format_final_exc_line(etype, value)]
|
||||
|
||||
stype = etype.__name__
|
||||
|
||||
if not issubclass(etype, SyntaxError):
|
||||
return [_format_final_exc_line(stype, value)]
|
||||
|
||||
# It was a syntax error; show exactly where the problem was found.
|
||||
lines = []
|
||||
try:
|
||||
msg, (filename, lineno, offset, badline) = value.args
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
filename = filename or "<string>"
|
||||
lines.append(' File "%s", line %d\n' % (filename, lineno))
|
||||
if badline is not None:
|
||||
if isinstance(badline, bytes): # python 2 only
|
||||
badline = badline.decode('utf-8', 'replace')
|
||||
lines.append(u' %s\n' % badline.strip())
|
||||
if offset is not None:
|
||||
caretspace = badline.rstrip('\n')[:offset].lstrip()
|
||||
# non-space whitespace (likes tabs) must be kept for alignment
|
||||
caretspace = ((c.isspace() and c or ' ') for c in caretspace)
|
||||
# only three spaces to account for offset1 == pos 0
|
||||
lines.append(' %s^\n' % ''.join(caretspace))
|
||||
value = msg
|
||||
|
||||
lines.append(_format_final_exc_line(stype, value))
|
||||
return lines
|
||||
|
||||
def _format_final_exc_line(etype, value):
|
||||
"""Return a list of a single line -- normal case for format_exception_only"""
|
||||
valuestr = _some_str(value)
|
||||
if value is None or not valuestr:
|
||||
line = "%s\n" % etype
|
||||
else:
|
||||
line = "%s: %s\n" % (etype, valuestr)
|
||||
return line
|
||||
|
||||
def _some_str(value):
|
||||
try:
|
||||
return unicode(value)
|
||||
except Exception:
|
||||
try:
|
||||
return str(value)
|
||||
except Exception:
|
||||
pass
|
||||
return '<unprintable %s object>' % type(value).__name__
|
||||
805
_pytest/_code/code.py
Normal file
805
_pytest/_code/code.py
Normal file
@@ -0,0 +1,805 @@
|
||||
import sys
|
||||
from inspect import CO_VARARGS, CO_VARKEYWORDS
|
||||
|
||||
import py
|
||||
|
||||
builtin_repr = repr
|
||||
|
||||
reprlib = py.builtin._tryimport('repr', 'reprlib')
|
||||
|
||||
if sys.version_info[0] >= 3:
|
||||
from traceback import format_exception_only
|
||||
else:
|
||||
from ._py2traceback import format_exception_only
|
||||
|
||||
class Code(object):
|
||||
""" wrapper around Python code objects """
|
||||
def __init__(self, rawcode):
|
||||
if not hasattr(rawcode, "co_filename"):
|
||||
rawcode = getrawcode(rawcode)
|
||||
try:
|
||||
self.filename = rawcode.co_filename
|
||||
self.firstlineno = rawcode.co_firstlineno - 1
|
||||
self.name = rawcode.co_name
|
||||
except AttributeError:
|
||||
raise TypeError("not a code object: %r" %(rawcode,))
|
||||
self.raw = rawcode
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.raw == other.raw
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
""" return a path object pointing to source code (note that it
|
||||
might not point to an actually existing file). """
|
||||
p = py.path.local(self.raw.co_filename)
|
||||
# maybe don't try this checking
|
||||
if not p.check():
|
||||
# XXX maybe try harder like the weird logic
|
||||
# in the standard lib [linecache.updatecache] does?
|
||||
p = self.raw.co_filename
|
||||
return p
|
||||
|
||||
@property
|
||||
def fullsource(self):
|
||||
""" return a _pytest._code.Source object for the full source file of the code
|
||||
"""
|
||||
from _pytest._code import source
|
||||
full, _ = source.findsource(self.raw)
|
||||
return full
|
||||
|
||||
def source(self):
|
||||
""" return a _pytest._code.Source object for the code object's source only
|
||||
"""
|
||||
# return source only for that part of code
|
||||
import _pytest._code
|
||||
return _pytest._code.Source(self.raw)
|
||||
|
||||
def getargs(self, var=False):
|
||||
""" return a tuple with the argument names for the code object
|
||||
|
||||
if 'var' is set True also return the names of the variable and
|
||||
keyword arguments when present
|
||||
"""
|
||||
# handfull shortcut for getting args
|
||||
raw = self.raw
|
||||
argcount = raw.co_argcount
|
||||
if var:
|
||||
argcount += raw.co_flags & CO_VARARGS
|
||||
argcount += raw.co_flags & CO_VARKEYWORDS
|
||||
return raw.co_varnames[:argcount]
|
||||
|
||||
class Frame(object):
|
||||
"""Wrapper around a Python frame holding f_locals and f_globals
|
||||
in which expressions can be evaluated."""
|
||||
|
||||
def __init__(self, frame):
|
||||
self.lineno = frame.f_lineno - 1
|
||||
self.f_globals = frame.f_globals
|
||||
self.f_locals = frame.f_locals
|
||||
self.raw = frame
|
||||
self.code = Code(frame.f_code)
|
||||
|
||||
@property
|
||||
def statement(self):
|
||||
""" statement this frame is at """
|
||||
import _pytest._code
|
||||
if self.code.fullsource is None:
|
||||
return _pytest._code.Source("")
|
||||
return self.code.fullsource.getstatement(self.lineno)
|
||||
|
||||
def eval(self, code, **vars):
|
||||
""" evaluate 'code' in the frame
|
||||
|
||||
'vars' are optional additional local variables
|
||||
|
||||
returns the result of the evaluation
|
||||
"""
|
||||
f_locals = self.f_locals.copy()
|
||||
f_locals.update(vars)
|
||||
return eval(code, self.f_globals, f_locals)
|
||||
|
||||
def exec_(self, code, **vars):
|
||||
""" exec 'code' in the frame
|
||||
|
||||
'vars' are optiona; additional local variables
|
||||
"""
|
||||
f_locals = self.f_locals.copy()
|
||||
f_locals.update(vars)
|
||||
py.builtin.exec_(code, self.f_globals, f_locals )
|
||||
|
||||
def repr(self, object):
|
||||
""" return a 'safe' (non-recursive, one-line) string repr for 'object'
|
||||
"""
|
||||
return py.io.saferepr(object)
|
||||
|
||||
def is_true(self, object):
|
||||
return object
|
||||
|
||||
def getargs(self, var=False):
|
||||
""" return a list of tuples (name, value) for all arguments
|
||||
|
||||
if 'var' is set True also include the variable and keyword
|
||||
arguments when present
|
||||
"""
|
||||
retval = []
|
||||
for arg in self.code.getargs(var):
|
||||
try:
|
||||
retval.append((arg, self.f_locals[arg]))
|
||||
except KeyError:
|
||||
pass # this can occur when using Psyco
|
||||
return retval
|
||||
|
||||
class TracebackEntry(object):
|
||||
""" a single entry in a traceback """
|
||||
|
||||
_repr_style = None
|
||||
exprinfo = None
|
||||
|
||||
def __init__(self, rawentry):
|
||||
self._rawentry = rawentry
|
||||
self.lineno = rawentry.tb_lineno - 1
|
||||
|
||||
def set_repr_style(self, mode):
|
||||
assert mode in ("short", "long")
|
||||
self._repr_style = mode
|
||||
|
||||
@property
|
||||
def frame(self):
|
||||
import _pytest._code
|
||||
return _pytest._code.Frame(self._rawentry.tb_frame)
|
||||
|
||||
@property
|
||||
def relline(self):
|
||||
return self.lineno - self.frame.code.firstlineno
|
||||
|
||||
def __repr__(self):
|
||||
return "<TracebackEntry %s:%d>" %(self.frame.code.path, self.lineno+1)
|
||||
|
||||
@property
|
||||
def statement(self):
|
||||
""" _pytest._code.Source object for the current statement """
|
||||
source = self.frame.code.fullsource
|
||||
return source.getstatement(self.lineno)
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
""" path to the source code """
|
||||
return self.frame.code.path
|
||||
|
||||
def getlocals(self):
|
||||
return self.frame.f_locals
|
||||
locals = property(getlocals, None, None, "locals of underlaying frame")
|
||||
|
||||
def reinterpret(self):
|
||||
"""Reinterpret the failing statement and returns a detailed information
|
||||
about what operations are performed."""
|
||||
from _pytest.assertion.reinterpret import reinterpret
|
||||
if self.exprinfo is None:
|
||||
source = py.builtin._totext(self.statement).strip()
|
||||
x = reinterpret(source, self.frame, should_fail=True)
|
||||
if not py.builtin._istext(x):
|
||||
raise TypeError("interpret returned non-string %r" % (x,))
|
||||
self.exprinfo = x
|
||||
return self.exprinfo
|
||||
|
||||
def getfirstlinesource(self):
|
||||
# on Jython this firstlineno can be -1 apparently
|
||||
return max(self.frame.code.firstlineno, 0)
|
||||
|
||||
def getsource(self, astcache=None):
|
||||
""" return failing source code. """
|
||||
# we use the passed in astcache to not reparse asttrees
|
||||
# within exception info printing
|
||||
from _pytest._code.source import getstatementrange_ast
|
||||
source = self.frame.code.fullsource
|
||||
if source is None:
|
||||
return None
|
||||
key = astnode = None
|
||||
if astcache is not None:
|
||||
key = self.frame.code.path
|
||||
if key is not None:
|
||||
astnode = astcache.get(key, None)
|
||||
start = self.getfirstlinesource()
|
||||
try:
|
||||
astnode, _, end = getstatementrange_ast(self.lineno, source,
|
||||
astnode=astnode)
|
||||
except SyntaxError:
|
||||
end = self.lineno + 1
|
||||
else:
|
||||
if key is not None:
|
||||
astcache[key] = astnode
|
||||
return source[start:end]
|
||||
|
||||
source = property(getsource)
|
||||
|
||||
def ishidden(self):
|
||||
""" return True if the current frame has a var __tracebackhide__
|
||||
resolving to True
|
||||
|
||||
mostly for internal use
|
||||
"""
|
||||
try:
|
||||
return self.frame.f_locals['__tracebackhide__']
|
||||
except KeyError:
|
||||
try:
|
||||
return self.frame.f_globals['__tracebackhide__']
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
def __str__(self):
|
||||
try:
|
||||
fn = str(self.path)
|
||||
except py.error.Error:
|
||||
fn = '???'
|
||||
name = self.frame.code.name
|
||||
try:
|
||||
line = str(self.statement).lstrip()
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
line = "???"
|
||||
return " File %r:%d in %s\n %s\n" %(fn, self.lineno+1, name, line)
|
||||
|
||||
def name(self):
|
||||
return self.frame.code.raw.co_name
|
||||
name = property(name, None, None, "co_name of underlaying code")
|
||||
|
||||
class Traceback(list):
|
||||
""" Traceback objects encapsulate and offer higher level
|
||||
access to Traceback entries.
|
||||
"""
|
||||
Entry = TracebackEntry
|
||||
def __init__(self, tb):
|
||||
""" initialize from given python traceback object. """
|
||||
if hasattr(tb, 'tb_next'):
|
||||
def f(cur):
|
||||
while cur is not None:
|
||||
yield self.Entry(cur)
|
||||
cur = cur.tb_next
|
||||
list.__init__(self, f(tb))
|
||||
else:
|
||||
list.__init__(self, tb)
|
||||
|
||||
def cut(self, path=None, lineno=None, firstlineno=None, excludepath=None):
|
||||
""" return a Traceback instance wrapping part of this Traceback
|
||||
|
||||
by provding any combination of path, lineno and firstlineno, the
|
||||
first frame to start the to-be-returned traceback is determined
|
||||
|
||||
this allows cutting the first part of a Traceback instance e.g.
|
||||
for formatting reasons (removing some uninteresting bits that deal
|
||||
with handling of the exception/traceback)
|
||||
"""
|
||||
for x in self:
|
||||
code = x.frame.code
|
||||
codepath = code.path
|
||||
if ((path is None or codepath == path) and
|
||||
(excludepath is None or not hasattr(codepath, 'relto') or
|
||||
not codepath.relto(excludepath)) and
|
||||
(lineno is None or x.lineno == lineno) and
|
||||
(firstlineno is None or x.frame.code.firstlineno == firstlineno)):
|
||||
return Traceback(x._rawentry)
|
||||
return self
|
||||
|
||||
def __getitem__(self, key):
|
||||
val = super(Traceback, self).__getitem__(key)
|
||||
if isinstance(key, type(slice(0))):
|
||||
val = self.__class__(val)
|
||||
return val
|
||||
|
||||
def filter(self, fn=lambda x: not x.ishidden()):
|
||||
""" return a Traceback instance with certain items removed
|
||||
|
||||
fn is a function that gets a single argument, a TracebackEntry
|
||||
instance, and should return True when the item should be added
|
||||
to the Traceback, False when not
|
||||
|
||||
by default this removes all the TracebackEntries which are hidden
|
||||
(see ishidden() above)
|
||||
"""
|
||||
return Traceback(filter(fn, self))
|
||||
|
||||
def getcrashentry(self):
|
||||
""" return last non-hidden traceback entry that lead
|
||||
to the exception of a traceback.
|
||||
"""
|
||||
for i in range(-1, -len(self)-1, -1):
|
||||
entry = self[i]
|
||||
if not entry.ishidden():
|
||||
return entry
|
||||
return self[-1]
|
||||
|
||||
def recursionindex(self):
|
||||
""" return the index of the frame/TracebackEntry where recursion
|
||||
originates if appropriate, None if no recursion occurred
|
||||
"""
|
||||
cache = {}
|
||||
for i, entry in enumerate(self):
|
||||
# id for the code.raw is needed to work around
|
||||
# the strange metaprogramming in the decorator lib from pypi
|
||||
# which generates code objects that have hash/value equality
|
||||
#XXX needs a test
|
||||
key = entry.frame.code.path, id(entry.frame.code.raw), entry.lineno
|
||||
#print "checking for recursion at", key
|
||||
l = cache.setdefault(key, [])
|
||||
if l:
|
||||
f = entry.frame
|
||||
loc = f.f_locals
|
||||
for otherloc in l:
|
||||
if f.is_true(f.eval(co_equal,
|
||||
__recursioncache_locals_1=loc,
|
||||
__recursioncache_locals_2=otherloc)):
|
||||
return i
|
||||
l.append(entry.frame.f_locals)
|
||||
return None
|
||||
|
||||
co_equal = compile('__recursioncache_locals_1 == __recursioncache_locals_2',
|
||||
'?', 'eval')
|
||||
|
||||
class ExceptionInfo(object):
|
||||
""" wraps sys.exc_info() objects and offers
|
||||
help for navigating the traceback.
|
||||
"""
|
||||
_striptext = ''
|
||||
def __init__(self, tup=None, exprinfo=None):
|
||||
import _pytest._code
|
||||
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 = str(tup[1])
|
||||
if exprinfo and exprinfo.startswith('assert '):
|
||||
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)
|
||||
|
||||
def __repr__(self):
|
||||
return "<ExceptionInfo %s tblen=%d>" % (self.typename, len(self.traceback))
|
||||
|
||||
def exconly(self, tryshort=False):
|
||||
""" return the exception as a string
|
||||
|
||||
when 'tryshort' resolves to True, and the exception is a
|
||||
_pytest._code._AssertionError, only the actual exception part of
|
||||
the exception representation is returned (so 'AssertionError: ' is
|
||||
removed from the beginning)
|
||||
"""
|
||||
lines = format_exception_only(self.type, self.value)
|
||||
text = ''.join(lines)
|
||||
text = text.rstrip()
|
||||
if tryshort:
|
||||
if text.startswith(self._striptext):
|
||||
text = text[len(self._striptext):]
|
||||
return text
|
||||
|
||||
def errisinstance(self, exc):
|
||||
""" return True if the exception is an instance of exc """
|
||||
return isinstance(self.value, exc)
|
||||
|
||||
def _getreprcrash(self):
|
||||
exconly = self.exconly(tryshort=True)
|
||||
entry = self.traceback.getcrashentry()
|
||||
path, lineno = entry.frame.code.raw.co_filename, entry.lineno
|
||||
return ReprFileLocation(path, lineno+1, exconly)
|
||||
|
||||
def getrepr(self, showlocals=False, style="long",
|
||||
abspath=False, tbfilter=True, funcargs=False):
|
||||
""" return str()able representation of this exception info.
|
||||
showlocals: show locals per traceback entry
|
||||
style: long|short|no|native traceback style
|
||||
tbfilter: hide entries (where __tracebackhide__ is true)
|
||||
|
||||
in case of style==native, tbfilter and showlocals is ignored.
|
||||
"""
|
||||
if style == 'native':
|
||||
return ReprExceptionInfo(ReprTracebackNative(
|
||||
py.std.traceback.format_exception(
|
||||
self.type,
|
||||
self.value,
|
||||
self.traceback[0]._rawentry,
|
||||
)), self._getreprcrash())
|
||||
|
||||
fmt = FormattedExcinfo(showlocals=showlocals, style=style,
|
||||
abspath=abspath, tbfilter=tbfilter, funcargs=funcargs)
|
||||
return fmt.repr_excinfo(self)
|
||||
|
||||
def __str__(self):
|
||||
entry = self.traceback[-1]
|
||||
loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
|
||||
return str(loc)
|
||||
|
||||
def __unicode__(self):
|
||||
entry = self.traceback[-1]
|
||||
loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
|
||||
return unicode(loc)
|
||||
|
||||
|
||||
class FormattedExcinfo(object):
|
||||
""" presenting information about failing Functions and Generators. """
|
||||
# for traceback entries
|
||||
flow_marker = ">"
|
||||
fail_marker = "E"
|
||||
|
||||
def __init__(self, showlocals=False, style="long", abspath=True, tbfilter=True, funcargs=False):
|
||||
self.showlocals = showlocals
|
||||
self.style = style
|
||||
self.tbfilter = tbfilter
|
||||
self.funcargs = funcargs
|
||||
self.abspath = abspath
|
||||
self.astcache = {}
|
||||
|
||||
def _getindent(self, source):
|
||||
# figure out indent for given source
|
||||
try:
|
||||
s = str(source.getstatement(len(source)-1))
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
try:
|
||||
s = str(source[-1])
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
return 0
|
||||
return 4 + (len(s) - len(s.lstrip()))
|
||||
|
||||
def _getentrysource(self, entry):
|
||||
source = entry.getsource(self.astcache)
|
||||
if source is not None:
|
||||
source = source.deindent()
|
||||
return source
|
||||
|
||||
def _saferepr(self, obj):
|
||||
return py.io.saferepr(obj)
|
||||
|
||||
def repr_args(self, entry):
|
||||
if self.funcargs:
|
||||
args = []
|
||||
for argname, argvalue in entry.frame.getargs(var=True):
|
||||
args.append((argname, self._saferepr(argvalue)))
|
||||
return ReprFuncArgs(args)
|
||||
|
||||
def get_source(self, source, line_index=-1, excinfo=None, short=False):
|
||||
""" return formatted and marked up source lines. """
|
||||
import _pytest._code
|
||||
lines = []
|
||||
if source is None or line_index >= len(source.lines):
|
||||
source = _pytest._code.Source("???")
|
||||
line_index = 0
|
||||
if line_index < 0:
|
||||
line_index += len(source)
|
||||
space_prefix = " "
|
||||
if short:
|
||||
lines.append(space_prefix + source.lines[line_index].strip())
|
||||
else:
|
||||
for line in source.lines[:line_index]:
|
||||
lines.append(space_prefix + line)
|
||||
lines.append(self.flow_marker + " " + source.lines[line_index])
|
||||
for line in source.lines[line_index+1:]:
|
||||
lines.append(space_prefix + line)
|
||||
if excinfo is not None:
|
||||
indent = 4 if short else self._getindent(source)
|
||||
lines.extend(self.get_exconly(excinfo, indent=indent, markall=True))
|
||||
return lines
|
||||
|
||||
def get_exconly(self, excinfo, indent=4, markall=False):
|
||||
lines = []
|
||||
indent = " " * indent
|
||||
# get the real exception information out
|
||||
exlines = excinfo.exconly(tryshort=True).split('\n')
|
||||
failindent = self.fail_marker + indent[1:]
|
||||
for line in exlines:
|
||||
lines.append(failindent + line)
|
||||
if not markall:
|
||||
failindent = indent
|
||||
return lines
|
||||
|
||||
def repr_locals(self, locals):
|
||||
if self.showlocals:
|
||||
lines = []
|
||||
keys = [loc for loc in locals if loc[0] != "@"]
|
||||
keys.sort()
|
||||
for name in keys:
|
||||
value = locals[name]
|
||||
if name == '__builtins__':
|
||||
lines.append("__builtins__ = <builtins>")
|
||||
else:
|
||||
# This formatting could all be handled by the
|
||||
# _repr() function, which is only reprlib.Repr in
|
||||
# disguise, so is very configurable.
|
||||
str_repr = self._saferepr(value)
|
||||
#if len(str_repr) < 70 or not isinstance(value,
|
||||
# (list, tuple, dict)):
|
||||
lines.append("%-10s = %s" %(name, str_repr))
|
||||
#else:
|
||||
# self._line("%-10s =\\" % (name,))
|
||||
# # XXX
|
||||
# py.std.pprint.pprint(value, stream=self.excinfowriter)
|
||||
return ReprLocals(lines)
|
||||
|
||||
def repr_traceback_entry(self, entry, excinfo=None):
|
||||
import _pytest._code
|
||||
source = self._getentrysource(entry)
|
||||
if source is None:
|
||||
source = _pytest._code.Source("???")
|
||||
line_index = 0
|
||||
else:
|
||||
# entry.getfirstlinesource() can be -1, should be 0 on jython
|
||||
line_index = entry.lineno - max(entry.getfirstlinesource(), 0)
|
||||
|
||||
lines = []
|
||||
style = entry._repr_style
|
||||
if style is None:
|
||||
style = self.style
|
||||
if style in ("short", "long"):
|
||||
short = style == "short"
|
||||
reprargs = self.repr_args(entry) if not short else None
|
||||
s = self.get_source(source, line_index, excinfo, short=short)
|
||||
lines.extend(s)
|
||||
if short:
|
||||
message = "in %s" %(entry.name)
|
||||
else:
|
||||
message = excinfo and excinfo.typename or ""
|
||||
path = self._makepath(entry.path)
|
||||
filelocrepr = ReprFileLocation(path, entry.lineno+1, message)
|
||||
localsrepr = None
|
||||
if not short:
|
||||
localsrepr = self.repr_locals(entry.locals)
|
||||
return ReprEntry(lines, reprargs, localsrepr, filelocrepr, style)
|
||||
if excinfo:
|
||||
lines.extend(self.get_exconly(excinfo, indent=4))
|
||||
return ReprEntry(lines, None, None, None, style)
|
||||
|
||||
def _makepath(self, path):
|
||||
if not self.abspath:
|
||||
try:
|
||||
np = py.path.local().bestrelpath(path)
|
||||
except OSError:
|
||||
return path
|
||||
if len(np) < len(str(path)):
|
||||
path = np
|
||||
return path
|
||||
|
||||
def repr_traceback(self, excinfo):
|
||||
traceback = excinfo.traceback
|
||||
if self.tbfilter:
|
||||
traceback = traceback.filter()
|
||||
recursionindex = None
|
||||
if is_recursion_error(excinfo):
|
||||
recursionindex = traceback.recursionindex()
|
||||
last = traceback[-1]
|
||||
entries = []
|
||||
extraline = None
|
||||
for index, entry in enumerate(traceback):
|
||||
einfo = (last == entry) and excinfo or None
|
||||
reprentry = self.repr_traceback_entry(entry, einfo)
|
||||
entries.append(reprentry)
|
||||
if index == recursionindex:
|
||||
extraline = "!!! Recursion detected (same locals & position)"
|
||||
break
|
||||
return ReprTraceback(entries, extraline, style=self.style)
|
||||
|
||||
def repr_excinfo(self, excinfo):
|
||||
reprtraceback = self.repr_traceback(excinfo)
|
||||
reprcrash = excinfo._getreprcrash()
|
||||
return ReprExceptionInfo(reprtraceback, reprcrash)
|
||||
|
||||
class TerminalRepr:
|
||||
def __str__(self):
|
||||
s = self.__unicode__()
|
||||
if sys.version_info[0] < 3:
|
||||
s = s.encode('utf-8')
|
||||
return s
|
||||
|
||||
def __unicode__(self):
|
||||
# FYI this is called from pytest-xdist's serialization of exception
|
||||
# information.
|
||||
io = py.io.TextIO()
|
||||
tw = py.io.TerminalWriter(file=io)
|
||||
self.toterminal(tw)
|
||||
return io.getvalue().strip()
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s instance at %0x>" %(self.__class__, id(self))
|
||||
|
||||
|
||||
class ReprExceptionInfo(TerminalRepr):
|
||||
def __init__(self, reprtraceback, reprcrash):
|
||||
self.reprtraceback = reprtraceback
|
||||
self.reprcrash = reprcrash
|
||||
self.sections = []
|
||||
|
||||
def addsection(self, name, content, sep="-"):
|
||||
self.sections.append((name, content, sep))
|
||||
|
||||
def toterminal(self, tw):
|
||||
self.reprtraceback.toterminal(tw)
|
||||
for name, content, sep in self.sections:
|
||||
tw.sep(sep, name)
|
||||
tw.line(content)
|
||||
|
||||
class ReprTraceback(TerminalRepr):
|
||||
entrysep = "_ "
|
||||
|
||||
def __init__(self, reprentries, extraline, style):
|
||||
self.reprentries = reprentries
|
||||
self.extraline = extraline
|
||||
self.style = style
|
||||
|
||||
def toterminal(self, tw):
|
||||
# the entries might have different styles
|
||||
for i, entry in enumerate(self.reprentries):
|
||||
if entry.style == "long":
|
||||
tw.line("")
|
||||
entry.toterminal(tw)
|
||||
if i < len(self.reprentries) - 1:
|
||||
next_entry = self.reprentries[i+1]
|
||||
if entry.style == "long" or \
|
||||
entry.style == "short" and next_entry.style == "long":
|
||||
tw.sep(self.entrysep)
|
||||
|
||||
if self.extraline:
|
||||
tw.line(self.extraline)
|
||||
|
||||
class ReprTracebackNative(ReprTraceback):
|
||||
def __init__(self, tblines):
|
||||
self.style = "native"
|
||||
self.reprentries = [ReprEntryNative(tblines)]
|
||||
self.extraline = None
|
||||
|
||||
class ReprEntryNative(TerminalRepr):
|
||||
style = "native"
|
||||
|
||||
def __init__(self, tblines):
|
||||
self.lines = tblines
|
||||
|
||||
def toterminal(self, tw):
|
||||
tw.write("".join(self.lines))
|
||||
|
||||
class ReprEntry(TerminalRepr):
|
||||
localssep = "_ "
|
||||
|
||||
def __init__(self, lines, reprfuncargs, reprlocals, filelocrepr, style):
|
||||
self.lines = lines
|
||||
self.reprfuncargs = reprfuncargs
|
||||
self.reprlocals = reprlocals
|
||||
self.reprfileloc = filelocrepr
|
||||
self.style = style
|
||||
|
||||
def toterminal(self, tw):
|
||||
if self.style == "short":
|
||||
self.reprfileloc.toterminal(tw)
|
||||
for line in self.lines:
|
||||
red = line.startswith("E ")
|
||||
tw.line(line, bold=True, red=red)
|
||||
#tw.line("")
|
||||
return
|
||||
if self.reprfuncargs:
|
||||
self.reprfuncargs.toterminal(tw)
|
||||
for line in self.lines:
|
||||
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:
|
||||
if self.lines:
|
||||
tw.line("")
|
||||
self.reprfileloc.toterminal(tw)
|
||||
|
||||
def __str__(self):
|
||||
return "%s\n%s\n%s" % ("\n".join(self.lines),
|
||||
self.reprlocals,
|
||||
self.reprfileloc)
|
||||
|
||||
class ReprFileLocation(TerminalRepr):
|
||||
def __init__(self, path, lineno, message):
|
||||
self.path = str(path)
|
||||
self.lineno = lineno
|
||||
self.message = message
|
||||
|
||||
def toterminal(self, tw):
|
||||
# filename and lineno output for each entry,
|
||||
# using an output format that most editors unterstand
|
||||
msg = self.message
|
||||
i = msg.find("\n")
|
||||
if i != -1:
|
||||
msg = msg[:i]
|
||||
tw.line("%s:%s: %s" %(self.path, self.lineno, msg))
|
||||
|
||||
class ReprLocals(TerminalRepr):
|
||||
def __init__(self, lines):
|
||||
self.lines = lines
|
||||
|
||||
def toterminal(self, tw):
|
||||
for line in self.lines:
|
||||
tw.line(line)
|
||||
|
||||
class ReprFuncArgs(TerminalRepr):
|
||||
def __init__(self, args):
|
||||
self.args = args
|
||||
|
||||
def toterminal(self, tw):
|
||||
if self.args:
|
||||
linesofar = ""
|
||||
for name, value in self.args:
|
||||
ns = "%s = %s" %(name, value)
|
||||
if len(ns) + len(linesofar) + 2 > tw.fullwidth:
|
||||
if linesofar:
|
||||
tw.line(linesofar)
|
||||
linesofar = ns
|
||||
else:
|
||||
if linesofar:
|
||||
linesofar += ", " + ns
|
||||
else:
|
||||
linesofar = ns
|
||||
if linesofar:
|
||||
tw.line(linesofar)
|
||||
tw.line("")
|
||||
|
||||
|
||||
|
||||
oldbuiltins = {}
|
||||
|
||||
def patch_builtins(assertion=True, compile=True):
|
||||
""" put compile and AssertionError builtins to Python's builtins. """
|
||||
if assertion:
|
||||
from _pytest.assertion import reinterpret
|
||||
l = oldbuiltins.setdefault('AssertionError', [])
|
||||
l.append(py.builtin.builtins.AssertionError)
|
||||
py.builtin.builtins.AssertionError = reinterpret.AssertionError
|
||||
if compile:
|
||||
import _pytest._code
|
||||
l = oldbuiltins.setdefault('compile', [])
|
||||
l.append(py.builtin.builtins.compile)
|
||||
py.builtin.builtins.compile = _pytest._code.compile
|
||||
|
||||
def unpatch_builtins(assertion=True, compile=True):
|
||||
""" remove compile and AssertionError builtins from Python builtins. """
|
||||
if assertion:
|
||||
py.builtin.builtins.AssertionError = oldbuiltins['AssertionError'].pop()
|
||||
if compile:
|
||||
py.builtin.builtins.compile = oldbuiltins['compile'].pop()
|
||||
|
||||
def getrawcode(obj, trycall=True):
|
||||
""" return code object for given function. """
|
||||
try:
|
||||
return obj.__code__
|
||||
except AttributeError:
|
||||
obj = getattr(obj, 'im_func', obj)
|
||||
obj = getattr(obj, 'func_code', obj)
|
||||
obj = getattr(obj, 'f_code', obj)
|
||||
obj = getattr(obj, '__code__', obj)
|
||||
if trycall and not hasattr(obj, 'co_firstlineno'):
|
||||
if hasattr(obj, '__call__') and not py.std.inspect.isclass(obj):
|
||||
x = getrawcode(obj.__call__, trycall=False)
|
||||
if hasattr(x, 'co_firstlineno'):
|
||||
return x
|
||||
return obj
|
||||
|
||||
if sys.version_info[:2] >= (3, 5): # RecursionError introduced in 3.5
|
||||
def is_recursion_error(excinfo):
|
||||
return excinfo.errisinstance(RecursionError) # noqa
|
||||
else:
|
||||
def is_recursion_error(excinfo):
|
||||
if not excinfo.errisinstance(RuntimeError):
|
||||
return False
|
||||
try:
|
||||
return "maximum recursion depth exceeded" in str(excinfo.value)
|
||||
except UnicodeError:
|
||||
return False
|
||||
421
_pytest/_code/source.py
Normal file
421
_pytest/_code/source.py
Normal file
@@ -0,0 +1,421 @@
|
||||
from __future__ import generators
|
||||
|
||||
from bisect import bisect_right
|
||||
import sys
|
||||
import inspect, tokenize
|
||||
import py
|
||||
from types import ModuleType
|
||||
cpy_compile = compile
|
||||
|
||||
try:
|
||||
import _ast
|
||||
from _ast import PyCF_ONLY_AST as _AST_FLAG
|
||||
except ImportError:
|
||||
_AST_FLAG = 0
|
||||
_ast = None
|
||||
|
||||
|
||||
class Source(object):
|
||||
""" a immutable object holding a source code fragment,
|
||||
possibly deindenting it.
|
||||
"""
|
||||
_compilecounter = 0
|
||||
def __init__(self, *parts, **kwargs):
|
||||
self.lines = lines = []
|
||||
de = kwargs.get('deindent', True)
|
||||
rstrip = kwargs.get('rstrip', True)
|
||||
for part in parts:
|
||||
if not part:
|
||||
partlines = []
|
||||
if isinstance(part, Source):
|
||||
partlines = part.lines
|
||||
elif isinstance(part, (tuple, list)):
|
||||
partlines = [x.rstrip("\n") for x in part]
|
||||
elif isinstance(part, py.builtin._basestring):
|
||||
partlines = part.split('\n')
|
||||
if rstrip:
|
||||
while partlines:
|
||||
if partlines[-1].strip():
|
||||
break
|
||||
partlines.pop()
|
||||
else:
|
||||
partlines = getsource(part, deindent=de).lines
|
||||
if de:
|
||||
partlines = deindent(partlines)
|
||||
lines.extend(partlines)
|
||||
|
||||
def __eq__(self, other):
|
||||
try:
|
||||
return self.lines == other.lines
|
||||
except AttributeError:
|
||||
if isinstance(other, str):
|
||||
return str(self) == other
|
||||
return False
|
||||
|
||||
def __getitem__(self, key):
|
||||
if isinstance(key, int):
|
||||
return self.lines[key]
|
||||
else:
|
||||
if key.step not in (None, 1):
|
||||
raise IndexError("cannot slice a Source with a step")
|
||||
return self.__getslice__(key.start, key.stop)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.lines)
|
||||
|
||||
def __getslice__(self, start, end):
|
||||
newsource = Source()
|
||||
newsource.lines = self.lines[start:end]
|
||||
return newsource
|
||||
|
||||
def strip(self):
|
||||
""" return new source object with trailing
|
||||
and leading blank lines removed.
|
||||
"""
|
||||
start, end = 0, len(self)
|
||||
while start < end and not self.lines[start].strip():
|
||||
start += 1
|
||||
while end > start and not self.lines[end-1].strip():
|
||||
end -= 1
|
||||
source = Source()
|
||||
source.lines[:] = self.lines[start:end]
|
||||
return source
|
||||
|
||||
def putaround(self, before='', after='', indent=' ' * 4):
|
||||
""" return a copy of the source object with
|
||||
'before' and 'after' wrapped around it.
|
||||
"""
|
||||
before = Source(before)
|
||||
after = Source(after)
|
||||
newsource = Source()
|
||||
lines = [ (indent + line) for line in self.lines]
|
||||
newsource.lines = before.lines + lines + after.lines
|
||||
return newsource
|
||||
|
||||
def indent(self, indent=' ' * 4):
|
||||
""" return a copy of the source object with
|
||||
all lines indented by the given indent-string.
|
||||
"""
|
||||
newsource = Source()
|
||||
newsource.lines = [(indent+line) for line in self.lines]
|
||||
return newsource
|
||||
|
||||
def getstatement(self, lineno, assertion=False):
|
||||
""" return Source statement which contains the
|
||||
given linenumber (counted from 0).
|
||||
"""
|
||||
start, end = self.getstatementrange(lineno, assertion)
|
||||
return self[start:end]
|
||||
|
||||
def getstatementrange(self, lineno, assertion=False):
|
||||
""" return (start, end) tuple which spans the minimal
|
||||
statement region which containing the given lineno.
|
||||
"""
|
||||
if not (0 <= lineno < len(self)):
|
||||
raise IndexError("lineno out of range")
|
||||
ast, start, end = getstatementrange_ast(lineno, self)
|
||||
return start, end
|
||||
|
||||
def deindent(self, offset=None):
|
||||
""" return a new source object deindented by offset.
|
||||
If offset is None then guess an indentation offset from
|
||||
the first non-blank line. Subsequent lines which have a
|
||||
lower indentation offset will be copied verbatim as
|
||||
they are assumed to be part of multilines.
|
||||
"""
|
||||
# XXX maybe use the tokenizer to properly handle multiline
|
||||
# strings etc.pp?
|
||||
newsource = Source()
|
||||
newsource.lines[:] = deindent(self.lines, offset)
|
||||
return newsource
|
||||
|
||||
def isparseable(self, deindent=True):
|
||||
""" return True if source is parseable, heuristically
|
||||
deindenting it by default.
|
||||
"""
|
||||
try:
|
||||
import parser
|
||||
except ImportError:
|
||||
syntax_checker = lambda x: compile(x, 'asd', 'exec')
|
||||
else:
|
||||
syntax_checker = parser.suite
|
||||
|
||||
if deindent:
|
||||
source = str(self.deindent())
|
||||
else:
|
||||
source = str(self)
|
||||
try:
|
||||
#compile(source+'\n', "x", "exec")
|
||||
syntax_checker(source+'\n')
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except Exception:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def __str__(self):
|
||||
return "\n".join(self.lines)
|
||||
|
||||
def compile(self, filename=None, mode='exec',
|
||||
flag=generators.compiler_flag,
|
||||
dont_inherit=0, _genframe=None):
|
||||
""" return compiled code object. if filename is None
|
||||
invent an artificial filename which displays
|
||||
the source/line position of the caller frame.
|
||||
"""
|
||||
if not filename or py.path.local(filename).check(file=0):
|
||||
if _genframe is None:
|
||||
_genframe = sys._getframe(1) # the caller
|
||||
fn,lineno = _genframe.f_code.co_filename, _genframe.f_lineno
|
||||
base = "<%d-codegen " % self._compilecounter
|
||||
self.__class__._compilecounter += 1
|
||||
if not filename:
|
||||
filename = base + '%s:%d>' % (fn, lineno)
|
||||
else:
|
||||
filename = base + '%r %s:%d>' % (filename, fn, lineno)
|
||||
source = "\n".join(self.lines) + '\n'
|
||||
try:
|
||||
co = cpy_compile(source, filename, mode, flag)
|
||||
except SyntaxError:
|
||||
ex = sys.exc_info()[1]
|
||||
# re-represent syntax errors from parsing python strings
|
||||
msglines = self.lines[:ex.lineno]
|
||||
if ex.offset:
|
||||
msglines.append(" "*ex.offset + '^')
|
||||
msglines.append("(code was compiled probably from here: %s)" % filename)
|
||||
newex = SyntaxError('\n'.join(msglines))
|
||||
newex.offset = ex.offset
|
||||
newex.lineno = ex.lineno
|
||||
newex.text = ex.text
|
||||
raise newex
|
||||
else:
|
||||
if flag & _AST_FLAG:
|
||||
return co
|
||||
lines = [(x + "\n") for x in self.lines]
|
||||
if sys.version_info[0] >= 3:
|
||||
# XXX py3's inspect.getsourcefile() checks for a module
|
||||
# and a pep302 __loader__ ... we don't have a module
|
||||
# at code compile-time so we need to fake it here
|
||||
m = ModuleType("_pycodecompile_pseudo_module")
|
||||
py.std.inspect.modulesbyfile[filename] = None
|
||||
py.std.sys.modules[None] = m
|
||||
m.__loader__ = 1
|
||||
py.std.linecache.cache[filename] = (1, None, lines, filename)
|
||||
return co
|
||||
|
||||
#
|
||||
# public API shortcut functions
|
||||
#
|
||||
|
||||
def compile_(source, filename=None, mode='exec', flags=
|
||||
generators.compiler_flag, dont_inherit=0):
|
||||
""" compile the given source to a raw code object,
|
||||
and maintain an internal cache which allows later
|
||||
retrieval of the source code for the code object
|
||||
and any recursively created code objects.
|
||||
"""
|
||||
if _ast is not None and isinstance(source, _ast.AST):
|
||||
# XXX should Source support having AST?
|
||||
return cpy_compile(source, filename, mode, flags, dont_inherit)
|
||||
_genframe = sys._getframe(1) # the caller
|
||||
s = Source(source)
|
||||
co = s.compile(filename, mode, flags, _genframe=_genframe)
|
||||
return co
|
||||
|
||||
|
||||
def getfslineno(obj):
|
||||
""" Return source location (path, lineno) for the given object.
|
||||
If the source cannot be determined return ("", -1)
|
||||
"""
|
||||
import _pytest._code
|
||||
try:
|
||||
code = _pytest._code.Code(obj)
|
||||
except TypeError:
|
||||
try:
|
||||
fn = (py.std.inspect.getsourcefile(obj) or
|
||||
py.std.inspect.getfile(obj))
|
||||
except TypeError:
|
||||
return "", -1
|
||||
|
||||
fspath = fn and py.path.local(fn) or None
|
||||
lineno = -1
|
||||
if fspath:
|
||||
try:
|
||||
_, lineno = findsource(obj)
|
||||
except IOError:
|
||||
pass
|
||||
else:
|
||||
fspath = code.path
|
||||
lineno = code.firstlineno
|
||||
assert isinstance(lineno, int)
|
||||
return fspath, lineno
|
||||
|
||||
#
|
||||
# helper functions
|
||||
#
|
||||
|
||||
def findsource(obj):
|
||||
try:
|
||||
sourcelines, lineno = py.std.inspect.findsource(obj)
|
||||
except py.builtin._sysex:
|
||||
raise
|
||||
except:
|
||||
return None, -1
|
||||
source = Source()
|
||||
source.lines = [line.rstrip() for line in sourcelines]
|
||||
return source, lineno
|
||||
|
||||
def getsource(obj, **kwargs):
|
||||
import _pytest._code
|
||||
obj = _pytest._code.getrawcode(obj)
|
||||
try:
|
||||
strsrc = inspect.getsource(obj)
|
||||
except IndentationError:
|
||||
strsrc = "\"Buggy python version consider upgrading, cannot get source\""
|
||||
assert isinstance(strsrc, str)
|
||||
return Source(strsrc, **kwargs)
|
||||
|
||||
def deindent(lines, offset=None):
|
||||
if offset is None:
|
||||
for line in lines:
|
||||
line = line.expandtabs()
|
||||
s = line.lstrip()
|
||||
if s:
|
||||
offset = len(line)-len(s)
|
||||
break
|
||||
else:
|
||||
offset = 0
|
||||
if offset == 0:
|
||||
return list(lines)
|
||||
newlines = []
|
||||
def readline_generator(lines):
|
||||
for line in lines:
|
||||
yield line + '\n'
|
||||
while True:
|
||||
yield ''
|
||||
|
||||
it = readline_generator(lines)
|
||||
|
||||
try:
|
||||
for _, _, (sline, _), (eline, _), _ in tokenize.generate_tokens(lambda: next(it)):
|
||||
if sline > len(lines):
|
||||
break # End of input reached
|
||||
if sline > len(newlines):
|
||||
line = lines[sline - 1].expandtabs()
|
||||
if line.lstrip() and line[:offset].isspace():
|
||||
line = line[offset:] # Deindent
|
||||
newlines.append(line)
|
||||
|
||||
for i in range(sline, eline):
|
||||
# Don't deindent continuing lines of
|
||||
# multiline tokens (i.e. multiline strings)
|
||||
newlines.append(lines[i])
|
||||
except (IndentationError, tokenize.TokenError):
|
||||
pass
|
||||
# Add any lines we didn't see. E.g. if an exception was raised.
|
||||
newlines.extend(lines[len(newlines):])
|
||||
return newlines
|
||||
|
||||
|
||||
def get_statement_startend2(lineno, node):
|
||||
import ast
|
||||
# flatten all statements and except handlers into one lineno-list
|
||||
# AST's line numbers start indexing at 1
|
||||
l = []
|
||||
for x in ast.walk(node):
|
||||
if isinstance(x, _ast.stmt) or isinstance(x, _ast.ExceptHandler):
|
||||
l.append(x.lineno - 1)
|
||||
for name in "finalbody", "orelse":
|
||||
val = getattr(x, name, None)
|
||||
if val:
|
||||
# treat the finally/orelse part as its own statement
|
||||
l.append(val[0].lineno - 1 - 1)
|
||||
l.sort()
|
||||
insert_index = bisect_right(l, lineno)
|
||||
start = l[insert_index - 1]
|
||||
if insert_index >= len(l):
|
||||
end = None
|
||||
else:
|
||||
end = l[insert_index]
|
||||
return start, end
|
||||
|
||||
|
||||
def getstatementrange_ast(lineno, source, assertion=False, astnode=None):
|
||||
if astnode is None:
|
||||
content = str(source)
|
||||
if sys.version_info < (2,7):
|
||||
content += "\n"
|
||||
try:
|
||||
astnode = compile(content, "source", "exec", 1024) # 1024 for AST
|
||||
except ValueError:
|
||||
start, end = getstatementrange_old(lineno, source, assertion)
|
||||
return None, start, end
|
||||
start, end = get_statement_startend2(lineno, astnode)
|
||||
# we need to correct the end:
|
||||
# - ast-parsing strips comments
|
||||
# - there might be empty lines
|
||||
# - we might have lesser indented code blocks at the end
|
||||
if end is None:
|
||||
end = len(source.lines)
|
||||
|
||||
if end > start + 1:
|
||||
# make sure we don't span differently indented code blocks
|
||||
# by using the BlockFinder helper used which inspect.getsource() uses itself
|
||||
block_finder = inspect.BlockFinder()
|
||||
# if we start with an indented line, put blockfinder to "started" mode
|
||||
block_finder.started = source.lines[start][0].isspace()
|
||||
it = ((x + "\n") for x in source.lines[start:end])
|
||||
try:
|
||||
for tok in tokenize.generate_tokens(lambda: next(it)):
|
||||
block_finder.tokeneater(*tok)
|
||||
except (inspect.EndOfBlock, IndentationError):
|
||||
end = block_finder.last + start
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# the end might still point to a comment or empty line, correct it
|
||||
while end:
|
||||
line = source.lines[end - 1].lstrip()
|
||||
if line.startswith("#") or not line:
|
||||
end -= 1
|
||||
else:
|
||||
break
|
||||
return astnode, start, end
|
||||
|
||||
|
||||
def getstatementrange_old(lineno, source, assertion=False):
|
||||
""" return (start, end) tuple which spans the minimal
|
||||
statement region which containing the given lineno.
|
||||
raise an IndexError if no such statementrange can be found.
|
||||
"""
|
||||
# XXX this logic is only used on python2.4 and below
|
||||
# 1. find the start of the statement
|
||||
from codeop import compile_command
|
||||
for start in range(lineno, -1, -1):
|
||||
if assertion:
|
||||
line = source.lines[start]
|
||||
# the following lines are not fully tested, change with care
|
||||
if 'super' in line and 'self' in line and '__init__' in line:
|
||||
raise IndexError("likely a subclass")
|
||||
if "assert" not in line and "raise" not in line:
|
||||
continue
|
||||
trylines = source.lines[start:lineno+1]
|
||||
# quick hack to prepare parsing an indented line with
|
||||
# compile_command() (which errors on "return" outside defs)
|
||||
trylines.insert(0, 'def xxx():')
|
||||
trysource = '\n '.join(trylines)
|
||||
# ^ space here
|
||||
try:
|
||||
compile_command(trysource)
|
||||
except (SyntaxError, OverflowError, ValueError):
|
||||
continue
|
||||
|
||||
# 2. find the end of the statement
|
||||
for end in range(lineno+1, len(source)+1):
|
||||
trysource = source[start:end]
|
||||
if trysource.isparseable():
|
||||
return start, end
|
||||
raise SyntaxError("no valid source range around line %d " % (lineno,))
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
support for presenting detailed information in failing assertions.
|
||||
"""
|
||||
import py
|
||||
import os
|
||||
import sys
|
||||
from _pytest.monkeypatch import monkeypatch
|
||||
from _pytest.assertion import util
|
||||
@@ -86,6 +87,12 @@ def pytest_collection(session):
|
||||
hook.set_session(session)
|
||||
|
||||
|
||||
def _running_on_ci():
|
||||
"""Check if we're currently running on a CI system."""
|
||||
env_vars = ['CI', 'BUILD_NUMBER']
|
||||
return any(var in os.environ for var in env_vars)
|
||||
|
||||
|
||||
def pytest_runtest_setup(item):
|
||||
"""Setup the pytest_assertrepr_compare hook
|
||||
|
||||
@@ -99,7 +106,8 @@ def pytest_runtest_setup(item):
|
||||
|
||||
This uses the first result from the hook and then ensures the
|
||||
following:
|
||||
* Overly verbose explanations are dropped unles -vv was used.
|
||||
* Overly verbose explanations are dropped unless -vv was used or
|
||||
running on a CI.
|
||||
* Embedded newlines are escaped to help util.format_explanation()
|
||||
later.
|
||||
* If the rewrite mode is used embedded %-characters are replaced
|
||||
@@ -112,8 +120,9 @@ def pytest_runtest_setup(item):
|
||||
config=item.config, op=op, left=left, right=right)
|
||||
for new_expl in hook_result:
|
||||
if new_expl:
|
||||
if (sum(len(p) for p in new_expl[1:]) > 80*8
|
||||
and item.config.option.verbose < 2):
|
||||
if (sum(len(p) for p in new_expl[1:]) > 80*8 and
|
||||
item.config.option.verbose < 2 and
|
||||
not _running_on_ci()):
|
||||
show_max = 10
|
||||
truncated_lines = len(new_expl) - show_max
|
||||
new_expl[show_max:] = [py.builtin._totext(
|
||||
|
||||
@@ -1,365 +0,0 @@
|
||||
"""
|
||||
Find intermediate evalutation results in assert statements through builtin AST.
|
||||
This should replace oldinterpret.py eventually.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import ast
|
||||
|
||||
import py
|
||||
from _pytest.assertion import util
|
||||
from _pytest.assertion.reinterpret import BuiltinAssertionError
|
||||
|
||||
|
||||
if sys.platform.startswith("java"):
|
||||
# See http://bugs.jython.org/issue1497
|
||||
_exprs = ("BoolOp", "BinOp", "UnaryOp", "Lambda", "IfExp", "Dict",
|
||||
"ListComp", "GeneratorExp", "Yield", "Compare", "Call",
|
||||
"Repr", "Num", "Str", "Attribute", "Subscript", "Name",
|
||||
"List", "Tuple")
|
||||
_stmts = ("FunctionDef", "ClassDef", "Return", "Delete", "Assign",
|
||||
"AugAssign", "Print", "For", "While", "If", "With", "Raise",
|
||||
"TryExcept", "TryFinally", "Assert", "Import", "ImportFrom",
|
||||
"Exec", "Global", "Expr", "Pass", "Break", "Continue")
|
||||
_expr_nodes = set(getattr(ast, name) for name in _exprs)
|
||||
_stmt_nodes = set(getattr(ast, name) for name in _stmts)
|
||||
def _is_ast_expr(node):
|
||||
return node.__class__ in _expr_nodes
|
||||
def _is_ast_stmt(node):
|
||||
return node.__class__ in _stmt_nodes
|
||||
else:
|
||||
def _is_ast_expr(node):
|
||||
return isinstance(node, ast.expr)
|
||||
def _is_ast_stmt(node):
|
||||
return isinstance(node, ast.stmt)
|
||||
|
||||
try:
|
||||
_Starred = ast.Starred
|
||||
except AttributeError:
|
||||
# Python 2. Define a dummy class so isinstance() will always be False.
|
||||
class _Starred(object): pass
|
||||
|
||||
|
||||
class Failure(Exception):
|
||||
"""Error found while interpreting AST."""
|
||||
|
||||
def __init__(self, explanation=""):
|
||||
self.cause = sys.exc_info()
|
||||
self.explanation = explanation
|
||||
|
||||
|
||||
def interpret(source, frame, should_fail=False):
|
||||
mod = ast.parse(source)
|
||||
visitor = DebugInterpreter(frame)
|
||||
try:
|
||||
visitor.visit(mod)
|
||||
except Failure:
|
||||
failure = sys.exc_info()[1]
|
||||
return getfailure(failure)
|
||||
if should_fail:
|
||||
return ("(assertion failed, but when it was re-run for "
|
||||
"printing intermediate values, it did not fail. Suggestions: "
|
||||
"compute assert expression before the assert or use --assert=plain)")
|
||||
|
||||
def run(offending_line, frame=None):
|
||||
if frame is None:
|
||||
frame = py.code.Frame(sys._getframe(1))
|
||||
return interpret(offending_line, frame)
|
||||
|
||||
def getfailure(e):
|
||||
explanation = util.format_explanation(e.explanation)
|
||||
value = e.cause[1]
|
||||
if str(value):
|
||||
lines = explanation.split('\n')
|
||||
lines[0] += " << %s" % (value,)
|
||||
explanation = '\n'.join(lines)
|
||||
text = "%s: %s" % (e.cause[0].__name__, explanation)
|
||||
if text.startswith('AssertionError: assert '):
|
||||
text = text[16:]
|
||||
return text
|
||||
|
||||
operator_map = {
|
||||
ast.BitOr : "|",
|
||||
ast.BitXor : "^",
|
||||
ast.BitAnd : "&",
|
||||
ast.LShift : "<<",
|
||||
ast.RShift : ">>",
|
||||
ast.Add : "+",
|
||||
ast.Sub : "-",
|
||||
ast.Mult : "*",
|
||||
ast.Div : "/",
|
||||
ast.FloorDiv : "//",
|
||||
ast.Mod : "%",
|
||||
ast.Eq : "==",
|
||||
ast.NotEq : "!=",
|
||||
ast.Lt : "<",
|
||||
ast.LtE : "<=",
|
||||
ast.Gt : ">",
|
||||
ast.GtE : ">=",
|
||||
ast.Pow : "**",
|
||||
ast.Is : "is",
|
||||
ast.IsNot : "is not",
|
||||
ast.In : "in",
|
||||
ast.NotIn : "not in"
|
||||
}
|
||||
|
||||
unary_map = {
|
||||
ast.Not : "not %s",
|
||||
ast.Invert : "~%s",
|
||||
ast.USub : "-%s",
|
||||
ast.UAdd : "+%s"
|
||||
}
|
||||
|
||||
|
||||
class DebugInterpreter(ast.NodeVisitor):
|
||||
"""Interpret AST nodes to gleam useful debugging information. """
|
||||
|
||||
def __init__(self, frame):
|
||||
self.frame = frame
|
||||
|
||||
def generic_visit(self, node):
|
||||
# Fallback when we don't have a special implementation.
|
||||
if _is_ast_expr(node):
|
||||
mod = ast.Expression(node)
|
||||
co = self._compile(mod)
|
||||
try:
|
||||
result = self.frame.eval(co)
|
||||
except Exception:
|
||||
raise Failure()
|
||||
explanation = self.frame.repr(result)
|
||||
return explanation, result
|
||||
elif _is_ast_stmt(node):
|
||||
mod = ast.Module([node])
|
||||
co = self._compile(mod, "exec")
|
||||
try:
|
||||
self.frame.exec_(co)
|
||||
except Exception:
|
||||
raise Failure()
|
||||
return None, None
|
||||
else:
|
||||
raise AssertionError("can't handle %s" %(node,))
|
||||
|
||||
def _compile(self, source, mode="eval"):
|
||||
return compile(source, "<assertion interpretation>", mode)
|
||||
|
||||
def visit_Expr(self, expr):
|
||||
return self.visit(expr.value)
|
||||
|
||||
def visit_Module(self, mod):
|
||||
for stmt in mod.body:
|
||||
self.visit(stmt)
|
||||
|
||||
def visit_Name(self, name):
|
||||
explanation, result = self.generic_visit(name)
|
||||
# See if the name is local.
|
||||
source = "%r in locals() is not globals()" % (name.id,)
|
||||
co = self._compile(source)
|
||||
try:
|
||||
local = self.frame.eval(co)
|
||||
except Exception:
|
||||
# have to assume it isn't
|
||||
local = None
|
||||
if local is None or not self.frame.is_true(local):
|
||||
return name.id, result
|
||||
return explanation, result
|
||||
|
||||
def visit_Compare(self, comp):
|
||||
left = comp.left
|
||||
left_explanation, left_result = self.visit(left)
|
||||
for op, next_op in zip(comp.ops, comp.comparators):
|
||||
next_explanation, next_result = self.visit(next_op)
|
||||
op_symbol = operator_map[op.__class__]
|
||||
explanation = "%s %s %s" % (left_explanation, op_symbol,
|
||||
next_explanation)
|
||||
source = "__exprinfo_left %s __exprinfo_right" % (op_symbol,)
|
||||
co = self._compile(source)
|
||||
try:
|
||||
result = self.frame.eval(co, __exprinfo_left=left_result,
|
||||
__exprinfo_right=next_result)
|
||||
except Exception:
|
||||
raise Failure(explanation)
|
||||
try:
|
||||
if not self.frame.is_true(result):
|
||||
break
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
break
|
||||
left_explanation, left_result = next_explanation, next_result
|
||||
|
||||
if util._reprcompare is not None:
|
||||
res = util._reprcompare(op_symbol, left_result, next_result)
|
||||
if res:
|
||||
explanation = res
|
||||
return explanation, result
|
||||
|
||||
def visit_BoolOp(self, boolop):
|
||||
is_or = isinstance(boolop.op, ast.Or)
|
||||
explanations = []
|
||||
for operand in boolop.values:
|
||||
explanation, result = self.visit(operand)
|
||||
explanations.append(explanation)
|
||||
if result == is_or:
|
||||
break
|
||||
name = is_or and " or " or " and "
|
||||
explanation = "(" + name.join(explanations) + ")"
|
||||
return explanation, result
|
||||
|
||||
def visit_UnaryOp(self, unary):
|
||||
pattern = unary_map[unary.op.__class__]
|
||||
operand_explanation, operand_result = self.visit(unary.operand)
|
||||
explanation = pattern % (operand_explanation,)
|
||||
co = self._compile(pattern % ("__exprinfo_expr",))
|
||||
try:
|
||||
result = self.frame.eval(co, __exprinfo_expr=operand_result)
|
||||
except Exception:
|
||||
raise Failure(explanation)
|
||||
return explanation, result
|
||||
|
||||
def visit_BinOp(self, binop):
|
||||
left_explanation, left_result = self.visit(binop.left)
|
||||
right_explanation, right_result = self.visit(binop.right)
|
||||
symbol = operator_map[binop.op.__class__]
|
||||
explanation = "(%s %s %s)" % (left_explanation, symbol,
|
||||
right_explanation)
|
||||
source = "__exprinfo_left %s __exprinfo_right" % (symbol,)
|
||||
co = self._compile(source)
|
||||
try:
|
||||
result = self.frame.eval(co, __exprinfo_left=left_result,
|
||||
__exprinfo_right=right_result)
|
||||
except Exception:
|
||||
raise Failure(explanation)
|
||||
return explanation, result
|
||||
|
||||
def visit_Call(self, call):
|
||||
func_explanation, func = self.visit(call.func)
|
||||
arg_explanations = []
|
||||
ns = {"__exprinfo_func" : func}
|
||||
arguments = []
|
||||
for arg in call.args:
|
||||
arg_explanation, arg_result = self.visit(arg)
|
||||
if isinstance(arg, _Starred):
|
||||
arg_name = "__exprinfo_star"
|
||||
ns[arg_name] = arg_result
|
||||
arguments.append("*%s" % (arg_name,))
|
||||
arg_explanations.append("*%s" % (arg_explanation,))
|
||||
else:
|
||||
arg_name = "__exprinfo_%s" % (len(ns),)
|
||||
ns[arg_name] = arg_result
|
||||
arguments.append(arg_name)
|
||||
arg_explanations.append(arg_explanation)
|
||||
for keyword in call.keywords:
|
||||
arg_explanation, arg_result = self.visit(keyword.value)
|
||||
if keyword.arg:
|
||||
arg_name = "__exprinfo_%s" % (len(ns),)
|
||||
keyword_source = "%s=%%s" % (keyword.arg)
|
||||
arguments.append(keyword_source % (arg_name,))
|
||||
arg_explanations.append(keyword_source % (arg_explanation,))
|
||||
else:
|
||||
arg_name = "__exprinfo_kwds"
|
||||
arguments.append("**%s" % (arg_name,))
|
||||
arg_explanations.append("**%s" % (arg_explanation,))
|
||||
|
||||
ns[arg_name] = arg_result
|
||||
|
||||
if getattr(call, 'starargs', None):
|
||||
arg_explanation, arg_result = self.visit(call.starargs)
|
||||
arg_name = "__exprinfo_star"
|
||||
ns[arg_name] = arg_result
|
||||
arguments.append("*%s" % (arg_name,))
|
||||
arg_explanations.append("*%s" % (arg_explanation,))
|
||||
|
||||
if getattr(call, 'kwargs', None):
|
||||
arg_explanation, arg_result = self.visit(call.kwargs)
|
||||
arg_name = "__exprinfo_kwds"
|
||||
ns[arg_name] = arg_result
|
||||
arguments.append("**%s" % (arg_name,))
|
||||
arg_explanations.append("**%s" % (arg_explanation,))
|
||||
args_explained = ", ".join(arg_explanations)
|
||||
explanation = "%s(%s)" % (func_explanation, args_explained)
|
||||
args = ", ".join(arguments)
|
||||
source = "__exprinfo_func(%s)" % (args,)
|
||||
co = self._compile(source)
|
||||
try:
|
||||
result = self.frame.eval(co, **ns)
|
||||
except Exception:
|
||||
raise Failure(explanation)
|
||||
pattern = "%s\n{%s = %s\n}"
|
||||
rep = self.frame.repr(result)
|
||||
explanation = pattern % (rep, rep, explanation)
|
||||
return explanation, result
|
||||
|
||||
def _is_builtin_name(self, name):
|
||||
pattern = "%r not in globals() and %r not in locals()"
|
||||
source = pattern % (name.id, name.id)
|
||||
co = self._compile(source)
|
||||
try:
|
||||
return self.frame.eval(co)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def visit_Attribute(self, attr):
|
||||
if not isinstance(attr.ctx, ast.Load):
|
||||
return self.generic_visit(attr)
|
||||
source_explanation, source_result = self.visit(attr.value)
|
||||
explanation = "%s.%s" % (source_explanation, attr.attr)
|
||||
source = "__exprinfo_expr.%s" % (attr.attr,)
|
||||
co = self._compile(source)
|
||||
try:
|
||||
try:
|
||||
result = self.frame.eval(co, __exprinfo_expr=source_result)
|
||||
except AttributeError:
|
||||
# Maybe the attribute name needs to be mangled?
|
||||
if not attr.attr.startswith("__") or attr.attr.endswith("__"):
|
||||
raise
|
||||
source = "getattr(__exprinfo_expr.__class__, '__name__', '')"
|
||||
co = self._compile(source)
|
||||
class_name = self.frame.eval(co, __exprinfo_expr=source_result)
|
||||
mangled_attr = "_" + class_name + attr.attr
|
||||
source = "__exprinfo_expr.%s" % (mangled_attr,)
|
||||
co = self._compile(source)
|
||||
result = self.frame.eval(co, __exprinfo_expr=source_result)
|
||||
except Exception:
|
||||
raise Failure(explanation)
|
||||
explanation = "%s\n{%s = %s.%s\n}" % (self.frame.repr(result),
|
||||
self.frame.repr(result),
|
||||
source_explanation, attr.attr)
|
||||
# Check if the attr is from an instance.
|
||||
source = "%r in getattr(__exprinfo_expr, '__dict__', {})"
|
||||
source = source % (attr.attr,)
|
||||
co = self._compile(source)
|
||||
try:
|
||||
from_instance = self.frame.eval(co, __exprinfo_expr=source_result)
|
||||
except Exception:
|
||||
from_instance = None
|
||||
if from_instance is None or self.frame.is_true(from_instance):
|
||||
rep = self.frame.repr(result)
|
||||
pattern = "%s\n{%s = %s\n}"
|
||||
explanation = pattern % (rep, rep, explanation)
|
||||
return explanation, result
|
||||
|
||||
def visit_Assert(self, assrt):
|
||||
test_explanation, test_result = self.visit(assrt.test)
|
||||
explanation = "assert %s" % (test_explanation,)
|
||||
if not self.frame.is_true(test_result):
|
||||
try:
|
||||
raise BuiltinAssertionError
|
||||
except Exception:
|
||||
raise Failure(explanation)
|
||||
return explanation, test_result
|
||||
|
||||
def visit_Assign(self, assign):
|
||||
value_explanation, value_result = self.visit(assign.value)
|
||||
explanation = "... = %s" % (value_explanation,)
|
||||
name = ast.Name("__exprinfo_expr", ast.Load(),
|
||||
lineno=assign.value.lineno,
|
||||
col_offset=assign.value.col_offset)
|
||||
new_assign = ast.Assign(assign.targets, name, lineno=assign.lineno,
|
||||
col_offset=assign.col_offset)
|
||||
mod = ast.Module([new_assign])
|
||||
co = self._compile(mod, "exec")
|
||||
try:
|
||||
self.frame.exec_(co, __exprinfo_expr=value_result)
|
||||
except Exception:
|
||||
raise Failure(explanation)
|
||||
return explanation, value_result
|
||||
@@ -1,566 +0,0 @@
|
||||
import traceback
|
||||
import types
|
||||
import py
|
||||
import sys, inspect
|
||||
from compiler import parse, ast, pycodegen
|
||||
from _pytest.assertion.util import format_explanation, BuiltinAssertionError
|
||||
|
||||
passthroughex = py.builtin._sysex
|
||||
|
||||
class Failure:
|
||||
def __init__(self, node):
|
||||
self.exc, self.value, self.tb = sys.exc_info()
|
||||
self.node = node
|
||||
|
||||
class View(object):
|
||||
"""View base class.
|
||||
|
||||
If C is a subclass of View, then C(x) creates a proxy object around
|
||||
the object x. The actual class of the proxy is not C in general,
|
||||
but a *subclass* of C determined by the rules below. To avoid confusion
|
||||
we call view class the class of the proxy (a subclass of C, so of View)
|
||||
and object class the class of x.
|
||||
|
||||
Attributes and methods not found in the proxy are automatically read on x.
|
||||
Other operations like setting attributes are performed on the proxy, as
|
||||
determined by its view class. The object x is available from the proxy
|
||||
as its __obj__ attribute.
|
||||
|
||||
The view class selection is determined by the __view__ tuples and the
|
||||
optional __viewkey__ method. By default, the selected view class is the
|
||||
most specific subclass of C whose __view__ mentions the class of x.
|
||||
If no such subclass is found, the search proceeds with the parent
|
||||
object classes. For example, C(True) will first look for a subclass
|
||||
of C with __view__ = (..., bool, ...) and only if it doesn't find any
|
||||
look for one with __view__ = (..., int, ...), and then ..., object,...
|
||||
If everything fails the class C itself is considered to be the default.
|
||||
|
||||
Alternatively, the view class selection can be driven by another aspect
|
||||
of the object x, instead of the class of x, by overriding __viewkey__.
|
||||
See last example at the end of this module.
|
||||
"""
|
||||
|
||||
_viewcache = {}
|
||||
__view__ = ()
|
||||
|
||||
def __new__(rootclass, obj, *args, **kwds):
|
||||
self = object.__new__(rootclass)
|
||||
self.__obj__ = obj
|
||||
self.__rootclass__ = rootclass
|
||||
key = self.__viewkey__()
|
||||
try:
|
||||
self.__class__ = self._viewcache[key]
|
||||
except KeyError:
|
||||
self.__class__ = self._selectsubclass(key)
|
||||
return self
|
||||
|
||||
def __getattr__(self, attr):
|
||||
# attributes not found in the normal hierarchy rooted on View
|
||||
# are looked up in the object's real class
|
||||
return getattr(object.__getattribute__(self, '__obj__'), attr)
|
||||
|
||||
def __viewkey__(self):
|
||||
return self.__obj__.__class__
|
||||
|
||||
def __matchkey__(self, key, subclasses):
|
||||
if inspect.isclass(key):
|
||||
keys = inspect.getmro(key)
|
||||
else:
|
||||
keys = [key]
|
||||
for key in keys:
|
||||
result = [C for C in subclasses if key in C.__view__]
|
||||
if result:
|
||||
return result
|
||||
return []
|
||||
|
||||
def _selectsubclass(self, key):
|
||||
subclasses = list(enumsubclasses(self.__rootclass__))
|
||||
for C in subclasses:
|
||||
if not isinstance(C.__view__, tuple):
|
||||
C.__view__ = (C.__view__,)
|
||||
choices = self.__matchkey__(key, subclasses)
|
||||
if not choices:
|
||||
return self.__rootclass__
|
||||
elif len(choices) == 1:
|
||||
return choices[0]
|
||||
else:
|
||||
# combine the multiple choices
|
||||
return type('?', tuple(choices), {})
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%r)' % (self.__rootclass__.__name__, self.__obj__)
|
||||
|
||||
|
||||
def enumsubclasses(cls):
|
||||
for subcls in cls.__subclasses__():
|
||||
for subsubclass in enumsubclasses(subcls):
|
||||
yield subsubclass
|
||||
yield cls
|
||||
|
||||
|
||||
class Interpretable(View):
|
||||
"""A parse tree node with a few extra methods."""
|
||||
explanation = None
|
||||
|
||||
def is_builtin(self, frame):
|
||||
return False
|
||||
|
||||
def eval(self, frame):
|
||||
# fall-back for unknown expression nodes
|
||||
try:
|
||||
expr = ast.Expression(self.__obj__)
|
||||
expr.filename = '<eval>'
|
||||
self.__obj__.filename = '<eval>'
|
||||
co = pycodegen.ExpressionCodeGenerator(expr).getCode()
|
||||
result = frame.eval(co)
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
raise Failure(self)
|
||||
self.result = result
|
||||
self.explanation = self.explanation or frame.repr(self.result)
|
||||
|
||||
def run(self, frame):
|
||||
# fall-back for unknown statement nodes
|
||||
try:
|
||||
expr = ast.Module(None, ast.Stmt([self.__obj__]))
|
||||
expr.filename = '<run>'
|
||||
co = pycodegen.ModuleCodeGenerator(expr).getCode()
|
||||
frame.exec_(co)
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
raise Failure(self)
|
||||
|
||||
def nice_explanation(self):
|
||||
return format_explanation(self.explanation)
|
||||
|
||||
|
||||
class Name(Interpretable):
|
||||
__view__ = ast.Name
|
||||
|
||||
def is_local(self, frame):
|
||||
source = '%r in locals() is not globals()' % self.name
|
||||
try:
|
||||
return frame.is_true(frame.eval(source))
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
return False
|
||||
|
||||
def is_global(self, frame):
|
||||
source = '%r in globals()' % self.name
|
||||
try:
|
||||
return frame.is_true(frame.eval(source))
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
return False
|
||||
|
||||
def is_builtin(self, frame):
|
||||
source = '%r not in locals() and %r not in globals()' % (
|
||||
self.name, self.name)
|
||||
try:
|
||||
return frame.is_true(frame.eval(source))
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
return False
|
||||
|
||||
def eval(self, frame):
|
||||
super(Name, self).eval(frame)
|
||||
if not self.is_local(frame):
|
||||
self.explanation = self.name
|
||||
|
||||
class Compare(Interpretable):
|
||||
__view__ = ast.Compare
|
||||
|
||||
def eval(self, frame):
|
||||
expr = Interpretable(self.expr)
|
||||
expr.eval(frame)
|
||||
for operation, expr2 in self.ops:
|
||||
if hasattr(self, 'result'):
|
||||
# shortcutting in chained expressions
|
||||
if not frame.is_true(self.result):
|
||||
break
|
||||
expr2 = Interpretable(expr2)
|
||||
expr2.eval(frame)
|
||||
self.explanation = "%s %s %s" % (
|
||||
expr.explanation, operation, expr2.explanation)
|
||||
source = "__exprinfo_left %s __exprinfo_right" % operation
|
||||
try:
|
||||
self.result = frame.eval(source,
|
||||
__exprinfo_left=expr.result,
|
||||
__exprinfo_right=expr2.result)
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
raise Failure(self)
|
||||
expr = expr2
|
||||
|
||||
class And(Interpretable):
|
||||
__view__ = ast.And
|
||||
|
||||
def eval(self, frame):
|
||||
explanations = []
|
||||
for expr in self.nodes:
|
||||
expr = Interpretable(expr)
|
||||
expr.eval(frame)
|
||||
explanations.append(expr.explanation)
|
||||
self.result = expr.result
|
||||
if not frame.is_true(expr.result):
|
||||
break
|
||||
self.explanation = '(' + ' and '.join(explanations) + ')'
|
||||
|
||||
class Or(Interpretable):
|
||||
__view__ = ast.Or
|
||||
|
||||
def eval(self, frame):
|
||||
explanations = []
|
||||
for expr in self.nodes:
|
||||
expr = Interpretable(expr)
|
||||
expr.eval(frame)
|
||||
explanations.append(expr.explanation)
|
||||
self.result = expr.result
|
||||
if frame.is_true(expr.result):
|
||||
break
|
||||
self.explanation = '(' + ' or '.join(explanations) + ')'
|
||||
|
||||
|
||||
# == Unary operations ==
|
||||
keepalive = []
|
||||
for astclass, astpattern in {
|
||||
ast.Not : 'not __exprinfo_expr',
|
||||
ast.Invert : '(~__exprinfo_expr)',
|
||||
}.items():
|
||||
|
||||
class UnaryArith(Interpretable):
|
||||
__view__ = astclass
|
||||
|
||||
def eval(self, frame, astpattern=astpattern):
|
||||
expr = Interpretable(self.expr)
|
||||
expr.eval(frame)
|
||||
self.explanation = astpattern.replace('__exprinfo_expr',
|
||||
expr.explanation)
|
||||
try:
|
||||
self.result = frame.eval(astpattern,
|
||||
__exprinfo_expr=expr.result)
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
raise Failure(self)
|
||||
|
||||
keepalive.append(UnaryArith)
|
||||
|
||||
# == Binary operations ==
|
||||
for astclass, astpattern in {
|
||||
ast.Add : '(__exprinfo_left + __exprinfo_right)',
|
||||
ast.Sub : '(__exprinfo_left - __exprinfo_right)',
|
||||
ast.Mul : '(__exprinfo_left * __exprinfo_right)',
|
||||
ast.Div : '(__exprinfo_left / __exprinfo_right)',
|
||||
ast.Mod : '(__exprinfo_left % __exprinfo_right)',
|
||||
ast.Power : '(__exprinfo_left ** __exprinfo_right)',
|
||||
}.items():
|
||||
|
||||
class BinaryArith(Interpretable):
|
||||
__view__ = astclass
|
||||
|
||||
def eval(self, frame, astpattern=astpattern):
|
||||
left = Interpretable(self.left)
|
||||
left.eval(frame)
|
||||
right = Interpretable(self.right)
|
||||
right.eval(frame)
|
||||
self.explanation = (astpattern
|
||||
.replace('__exprinfo_left', left .explanation)
|
||||
.replace('__exprinfo_right', right.explanation))
|
||||
try:
|
||||
self.result = frame.eval(astpattern,
|
||||
__exprinfo_left=left.result,
|
||||
__exprinfo_right=right.result)
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
raise Failure(self)
|
||||
|
||||
keepalive.append(BinaryArith)
|
||||
|
||||
|
||||
class CallFunc(Interpretable):
|
||||
__view__ = ast.CallFunc
|
||||
|
||||
def is_bool(self, frame):
|
||||
source = 'isinstance(__exprinfo_value, bool)'
|
||||
try:
|
||||
return frame.is_true(frame.eval(source,
|
||||
__exprinfo_value=self.result))
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
return False
|
||||
|
||||
def eval(self, frame):
|
||||
node = Interpretable(self.node)
|
||||
node.eval(frame)
|
||||
explanations = []
|
||||
vars = {'__exprinfo_fn': node.result}
|
||||
source = '__exprinfo_fn('
|
||||
for a in self.args:
|
||||
if isinstance(a, ast.Keyword):
|
||||
keyword = a.name
|
||||
a = a.expr
|
||||
else:
|
||||
keyword = None
|
||||
a = Interpretable(a)
|
||||
a.eval(frame)
|
||||
argname = '__exprinfo_%d' % len(vars)
|
||||
vars[argname] = a.result
|
||||
if keyword is None:
|
||||
source += argname + ','
|
||||
explanations.append(a.explanation)
|
||||
else:
|
||||
source += '%s=%s,' % (keyword, argname)
|
||||
explanations.append('%s=%s' % (keyword, a.explanation))
|
||||
if self.star_args:
|
||||
star_args = Interpretable(self.star_args)
|
||||
star_args.eval(frame)
|
||||
argname = '__exprinfo_star'
|
||||
vars[argname] = star_args.result
|
||||
source += '*' + argname + ','
|
||||
explanations.append('*' + star_args.explanation)
|
||||
if self.dstar_args:
|
||||
dstar_args = Interpretable(self.dstar_args)
|
||||
dstar_args.eval(frame)
|
||||
argname = '__exprinfo_kwds'
|
||||
vars[argname] = dstar_args.result
|
||||
source += '**' + argname + ','
|
||||
explanations.append('**' + dstar_args.explanation)
|
||||
self.explanation = "%s(%s)" % (
|
||||
node.explanation, ', '.join(explanations))
|
||||
if source.endswith(','):
|
||||
source = source[:-1]
|
||||
source += ')'
|
||||
try:
|
||||
self.result = frame.eval(source, **vars)
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
raise Failure(self)
|
||||
if not node.is_builtin(frame) or not self.is_bool(frame):
|
||||
r = frame.repr(self.result)
|
||||
self.explanation = '%s\n{%s = %s\n}' % (r, r, self.explanation)
|
||||
|
||||
class Getattr(Interpretable):
|
||||
__view__ = ast.Getattr
|
||||
|
||||
def eval(self, frame):
|
||||
expr = Interpretable(self.expr)
|
||||
expr.eval(frame)
|
||||
source = '__exprinfo_expr.%s' % self.attrname
|
||||
try:
|
||||
try:
|
||||
self.result = frame.eval(source, __exprinfo_expr=expr.result)
|
||||
except AttributeError:
|
||||
# Maybe the attribute name needs to be mangled?
|
||||
if (not self.attrname.startswith("__") or
|
||||
self.attrname.endswith("__")):
|
||||
raise
|
||||
source = "getattr(__exprinfo_expr.__class__, '__name__', '')"
|
||||
class_name = frame.eval(source, __exprinfo_expr=expr.result)
|
||||
mangled_attr = "_" + class_name + self.attrname
|
||||
source = "__exprinfo_expr.%s" % (mangled_attr,)
|
||||
self.result = frame.eval(source, __exprinfo_expr=expr.result)
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
raise Failure(self)
|
||||
self.explanation = '%s.%s' % (expr.explanation, self.attrname)
|
||||
# if the attribute comes from the instance, its value is interesting
|
||||
source = ('hasattr(__exprinfo_expr, "__dict__") and '
|
||||
'%r in __exprinfo_expr.__dict__' % self.attrname)
|
||||
try:
|
||||
from_instance = frame.is_true(
|
||||
frame.eval(source, __exprinfo_expr=expr.result))
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
from_instance = True
|
||||
if from_instance:
|
||||
r = frame.repr(self.result)
|
||||
self.explanation = '%s\n{%s = %s\n}' % (r, r, self.explanation)
|
||||
|
||||
# == Re-interpretation of full statements ==
|
||||
|
||||
class Assert(Interpretable):
|
||||
__view__ = ast.Assert
|
||||
|
||||
def run(self, frame):
|
||||
test = Interpretable(self.test)
|
||||
test.eval(frame)
|
||||
# print the result as 'assert <explanation>'
|
||||
self.result = test.result
|
||||
self.explanation = 'assert ' + test.explanation
|
||||
if not frame.is_true(test.result):
|
||||
try:
|
||||
raise BuiltinAssertionError
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
raise Failure(self)
|
||||
|
||||
class Assign(Interpretable):
|
||||
__view__ = ast.Assign
|
||||
|
||||
def run(self, frame):
|
||||
expr = Interpretable(self.expr)
|
||||
expr.eval(frame)
|
||||
self.result = expr.result
|
||||
self.explanation = '... = ' + expr.explanation
|
||||
# fall-back-run the rest of the assignment
|
||||
ass = ast.Assign(self.nodes, ast.Name('__exprinfo_expr'))
|
||||
mod = ast.Module(None, ast.Stmt([ass]))
|
||||
mod.filename = '<run>'
|
||||
co = pycodegen.ModuleCodeGenerator(mod).getCode()
|
||||
try:
|
||||
frame.exec_(co, __exprinfo_expr=expr.result)
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
raise Failure(self)
|
||||
|
||||
class Discard(Interpretable):
|
||||
__view__ = ast.Discard
|
||||
|
||||
def run(self, frame):
|
||||
expr = Interpretable(self.expr)
|
||||
expr.eval(frame)
|
||||
self.result = expr.result
|
||||
self.explanation = expr.explanation
|
||||
|
||||
class Stmt(Interpretable):
|
||||
__view__ = ast.Stmt
|
||||
|
||||
def run(self, frame):
|
||||
for stmt in self.nodes:
|
||||
stmt = Interpretable(stmt)
|
||||
stmt.run(frame)
|
||||
|
||||
|
||||
def report_failure(e):
|
||||
explanation = e.node.nice_explanation()
|
||||
if explanation:
|
||||
explanation = ", in: " + explanation
|
||||
else:
|
||||
explanation = ""
|
||||
sys.stdout.write("%s: %s%s\n" % (e.exc.__name__, e.value, explanation))
|
||||
|
||||
def check(s, frame=None):
|
||||
if frame is None:
|
||||
frame = sys._getframe(1)
|
||||
frame = py.code.Frame(frame)
|
||||
expr = parse(s, 'eval')
|
||||
assert isinstance(expr, ast.Expression)
|
||||
node = Interpretable(expr.node)
|
||||
try:
|
||||
node.eval(frame)
|
||||
except passthroughex:
|
||||
raise
|
||||
except Failure:
|
||||
e = sys.exc_info()[1]
|
||||
report_failure(e)
|
||||
else:
|
||||
if not frame.is_true(node.result):
|
||||
sys.stderr.write("assertion failed: %s\n" % node.nice_explanation())
|
||||
|
||||
|
||||
###########################################################
|
||||
# API / Entry points
|
||||
# #########################################################
|
||||
|
||||
def interpret(source, frame, should_fail=False):
|
||||
module = Interpretable(parse(source, 'exec').node)
|
||||
#print "got module", module
|
||||
if isinstance(frame, types.FrameType):
|
||||
frame = py.code.Frame(frame)
|
||||
try:
|
||||
module.run(frame)
|
||||
except Failure:
|
||||
e = sys.exc_info()[1]
|
||||
return getfailure(e)
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
traceback.print_exc()
|
||||
if should_fail:
|
||||
return ("(assertion failed, but when it was re-run for "
|
||||
"printing intermediate values, it did not fail. Suggestions: "
|
||||
"compute assert expression before the assert or use --assert=plain)")
|
||||
else:
|
||||
return None
|
||||
|
||||
def getmsg(excinfo):
|
||||
if isinstance(excinfo, tuple):
|
||||
excinfo = py.code.ExceptionInfo(excinfo)
|
||||
#frame, line = gettbline(tb)
|
||||
#frame = py.code.Frame(frame)
|
||||
#return interpret(line, frame)
|
||||
|
||||
tb = excinfo.traceback[-1]
|
||||
source = str(tb.statement).strip()
|
||||
x = interpret(source, tb.frame, should_fail=True)
|
||||
if not isinstance(x, str):
|
||||
raise TypeError("interpret returned non-string %r" % (x,))
|
||||
return x
|
||||
|
||||
def getfailure(e):
|
||||
explanation = e.node.nice_explanation()
|
||||
if str(e.value):
|
||||
lines = explanation.split('\n')
|
||||
lines[0] += " << %s" % (e.value,)
|
||||
explanation = '\n'.join(lines)
|
||||
text = "%s: %s" % (e.exc.__name__, explanation)
|
||||
if text.startswith('AssertionError: assert '):
|
||||
text = text[16:]
|
||||
return text
|
||||
|
||||
def run(s, frame=None):
|
||||
if frame is None:
|
||||
frame = sys._getframe(1)
|
||||
frame = py.code.Frame(frame)
|
||||
module = Interpretable(parse(s, 'exec').node)
|
||||
try:
|
||||
module.run(frame)
|
||||
except Failure:
|
||||
e = sys.exc_info()[1]
|
||||
report_failure(e)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# example:
|
||||
def f():
|
||||
return 5
|
||||
|
||||
def g():
|
||||
return 3
|
||||
|
||||
def h(x):
|
||||
return 'never'
|
||||
|
||||
check("f() * g() == 5")
|
||||
check("not f()")
|
||||
check("not (f() and g() or 0)")
|
||||
check("f() == g()")
|
||||
i = 4
|
||||
check("i == f()")
|
||||
check("len(f()) == 0")
|
||||
check("isinstance(2+3+4, float)")
|
||||
|
||||
run("x = i")
|
||||
check("x == 5")
|
||||
|
||||
run("assert not f(), 'oops'")
|
||||
run("a, b, c = 1, 2")
|
||||
run("a, b, c = f()")
|
||||
|
||||
check("max([f(),g()]) == 4")
|
||||
check("'hello'[g()] == 'h'")
|
||||
run("'guk%d' % h(f())")
|
||||
@@ -1,12 +1,18 @@
|
||||
"""
|
||||
Find intermediate evalutation results in assert statements through builtin AST.
|
||||
"""
|
||||
import ast
|
||||
import sys
|
||||
|
||||
import _pytest._code
|
||||
import py
|
||||
from _pytest.assertion.util import BuiltinAssertionError
|
||||
from _pytest.assertion import util
|
||||
u = py.builtin._totext
|
||||
|
||||
|
||||
class AssertionError(BuiltinAssertionError):
|
||||
class AssertionError(util.BuiltinAssertionError):
|
||||
def __init__(self, *args):
|
||||
BuiltinAssertionError.__init__(self, *args)
|
||||
util.BuiltinAssertionError.__init__(self, *args)
|
||||
if args:
|
||||
# on Python2.6 we get len(args)==2 for: assert 0, (x,y)
|
||||
# on Python2.7 and above we always get len(args) == 1
|
||||
@@ -22,7 +28,7 @@ class AssertionError(BuiltinAssertionError):
|
||||
"<[broken __repr__] %s at %0xd>"
|
||||
% (toprint.__class__, id(toprint)))
|
||||
else:
|
||||
f = py.code.Frame(sys._getframe(1))
|
||||
f = _pytest._code.Frame(sys._getframe(1))
|
||||
try:
|
||||
source = f.code.fullsource
|
||||
if source is not None:
|
||||
@@ -46,7 +52,356 @@ class AssertionError(BuiltinAssertionError):
|
||||
if sys.version_info > (3, 0):
|
||||
AssertionError.__module__ = "builtins"
|
||||
|
||||
if sys.version_info >= (2, 6) or sys.platform.startswith("java"):
|
||||
from _pytest.assertion.newinterpret import interpret as reinterpret
|
||||
if sys.platform.startswith("java"):
|
||||
# See http://bugs.jython.org/issue1497
|
||||
_exprs = ("BoolOp", "BinOp", "UnaryOp", "Lambda", "IfExp", "Dict",
|
||||
"ListComp", "GeneratorExp", "Yield", "Compare", "Call",
|
||||
"Repr", "Num", "Str", "Attribute", "Subscript", "Name",
|
||||
"List", "Tuple")
|
||||
_stmts = ("FunctionDef", "ClassDef", "Return", "Delete", "Assign",
|
||||
"AugAssign", "Print", "For", "While", "If", "With", "Raise",
|
||||
"TryExcept", "TryFinally", "Assert", "Import", "ImportFrom",
|
||||
"Exec", "Global", "Expr", "Pass", "Break", "Continue")
|
||||
_expr_nodes = set(getattr(ast, name) for name in _exprs)
|
||||
_stmt_nodes = set(getattr(ast, name) for name in _stmts)
|
||||
def _is_ast_expr(node):
|
||||
return node.__class__ in _expr_nodes
|
||||
def _is_ast_stmt(node):
|
||||
return node.__class__ in _stmt_nodes
|
||||
else:
|
||||
from _pytest.assertion.oldinterpret import interpret as reinterpret
|
||||
def _is_ast_expr(node):
|
||||
return isinstance(node, ast.expr)
|
||||
def _is_ast_stmt(node):
|
||||
return isinstance(node, ast.stmt)
|
||||
|
||||
try:
|
||||
_Starred = ast.Starred
|
||||
except AttributeError:
|
||||
# Python 2. Define a dummy class so isinstance() will always be False.
|
||||
class _Starred(object): pass
|
||||
|
||||
|
||||
class Failure(Exception):
|
||||
"""Error found while interpreting AST."""
|
||||
|
||||
def __init__(self, explanation=""):
|
||||
self.cause = sys.exc_info()
|
||||
self.explanation = explanation
|
||||
|
||||
|
||||
def reinterpret(source, frame, should_fail=False):
|
||||
mod = ast.parse(source)
|
||||
visitor = DebugInterpreter(frame)
|
||||
try:
|
||||
visitor.visit(mod)
|
||||
except Failure:
|
||||
failure = sys.exc_info()[1]
|
||||
return getfailure(failure)
|
||||
if should_fail:
|
||||
return ("(assertion failed, but when it was re-run for "
|
||||
"printing intermediate values, it did not fail. Suggestions: "
|
||||
"compute assert expression before the assert or use --assert=plain)")
|
||||
|
||||
def run(offending_line, frame=None):
|
||||
if frame is None:
|
||||
frame = _pytest._code.Frame(sys._getframe(1))
|
||||
return reinterpret(offending_line, frame)
|
||||
|
||||
def getfailure(e):
|
||||
explanation = util.format_explanation(e.explanation)
|
||||
value = e.cause[1]
|
||||
if str(value):
|
||||
lines = explanation.split('\n')
|
||||
lines[0] += " << %s" % (value,)
|
||||
explanation = '\n'.join(lines)
|
||||
text = "%s: %s" % (e.cause[0].__name__, explanation)
|
||||
if text.startswith('AssertionError: assert '):
|
||||
text = text[16:]
|
||||
return text
|
||||
|
||||
operator_map = {
|
||||
ast.BitOr : "|",
|
||||
ast.BitXor : "^",
|
||||
ast.BitAnd : "&",
|
||||
ast.LShift : "<<",
|
||||
ast.RShift : ">>",
|
||||
ast.Add : "+",
|
||||
ast.Sub : "-",
|
||||
ast.Mult : "*",
|
||||
ast.Div : "/",
|
||||
ast.FloorDiv : "//",
|
||||
ast.Mod : "%",
|
||||
ast.Eq : "==",
|
||||
ast.NotEq : "!=",
|
||||
ast.Lt : "<",
|
||||
ast.LtE : "<=",
|
||||
ast.Gt : ">",
|
||||
ast.GtE : ">=",
|
||||
ast.Pow : "**",
|
||||
ast.Is : "is",
|
||||
ast.IsNot : "is not",
|
||||
ast.In : "in",
|
||||
ast.NotIn : "not in"
|
||||
}
|
||||
|
||||
unary_map = {
|
||||
ast.Not : "not %s",
|
||||
ast.Invert : "~%s",
|
||||
ast.USub : "-%s",
|
||||
ast.UAdd : "+%s"
|
||||
}
|
||||
|
||||
|
||||
class DebugInterpreter(ast.NodeVisitor):
|
||||
"""Interpret AST nodes to gleam useful debugging information. """
|
||||
|
||||
def __init__(self, frame):
|
||||
self.frame = frame
|
||||
|
||||
def generic_visit(self, node):
|
||||
# Fallback when we don't have a special implementation.
|
||||
if _is_ast_expr(node):
|
||||
mod = ast.Expression(node)
|
||||
co = self._compile(mod)
|
||||
try:
|
||||
result = self.frame.eval(co)
|
||||
except Exception:
|
||||
raise Failure()
|
||||
explanation = self.frame.repr(result)
|
||||
return explanation, result
|
||||
elif _is_ast_stmt(node):
|
||||
mod = ast.Module([node])
|
||||
co = self._compile(mod, "exec")
|
||||
try:
|
||||
self.frame.exec_(co)
|
||||
except Exception:
|
||||
raise Failure()
|
||||
return None, None
|
||||
else:
|
||||
raise AssertionError("can't handle %s" %(node,))
|
||||
|
||||
def _compile(self, source, mode="eval"):
|
||||
return compile(source, "<assertion interpretation>", mode)
|
||||
|
||||
def visit_Expr(self, expr):
|
||||
return self.visit(expr.value)
|
||||
|
||||
def visit_Module(self, mod):
|
||||
for stmt in mod.body:
|
||||
self.visit(stmt)
|
||||
|
||||
def visit_Name(self, name):
|
||||
explanation, result = self.generic_visit(name)
|
||||
# See if the name is local.
|
||||
source = "%r in locals() is not globals()" % (name.id,)
|
||||
co = self._compile(source)
|
||||
try:
|
||||
local = self.frame.eval(co)
|
||||
except Exception:
|
||||
# have to assume it isn't
|
||||
local = None
|
||||
if local is None or not self.frame.is_true(local):
|
||||
return name.id, result
|
||||
return explanation, result
|
||||
|
||||
def visit_Compare(self, comp):
|
||||
left = comp.left
|
||||
left_explanation, left_result = self.visit(left)
|
||||
for op, next_op in zip(comp.ops, comp.comparators):
|
||||
next_explanation, next_result = self.visit(next_op)
|
||||
op_symbol = operator_map[op.__class__]
|
||||
explanation = "%s %s %s" % (left_explanation, op_symbol,
|
||||
next_explanation)
|
||||
source = "__exprinfo_left %s __exprinfo_right" % (op_symbol,)
|
||||
co = self._compile(source)
|
||||
try:
|
||||
result = self.frame.eval(co, __exprinfo_left=left_result,
|
||||
__exprinfo_right=next_result)
|
||||
except Exception:
|
||||
raise Failure(explanation)
|
||||
try:
|
||||
if not self.frame.is_true(result):
|
||||
break
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
break
|
||||
left_explanation, left_result = next_explanation, next_result
|
||||
|
||||
if util._reprcompare is not None:
|
||||
res = util._reprcompare(op_symbol, left_result, next_result)
|
||||
if res:
|
||||
explanation = res
|
||||
return explanation, result
|
||||
|
||||
def visit_BoolOp(self, boolop):
|
||||
is_or = isinstance(boolop.op, ast.Or)
|
||||
explanations = []
|
||||
for operand in boolop.values:
|
||||
explanation, result = self.visit(operand)
|
||||
explanations.append(explanation)
|
||||
if result == is_or:
|
||||
break
|
||||
name = is_or and " or " or " and "
|
||||
explanation = "(" + name.join(explanations) + ")"
|
||||
return explanation, result
|
||||
|
||||
def visit_UnaryOp(self, unary):
|
||||
pattern = unary_map[unary.op.__class__]
|
||||
operand_explanation, operand_result = self.visit(unary.operand)
|
||||
explanation = pattern % (operand_explanation,)
|
||||
co = self._compile(pattern % ("__exprinfo_expr",))
|
||||
try:
|
||||
result = self.frame.eval(co, __exprinfo_expr=operand_result)
|
||||
except Exception:
|
||||
raise Failure(explanation)
|
||||
return explanation, result
|
||||
|
||||
def visit_BinOp(self, binop):
|
||||
left_explanation, left_result = self.visit(binop.left)
|
||||
right_explanation, right_result = self.visit(binop.right)
|
||||
symbol = operator_map[binop.op.__class__]
|
||||
explanation = "(%s %s %s)" % (left_explanation, symbol,
|
||||
right_explanation)
|
||||
source = "__exprinfo_left %s __exprinfo_right" % (symbol,)
|
||||
co = self._compile(source)
|
||||
try:
|
||||
result = self.frame.eval(co, __exprinfo_left=left_result,
|
||||
__exprinfo_right=right_result)
|
||||
except Exception:
|
||||
raise Failure(explanation)
|
||||
return explanation, result
|
||||
|
||||
def visit_Call(self, call):
|
||||
func_explanation, func = self.visit(call.func)
|
||||
arg_explanations = []
|
||||
ns = {"__exprinfo_func" : func}
|
||||
arguments = []
|
||||
for arg in call.args:
|
||||
arg_explanation, arg_result = self.visit(arg)
|
||||
if isinstance(arg, _Starred):
|
||||
arg_name = "__exprinfo_star"
|
||||
ns[arg_name] = arg_result
|
||||
arguments.append("*%s" % (arg_name,))
|
||||
arg_explanations.append("*%s" % (arg_explanation,))
|
||||
else:
|
||||
arg_name = "__exprinfo_%s" % (len(ns),)
|
||||
ns[arg_name] = arg_result
|
||||
arguments.append(arg_name)
|
||||
arg_explanations.append(arg_explanation)
|
||||
for keyword in call.keywords:
|
||||
arg_explanation, arg_result = self.visit(keyword.value)
|
||||
if keyword.arg:
|
||||
arg_name = "__exprinfo_%s" % (len(ns),)
|
||||
keyword_source = "%s=%%s" % (keyword.arg)
|
||||
arguments.append(keyword_source % (arg_name,))
|
||||
arg_explanations.append(keyword_source % (arg_explanation,))
|
||||
else:
|
||||
arg_name = "__exprinfo_kwds"
|
||||
arguments.append("**%s" % (arg_name,))
|
||||
arg_explanations.append("**%s" % (arg_explanation,))
|
||||
|
||||
ns[arg_name] = arg_result
|
||||
|
||||
if getattr(call, 'starargs', None):
|
||||
arg_explanation, arg_result = self.visit(call.starargs)
|
||||
arg_name = "__exprinfo_star"
|
||||
ns[arg_name] = arg_result
|
||||
arguments.append("*%s" % (arg_name,))
|
||||
arg_explanations.append("*%s" % (arg_explanation,))
|
||||
|
||||
if getattr(call, 'kwargs', None):
|
||||
arg_explanation, arg_result = self.visit(call.kwargs)
|
||||
arg_name = "__exprinfo_kwds"
|
||||
ns[arg_name] = arg_result
|
||||
arguments.append("**%s" % (arg_name,))
|
||||
arg_explanations.append("**%s" % (arg_explanation,))
|
||||
args_explained = ", ".join(arg_explanations)
|
||||
explanation = "%s(%s)" % (func_explanation, args_explained)
|
||||
args = ", ".join(arguments)
|
||||
source = "__exprinfo_func(%s)" % (args,)
|
||||
co = self._compile(source)
|
||||
try:
|
||||
result = self.frame.eval(co, **ns)
|
||||
except Exception:
|
||||
raise Failure(explanation)
|
||||
pattern = "%s\n{%s = %s\n}"
|
||||
rep = self.frame.repr(result)
|
||||
explanation = pattern % (rep, rep, explanation)
|
||||
return explanation, result
|
||||
|
||||
def _is_builtin_name(self, name):
|
||||
pattern = "%r not in globals() and %r not in locals()"
|
||||
source = pattern % (name.id, name.id)
|
||||
co = self._compile(source)
|
||||
try:
|
||||
return self.frame.eval(co)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def visit_Attribute(self, attr):
|
||||
if not isinstance(attr.ctx, ast.Load):
|
||||
return self.generic_visit(attr)
|
||||
source_explanation, source_result = self.visit(attr.value)
|
||||
explanation = "%s.%s" % (source_explanation, attr.attr)
|
||||
source = "__exprinfo_expr.%s" % (attr.attr,)
|
||||
co = self._compile(source)
|
||||
try:
|
||||
try:
|
||||
result = self.frame.eval(co, __exprinfo_expr=source_result)
|
||||
except AttributeError:
|
||||
# Maybe the attribute name needs to be mangled?
|
||||
if not attr.attr.startswith("__") or attr.attr.endswith("__"):
|
||||
raise
|
||||
source = "getattr(__exprinfo_expr.__class__, '__name__', '')"
|
||||
co = self._compile(source)
|
||||
class_name = self.frame.eval(co, __exprinfo_expr=source_result)
|
||||
mangled_attr = "_" + class_name + attr.attr
|
||||
source = "__exprinfo_expr.%s" % (mangled_attr,)
|
||||
co = self._compile(source)
|
||||
result = self.frame.eval(co, __exprinfo_expr=source_result)
|
||||
except Exception:
|
||||
raise Failure(explanation)
|
||||
explanation = "%s\n{%s = %s.%s\n}" % (self.frame.repr(result),
|
||||
self.frame.repr(result),
|
||||
source_explanation, attr.attr)
|
||||
# Check if the attr is from an instance.
|
||||
source = "%r in getattr(__exprinfo_expr, '__dict__', {})"
|
||||
source = source % (attr.attr,)
|
||||
co = self._compile(source)
|
||||
try:
|
||||
from_instance = self.frame.eval(co, __exprinfo_expr=source_result)
|
||||
except Exception:
|
||||
from_instance = None
|
||||
if from_instance is None or self.frame.is_true(from_instance):
|
||||
rep = self.frame.repr(result)
|
||||
pattern = "%s\n{%s = %s\n}"
|
||||
explanation = pattern % (rep, rep, explanation)
|
||||
return explanation, result
|
||||
|
||||
def visit_Assert(self, assrt):
|
||||
test_explanation, test_result = self.visit(assrt.test)
|
||||
explanation = "assert %s" % (test_explanation,)
|
||||
if not self.frame.is_true(test_result):
|
||||
try:
|
||||
raise util.BuiltinAssertionError
|
||||
except Exception:
|
||||
raise Failure(explanation)
|
||||
return explanation, test_result
|
||||
|
||||
def visit_Assign(self, assign):
|
||||
value_explanation, value_result = self.visit(assign.value)
|
||||
explanation = "... = %s" % (value_explanation,)
|
||||
name = ast.Name("__exprinfo_expr", ast.Load(),
|
||||
lineno=assign.value.lineno,
|
||||
col_offset=assign.value.col_offset)
|
||||
new_assign = ast.Assign(assign.targets, name, lineno=assign.lineno,
|
||||
col_offset=assign.col_offset)
|
||||
mod = ast.Module([new_assign])
|
||||
co = self._compile(mod, "exec")
|
||||
try:
|
||||
self.frame.exec_(co, __exprinfo_expr=value_result)
|
||||
except Exception:
|
||||
raise Failure(explanation)
|
||||
return explanation, value_result
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ class AssertionRewritingHook(object):
|
||||
# One of the path components was not a directory, likely
|
||||
# because we're in a zip file.
|
||||
write = False
|
||||
elif e in [errno.EACCES, errno.EROFS]:
|
||||
elif e in [errno.EACCES, errno.EROFS, errno.EPERM]:
|
||||
state.trace("read only directory: %r" % fn_pypath.dirname)
|
||||
write = False
|
||||
else:
|
||||
@@ -453,6 +453,11 @@ binop_map = {
|
||||
ast.In: "in",
|
||||
ast.NotIn: "not in"
|
||||
}
|
||||
# Python 3.5+ compatibility
|
||||
try:
|
||||
binop_map[ast.MatMult] = "@"
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
# Python 3.4+ compatibility
|
||||
if hasattr(ast, "NameConstant"):
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Utilities for assertion debugging"""
|
||||
import pprint
|
||||
|
||||
import _pytest._code
|
||||
import py
|
||||
try:
|
||||
from collections import Sequence
|
||||
@@ -17,6 +18,15 @@ u = py.builtin._totext
|
||||
_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:
|
||||
return s
|
||||
|
||||
|
||||
def format_explanation(explanation):
|
||||
"""This formats an explanation
|
||||
|
||||
@@ -27,6 +37,7 @@ def format_explanation(explanation):
|
||||
for when one explanation needs to span multiple lines, e.g. when
|
||||
displaying diffs.
|
||||
"""
|
||||
explanation = ecu(explanation)
|
||||
explanation = _collapse_false(explanation)
|
||||
lines = _split_explanation(explanation)
|
||||
result = _format_lines(lines)
|
||||
@@ -130,18 +141,10 @@ def assertrepr_compare(config, op, left, right):
|
||||
left_repr = py.io.saferepr(left, maxsize=int(width/2))
|
||||
right_repr = py.io.saferepr(right, maxsize=width-len(left_repr))
|
||||
|
||||
# the re-encoding is needed for python2 repr
|
||||
# with non-ascii characters (see issue 877)
|
||||
def ecu(s):
|
||||
try:
|
||||
return u(s, 'utf-8', 'replace')
|
||||
except TypeError:
|
||||
return s
|
||||
|
||||
summary = u('%s %s %s') % (ecu(left_repr), op, ecu(right_repr))
|
||||
|
||||
issequence = lambda x: (isinstance(x, (list, tuple, Sequence))
|
||||
and not isinstance(x, basestring))
|
||||
issequence = lambda x: (isinstance(x, (list, tuple, Sequence)) and
|
||||
not isinstance(x, basestring))
|
||||
istext = lambda x: isinstance(x, basestring)
|
||||
isdict = lambda x: isinstance(x, dict)
|
||||
isset = lambda x: isinstance(x, (set, frozenset))
|
||||
@@ -179,7 +182,7 @@ def assertrepr_compare(config, op, left, right):
|
||||
explanation = [
|
||||
u('(pytest_assertion plugin: representation of details failed. '
|
||||
'Probably an object has a faulty __repr__.)'),
|
||||
u(py.code.ExceptionInfo())]
|
||||
u(_pytest._code.ExceptionInfo())]
|
||||
|
||||
if not explanation:
|
||||
return None
|
||||
@@ -263,8 +266,7 @@ def _compare_eq_sequence(left, right, verbose=False):
|
||||
explanation += [
|
||||
u('Right contains more items, first extra item: %s') %
|
||||
py.io.saferepr(right[len(left)],)]
|
||||
return explanation # + _diff_text(pprint.pformat(left),
|
||||
# pprint.pformat(right))
|
||||
return explanation
|
||||
|
||||
|
||||
def _compare_eq_set(left, right, verbose=False):
|
||||
|
||||
@@ -149,17 +149,19 @@ class LFPlugin:
|
||||
config = self.config
|
||||
if config.getvalue("cacheshow") or hasattr(config, "slaveinput"):
|
||||
return
|
||||
config.cache.set("cache/lastfailed", self.lastfailed)
|
||||
prev_failed = config.cache.get("cache/lastfailed", None) is not None
|
||||
if (session.testscollected and prev_failed) or self.lastfailed:
|
||||
config.cache.set("cache/lastfailed", self.lastfailed)
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("general")
|
||||
group.addoption(
|
||||
'--lf', action='store_true', dest="lf",
|
||||
'--lf', '--last-failed', action='store_true', dest="lf",
|
||||
help="rerun only the tests that failed "
|
||||
"at the last run (or all if none failed)")
|
||||
group.addoption(
|
||||
'--ff', action='store_true', dest="failedfirst",
|
||||
'--ff', '--failed-first', action='store_true', dest="failedfirst",
|
||||
help="run all tests but run the last failures first. "
|
||||
"This may re-order tests and thus lead to "
|
||||
"repeated fixture setup/teardown")
|
||||
@@ -192,8 +194,8 @@ def cache(request):
|
||||
cache.get(key, default)
|
||||
cache.set(key, value)
|
||||
|
||||
Keys must be strings not containing a "/" separator. Add a unique identifier
|
||||
(such as plugin/app name) to avoid clashes with other cache users.
|
||||
Keys must be a ``/`` separated value, where the first part is usually the
|
||||
name of your plugin or application to avoid clashes with other cache users.
|
||||
|
||||
Values can be any object handled by the json stdlib module.
|
||||
"""
|
||||
|
||||
@@ -31,6 +31,7 @@ def pytest_addoption(parser):
|
||||
|
||||
@pytest.hookimpl(hookwrapper=True)
|
||||
def pytest_load_initial_conftests(early_config, parser, args):
|
||||
_readline_workaround()
|
||||
ns = early_config.known_args_namespace
|
||||
pluginmanager = early_config.pluginmanager
|
||||
capman = CaptureManager(ns.capture)
|
||||
@@ -442,3 +443,30 @@ class DontReadFromInput:
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
|
||||
def _readline_workaround():
|
||||
"""
|
||||
Ensure readline is imported so that it attaches to the correct stdio
|
||||
handles on Windows.
|
||||
|
||||
Pdb uses readline support where available--when not running from the Python
|
||||
prompt, the readline module is not imported until running the pdb REPL. If
|
||||
running py.test with the --pdb option this means the readline module is not
|
||||
imported until after I/O capture has been started.
|
||||
|
||||
This is a problem for pyreadline, which is often used to implement readline
|
||||
support on Windows, as it does not attach to the correct handles for stdout
|
||||
and/or stdin if they have been redirected by the FDCapture mechanism. This
|
||||
workaround ensures that readline is imported before I/O capture is setup so
|
||||
that it can attach to the actual stdin/out for the console.
|
||||
|
||||
See https://github.com/pytest-dev/pytest/pull/1281
|
||||
"""
|
||||
|
||||
if not sys.platform.startswith('win32'):
|
||||
return
|
||||
try:
|
||||
import readline # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
@@ -8,6 +8,7 @@ import warnings
|
||||
import py
|
||||
# DON't import pytest here because it causes import cycle troubles
|
||||
import sys, os
|
||||
import _pytest._code
|
||||
import _pytest.hookspec # the extension point definitions
|
||||
from _pytest._pluggy import PluginManager, HookimplMarker, HookspecMarker
|
||||
|
||||
@@ -103,7 +104,7 @@ def _prepareconfig(args=None, plugins=None):
|
||||
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)
|
||||
args = shlex.split(args, posix=sys.platform != "win32")
|
||||
config = get_config()
|
||||
pluginmanager = config.pluginmanager
|
||||
try:
|
||||
@@ -158,7 +159,7 @@ class PytestPluginManager(PluginManager):
|
||||
Use :py:meth:`pluggy.PluginManager.add_hookspecs` instead.
|
||||
"""
|
||||
warning = dict(code="I2",
|
||||
fslocation=py.code.getfslineno(sys._getframe(1)),
|
||||
fslocation=_pytest._code.getfslineno(sys._getframe(1)),
|
||||
nodeid=None,
|
||||
message="use pluginmanager.add_hookspecs instead of "
|
||||
"deprecated addhooks() method.")
|
||||
@@ -195,7 +196,7 @@ class PytestPluginManager(PluginManager):
|
||||
def _verify_hook(self, hook, hookmethod):
|
||||
super(PytestPluginManager, self)._verify_hook(hook, hookmethod)
|
||||
if "__multicall__" in hookmethod.argnames:
|
||||
fslineno = py.code.getfslineno(hookmethod.function)
|
||||
fslineno = _pytest._code.getfslineno(hookmethod.function)
|
||||
warning = dict(code="I1",
|
||||
fslocation=fslineno,
|
||||
nodeid=None,
|
||||
@@ -382,8 +383,13 @@ class PytestPluginManager(PluginManager):
|
||||
importspec = modname
|
||||
try:
|
||||
__import__(importspec)
|
||||
except ImportError:
|
||||
raise
|
||||
except ImportError as e:
|
||||
new_exc = ImportError('Error importing plugin "%s": %s' % (modname, e))
|
||||
# copy over name and path attributes
|
||||
for attr in ('name', 'path'):
|
||||
if hasattr(e, attr):
|
||||
setattr(new_exc, attr, getattr(e, attr))
|
||||
raise new_exc
|
||||
except Exception as e:
|
||||
import pytest
|
||||
if not hasattr(pytest, 'skip') or not isinstance(e, pytest.skip.Exception):
|
||||
@@ -455,11 +461,11 @@ class Parser:
|
||||
"""
|
||||
self._anonymous.addoption(*opts, **attrs)
|
||||
|
||||
def parse(self, args):
|
||||
def parse(self, args, namespace=None):
|
||||
from _pytest._argcomplete import try_argcomplete
|
||||
self.optparser = self._getparser()
|
||||
try_argcomplete(self.optparser)
|
||||
return self.optparser.parse_args([str(x) for x in args])
|
||||
return self.optparser.parse_args([str(x) for x in args], namespace=namespace)
|
||||
|
||||
def _getparser(self):
|
||||
from _pytest._argcomplete import filescompleter
|
||||
@@ -477,37 +483,38 @@ class Parser:
|
||||
optparser.add_argument(FILE_OR_DIR, nargs='*').completer=filescompleter
|
||||
return optparser
|
||||
|
||||
def parse_setoption(self, args, option):
|
||||
parsedoption = self.parse(args)
|
||||
def parse_setoption(self, args, option, namespace=None):
|
||||
parsedoption = self.parse(args, namespace=namespace)
|
||||
for name, value in parsedoption.__dict__.items():
|
||||
setattr(option, name, value)
|
||||
return getattr(parsedoption, FILE_OR_DIR)
|
||||
|
||||
def parse_known_args(self, args):
|
||||
def parse_known_args(self, args, namespace=None):
|
||||
"""parses and returns a namespace object with known arguments at this
|
||||
point.
|
||||
"""
|
||||
return self.parse_known_and_unknown_args(args)[0]
|
||||
return self.parse_known_and_unknown_args(args, namespace=namespace)[0]
|
||||
|
||||
def parse_known_and_unknown_args(self, args):
|
||||
def parse_known_and_unknown_args(self, args, namespace=None):
|
||||
"""parses and returns a namespace object with known arguments, and
|
||||
the remaining arguments unknown at this point.
|
||||
"""
|
||||
optparser = self._getparser()
|
||||
args = [str(x) for x in args]
|
||||
return optparser.parse_known_args(args)
|
||||
return optparser.parse_known_args(args, namespace=namespace)
|
||||
|
||||
def addini(self, name, help, type=None, default=None):
|
||||
""" register an ini-file option.
|
||||
|
||||
:name: name of the ini-variable
|
||||
:type: type of the variable, can be ``pathlist``, ``args`` or ``linelist``.
|
||||
:type: type of the variable, can be ``pathlist``, ``args``, ``linelist``
|
||||
or ``bool``.
|
||||
:default: default value if no ini-file option exists but is queried.
|
||||
|
||||
The value of ini-variables can be retrieved via a call to
|
||||
:py:func:`config.getini(name) <_pytest.config.Config.getini>`.
|
||||
"""
|
||||
assert type in (None, "pathlist", "args", "linelist")
|
||||
assert type in (None, "pathlist", "args", "linelist", "bool")
|
||||
self._inidict[name] = (help, type, default)
|
||||
self._ininames.append(name)
|
||||
|
||||
@@ -779,10 +786,12 @@ def _ensure_removed_sysmodule(modname):
|
||||
|
||||
class CmdOptions(object):
|
||||
""" holds cmdline options as attributes."""
|
||||
def __init__(self, **kwargs):
|
||||
self.__dict__.update(kwargs)
|
||||
def __init__(self, values=()):
|
||||
self.__dict__.update(values)
|
||||
def __repr__(self):
|
||||
return "<CmdOptions %r>" %(self.__dict__,)
|
||||
def copy(self):
|
||||
return CmdOptions(self.__dict__)
|
||||
|
||||
class Notset:
|
||||
def __repr__(self):
|
||||
@@ -879,8 +888,8 @@ class Config(object):
|
||||
def fromdictargs(cls, option_dict, args):
|
||||
""" constructor useable for subprocesses. """
|
||||
config = get_config()
|
||||
config._preparse(args, addopts=False)
|
||||
config.option.__dict__.update(option_dict)
|
||||
config.parse(args, addopts=False)
|
||||
for x in config.option.plugins:
|
||||
config.pluginmanager.consider_pluginarg(x)
|
||||
return config
|
||||
@@ -898,7 +907,7 @@ class Config(object):
|
||||
self.pluginmanager._set_initial_conftests(early_config.known_args_namespace)
|
||||
|
||||
def _initini(self, args):
|
||||
ns, unknown_args = self._parser.parse_known_and_unknown_args(args)
|
||||
ns, unknown_args = self._parser.parse_known_and_unknown_args(args, namespace=self.option.copy())
|
||||
r = determine_setup(ns.inifilename, ns.file_or_dir + unknown_args)
|
||||
self.rootdir, self.inifile, self.inicfg = r
|
||||
self._parser.extra_info['rootdir'] = self.rootdir
|
||||
@@ -919,7 +928,7 @@ class Config(object):
|
||||
except ImportError as e:
|
||||
self.warn("I2", "could not load setuptools entry import: %s" % (e,))
|
||||
self.pluginmanager.consider_env()
|
||||
self.known_args_namespace = ns = self._parser.parse_known_args(args)
|
||||
self.known_args_namespace = ns = self._parser.parse_known_args(args, namespace=self.option.copy())
|
||||
if self.known_args_namespace.confcutdir is None and self.inifile:
|
||||
confcutdir = py.path.local(self.inifile).dirname
|
||||
self.known_args_namespace.confcutdir = confcutdir
|
||||
@@ -947,17 +956,17 @@ class Config(object):
|
||||
self.inicfg.config.path, self.inicfg.lineof('minversion'),
|
||||
minver, pytest.__version__))
|
||||
|
||||
def parse(self, args):
|
||||
def parse(self, args, addopts=True):
|
||||
# parse given cmdline arguments into this config object.
|
||||
assert not hasattr(self, 'args'), (
|
||||
"can only parse cmdline args at most once per Config object")
|
||||
self._origargs = args
|
||||
self.hook.pytest_addhooks.call_historic(
|
||||
kwargs=dict(pluginmanager=self.pluginmanager))
|
||||
self._preparse(args)
|
||||
self._preparse(args, addopts=addopts)
|
||||
# XXX deprecated hook:
|
||||
self.hook.pytest_cmdline_preparse(config=self, args=args)
|
||||
args = self._parser.parse_setoption(args, self.option)
|
||||
args = self._parser.parse_setoption(args, self.option, namespace=self.option)
|
||||
if not args:
|
||||
cwd = os.getcwd()
|
||||
if cwd == self.rootdir:
|
||||
@@ -1008,6 +1017,8 @@ class Config(object):
|
||||
return shlex.split(value)
|
||||
elif type == "linelist":
|
||||
return [t for t in map(lambda x: x.strip(), value.split("\n")) if t]
|
||||
elif type == "bool":
|
||||
return bool(_strtobool(value.strip()))
|
||||
else:
|
||||
assert type is None
|
||||
return value
|
||||
@@ -1161,3 +1172,21 @@ def create_terminal_writer(config, *args, **kwargs):
|
||||
if config.option.color == 'no':
|
||||
tw.hasmarkup = False
|
||||
return tw
|
||||
|
||||
|
||||
def _strtobool(val):
|
||||
"""Convert a string representation of truth to true (1) or false (0).
|
||||
|
||||
True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
|
||||
are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if
|
||||
'val' is anything else.
|
||||
|
||||
.. note:: copied from distutils.util
|
||||
"""
|
||||
val = val.lower()
|
||||
if val in ('y', 'yes', 't', 'true', 'on', '1'):
|
||||
return 1
|
||||
elif val in ('n', 'no', 'f', 'false', 'off', '0'):
|
||||
return 0
|
||||
else:
|
||||
raise ValueError("invalid truth value %r" % (val,))
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
""" discover and run doctests in modules and test files."""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import traceback
|
||||
import pytest, py
|
||||
|
||||
import pytest
|
||||
from _pytest._code.code import TerminalRepr, ReprFileLocation, ExceptionInfo
|
||||
from _pytest.python import FixtureRequest
|
||||
from py._code.code import TerminalRepr, ReprFileLocation
|
||||
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addini('doctest_optionflags', 'option flags for doctests',
|
||||
@@ -14,7 +18,7 @@ def pytest_addoption(parser):
|
||||
help="run doctests in all .py modules",
|
||||
dest="doctestmodules")
|
||||
group.addoption("--doctest-glob",
|
||||
action="store", default="test*.txt", metavar="pat",
|
||||
action="append", default=[], metavar="pat",
|
||||
help="doctests file matching pattern, default: test*.txt",
|
||||
dest="doctestglob")
|
||||
group.addoption("--doctest-ignore-import-errors",
|
||||
@@ -22,29 +26,52 @@ def pytest_addoption(parser):
|
||||
help="ignore doctest ImportErrors",
|
||||
dest="doctest_ignore_import_errors")
|
||||
|
||||
|
||||
def pytest_collect_file(path, parent):
|
||||
config = parent.config
|
||||
if path.ext == ".py":
|
||||
if config.option.doctestmodules:
|
||||
return DoctestModule(path, parent)
|
||||
elif (path.ext in ('.txt', '.rst') and parent.session.isinitpath(path)) or \
|
||||
path.check(fnmatch=config.getvalue("doctestglob")):
|
||||
elif _is_doctest(config, path, parent):
|
||||
return DoctestTextfile(path, parent)
|
||||
|
||||
|
||||
def _is_doctest(config, path, parent):
|
||||
if path.ext in ('.txt', '.rst') and parent.session.isinitpath(path):
|
||||
return True
|
||||
globs = config.getoption("doctestglob") or ['test*.txt']
|
||||
for glob in globs:
|
||||
if path.check(fnmatch=glob):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class ReprFailDoctest(TerminalRepr):
|
||||
|
||||
def __init__(self, reprlocation, lines):
|
||||
self.reprlocation = reprlocation
|
||||
self.lines = lines
|
||||
|
||||
def toterminal(self, tw):
|
||||
for line in self.lines:
|
||||
tw.line(line)
|
||||
self.reprlocation.toterminal(tw)
|
||||
|
||||
|
||||
class DoctestItem(pytest.Item):
|
||||
|
||||
def __init__(self, name, parent, runner=None, dtest=None):
|
||||
super(DoctestItem, self).__init__(name, parent)
|
||||
self.runner = runner
|
||||
self.dtest = dtest
|
||||
self.obj = None
|
||||
self.fixture_request = None
|
||||
|
||||
def setup(self):
|
||||
if self.dtest is not None:
|
||||
self.fixture_request = _setup_fixtures(self)
|
||||
globs = dict(getfixture=self.fixture_request.getfuncargvalue)
|
||||
self.dtest.globs.update(globs)
|
||||
|
||||
def runtest(self):
|
||||
_check_all_skipped(self.dtest)
|
||||
@@ -64,17 +91,17 @@ class DoctestItem(pytest.Item):
|
||||
lineno = test.lineno + example.lineno + 1
|
||||
message = excinfo.type.__name__
|
||||
reprlocation = ReprFileLocation(filename, lineno, message)
|
||||
checker = _get_unicode_checker()
|
||||
checker = _get_checker()
|
||||
REPORT_UDIFF = doctest.REPORT_UDIFF
|
||||
filelines = py.path.local(filename).readlines(cr=0)
|
||||
lines = []
|
||||
if lineno is not None:
|
||||
i = max(test.lineno, max(0, lineno - 10)) # XXX?
|
||||
for line in filelines[i:lineno]:
|
||||
lines.append("%03d %s" % (i+1, line))
|
||||
i += 1
|
||||
lines = doctestfailure.test.docstring.splitlines(False)
|
||||
# add line numbers to the left of the error message
|
||||
lines = ["%03d %s" % (i + test.lineno + 1, x)
|
||||
for (i, x) in enumerate(lines)]
|
||||
# trim docstring error lines to 10
|
||||
lines = lines[example.lineno - 9:example.lineno + 1]
|
||||
else:
|
||||
lines.append('EXAMPLE LOCATION UNKNOWN, not showing all tests of that example')
|
||||
lines = ['EXAMPLE LOCATION UNKNOWN, not showing all tests of that example']
|
||||
indent = '>>>'
|
||||
for line in example.source.splitlines():
|
||||
lines.append('??? %s %s' % (indent, line))
|
||||
@@ -83,7 +110,7 @@ class DoctestItem(pytest.Item):
|
||||
lines += checker.output_difference(example,
|
||||
doctestfailure.got, REPORT_UDIFF).split("\n")
|
||||
else:
|
||||
inner_excinfo = py.code.ExceptionInfo(excinfo.value.exc_info)
|
||||
inner_excinfo = ExceptionInfo(excinfo.value.exc_info)
|
||||
lines += ["UNEXPECTED EXCEPTION: %s" %
|
||||
repr(inner_excinfo.value)]
|
||||
lines += traceback.format_exception(*excinfo.value.exc_info)
|
||||
@@ -94,6 +121,7 @@ class DoctestItem(pytest.Item):
|
||||
def reportinfo(self):
|
||||
return self.fspath, None, "[doctest] %s" % self.name
|
||||
|
||||
|
||||
def _get_flag_lookup():
|
||||
import doctest
|
||||
return dict(DONT_ACCEPT_TRUE_FOR_1=doctest.DONT_ACCEPT_TRUE_FOR_1,
|
||||
@@ -102,7 +130,10 @@ def _get_flag_lookup():
|
||||
ELLIPSIS=doctest.ELLIPSIS,
|
||||
IGNORE_EXCEPTION_DETAIL=doctest.IGNORE_EXCEPTION_DETAIL,
|
||||
COMPARISON_FLAGS=doctest.COMPARISON_FLAGS,
|
||||
ALLOW_UNICODE=_get_allow_unicode_flag())
|
||||
ALLOW_UNICODE=_get_allow_unicode_flag(),
|
||||
ALLOW_BYTES=_get_allow_bytes_flag(),
|
||||
)
|
||||
|
||||
|
||||
def get_optionflags(parent):
|
||||
optionflags_str = parent.config.getini("doctest_optionflags")
|
||||
@@ -113,7 +144,7 @@ def get_optionflags(parent):
|
||||
return flag_acc
|
||||
|
||||
|
||||
class DoctestTextfile(DoctestItem, pytest.File):
|
||||
class DoctestTextfile(DoctestItem, pytest.Module):
|
||||
|
||||
def runtest(self):
|
||||
import doctest
|
||||
@@ -130,7 +161,7 @@ class DoctestTextfile(DoctestItem, pytest.File):
|
||||
|
||||
optionflags = get_optionflags(self)
|
||||
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
|
||||
checker=_get_unicode_checker())
|
||||
checker=_get_checker())
|
||||
|
||||
parser = doctest.DocTestParser()
|
||||
test = parser.get_doctest(text, globs, name, filename, 0)
|
||||
@@ -148,7 +179,7 @@ def _check_all_skipped(test):
|
||||
pytest.skip('all tests skipped by +SKIP option')
|
||||
|
||||
|
||||
class DoctestModule(pytest.File):
|
||||
class DoctestModule(pytest.Module):
|
||||
def collect(self):
|
||||
import doctest
|
||||
if self.fspath.basename == "conftest.py":
|
||||
@@ -161,23 +192,19 @@ class DoctestModule(pytest.File):
|
||||
pytest.skip('unable to import module %r' % self.fspath)
|
||||
else:
|
||||
raise
|
||||
# satisfy `FixtureRequest` constructor...
|
||||
fixture_request = _setup_fixtures(self)
|
||||
doctest_globals = dict(getfixture=fixture_request.getfuncargvalue)
|
||||
# uses internal doctest module parsing mechanism
|
||||
finder = doctest.DocTestFinder()
|
||||
optionflags = get_optionflags(self)
|
||||
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
|
||||
checker=_get_unicode_checker())
|
||||
for test in finder.find(module, module.__name__,
|
||||
extraglobs=doctest_globals):
|
||||
checker=_get_checker())
|
||||
for test in finder.find(module, module.__name__):
|
||||
if test.examples: # skip empty doctests
|
||||
yield DoctestItem(test.name, self, runner, test)
|
||||
|
||||
|
||||
def _setup_fixtures(doctest_item):
|
||||
"""
|
||||
Used by DoctestTextfile and DoctestModule to setup fixture information.
|
||||
Used by DoctestTextfile and DoctestItem to setup fixture information.
|
||||
"""
|
||||
def func():
|
||||
pass
|
||||
@@ -191,28 +218,32 @@ def _setup_fixtures(doctest_item):
|
||||
return fixture_request
|
||||
|
||||
|
||||
def _get_unicode_checker():
|
||||
def _get_checker():
|
||||
"""
|
||||
Returns a doctest.OutputChecker subclass that takes in account the
|
||||
ALLOW_UNICODE option to ignore u'' prefixes in strings. Useful
|
||||
when the same doctest should run in Python 2 and Python 3.
|
||||
ALLOW_UNICODE option to ignore u'' prefixes in strings and ALLOW_BYTES
|
||||
to strip b'' prefixes.
|
||||
Useful when the same doctest should run in Python 2 and Python 3.
|
||||
|
||||
An inner class is used to avoid importing "doctest" at the module
|
||||
level.
|
||||
"""
|
||||
if hasattr(_get_unicode_checker, 'UnicodeOutputChecker'):
|
||||
return _get_unicode_checker.UnicodeOutputChecker()
|
||||
if hasattr(_get_checker, 'LiteralsOutputChecker'):
|
||||
return _get_checker.LiteralsOutputChecker()
|
||||
|
||||
import doctest
|
||||
import re
|
||||
|
||||
class UnicodeOutputChecker(doctest.OutputChecker):
|
||||
class LiteralsOutputChecker(doctest.OutputChecker):
|
||||
"""
|
||||
Copied from doctest_nose_plugin.py from the nltk project:
|
||||
https://github.com/nltk/nltk
|
||||
|
||||
Further extended to also support byte literals.
|
||||
"""
|
||||
|
||||
_literal_re = re.compile(r"(\W|^)[uU]([rR]?[\'\"])", re.UNICODE)
|
||||
_unicode_literal_re = re.compile(r"(\W|^)[uU]([rR]?[\'\"])", re.UNICODE)
|
||||
_bytes_literal_re = re.compile(r"(\W|^)[bB]([rR]?[\'\"])", re.UNICODE)
|
||||
|
||||
def check_output(self, want, got, optionflags):
|
||||
res = doctest.OutputChecker.check_output(self, want, got,
|
||||
@@ -220,23 +251,27 @@ def _get_unicode_checker():
|
||||
if res:
|
||||
return True
|
||||
|
||||
if not (optionflags & _get_allow_unicode_flag()):
|
||||
allow_unicode = optionflags & _get_allow_unicode_flag()
|
||||
allow_bytes = optionflags & _get_allow_bytes_flag()
|
||||
if not allow_unicode and not allow_bytes:
|
||||
return False
|
||||
|
||||
else: # pragma: no cover
|
||||
# the code below will end up executed only in Python 2 in
|
||||
# our tests, and our coverage check runs in Python 3 only
|
||||
def remove_u_prefixes(txt):
|
||||
return re.sub(self._literal_re, r'\1\2', txt)
|
||||
def remove_prefixes(regex, txt):
|
||||
return re.sub(regex, r'\1\2', txt)
|
||||
|
||||
want = remove_u_prefixes(want)
|
||||
got = remove_u_prefixes(got)
|
||||
if allow_unicode:
|
||||
want = remove_prefixes(self._unicode_literal_re, want)
|
||||
got = remove_prefixes(self._unicode_literal_re, got)
|
||||
if allow_bytes:
|
||||
want = remove_prefixes(self._bytes_literal_re, want)
|
||||
got = remove_prefixes(self._bytes_literal_re, got)
|
||||
res = doctest.OutputChecker.check_output(self, want, got,
|
||||
optionflags)
|
||||
return res
|
||||
|
||||
_get_unicode_checker.UnicodeOutputChecker = UnicodeOutputChecker
|
||||
return _get_unicode_checker.UnicodeOutputChecker()
|
||||
_get_checker.LiteralsOutputChecker = LiteralsOutputChecker
|
||||
return _get_checker.LiteralsOutputChecker()
|
||||
|
||||
|
||||
def _get_allow_unicode_flag():
|
||||
@@ -245,3 +280,11 @@ def _get_allow_unicode_flag():
|
||||
"""
|
||||
import doctest
|
||||
return doctest.register_optionflag('ALLOW_UNICODE')
|
||||
|
||||
|
||||
def _get_allow_bytes_flag():
|
||||
"""
|
||||
Registers and returns the ALLOW_BYTES flag.
|
||||
"""
|
||||
import doctest
|
||||
return doctest.register_optionflag('ALLOW_BYTES')
|
||||
|
||||
@@ -80,13 +80,23 @@ def showhelp(config):
|
||||
line = " %-24s %s" %(spec, help)
|
||||
tw.line(line[:tw.fullwidth])
|
||||
|
||||
tw.line() ; tw.line()
|
||||
#tw.sep("=")
|
||||
tw.line()
|
||||
tw.line("environment variables:")
|
||||
vars = [
|
||||
("PYTEST_ADDOPTS", "extra command line options"),
|
||||
("PYTEST_PLUGINS", "comma-separated plugins to load during startup"),
|
||||
("PYTEST_DEBUG", "set to enable debug tracing of pytest's internals")
|
||||
]
|
||||
for name, help in vars:
|
||||
tw.line(" %-24s %s" % (name, help))
|
||||
tw.line()
|
||||
tw.line()
|
||||
|
||||
tw.line("to see available markers type: py.test --markers")
|
||||
tw.line("to see available fixtures type: py.test --fixtures")
|
||||
tw.line("(shown according to specified file_or_dir or current dir "
|
||||
"if not specified)")
|
||||
tw.line(str(reporter.stats))
|
||||
|
||||
for warningreport in reporter.stats.get('warnings', []):
|
||||
tw.line("warning : " + warningreport.message, red=True)
|
||||
return
|
||||
|
||||
@@ -28,17 +28,14 @@ def pytest_plugin_registered(plugin, manager):
|
||||
|
||||
@hookspec(historic=True)
|
||||
def pytest_addoption(parser):
|
||||
"""register argparse-style options and ini-style config values.
|
||||
"""register argparse-style options and ini-style config values,
|
||||
called once at the beginning of a test run.
|
||||
|
||||
.. warning::
|
||||
.. note::
|
||||
|
||||
This function must be implemented in a :ref:`plugin <pluginorder>`
|
||||
and is called once at the beginning of a test run.
|
||||
|
||||
Implementing this hook from ``conftest.py`` files is **strongly**
|
||||
discouraged because ``conftest.py`` files are lazily loaded and
|
||||
may give strange *unknown option* errors depending on the directory
|
||||
``py.test`` is invoked from.
|
||||
This function should be implemented only in plugins or ``conftest.py``
|
||||
files situated at the tests root directory due to how py.test
|
||||
:ref:`discovers plugins during startup <pluginorder>`.
|
||||
|
||||
:arg parser: To add command line options, call
|
||||
:py:func:`parser.addoption(...) <_pytest.config.Parser.addoption>`.
|
||||
@@ -84,7 +81,7 @@ def pytest_cmdline_main(config):
|
||||
""" called for performing the main command line action. The default
|
||||
implementation will invoke the configure hooks and runtest_mainloop. """
|
||||
|
||||
def pytest_load_initial_conftests(args, early_config, parser):
|
||||
def pytest_load_initial_conftests(early_config, parser, args):
|
||||
""" implements the loading of initial conftest files ahead
|
||||
of command line option parsing. """
|
||||
|
||||
@@ -282,13 +279,17 @@ def pytest_keyboard_interrupt(excinfo):
|
||||
""" called for keyboard interrupt. """
|
||||
|
||||
def pytest_exception_interact(node, call, report):
|
||||
""" (experimental, new in 2.4) called when
|
||||
an exception was raised which can potentially be
|
||||
"""called when an exception was raised which can potentially be
|
||||
interactively handled.
|
||||
|
||||
This hook is only called if an exception was raised
|
||||
that is not an internal exception like "skip.Exception".
|
||||
that is not an internal exception like ``skip.Exception``.
|
||||
"""
|
||||
|
||||
def pytest_enter_pdb():
|
||||
""" called upon pdb.set_trace()"""
|
||||
def pytest_enter_pdb(config):
|
||||
""" called upon pdb.set_trace(), can be used by plugins to take special
|
||||
action just before the python debugger enters in interactive mode.
|
||||
|
||||
:arg config: pytest config object
|
||||
:type config: _pytest.config.Config
|
||||
"""
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
""" report test results in JUnit-XML format, for use with Hudson and build integration servers.
|
||||
"""
|
||||
report test results in JUnit-XML format,
|
||||
for use with Jenkins and build integration servers.
|
||||
|
||||
Output conforms to https://github.com/jenkinsci/xunit-plugin/blob/master/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd
|
||||
|
||||
Based on initial code from Ross Lawley.
|
||||
"""
|
||||
# Output conforms to https://github.com/jenkinsci/xunit-plugin/blob/master/
|
||||
# src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd
|
||||
|
||||
import py
|
||||
import os
|
||||
import re
|
||||
@@ -19,10 +23,10 @@ else:
|
||||
unicode = str
|
||||
long = int
|
||||
|
||||
|
||||
class Junit(py.xml.Namespace):
|
||||
pass
|
||||
|
||||
|
||||
# We need to get the subset of the invalid unicode ranges according to
|
||||
# XML 1.0 which are valid in this python build. Hence we calculate
|
||||
# this dynamically instead of hardcoding it. The spec range of valid
|
||||
@@ -30,21 +34,21 @@ class Junit(py.xml.Namespace):
|
||||
# | [#x10000-#x10FFFF]
|
||||
_legal_chars = (0x09, 0x0A, 0x0d)
|
||||
_legal_ranges = (
|
||||
(0x20, 0x7E),
|
||||
(0x80, 0xD7FF),
|
||||
(0xE000, 0xFFFD),
|
||||
(0x10000, 0x10FFFF),
|
||||
(0x20, 0x7E), (0x80, 0xD7FF), (0xE000, 0xFFFD), (0x10000, 0x10FFFF),
|
||||
)
|
||||
_legal_xml_re = [unicode("%s-%s") % (unichr(low), unichr(high))
|
||||
for (low, high) in _legal_ranges
|
||||
if low < sys.maxunicode]
|
||||
_legal_xml_re = [
|
||||
unicode("%s-%s") % (unichr(low), unichr(high))
|
||||
for (low, high) in _legal_ranges if low < sys.maxunicode
|
||||
]
|
||||
_legal_xml_re = [unichr(x) for x in _legal_chars] + _legal_xml_re
|
||||
illegal_xml_re = re.compile(unicode('[^%s]') %
|
||||
unicode('').join(_legal_xml_re))
|
||||
illegal_xml_re = re.compile(unicode('[^%s]') % unicode('').join(_legal_xml_re))
|
||||
del _legal_chars
|
||||
del _legal_ranges
|
||||
del _legal_xml_re
|
||||
|
||||
_py_ext_re = re.compile(r"\.py$")
|
||||
|
||||
|
||||
def bin_xml_escape(arg):
|
||||
def repl(matchobj):
|
||||
i = ord(matchobj.group())
|
||||
@@ -52,122 +56,86 @@ def bin_xml_escape(arg):
|
||||
return unicode('#x%02X') % i
|
||||
else:
|
||||
return unicode('#x%04X') % i
|
||||
|
||||
return py.xml.raw(illegal_xml_re.sub(repl, py.xml.escape(arg)))
|
||||
|
||||
@pytest.fixture
|
||||
def record_xml_property(request):
|
||||
"""Fixture that adds extra xml properties to the tag for the calling test.
|
||||
The fixture is callable with (name, value), with value being automatically
|
||||
xml-encoded.
|
||||
"""
|
||||
def inner(name, value):
|
||||
if hasattr(request.config, "_xml"):
|
||||
request.config._xml.add_custom_property(name, value)
|
||||
msg = 'record_xml_property is an experimental feature'
|
||||
request.config.warn(code='C3', message=msg,
|
||||
fslocation=request.node.location[:2])
|
||||
return inner
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("terminal reporting")
|
||||
group.addoption('--junitxml', '--junit-xml', action="store",
|
||||
dest="xmlpath", metavar="path", default=None,
|
||||
help="create junit-xml style report file at given path.")
|
||||
group.addoption('--junitprefix', '--junit-prefix', action="store",
|
||||
metavar="str", default=None,
|
||||
help="prepend prefix to classnames in junit-xml output")
|
||||
class _NodeReporter(object):
|
||||
def __init__(self, nodeid, xml):
|
||||
|
||||
def pytest_configure(config):
|
||||
xmlpath = config.option.xmlpath
|
||||
# prevent opening xmllog on slave nodes (xdist)
|
||||
if xmlpath and not hasattr(config, 'slaveinput'):
|
||||
config._xml = LogXML(xmlpath, config.option.junitprefix)
|
||||
config.pluginmanager.register(config._xml)
|
||||
self.id = nodeid
|
||||
self.xml = xml
|
||||
self.add_stats = self.xml.add_stats
|
||||
self.duration = 0
|
||||
self.properties = []
|
||||
self.nodes = []
|
||||
self.testcase = None
|
||||
self.attrs = {}
|
||||
|
||||
def pytest_unconfigure(config):
|
||||
xml = getattr(config, '_xml', None)
|
||||
if xml:
|
||||
del config._xml
|
||||
config.pluginmanager.unregister(xml)
|
||||
def append(self, node):
|
||||
self.xml.add_stats(type(node).__name__)
|
||||
self.nodes.append(node)
|
||||
|
||||
def mangle_testnames(names):
|
||||
names = [x.replace(".py", "") for x in names if x != '()']
|
||||
names[0] = names[0].replace("/", '.')
|
||||
return names
|
||||
def add_property(self, name, value):
|
||||
self.properties.append((str(name), bin_xml_escape(value)))
|
||||
|
||||
class LogXML(object):
|
||||
def __init__(self, logfile, prefix):
|
||||
logfile = os.path.expanduser(os.path.expandvars(logfile))
|
||||
self.logfile = os.path.normpath(os.path.abspath(logfile))
|
||||
self.prefix = prefix
|
||||
self.tests = []
|
||||
self.tests_by_nodeid = {} # nodeid -> Junit.testcase
|
||||
self.durations = {} # nodeid -> total duration (setup+call+teardown)
|
||||
self.passed = self.skipped = 0
|
||||
self.failed = self.errors = 0
|
||||
self.custom_properties = {}
|
||||
def make_properties_node(self):
|
||||
"""Return a Junit node containing custom properties, if any.
|
||||
"""
|
||||
if self.properties:
|
||||
return Junit.properties([
|
||||
Junit.property(name=name, value=value)
|
||||
for name, value in self.properties
|
||||
])
|
||||
return ''
|
||||
|
||||
def add_custom_property(self, name, value):
|
||||
self.custom_properties[str(name)] = bin_xml_escape(str(value))
|
||||
|
||||
def _opentestcase(self, report):
|
||||
names = mangle_testnames(report.nodeid.split("::"))
|
||||
def record_testreport(self, testreport):
|
||||
assert not self.testcase
|
||||
names = mangle_test_address(testreport.nodeid)
|
||||
classnames = names[:-1]
|
||||
if self.prefix:
|
||||
classnames.insert(0, self.prefix)
|
||||
if self.xml.prefix:
|
||||
classnames.insert(0, self.xml.prefix)
|
||||
attrs = {
|
||||
"classname": ".".join(classnames),
|
||||
"name": bin_xml_escape(names[-1]),
|
||||
"file": report.location[0],
|
||||
"time": self.durations.get(report.nodeid, 0),
|
||||
"file": testreport.location[0],
|
||||
}
|
||||
if report.location[1] is not None:
|
||||
attrs["line"] = report.location[1]
|
||||
testcase = Junit.testcase(**attrs)
|
||||
custom_properties = self.pop_custom_properties()
|
||||
if custom_properties:
|
||||
testcase.append(custom_properties)
|
||||
self.tests.append(testcase)
|
||||
self.tests_by_nodeid[report.nodeid] = testcase
|
||||
if testreport.location[1] is not None:
|
||||
attrs["line"] = testreport.location[1]
|
||||
self.attrs = attrs
|
||||
|
||||
def to_xml(self):
|
||||
testcase = Junit.testcase(time=self.duration, **self.attrs)
|
||||
testcase.append(self.make_properties_node())
|
||||
for node in self.nodes:
|
||||
testcase.append(node)
|
||||
return testcase
|
||||
|
||||
def _add_simple(self, kind, message, data=None):
|
||||
data = bin_xml_escape(data)
|
||||
node = kind(data, message=message)
|
||||
self.append(node)
|
||||
|
||||
def _write_captured_output(self, report):
|
||||
for capname in ('out', 'err'):
|
||||
allcontent = ""
|
||||
for name, content in report.get_sections("Captured std%s" %
|
||||
capname):
|
||||
capname):
|
||||
allcontent += content
|
||||
if allcontent:
|
||||
tag = getattr(Junit, 'system-'+capname)
|
||||
tag = getattr(Junit, 'system-' + capname)
|
||||
self.append(tag(bin_xml_escape(allcontent)))
|
||||
|
||||
def append(self, obj):
|
||||
self.tests[-1].append(obj)
|
||||
|
||||
def pop_custom_properties(self):
|
||||
"""Return a Junit node containing custom properties set for
|
||||
the current test, if any, and reset the current custom properties.
|
||||
"""
|
||||
if self.custom_properties:
|
||||
result = Junit.properties(
|
||||
[
|
||||
Junit.property(name=name, value=value)
|
||||
for name, value in self.custom_properties.items()
|
||||
]
|
||||
)
|
||||
self.custom_properties.clear()
|
||||
return result
|
||||
return None
|
||||
|
||||
def append_pass(self, report):
|
||||
self.passed += 1
|
||||
self.add_stats('passed')
|
||||
self._write_captured_output(report)
|
||||
|
||||
def append_failure(self, report):
|
||||
#msg = str(report.longrepr.reprtraceback.extraline)
|
||||
# msg = str(report.longrepr.reprtraceback.extraline)
|
||||
if hasattr(report, "wasxfail"):
|
||||
self.append(
|
||||
Junit.skipped(message="xfail-marked test passes unexpectedly"))
|
||||
self.skipped += 1
|
||||
self._add_simple(
|
||||
Junit.skipped,
|
||||
"xfail-marked test passes unexpectedly")
|
||||
else:
|
||||
if hasattr(report.longrepr, "reprcrash"):
|
||||
message = report.longrepr.reprcrash.message
|
||||
@@ -179,30 +147,27 @@ class LogXML(object):
|
||||
fail = Junit.failure(message=message)
|
||||
fail.append(bin_xml_escape(report.longrepr))
|
||||
self.append(fail)
|
||||
self.failed += 1
|
||||
self._write_captured_output(report)
|
||||
|
||||
def append_collect_error(self, report):
|
||||
#msg = str(report.longrepr.reprtraceback.extraline)
|
||||
# msg = str(report.longrepr.reprtraceback.extraline)
|
||||
self.append(Junit.error(bin_xml_escape(report.longrepr),
|
||||
message="collection failure"))
|
||||
self.errors += 1
|
||||
|
||||
def append_collect_skipped(self, report):
|
||||
#msg = str(report.longrepr.reprtraceback.extraline)
|
||||
self.append(Junit.skipped(bin_xml_escape(report.longrepr),
|
||||
message="collection skipped"))
|
||||
self.skipped += 1
|
||||
self._add_simple(
|
||||
Junit.skipped, "collection skipped", report.longrepr)
|
||||
|
||||
def append_error(self, report):
|
||||
self.append(Junit.error(bin_xml_escape(report.longrepr),
|
||||
message="test setup failure"))
|
||||
self.errors += 1
|
||||
self._add_simple(
|
||||
Junit.error, "test setup failure", report.longrepr)
|
||||
self._write_captured_output(report)
|
||||
|
||||
def append_skipped(self, report):
|
||||
if hasattr(report, "wasxfail"):
|
||||
self.append(Junit.skipped(bin_xml_escape(report.wasxfail),
|
||||
message="expected test failure"))
|
||||
self._add_simple(
|
||||
Junit.skipped, "expected test failure", report.wasxfail
|
||||
)
|
||||
else:
|
||||
filename, lineno, skipreason = report.longrepr
|
||||
if skipreason.startswith("Skipped: "):
|
||||
@@ -210,11 +175,129 @@ class LogXML(object):
|
||||
self.append(
|
||||
Junit.skipped("%s:%s: %s" % (filename, lineno, skipreason),
|
||||
type="pytest.skip",
|
||||
message=skipreason
|
||||
))
|
||||
self.skipped += 1
|
||||
message=skipreason))
|
||||
self._write_captured_output(report)
|
||||
|
||||
def finalize(self):
|
||||
data = self.to_xml().unicode(indent=0)
|
||||
self.__dict__.clear()
|
||||
self.to_xml = lambda: py.xml.raw(data)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def record_xml_property(request):
|
||||
"""Fixture that adds extra xml properties to the tag for the calling test.
|
||||
The fixture is callable with (name, value), with value being automatically
|
||||
xml-encoded.
|
||||
"""
|
||||
request.node.warn(
|
||||
code='C3',
|
||||
message='record_xml_property is an experimental feature',
|
||||
)
|
||||
xml = getattr(request.config, "_xml", None)
|
||||
if xml is not None:
|
||||
node_reporter = xml.node_reporter(request.node.nodeid)
|
||||
return node_reporter.add_property
|
||||
else:
|
||||
def add_property_noop(name, value):
|
||||
pass
|
||||
|
||||
return add_property_noop
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("terminal reporting")
|
||||
group.addoption(
|
||||
'--junitxml', '--junit-xml',
|
||||
action="store",
|
||||
dest="xmlpath",
|
||||
metavar="path",
|
||||
default=None,
|
||||
help="create junit-xml style report file at given path.")
|
||||
group.addoption(
|
||||
'--junitprefix', '--junit-prefix',
|
||||
action="store",
|
||||
metavar="str",
|
||||
default=None,
|
||||
help="prepend prefix to classnames in junit-xml output")
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
xmlpath = config.option.xmlpath
|
||||
# prevent opening xmllog on slave nodes (xdist)
|
||||
if xmlpath and not hasattr(config, 'slaveinput'):
|
||||
config._xml = LogXML(xmlpath, config.option.junitprefix)
|
||||
config.pluginmanager.register(config._xml)
|
||||
|
||||
|
||||
def pytest_unconfigure(config):
|
||||
xml = getattr(config, '_xml', None)
|
||||
if xml:
|
||||
del config._xml
|
||||
config.pluginmanager.unregister(xml)
|
||||
|
||||
|
||||
def mangle_test_address(address):
|
||||
path, possible_open_bracket, params = address.partition('[')
|
||||
names = path.split("::")
|
||||
try:
|
||||
names.remove('()')
|
||||
except ValueError:
|
||||
pass
|
||||
# convert file path to dotted path
|
||||
names[0] = names[0].replace("/", '.')
|
||||
names[0] = _py_ext_re.sub("", names[0])
|
||||
# put any params back
|
||||
names[-1] += possible_open_bracket + params
|
||||
return names
|
||||
|
||||
|
||||
class LogXML(object):
|
||||
def __init__(self, logfile, prefix):
|
||||
logfile = os.path.expanduser(os.path.expandvars(logfile))
|
||||
self.logfile = os.path.normpath(os.path.abspath(logfile))
|
||||
self.prefix = prefix
|
||||
self.stats = dict.fromkeys([
|
||||
'error',
|
||||
'passed',
|
||||
'failure',
|
||||
'skipped',
|
||||
], 0)
|
||||
self.node_reporters = {} # nodeid -> _NodeReporter
|
||||
self.node_reporters_ordered = []
|
||||
|
||||
def finalize(self, report):
|
||||
nodeid = getattr(report, 'nodeid', report)
|
||||
# local hack to handle xdist report order
|
||||
slavenode = getattr(report, 'node', None)
|
||||
reporter = self.node_reporters.pop((nodeid, slavenode))
|
||||
if reporter is not None:
|
||||
reporter.finalize()
|
||||
|
||||
def node_reporter(self, report):
|
||||
nodeid = getattr(report, 'nodeid', report)
|
||||
# local hack to handle xdist report order
|
||||
slavenode = getattr(report, 'node', None)
|
||||
|
||||
key = nodeid, slavenode
|
||||
|
||||
if key in self.node_reporters:
|
||||
# TODO: breasks for --dist=each
|
||||
return self.node_reporters[key]
|
||||
reporter = _NodeReporter(nodeid, self)
|
||||
self.node_reporters[key] = reporter
|
||||
self.node_reporters_ordered.append(reporter)
|
||||
return reporter
|
||||
|
||||
def add_stats(self, key):
|
||||
if key in self.stats:
|
||||
self.stats[key] += 1
|
||||
|
||||
def _opentestcase(self, report):
|
||||
reporter = self.node_reporter(report)
|
||||
reporter.record_testreport(report)
|
||||
return reporter
|
||||
|
||||
def pytest_runtest_logreport(self, report):
|
||||
"""handle a setup/call/teardown report, generating the appropriate
|
||||
xml tags as necessary.
|
||||
@@ -240,47 +323,40 @@ class LogXML(object):
|
||||
"""
|
||||
if report.passed:
|
||||
if report.when == "call": # ignore setup/teardown
|
||||
self._opentestcase(report)
|
||||
self.append_pass(report)
|
||||
reporter = self._opentestcase(report)
|
||||
reporter.append_pass(report)
|
||||
elif report.failed:
|
||||
self._opentestcase(report)
|
||||
if report.when != "call":
|
||||
self.append_error(report)
|
||||
reporter = self._opentestcase(report)
|
||||
if report.when == "call":
|
||||
reporter.append_failure(report)
|
||||
else:
|
||||
self.append_failure(report)
|
||||
reporter.append_error(report)
|
||||
elif report.skipped:
|
||||
self._opentestcase(report)
|
||||
self.append_skipped(report)
|
||||
reporter = self._opentestcase(report)
|
||||
reporter.append_skipped(report)
|
||||
self.update_testcase_duration(report)
|
||||
if report.when == "teardown":
|
||||
self.finalize(report)
|
||||
|
||||
def update_testcase_duration(self, report):
|
||||
"""accumulates total duration for nodeid from given report and updates
|
||||
the Junit.testcase with the new total if already created.
|
||||
"""
|
||||
total = self.durations.get(report.nodeid, 0.0)
|
||||
total += getattr(report, 'duration', 0.0)
|
||||
self.durations[report.nodeid] = total
|
||||
|
||||
testcase = self.tests_by_nodeid.get(report.nodeid)
|
||||
if testcase is not None:
|
||||
testcase.attr.time = total
|
||||
reporter = self.node_reporter(report)
|
||||
reporter.duration += getattr(report, 'duration', 0.0)
|
||||
|
||||
def pytest_collectreport(self, report):
|
||||
if not report.passed:
|
||||
self._opentestcase(report)
|
||||
reporter = self._opentestcase(report)
|
||||
if report.failed:
|
||||
self.append_collect_error(report)
|
||||
reporter.append_collect_error(report)
|
||||
else:
|
||||
self.append_collect_skipped(report)
|
||||
reporter.append_collect_skipped(report)
|
||||
|
||||
def pytest_internalerror(self, excrepr):
|
||||
self.errors += 1
|
||||
data = bin_xml_escape(excrepr)
|
||||
self.tests.append(
|
||||
Junit.testcase(
|
||||
Junit.error(data, message="internal error"),
|
||||
classname="pytest",
|
||||
name="internal"))
|
||||
reporter = self.node_reporter('internal')
|
||||
reporter.attrs.update(classname="pytest", name='internal')
|
||||
reporter._add_simple(Junit.error, 'internal error', excrepr)
|
||||
|
||||
def pytest_sessionstart(self):
|
||||
self.suite_start_time = time.time()
|
||||
@@ -292,19 +368,20 @@ class LogXML(object):
|
||||
logfile = open(self.logfile, 'w', encoding='utf-8')
|
||||
suite_stop_time = time.time()
|
||||
suite_time_delta = suite_stop_time - self.suite_start_time
|
||||
numtests = self.passed + self.failed
|
||||
|
||||
numtests = self.stats['passed'] + self.stats['failure'] + self.stats['skipped']
|
||||
|
||||
logfile.write('<?xml version="1.0" encoding="utf-8"?>')
|
||||
logfile.write(Junit.testsuite(
|
||||
self.tests,
|
||||
[x.to_xml() for x in self.node_reporters_ordered],
|
||||
name="pytest",
|
||||
errors=self.errors,
|
||||
failures=self.failed,
|
||||
skips=self.skipped,
|
||||
errors=self.stats['error'],
|
||||
failures=self.stats['failure'],
|
||||
skips=self.stats['skipped'],
|
||||
tests=numtests,
|
||||
time="%.3f" % suite_time_delta,
|
||||
).unicode(indent=0))
|
||||
time="%.3f" % suite_time_delta, ).unicode(indent=0))
|
||||
logfile.close()
|
||||
|
||||
def pytest_terminal_summary(self, terminalreporter):
|
||||
terminalreporter.write_sep("-", "generated xml file: %s" % (self.logfile))
|
||||
terminalreporter.write_sep("-",
|
||||
"generated xml file: %s" % (self.logfile))
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
""" core implementation of testing process: init, session, runtest loop. """
|
||||
import imp
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
import _pytest
|
||||
import _pytest._code
|
||||
import py
|
||||
import pytest, _pytest
|
||||
import os, sys, imp
|
||||
import pytest
|
||||
try:
|
||||
from collections import MutableMapping as MappingMixin
|
||||
except ImportError:
|
||||
@@ -91,11 +95,11 @@ def wrap_session(config, doit):
|
||||
except pytest.UsageError:
|
||||
raise
|
||||
except KeyboardInterrupt:
|
||||
excinfo = py.code.ExceptionInfo()
|
||||
excinfo = _pytest._code.ExceptionInfo()
|
||||
config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
|
||||
session.exitstatus = EXIT_INTERRUPTED
|
||||
except:
|
||||
excinfo = py.code.ExceptionInfo()
|
||||
excinfo = _pytest._code.ExceptionInfo()
|
||||
config.notify_exception(excinfo, config.option)
|
||||
session.exitstatus = EXIT_INTERNALERROR
|
||||
if excinfo.errisinstance(SystemExit):
|
||||
@@ -240,17 +244,11 @@ class Node(object):
|
||||
# used for storing artificial fixturedefs for direct parametrization
|
||||
self._name2pseudofixturedef = {}
|
||||
|
||||
#self.extrainit()
|
||||
|
||||
@property
|
||||
def ihook(self):
|
||||
""" fspath sensitive hook proxy used to call pytest hooks"""
|
||||
return self.session.gethookproxy(self.fspath)
|
||||
|
||||
#def extrainit(self):
|
||||
# """"extra initialization after Node is initialized. Implemented
|
||||
# by some subclasses. """
|
||||
|
||||
Module = compatproperty("Module")
|
||||
Class = compatproperty("Class")
|
||||
Instance = compatproperty("Instance")
|
||||
@@ -720,7 +718,8 @@ class Session(FSCollector):
|
||||
if rep.passed:
|
||||
has_matched = False
|
||||
for x in rep.result:
|
||||
if x.name == name:
|
||||
# TODO: remove parametrized workaround once collection structure contains parametrization
|
||||
if x.name == name or x.name.split("[")[0] == name:
|
||||
resultnodes.extend(self.matchnodes([x], nextnames))
|
||||
has_matched = True
|
||||
# XXX accept IDs that don't have "()" for class instances
|
||||
|
||||
@@ -58,7 +58,7 @@ pytest_cmdline_main.tryfirst = True
|
||||
|
||||
|
||||
def pytest_collection_modifyitems(items, config):
|
||||
keywordexpr = config.option.keyword
|
||||
keywordexpr = config.option.keyword.lstrip()
|
||||
matchexpr = config.option.markexpr
|
||||
if not keywordexpr and not matchexpr:
|
||||
return
|
||||
@@ -169,7 +169,7 @@ class MarkGenerator:
|
||||
""" Factory for :class:`MarkDecorator` objects - exposed as
|
||||
a ``pytest.mark`` singleton instance. Example::
|
||||
|
||||
import py
|
||||
import pytest
|
||||
@pytest.mark.slowtest
|
||||
def test_function():
|
||||
pass
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
""" monkeypatching and mocking functionality. """
|
||||
|
||||
import os, sys
|
||||
import re
|
||||
|
||||
from py.builtin import _basestring
|
||||
|
||||
RE_IMPORT_ERROR_NAME = re.compile("^No module named (.*)$")
|
||||
|
||||
|
||||
def pytest_funcarg__monkeypatch(request):
|
||||
"""The returned ``monkeypatch`` funcarg provides these
|
||||
helper methods to modify objects, dictionaries or os.environ::
|
||||
@@ -26,48 +31,71 @@ def pytest_funcarg__monkeypatch(request):
|
||||
return mpatch
|
||||
|
||||
|
||||
def resolve(name):
|
||||
# simplified from zope.dottedname
|
||||
parts = name.split('.')
|
||||
|
||||
used = parts.pop(0)
|
||||
found = __import__(used)
|
||||
for part in parts:
|
||||
used += '.' + part
|
||||
try:
|
||||
found = getattr(found, part)
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
continue
|
||||
# we use explicit un-nesting of the handling block in order
|
||||
# to avoid nested exceptions on python 3
|
||||
try:
|
||||
__import__(used)
|
||||
except ImportError as ex:
|
||||
# str is used for py2 vs py3
|
||||
expected = str(ex).split()[-1]
|
||||
if expected == used:
|
||||
raise
|
||||
else:
|
||||
raise ImportError(
|
||||
'import error in %s: %s' % (used, ex)
|
||||
)
|
||||
found = annotated_getattr(found, part, used)
|
||||
return found
|
||||
|
||||
|
||||
def annotated_getattr(obj, name, ann):
|
||||
try:
|
||||
obj = getattr(obj, name)
|
||||
except AttributeError:
|
||||
raise AttributeError(
|
||||
'%r object at %s has no attribute %r' % (
|
||||
type(obj).__name__, ann, name
|
||||
)
|
||||
)
|
||||
return obj
|
||||
|
||||
|
||||
def derive_importpath(import_path, raising):
|
||||
import pytest
|
||||
if not isinstance(import_path, _basestring) or "." not in import_path:
|
||||
raise TypeError("must be absolute import path string, not %r" %
|
||||
(import_path,))
|
||||
rest = []
|
||||
target = import_path
|
||||
while target:
|
||||
try:
|
||||
obj = __import__(target, None, None, "__doc__")
|
||||
except ImportError:
|
||||
if "." not in target:
|
||||
__tracebackhide__ = True
|
||||
pytest.fail("could not import any sub part: %s" %
|
||||
import_path)
|
||||
target, name = target.rsplit(".", 1)
|
||||
rest.append(name)
|
||||
else:
|
||||
assert rest
|
||||
try:
|
||||
while len(rest) > 1:
|
||||
attr = rest.pop()
|
||||
obj = getattr(obj, attr)
|
||||
attr = rest[0]
|
||||
if raising:
|
||||
getattr(obj, attr)
|
||||
except AttributeError:
|
||||
__tracebackhide__ = True
|
||||
pytest.fail("object %r has no attribute %r" % (obj, attr))
|
||||
return attr, obj
|
||||
|
||||
module, attr = import_path.rsplit('.', 1)
|
||||
target = resolve(module)
|
||||
if raising:
|
||||
annotated_getattr(target, attr, ann=module)
|
||||
return attr, target
|
||||
|
||||
|
||||
class Notset:
|
||||
def __repr__(self):
|
||||
return "<notset>"
|
||||
|
||||
|
||||
notset = Notset()
|
||||
|
||||
|
||||
class monkeypatch:
|
||||
""" Object keeping a record of setattr/item/env/syspath changes. """
|
||||
|
||||
def __init__(self):
|
||||
self._setattr = []
|
||||
self._setitem = []
|
||||
@@ -94,19 +122,19 @@ class monkeypatch:
|
||||
if value is notset:
|
||||
if not isinstance(target, _basestring):
|
||||
raise TypeError("use setattr(target, name, value) or "
|
||||
"setattr(target, value) with target being a dotted "
|
||||
"import string")
|
||||
"setattr(target, value) with target being a dotted "
|
||||
"import string")
|
||||
value = name
|
||||
name, target = derive_importpath(target, raising)
|
||||
|
||||
oldval = getattr(target, name, notset)
|
||||
if raising and oldval is notset:
|
||||
raise AttributeError("%r has no attribute %r" %(target, name))
|
||||
raise AttributeError("%r has no attribute %r" % (target, name))
|
||||
|
||||
# avoid class descriptors like staticmethod/classmethod
|
||||
if inspect.isclass(target):
|
||||
oldval = target.__dict__.get(name, notset)
|
||||
self._setattr.insert(0, (target, name, oldval))
|
||||
self._setattr.append((target, name, oldval))
|
||||
setattr(target, name, value)
|
||||
|
||||
def delattr(self, target, name=notset, raising=True):
|
||||
@@ -132,13 +160,12 @@ class monkeypatch:
|
||||
if raising:
|
||||
raise AttributeError(name)
|
||||
else:
|
||||
self._setattr.insert(0, (target, name,
|
||||
getattr(target, name, notset)))
|
||||
self._setattr.append((target, name, getattr(target, name, notset)))
|
||||
delattr(target, name)
|
||||
|
||||
def setitem(self, dic, name, value):
|
||||
""" Set dictionary entry ``name`` to value. """
|
||||
self._setitem.insert(0, (dic, name, dic.get(name, notset)))
|
||||
self._setitem.append((dic, name, dic.get(name, notset)))
|
||||
dic[name] = value
|
||||
|
||||
def delitem(self, dic, name, raising=True):
|
||||
@@ -151,7 +178,7 @@ class monkeypatch:
|
||||
if raising:
|
||||
raise KeyError(name)
|
||||
else:
|
||||
self._setitem.insert(0, (dic, name, dic.get(name, notset)))
|
||||
self._setitem.append((dic, name, dic.get(name, notset)))
|
||||
del dic[name]
|
||||
|
||||
def setenv(self, name, value, prepend=None):
|
||||
@@ -203,18 +230,18 @@ class monkeypatch:
|
||||
calling `undo()` will undo all of the changes made in
|
||||
both functions.
|
||||
"""
|
||||
for obj, name, value in self._setattr:
|
||||
for obj, name, value in reversed(self._setattr):
|
||||
if value is not notset:
|
||||
setattr(obj, name, value)
|
||||
else:
|
||||
delattr(obj, name)
|
||||
self._setattr[:] = []
|
||||
for dictionary, name, value in self._setitem:
|
||||
for dictionary, name, value in reversed(self._setitem):
|
||||
if value is notset:
|
||||
try:
|
||||
del dictionary[name]
|
||||
except KeyError:
|
||||
pass # was already deleted, so we have the desired state
|
||||
pass # was already deleted, so we have the desired state
|
||||
else:
|
||||
dictionary[name] = value
|
||||
self._setitem[:] = []
|
||||
|
||||
@@ -13,17 +13,21 @@ def pytest_addoption(parser):
|
||||
|
||||
@pytest.hookimpl(trylast=True)
|
||||
def pytest_configure(config):
|
||||
import py
|
||||
if config.option.pastebin == "all":
|
||||
tr = config.pluginmanager.getplugin('terminalreporter')
|
||||
# if no terminal reporter plugin is present, nothing we can do here;
|
||||
# this can happen when this function executes in a slave node
|
||||
# when using pytest-xdist, for example
|
||||
if tr is not None:
|
||||
config._pastebinfile = tempfile.TemporaryFile('w+')
|
||||
# pastebin file will be utf-8 encoded binary file
|
||||
config._pastebinfile = tempfile.TemporaryFile('w+b')
|
||||
oldwrite = tr._tw.write
|
||||
def tee_write(s, **kwargs):
|
||||
oldwrite(s, **kwargs)
|
||||
config._pastebinfile.write(str(s))
|
||||
if py.builtin._istext(s):
|
||||
s = s.encode('utf-8')
|
||||
config._pastebinfile.write(s)
|
||||
tr._tw.write = tee_write
|
||||
|
||||
def pytest_unconfigure(config):
|
||||
@@ -45,7 +49,7 @@ def create_new_paste(contents):
|
||||
"""
|
||||
Creates a new paste using bpaste.net service.
|
||||
|
||||
:contents: paste contents
|
||||
:contents: paste contents as utf-8 encoded bytes
|
||||
:returns: url to the pasted contents
|
||||
"""
|
||||
import re
|
||||
@@ -61,8 +65,8 @@ def create_new_paste(contents):
|
||||
'expiry': '1week',
|
||||
}
|
||||
url = 'https://bpaste.net'
|
||||
response = urlopen(url, data=urlencode(params)).read()
|
||||
m = re.search(r'href="/raw/(\w+)"', response)
|
||||
response = urlopen(url, data=urlencode(params).encode('ascii')).read()
|
||||
m = re.search(r'href="/raw/(\w+)"', response.decode('utf-8'))
|
||||
if m:
|
||||
return '%s/show/%s' % (url, m.group(1))
|
||||
else:
|
||||
|
||||
@@ -37,7 +37,6 @@ class pytestPDB:
|
||||
""" invoke PDB set_trace debugging, dropping any IO capturing. """
|
||||
import _pytest.config
|
||||
frame = sys._getframe().f_back
|
||||
capman = None
|
||||
if self._pluginmanager is not None:
|
||||
capman = self._pluginmanager.getplugin("capturemanager")
|
||||
if capman:
|
||||
@@ -45,7 +44,7 @@ class pytestPDB:
|
||||
tw = _pytest.config.create_terminal_writer(self._config)
|
||||
tw.line()
|
||||
tw.sep(">", "PDB set_trace (IO-capturing turned off)")
|
||||
self._pluginmanager.hook.pytest_enter_pdb()
|
||||
self._pluginmanager.hook.pytest_enter_pdb(config=self._config)
|
||||
pdb.Pdb().set_trace(frame)
|
||||
|
||||
|
||||
@@ -53,7 +52,9 @@ class PdbInvoke:
|
||||
def pytest_exception_interact(self, node, call, report):
|
||||
capman = node.config.pluginmanager.getplugin("capturemanager")
|
||||
if capman:
|
||||
capman.suspendcapture(in_=True)
|
||||
out, err = capman.suspendcapture(in_=True)
|
||||
sys.stdout.write(out)
|
||||
sys.stdout.write(err)
|
||||
_enter_pdb(node, call.excinfo, report)
|
||||
|
||||
def pytest_internalerror(self, excrepr, excinfo):
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
""" (disabled by default) support for testing pytest and pytest plugins. """
|
||||
import gc
|
||||
import sys
|
||||
import traceback
|
||||
import os
|
||||
import codecs
|
||||
import re
|
||||
import time
|
||||
import gc
|
||||
import os
|
||||
import platform
|
||||
from fnmatch import fnmatch
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
from fnmatch import fnmatch
|
||||
|
||||
import py
|
||||
import pytest
|
||||
from py.builtin import print_
|
||||
|
||||
from _pytest._code import Source
|
||||
import py
|
||||
import pytest
|
||||
from _pytest.main import Session, EXIT_OK
|
||||
|
||||
|
||||
@@ -472,7 +473,7 @@ class Testdir:
|
||||
ret = None
|
||||
for name, value in items:
|
||||
p = self.tmpdir.join(name).new(ext=ext)
|
||||
source = py.code.Source(value)
|
||||
source = Source(value)
|
||||
def my_totext(s, encoding="utf-8"):
|
||||
if py.builtin._isbytes(s):
|
||||
s = py.builtin._totext(s, encoding=encoding)
|
||||
@@ -835,7 +836,7 @@ class Testdir:
|
||||
to the temporarly directory to ensure it is a package.
|
||||
|
||||
"""
|
||||
kw = {self.request.function.__name__: py.code.Source(source).strip()}
|
||||
kw = {self.request.function.__name__: Source(source).strip()}
|
||||
path = self.makepyfile(**kw)
|
||||
if withinit:
|
||||
self.makepyfile(__init__ = "#")
|
||||
@@ -1041,8 +1042,8 @@ class LineMatcher:
|
||||
|
||||
def _getlines(self, lines2):
|
||||
if isinstance(lines2, str):
|
||||
lines2 = py.code.Source(lines2)
|
||||
if isinstance(lines2, py.code.Source):
|
||||
lines2 = Source(lines2)
|
||||
if isinstance(lines2, Source):
|
||||
lines2 = lines2.strip().lines
|
||||
return lines2
|
||||
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
""" Python test discovery, setup and run of test functions. """
|
||||
import re
|
||||
import fnmatch
|
||||
import functools
|
||||
import py
|
||||
import inspect
|
||||
import re
|
||||
import types
|
||||
import sys
|
||||
|
||||
import py
|
||||
import pytest
|
||||
from _pytest._code.code import TerminalRepr
|
||||
from _pytest.mark import MarkDecorator, MarkerError
|
||||
from py._code.code import TerminalRepr
|
||||
|
||||
try:
|
||||
import enum
|
||||
@@ -43,13 +45,31 @@ else:
|
||||
def _format_args(func):
|
||||
return inspect.formatargspec(*inspect.getargspec(func))
|
||||
|
||||
if sys.version_info[:2] == (2, 6):
|
||||
def isclass(object):
|
||||
""" Return true if the object is a class. Overrides inspect.isclass for
|
||||
python 2.6 because it will return True for objects which always return
|
||||
something on __getattr__ calls (see #1035).
|
||||
Backport of https://hg.python.org/cpython/rev/35bf8f7a8edc
|
||||
"""
|
||||
return isinstance(object, (type, types.ClassType))
|
||||
|
||||
def _has_positional_arg(func):
|
||||
return func.__code__.co_argcount
|
||||
|
||||
|
||||
def filter_traceback(entry):
|
||||
return entry.path != cutdir1 and not entry.path.relto(cutdir2)
|
||||
# entry.path might sometimes return a str object when the entry
|
||||
# points to dynamically generated code
|
||||
# see https://bitbucket.org/pytest-dev/py/issues/71
|
||||
raw_filename = entry.frame.code.raw.co_filename
|
||||
is_generated = '<' in raw_filename and '>' in raw_filename
|
||||
if is_generated:
|
||||
return False
|
||||
# entry.path might point to an inexisting file, in which case it will
|
||||
# alsso return a str object. see #1133
|
||||
p = py.path.local(entry.path)
|
||||
return p != cutdir1 and not p.relto(cutdir2)
|
||||
|
||||
|
||||
def get_real_func(obj):
|
||||
@@ -67,7 +87,7 @@ def getfslineno(obj):
|
||||
obj = get_real_func(obj)
|
||||
if hasattr(obj, 'place_as'):
|
||||
obj = obj.place_as
|
||||
fslineno = py.code.getfslineno(obj)
|
||||
fslineno = _pytest._code.getfslineno(obj)
|
||||
assert isinstance(fslineno[1], int), obj
|
||||
return fslineno
|
||||
|
||||
@@ -296,11 +316,14 @@ def pytest_pycollect_makeitem(collector, name, obj):
|
||||
elif collector.istestfunction(obj, name):
|
||||
# mock seems to store unbound methods (issue473), normalize it
|
||||
obj = getattr(obj, "__func__", obj)
|
||||
if not isfunction(obj):
|
||||
# We need to try and unwrap the function if it's a functools.partial
|
||||
# or a funtools.wrapped.
|
||||
# We musn't if it's been wrapped with mock.patch (python 2 only)
|
||||
if not (isfunction(obj) or isfunction(get_real_func(obj))):
|
||||
collector.warn(code="C2", message=
|
||||
"cannot collect %r because it is not a function."
|
||||
% name, )
|
||||
if getattr(obj, "__test__", True):
|
||||
elif getattr(obj, "__test__", True):
|
||||
if is_generator(obj):
|
||||
res = Generator(name, parent=collector)
|
||||
else:
|
||||
@@ -309,7 +332,7 @@ def pytest_pycollect_makeitem(collector, name, obj):
|
||||
|
||||
def is_generator(func):
|
||||
try:
|
||||
return py.code.getrawcode(func).co_flags & 32 # generator function
|
||||
return _pytest._code.getrawcode(func).co_flags & 32 # generator function
|
||||
except AttributeError: # builtin functions have no bytecode
|
||||
# assume them to not be generators
|
||||
return False
|
||||
@@ -362,12 +385,13 @@ class PyobjMixin(PyobjContext):
|
||||
def reportinfo(self):
|
||||
# XXX caching?
|
||||
obj = self.obj
|
||||
if hasattr(obj, 'compat_co_firstlineno'):
|
||||
compat_co_firstlineno = getattr(obj, 'compat_co_firstlineno', None)
|
||||
if isinstance(compat_co_firstlineno, int):
|
||||
# nose compatibility
|
||||
fspath = sys.modules[obj.__module__].__file__
|
||||
if fspath.endswith(".pyc"):
|
||||
fspath = fspath[:-1]
|
||||
lineno = obj.compat_co_firstlineno
|
||||
lineno = compat_co_firstlineno
|
||||
else:
|
||||
fspath, lineno = getfslineno(obj)
|
||||
modpath = self.getmodpath()
|
||||
@@ -383,15 +407,18 @@ class PyCollector(PyobjMixin, pytest.Collector):
|
||||
""" Look for the __test__ attribute, which is applied by the
|
||||
@nose.tools.istest decorator
|
||||
"""
|
||||
return safe_getattr(obj, '__test__', False)
|
||||
# We explicitly check for "is True" here to not mistakenly treat
|
||||
# classes with a custom __getattr__ returning something truthy (like a
|
||||
# function) as test classes.
|
||||
return safe_getattr(obj, '__test__', False) is True
|
||||
|
||||
def classnamefilter(self, name):
|
||||
return self._matches_prefix_or_glob_option('python_classes', name)
|
||||
|
||||
def istestfunction(self, obj, name):
|
||||
return (
|
||||
(self.funcnamefilter(name) or self.isnosetest(obj))
|
||||
and safe_getattr(obj, "__call__", False) and getfixturemarker(obj) is None
|
||||
(self.funcnamefilter(name) or self.isnosetest(obj)) and
|
||||
safe_getattr(obj, "__call__", False) and getfixturemarker(obj) is None
|
||||
)
|
||||
|
||||
def istestclass(self, obj, name):
|
||||
@@ -425,7 +452,7 @@ class PyCollector(PyobjMixin, pytest.Collector):
|
||||
seen = {}
|
||||
l = []
|
||||
for dic in dicts:
|
||||
for name, obj in dic.items():
|
||||
for name, obj in list(dic.items()):
|
||||
if name in seen:
|
||||
continue
|
||||
seen[name] = True
|
||||
@@ -584,7 +611,7 @@ class Module(pytest.File, PyCollector):
|
||||
mod = self.fspath.pyimport(ensuresyspath=importmode)
|
||||
except SyntaxError:
|
||||
raise self.CollectError(
|
||||
py.code.ExceptionInfo().getrepr(style="short"))
|
||||
_pytest._code.ExceptionInfo().getrepr(style="short"))
|
||||
except self.fspath.ImportMismatchError:
|
||||
e = sys.exc_info()[1]
|
||||
raise self.CollectError(
|
||||
@@ -690,7 +717,7 @@ class FunctionMixin(PyobjMixin):
|
||||
|
||||
def _prunetraceback(self, excinfo):
|
||||
if hasattr(self, '_obj') and not self.config.option.fulltrace:
|
||||
code = py.code.Code(get_real_func(self.obj))
|
||||
code = _pytest._code.Code(get_real_func(self.obj))
|
||||
path, firstlineno = code.path, code.firstlineno
|
||||
traceback = excinfo.traceback
|
||||
ntraceback = traceback.cut(path=path, firstlineno=firstlineno)
|
||||
@@ -713,7 +740,7 @@ class FunctionMixin(PyobjMixin):
|
||||
def _repr_failure_py(self, excinfo, style="long"):
|
||||
if excinfo.errisinstance(pytest.fail.Exception):
|
||||
if not excinfo.value.pytrace:
|
||||
return str(excinfo.value)
|
||||
return py._builtin._totext(excinfo.value)
|
||||
return super(FunctionMixin, self)._repr_failure_py(excinfo,
|
||||
style=style)
|
||||
|
||||
@@ -844,8 +871,6 @@ class CallSpec2(object):
|
||||
getattr(self, valtype_for_arg)[arg] = val
|
||||
self.indices[arg] = param_index
|
||||
self._arg2scopenum[arg] = scopenum
|
||||
if val is _notexists:
|
||||
self._emptyparamspecified = True
|
||||
self._idlist.append(id)
|
||||
self.keywords.update(keywords)
|
||||
|
||||
@@ -963,6 +988,15 @@ class Metafunc(FuncargnamesCompatAttr):
|
||||
argvalues = [(val,) for val in argvalues]
|
||||
if not argvalues:
|
||||
argvalues = [(_notexists,) * len(argnames)]
|
||||
# we passed a empty list to parameterize, skip that test
|
||||
#
|
||||
fs, lineno = getfslineno(self.function)
|
||||
newmark = pytest.mark.skip(
|
||||
reason="got empty parameter set %r, function %s at %s:%d" % (
|
||||
argnames, self.function.__name__, fs, lineno))
|
||||
newmarks = newkeywords.setdefault(0, {})
|
||||
newmarks[newmark.markname] = newmark
|
||||
|
||||
|
||||
if scope is None:
|
||||
scope = "function"
|
||||
@@ -1041,6 +1075,8 @@ class Metafunc(FuncargnamesCompatAttr):
|
||||
|
||||
|
||||
if _PY3:
|
||||
import codecs
|
||||
|
||||
def _escape_bytes(val):
|
||||
"""
|
||||
If val is pure ascii, returns it as a str(), otherwise escapes
|
||||
@@ -1053,18 +1089,21 @@ if _PY3:
|
||||
want to return escaped bytes for any byte, even if they match
|
||||
a utf-8 string.
|
||||
"""
|
||||
# source: http://goo.gl/bGsnwC
|
||||
import codecs
|
||||
encoded_bytes, _ = codecs.escape_encode(val)
|
||||
return encoded_bytes.decode('ascii')
|
||||
if val:
|
||||
# source: http://goo.gl/bGsnwC
|
||||
encoded_bytes, _ = codecs.escape_encode(val)
|
||||
return encoded_bytes.decode('ascii')
|
||||
else:
|
||||
# empty bytes crashes codecs.escape_encode (#1087)
|
||||
return ''
|
||||
else:
|
||||
def _escape_bytes(val):
|
||||
"""
|
||||
In py2 bytes and str are the same, so return it unchanged if it
|
||||
In py2 bytes and str are the same type, so return it unchanged if it
|
||||
is a full ascii string, otherwise escape it into its binary form.
|
||||
"""
|
||||
try:
|
||||
return val.encode('ascii')
|
||||
return val.decode('ascii')
|
||||
except UnicodeDecodeError:
|
||||
return val.encode('string-escape')
|
||||
|
||||
@@ -1083,7 +1122,7 @@ def _idval(val, argname, idx, idfn):
|
||||
elif isinstance(val, (float, int, str, bool, NoneType)):
|
||||
return str(val)
|
||||
elif isinstance(val, REGEX_TYPE):
|
||||
return val.pattern
|
||||
return _escape_bytes(val.pattern) if isinstance(val.pattern, bytes) else val.pattern
|
||||
elif enum is not None and isinstance(val, enum.Enum):
|
||||
return str(val)
|
||||
elif isclass(val) and hasattr(val, '__name__'):
|
||||
@@ -1093,7 +1132,7 @@ def _idval(val, argname, idx, idfn):
|
||||
# convertible to ascii, return it as an str() object instead
|
||||
try:
|
||||
return str(val)
|
||||
except UnicodeDecodeError:
|
||||
except UnicodeError:
|
||||
# fallthrough
|
||||
pass
|
||||
return str(argname)+str(idx)
|
||||
@@ -1129,12 +1168,12 @@ def _showfixtures_main(config, session):
|
||||
assert fixturedefs is not None
|
||||
if not fixturedefs:
|
||||
continue
|
||||
fixturedef = fixturedefs[-1]
|
||||
loc = getlocation(fixturedef.func, curdir)
|
||||
available.append((len(fixturedef.baseid),
|
||||
fixturedef.func.__module__,
|
||||
curdir.bestrelpath(loc),
|
||||
fixturedef.argname, fixturedef))
|
||||
for fixturedef in fixturedefs:
|
||||
loc = getlocation(fixturedef.func, curdir)
|
||||
available.append((len(fixturedef.baseid),
|
||||
fixturedef.func.__module__,
|
||||
curdir.bestrelpath(loc),
|
||||
fixturedef.argname, fixturedef))
|
||||
|
||||
available.sort()
|
||||
currentmodule = None
|
||||
@@ -1171,10 +1210,10 @@ def getlocation(function, curdir):
|
||||
# builtin pytest.raises helper
|
||||
|
||||
def raises(expected_exception, *args, **kwargs):
|
||||
""" assert that a code block/function call raises @expected_exception
|
||||
""" assert that a code block/function call raises ``expected_exception``
|
||||
and raise a failure exception otherwise.
|
||||
|
||||
This helper produces a ``py.code.ExceptionInfo()`` object.
|
||||
This helper produces a ``ExceptionInfo()`` object (see below).
|
||||
|
||||
If using Python 2.5 or above, you may use this function as a
|
||||
context manager::
|
||||
@@ -1182,6 +1221,28 @@ def raises(expected_exception, *args, **kwargs):
|
||||
>>> with raises(ZeroDivisionError):
|
||||
... 1/0
|
||||
|
||||
.. note::
|
||||
|
||||
When using ``pytest.raises`` as a context manager, it's worthwhile to
|
||||
note that normal context manager rules apply and that the exception
|
||||
raised *must* be the final line in the scope of the context manager.
|
||||
Lines of code after that, within the scope of the context manager will
|
||||
not be executed. For example::
|
||||
|
||||
>>> with raises(OSError) as exc_info:
|
||||
assert 1 == 1 # this will execute as expected
|
||||
raise OSError(errno.EEXISTS, 'directory exists')
|
||||
assert exc_info.value.errno == errno.EEXISTS # this will not execute
|
||||
|
||||
Instead, the following approach must be taken (note the difference in
|
||||
scope)::
|
||||
|
||||
>>> with raises(OSError) as exc_info:
|
||||
assert 1 == 1 # this will execute as expected
|
||||
raise OSError(errno.EEXISTS, 'directory exists')
|
||||
|
||||
assert exc_info.value.errno == errno.EEXISTS # this will now execute
|
||||
|
||||
Or you can specify a callable by passing a to-be-called lambda::
|
||||
|
||||
>>> raises(ZeroDivisionError, lambda: 1/0)
|
||||
@@ -1201,21 +1262,22 @@ def raises(expected_exception, *args, **kwargs):
|
||||
>>> raises(ZeroDivisionError, "f(0)")
|
||||
<ExceptionInfo ...>
|
||||
|
||||
Performance note:
|
||||
-----------------
|
||||
.. autoclass:: _pytest._code.ExceptionInfo
|
||||
:members:
|
||||
|
||||
Similar to caught exception objects in Python, explicitly clearing
|
||||
local references to returned ``py.code.ExceptionInfo`` objects can
|
||||
help the Python interpreter speed up its garbage collection.
|
||||
.. note::
|
||||
Similar to caught exception objects in Python, explicitly clearing
|
||||
local references to returned ``ExceptionInfo`` objects can
|
||||
help the Python interpreter speed up its garbage collection.
|
||||
|
||||
Clearing those references breaks a reference cycle
|
||||
(``ExceptionInfo`` --> caught exception --> frame stack raising
|
||||
the exception --> current frame stack --> local variables -->
|
||||
``ExceptionInfo``) which makes Python keep all objects referenced
|
||||
from that cycle (including all local variables in the current
|
||||
frame) alive until the next cyclic garbage collection run. See the
|
||||
official Python ``try`` statement documentation for more detailed
|
||||
information.
|
||||
Clearing those references breaks a reference cycle
|
||||
(``ExceptionInfo`` --> caught exception --> frame stack raising
|
||||
the exception --> current frame stack --> local variables -->
|
||||
``ExceptionInfo``) which makes Python keep all objects referenced
|
||||
from that cycle (including all local variables in the current
|
||||
frame) alive until the next cyclic garbage collection run. See the
|
||||
official Python ``try`` statement documentation for more detailed
|
||||
information.
|
||||
|
||||
"""
|
||||
__tracebackhide__ = True
|
||||
@@ -1244,19 +1306,19 @@ def raises(expected_exception, *args, **kwargs):
|
||||
loc.update(kwargs)
|
||||
#print "raises frame scope: %r" % frame.f_locals
|
||||
try:
|
||||
code = py.code.Source(code).compile()
|
||||
code = _pytest._code.Source(code).compile()
|
||||
py.builtin.exec_(code, frame.f_globals, loc)
|
||||
# XXX didn'T mean f_globals == f_locals something special?
|
||||
# this is destroyed here ...
|
||||
except expected_exception:
|
||||
return py.code.ExceptionInfo()
|
||||
return _pytest._code.ExceptionInfo()
|
||||
else:
|
||||
func = args[0]
|
||||
try:
|
||||
func(*args[1:], **kwargs)
|
||||
except expected_exception:
|
||||
return py.code.ExceptionInfo()
|
||||
pytest.fail("DID NOT RAISE")
|
||||
return _pytest._code.ExceptionInfo()
|
||||
pytest.fail("DID NOT RAISE {0}".format(expected_exception))
|
||||
|
||||
class RaisesContext(object):
|
||||
def __init__(self, expected_exception):
|
||||
@@ -1264,7 +1326,7 @@ class RaisesContext(object):
|
||||
self.excinfo = None
|
||||
|
||||
def __enter__(self):
|
||||
self.excinfo = object.__new__(py.code.ExceptionInfo)
|
||||
self.excinfo = object.__new__(_pytest._code.ExceptionInfo)
|
||||
return self.excinfo
|
||||
|
||||
def __exit__(self, *tp):
|
||||
@@ -1353,15 +1415,6 @@ class Function(FunctionMixin, pytest.Item, FuncargnamesCompatAttr):
|
||||
self.ihook.pytest_pyfunc_call(pyfuncitem=self)
|
||||
|
||||
def setup(self):
|
||||
# check if parametrization happend with an empty list
|
||||
try:
|
||||
self.callspec._emptyparamspecified
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
fs, lineno = self._getfslineno()
|
||||
pytest.skip("got empty parameter set, function %s at %s:%d" %(
|
||||
self.function.__name__, fs, lineno))
|
||||
super(Function, self).setup()
|
||||
fillfixtures(self)
|
||||
|
||||
@@ -1396,7 +1449,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||
self._pyfuncitem = pyfuncitem
|
||||
#: fixture for which this request is being performed
|
||||
self.fixturename = None
|
||||
#: Scope string, one of "function", "cls", "module", "session"
|
||||
#: Scope string, one of "function", "class", "module", "session"
|
||||
self.scope = "function"
|
||||
self._funcargs = {}
|
||||
self._fixturedefs = {}
|
||||
@@ -1712,13 +1765,14 @@ class FixtureLookupError(LookupError):
|
||||
stack.extend(map(lambda x: x.func, self.fixturestack))
|
||||
msg = self.msg
|
||||
if msg is not None:
|
||||
stack = stack[:-1] # the last fixture raise an error, let's present
|
||||
# it at the requesting side
|
||||
# the last fixture raise an error, let's present
|
||||
# it at the requesting side
|
||||
stack = stack[:-1]
|
||||
for function in stack:
|
||||
fspath, lineno = getfslineno(function)
|
||||
try:
|
||||
lines, _ = inspect.getsourcelines(get_real_func(function))
|
||||
except IOError:
|
||||
except (IOError, IndexError):
|
||||
error_msg = "file %s, line %s: source code not available"
|
||||
addline(error_msg % (fspath, lineno+1))
|
||||
else:
|
||||
@@ -1970,7 +2024,7 @@ class FixtureManager:
|
||||
def fail_fixturefunc(fixturefunc, msg):
|
||||
fs, lineno = getfslineno(fixturefunc)
|
||||
location = "%s:%s" % (fs, lineno+1)
|
||||
source = py.code.Source(fixturefunc)
|
||||
source = _pytest._code.Source(fixturefunc)
|
||||
pytest.fail(msg + ":\n\n" + str(source.indent()) + "\n" + location,
|
||||
pytrace=False)
|
||||
|
||||
@@ -2103,7 +2157,7 @@ def num_mock_patch_args(function):
|
||||
|
||||
def getfuncargnames(function, startindex=None):
|
||||
# XXX merge with main.py's varnames
|
||||
#assert not inspect.isclass(function)
|
||||
#assert not isclass(function)
|
||||
realfunction = function
|
||||
while hasattr(realfunction, "__wrapped__"):
|
||||
realfunction = realfunction.__wrapped__
|
||||
@@ -2113,14 +2167,14 @@ def getfuncargnames(function, startindex=None):
|
||||
startindex += num_mock_patch_args(function)
|
||||
function = realfunction
|
||||
if isinstance(function, functools.partial):
|
||||
argnames = inspect.getargs(py.code.getrawcode(function.func))[0]
|
||||
argnames = inspect.getargs(_pytest._code.getrawcode(function.func))[0]
|
||||
partial = function
|
||||
argnames = argnames[len(partial.args):]
|
||||
if partial.keywords:
|
||||
for kw in partial.keywords:
|
||||
argnames.remove(kw)
|
||||
else:
|
||||
argnames = inspect.getargs(py.code.getrawcode(function))[0]
|
||||
argnames = inspect.getargs(_pytest._code.getrawcode(function))[0]
|
||||
defaults = getattr(function, 'func_defaults',
|
||||
getattr(function, '__defaults__', None)) or ()
|
||||
numdefaults = len(defaults)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
""" recording warnings during test function execution. """
|
||||
|
||||
import inspect
|
||||
|
||||
import _pytest._code
|
||||
import py
|
||||
import sys
|
||||
import warnings
|
||||
@@ -28,19 +30,48 @@ def pytest_namespace():
|
||||
'warns': warns}
|
||||
|
||||
|
||||
def deprecated_call(func, *args, **kwargs):
|
||||
"""Assert that ``func(*args, **kwargs)`` triggers a DeprecationWarning.
|
||||
"""
|
||||
wrec = WarningsRecorder()
|
||||
with wrec:
|
||||
warnings.simplefilter('always') # ensure all warnings are triggered
|
||||
ret = func(*args, **kwargs)
|
||||
def deprecated_call(func=None, *args, **kwargs):
|
||||
""" assert that calling ``func(*args, **kwargs)`` triggers a
|
||||
``DeprecationWarning`` or ``PendingDeprecationWarning``.
|
||||
|
||||
depwarnings = (DeprecationWarning, PendingDeprecationWarning)
|
||||
if not any(r.category in depwarnings for r in wrec):
|
||||
This function can be used as a context manager::
|
||||
|
||||
>>> with deprecated_call():
|
||||
... myobject.deprecated_method()
|
||||
|
||||
Note: we cannot use WarningsRecorder here because it is still subject
|
||||
to the mechanism that prevents warnings of the same type from being
|
||||
triggered twice for the same module. See #1190.
|
||||
"""
|
||||
if not func:
|
||||
return WarningsChecker(expected_warning=DeprecationWarning)
|
||||
|
||||
categories = []
|
||||
|
||||
def warn_explicit(message, category, *args, **kwargs):
|
||||
categories.append(category)
|
||||
old_warn_explicit(message, category, *args, **kwargs)
|
||||
|
||||
def warn(message, category=None, *args, **kwargs):
|
||||
if isinstance(message, Warning):
|
||||
categories.append(message.__class__)
|
||||
else:
|
||||
categories.append(category)
|
||||
old_warn(message, category, *args, **kwargs)
|
||||
|
||||
old_warn = warnings.warn
|
||||
old_warn_explicit = warnings.warn_explicit
|
||||
warnings.warn_explicit = warn_explicit
|
||||
warnings.warn = warn
|
||||
try:
|
||||
ret = func(*args, **kwargs)
|
||||
finally:
|
||||
warnings.warn_explicit = old_warn_explicit
|
||||
warnings.warn = old_warn
|
||||
deprecation_categories = (DeprecationWarning, PendingDeprecationWarning)
|
||||
if not any(issubclass(c, deprecation_categories) for c in categories):
|
||||
__tracebackhide__ = True
|
||||
raise AssertionError("%r did not produce DeprecationWarning" % (func,))
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
@@ -71,7 +102,7 @@ def warns(expected_warning, *args, **kwargs):
|
||||
loc.update(kwargs)
|
||||
|
||||
with wcheck:
|
||||
code = py.code.Source(code).compile()
|
||||
code = _pytest._code.Source(code).compile()
|
||||
py.builtin.exec_(code, frame.f_globals, loc)
|
||||
else:
|
||||
func = args[0]
|
||||
@@ -150,8 +181,8 @@ class WarningsRecorder(object):
|
||||
self._module.showwarning = showwarning
|
||||
|
||||
# allow the same warning to be raised more than once
|
||||
self._module.simplefilter('always', append=True)
|
||||
|
||||
self._module.simplefilter('always')
|
||||
return self
|
||||
|
||||
def __exit__(self, *exc_info):
|
||||
|
||||
@@ -5,7 +5,8 @@ from time import time
|
||||
|
||||
import py
|
||||
import pytest
|
||||
from py._code.code import TerminalRepr
|
||||
from _pytest._code.code import TerminalRepr, ExceptionInfo
|
||||
|
||||
|
||||
def pytest_namespace():
|
||||
return {
|
||||
@@ -151,7 +152,7 @@ class CallInfo:
|
||||
self.stop = time()
|
||||
raise
|
||||
except:
|
||||
self.excinfo = py.code.ExceptionInfo()
|
||||
self.excinfo = ExceptionInfo()
|
||||
self.stop = time()
|
||||
|
||||
def __repr__(self):
|
||||
@@ -177,9 +178,13 @@ class BaseReport(object):
|
||||
self.__dict__.update(kw)
|
||||
|
||||
def toterminal(self, out):
|
||||
longrepr = self.longrepr
|
||||
if hasattr(self, 'node'):
|
||||
out.line(getslaveinfoline(self.node))
|
||||
|
||||
longrepr = self.longrepr
|
||||
if longrepr is None:
|
||||
return
|
||||
|
||||
if hasattr(longrepr, 'toterminal'):
|
||||
longrepr.toterminal(out)
|
||||
else:
|
||||
@@ -211,7 +216,7 @@ def pytest_runtest_makereport(item, call):
|
||||
outcome = "passed"
|
||||
longrepr = None
|
||||
else:
|
||||
if not isinstance(excinfo, py.code.ExceptionInfo):
|
||||
if not isinstance(excinfo, ExceptionInfo):
|
||||
outcome = "failed"
|
||||
longrepr = excinfo
|
||||
elif excinfo.errisinstance(pytest.skip.Exception):
|
||||
@@ -430,7 +435,10 @@ class OutcomeException(Exception):
|
||||
|
||||
def __repr__(self):
|
||||
if self.msg:
|
||||
return str(self.msg)
|
||||
val = self.msg
|
||||
if isinstance(val, bytes):
|
||||
val = py._builtin._totext(val, errors='replace')
|
||||
return val
|
||||
return "<%s instance>" %(self.__class__.__name__,)
|
||||
__str__ = __repr__
|
||||
|
||||
@@ -469,7 +477,7 @@ def skip(msg=""):
|
||||
skip.Exception = Skipped
|
||||
|
||||
def fail(msg="", pytrace=True):
|
||||
""" explicitely fail an currently-executing test with the given Message.
|
||||
""" explicitly fail an currently-executing test with the given Message.
|
||||
|
||||
:arg pytrace: if false the msg represents the full failure information
|
||||
and no python traceback will be reported.
|
||||
|
||||
@@ -5,6 +5,8 @@ import traceback
|
||||
|
||||
import py
|
||||
import pytest
|
||||
from _pytest.mark import MarkInfo, MarkDecorator
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("general")
|
||||
@@ -12,6 +14,13 @@ def pytest_addoption(parser):
|
||||
action="store_true", dest="runxfail", default=False,
|
||||
help="run tests even if they are marked xfail")
|
||||
|
||||
parser.addini("xfail_strict", "default for the strict parameter of xfail "
|
||||
"markers when not given explicitly (default: "
|
||||
"False)",
|
||||
default=False,
|
||||
type="bool")
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
if config.option.runxfail:
|
||||
old = pytest.xfail
|
||||
@@ -21,6 +30,11 @@ def pytest_configure(config):
|
||||
nop.Exception = XFailed
|
||||
setattr(pytest, "xfail", nop)
|
||||
|
||||
config.addinivalue_line("markers",
|
||||
"skip(reason=None): skip the given test function with an optional reason. "
|
||||
"Example: skip(reason=\"no way of currently testing this\") skips the "
|
||||
"test."
|
||||
)
|
||||
config.addinivalue_line("markers",
|
||||
"skipif(condition): skip the given test function if eval(condition) "
|
||||
"results in a True value. Evaluation happens within the "
|
||||
@@ -29,27 +43,31 @@ def pytest_configure(config):
|
||||
"http://pytest.org/latest/skipping.html"
|
||||
)
|
||||
config.addinivalue_line("markers",
|
||||
"xfail(condition, reason=None, run=True, raises=None): mark the the test function "
|
||||
"as an expected failure if eval(condition) has a True value. "
|
||||
"Optionally specify a reason for better reporting and run=False if "
|
||||
"you don't even want to execute the test function. If only specific "
|
||||
"exception(s) are expected, you can list them in raises, and if the test fails "
|
||||
"in other ways, it will be reported as a true failure. "
|
||||
"See http://pytest.org/latest/skipping.html"
|
||||
"xfail(condition, reason=None, run=True, raises=None, strict=False): "
|
||||
"mark the the test function as an expected failure if eval(condition) "
|
||||
"has a True value. Optionally specify a reason for better reporting "
|
||||
"and run=False if you don't even want to execute the test function. "
|
||||
"If only specific exception(s) are expected, you can list them in "
|
||||
"raises, and if the test fails in other ways, it will be reported as "
|
||||
"a true failure. See http://pytest.org/latest/skipping.html"
|
||||
)
|
||||
|
||||
|
||||
def pytest_namespace():
|
||||
return dict(xfail=xfail)
|
||||
|
||||
|
||||
class XFailed(pytest.fail.Exception):
|
||||
""" raised from an explicit call to pytest.xfail() """
|
||||
|
||||
|
||||
def xfail(reason=""):
|
||||
""" xfail an executing test or setup functions with the given reason."""
|
||||
__tracebackhide__ = True
|
||||
raise XFailed(reason)
|
||||
xfail.Exception = XFailed
|
||||
|
||||
|
||||
class MarkEvaluator:
|
||||
def __init__(self, item, name):
|
||||
self.item = item
|
||||
@@ -102,7 +120,7 @@ class MarkEvaluator:
|
||||
return self.result
|
||||
if self.holder:
|
||||
d = self._getglobals()
|
||||
if self.holder.args:
|
||||
if self.holder.args or 'condition' in self.holder.kwargs:
|
||||
self.result = False
|
||||
# "holder" might be a MarkInfo or a MarkDecorator; only
|
||||
# MarkInfo keeps track of all parameters it received in an
|
||||
@@ -112,6 +130,8 @@ class MarkEvaluator:
|
||||
else:
|
||||
arglist = [(self.holder.args, self.holder.kwargs)]
|
||||
for args, kwargs in arglist:
|
||||
if 'condition' in kwargs:
|
||||
args = (kwargs['condition'],)
|
||||
for expr in args:
|
||||
self.expr = expr
|
||||
if isinstance(expr, py.builtin._basestring):
|
||||
@@ -147,23 +167,59 @@ class MarkEvaluator:
|
||||
|
||||
@pytest.hookimpl(tryfirst=True)
|
||||
def pytest_runtest_setup(item):
|
||||
evalskip = MarkEvaluator(item, 'skipif')
|
||||
if evalskip.istrue():
|
||||
item._evalskip = evalskip
|
||||
pytest.skip(evalskip.getexplanation())
|
||||
# Check if skip or skipif are specified as pytest marks
|
||||
|
||||
skipif_info = item.keywords.get('skipif')
|
||||
if isinstance(skipif_info, (MarkInfo, MarkDecorator)):
|
||||
eval_skipif = MarkEvaluator(item, 'skipif')
|
||||
if eval_skipif.istrue():
|
||||
item._evalskip = eval_skipif
|
||||
pytest.skip(eval_skipif.getexplanation())
|
||||
|
||||
skip_info = item.keywords.get('skip')
|
||||
if isinstance(skip_info, (MarkInfo, MarkDecorator)):
|
||||
item._evalskip = True
|
||||
if 'reason' in skip_info.kwargs:
|
||||
pytest.skip(skip_info.kwargs['reason'])
|
||||
elif skip_info.args:
|
||||
pytest.skip(skip_info.args[0])
|
||||
else:
|
||||
pytest.skip("unconditional skip")
|
||||
|
||||
item._evalxfail = MarkEvaluator(item, 'xfail')
|
||||
check_xfail_no_run(item)
|
||||
|
||||
|
||||
@pytest.mark.hookwrapper
|
||||
def pytest_pyfunc_call(pyfuncitem):
|
||||
check_xfail_no_run(pyfuncitem)
|
||||
outcome = yield
|
||||
passed = outcome.excinfo is None
|
||||
if passed:
|
||||
check_strict_xfail(pyfuncitem)
|
||||
|
||||
|
||||
def check_xfail_no_run(item):
|
||||
"""check xfail(run=False)"""
|
||||
if not item.config.option.runxfail:
|
||||
evalxfail = item._evalxfail
|
||||
if evalxfail.istrue():
|
||||
if not evalxfail.get('run', True):
|
||||
pytest.xfail("[NOTRUN] " + evalxfail.getexplanation())
|
||||
|
||||
|
||||
def check_strict_xfail(pyfuncitem):
|
||||
"""check xfail(strict=True) for the given PASSING test"""
|
||||
evalxfail = pyfuncitem._evalxfail
|
||||
if evalxfail.istrue():
|
||||
strict_default = pyfuncitem.config.getini('xfail_strict')
|
||||
is_strict_xfail = evalxfail.get('strict', strict_default)
|
||||
if is_strict_xfail:
|
||||
del pyfuncitem._evalxfail
|
||||
explanation = evalxfail.getexplanation()
|
||||
pytest.fail('[XPASS(strict)] ' + explanation, pytrace=False)
|
||||
|
||||
|
||||
@pytest.hookimpl(hookwrapper=True)
|
||||
def pytest_runtest_makereport(item, call):
|
||||
outcome = yield
|
||||
@@ -230,6 +286,9 @@ def pytest_terminal_summary(terminalreporter):
|
||||
show_skipped(terminalreporter, lines)
|
||||
elif char == "E":
|
||||
show_simple(terminalreporter, lines, 'error', "ERROR %s")
|
||||
elif char == 'p':
|
||||
show_simple(terminalreporter, lines, 'passed', "PASSED %s")
|
||||
|
||||
if lines:
|
||||
tr._tw.sep("=", "short test summary info")
|
||||
for line in lines:
|
||||
@@ -266,9 +325,8 @@ def cached_eval(config, expr, d):
|
||||
try:
|
||||
return config._evalcache[expr]
|
||||
except KeyError:
|
||||
#import sys
|
||||
#print >>sys.stderr, ("cache-miss: %r" % expr)
|
||||
exprcode = py.code.compile(expr, mode="eval")
|
||||
import _pytest._code
|
||||
exprcode = _pytest._code.compile(expr, mode="eval")
|
||||
config._evalcache[expr] = x = eval(exprcode, d)
|
||||
return x
|
||||
|
||||
|
||||
@@ -22,7 +22,8 @@ def pytest_addoption(parser):
|
||||
group._addoption('-r',
|
||||
action="store", dest="reportchars", default=None, metavar="chars",
|
||||
help="show extra test summary info as specified by chars (f)ailed, "
|
||||
"(E)error, (s)skipped, (x)failed, (X)passed (w)pytest-warnings (a)all.")
|
||||
"(E)error, (s)skipped, (x)failed, (X)passed (w)pytest-warnings "
|
||||
"(p)passed, (P)passed with output, (a)all except pP.")
|
||||
group._addoption('-l', '--showlocals',
|
||||
action="store_true", dest="showlocals", default=False,
|
||||
help="show locals in tracebacks (disabled by default).")
|
||||
@@ -111,6 +112,7 @@ class TerminalReporter:
|
||||
self.currentfspath = None
|
||||
self.reportchars = getreportopt(config)
|
||||
self.hasmarkup = self._tw.hasmarkup
|
||||
self.isatty = file.isatty()
|
||||
|
||||
def hasopt(self, char):
|
||||
char = {'xfailed': 'x', 'skipped': 's'}.get(char, char)
|
||||
@@ -233,7 +235,7 @@ class TerminalReporter:
|
||||
self.currentfspath = -2
|
||||
|
||||
def pytest_collection(self):
|
||||
if not self.hasmarkup and self.config.option.verbose >= 1:
|
||||
if not self.isatty and self.config.option.verbose >= 1:
|
||||
self.write("collecting ... ", bold=True)
|
||||
|
||||
def pytest_collectreport(self, report):
|
||||
@@ -243,7 +245,7 @@ class TerminalReporter:
|
||||
self.stats.setdefault("skipped", []).append(report)
|
||||
items = [x for x in report.result if isinstance(x, pytest.Item)]
|
||||
self._numcollected += len(items)
|
||||
if self.hasmarkup:
|
||||
if self.isatty:
|
||||
#self.write_fspath_result(report.nodeid, 'E')
|
||||
self.report_collect()
|
||||
|
||||
@@ -262,7 +264,7 @@ class TerminalReporter:
|
||||
line += " / %d errors" % errors
|
||||
if skipped:
|
||||
line += " / %d skipped" % skipped
|
||||
if self.hasmarkup:
|
||||
if self.isatty:
|
||||
if final:
|
||||
line += " \n"
|
||||
self.rewrite(line, bold=True)
|
||||
@@ -364,10 +366,11 @@ class TerminalReporter:
|
||||
EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, EXIT_USAGEERROR,
|
||||
EXIT_NOTESTSCOLLECTED)
|
||||
if exitstatus in summary_exit_codes:
|
||||
self.config.hook.pytest_terminal_summary(terminalreporter=self)
|
||||
self.summary_errors()
|
||||
self.summary_failures()
|
||||
self.summary_warnings()
|
||||
self.config.hook.pytest_terminal_summary(terminalreporter=self)
|
||||
self.summary_passes()
|
||||
if exitstatus == EXIT_INTERRUPTED:
|
||||
self._report_keyboardinterrupt()
|
||||
del self._keyboardinterrupt_memo
|
||||
@@ -389,6 +392,7 @@ class TerminalReporter:
|
||||
if self.config.option.fulltrace:
|
||||
excrepr.toterminal(self._tw)
|
||||
else:
|
||||
self._tw.line("to show a full traceback on KeyboardInterrupt use --fulltrace", yellow=True)
|
||||
excrepr.reprcrash.toterminal(self._tw)
|
||||
|
||||
def _locationline(self, nodeid, fspath, lineno, domain):
|
||||
@@ -446,6 +450,18 @@ class TerminalReporter:
|
||||
self._tw.line("W%s %s %s" % (w.code,
|
||||
w.fslocation, w.message))
|
||||
|
||||
def summary_passes(self):
|
||||
if self.config.option.tbstyle != "no":
|
||||
if self.hasopt("P"):
|
||||
reports = self.getreports('passed')
|
||||
if not reports:
|
||||
return
|
||||
self.write_sep("=", "PASSES")
|
||||
for rep in reports:
|
||||
msg = self._getfailureheadline(rep)
|
||||
self.write_sep("_", msg)
|
||||
self._outrep_summary(rep)
|
||||
|
||||
def summary_failures(self):
|
||||
if self.config.option.tbstyle != "no":
|
||||
reports = self.getreports('failed')
|
||||
@@ -458,7 +474,8 @@ class TerminalReporter:
|
||||
self.write_line(line)
|
||||
else:
|
||||
msg = self._getfailureheadline(rep)
|
||||
self.write_sep("_", msg)
|
||||
markup = {'red': True, 'bold': True}
|
||||
self.write_sep("_", msg, **markup)
|
||||
self._outrep_summary(rep)
|
||||
|
||||
def summary_errors(self):
|
||||
@@ -543,7 +560,11 @@ def build_summary_stats_line(stats):
|
||||
if val:
|
||||
key_name = key_translation.get(key, key)
|
||||
parts.append("%d %s" % (len(val), key_name))
|
||||
line = ", ".join(parts)
|
||||
|
||||
if parts:
|
||||
line = ", ".join(parts)
|
||||
else:
|
||||
line = "no tests ran"
|
||||
|
||||
if 'failed' in stats or 'error' in stats:
|
||||
color = 'red'
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
""" discovery and running of std-library "unittest" style tests. """
|
||||
from __future__ import absolute_import
|
||||
import traceback
|
||||
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
import pytest
|
||||
import py
|
||||
|
||||
|
||||
# for transfering markers
|
||||
import _pytest._code
|
||||
from _pytest.python import transfer_markers
|
||||
from _pytest.skipping import MarkEvaluator
|
||||
|
||||
@@ -24,9 +23,10 @@ def pytest_pycollect_makeitem(collector, name, obj):
|
||||
|
||||
|
||||
class UnitTestCase(pytest.Class):
|
||||
nofuncargs = True # marker for fixturemanger.getfixtureinfo()
|
||||
# to declare that our children do not support funcargs
|
||||
#
|
||||
# marker for fixturemanger.getfixtureinfo()
|
||||
# to declare that our children do not support funcargs
|
||||
nofuncargs = True
|
||||
|
||||
def setup(self):
|
||||
cls = self.obj
|
||||
if getattr(cls, '__unittest_skip__', False):
|
||||
@@ -69,12 +69,26 @@ class TestCaseFunction(pytest.Function):
|
||||
|
||||
def setup(self):
|
||||
self._testcase = self.parent.obj(self.name)
|
||||
self._fix_unittest_skip_decorator()
|
||||
self._obj = getattr(self._testcase, self.name)
|
||||
if hasattr(self._testcase, 'setup_method'):
|
||||
self._testcase.setup_method(self._obj)
|
||||
if hasattr(self, "_request"):
|
||||
self._request._fillfixtures()
|
||||
|
||||
def _fix_unittest_skip_decorator(self):
|
||||
"""
|
||||
The @unittest.skip decorator calls functools.wraps(self._testcase)
|
||||
The call to functools.wraps() fails unless self._testcase
|
||||
has a __name__ attribute. This is usually automatically supplied
|
||||
if the test is a function or method, but we need to add manually
|
||||
here.
|
||||
|
||||
See issue #1169
|
||||
"""
|
||||
if sys.version_info[0] == 2:
|
||||
setattr(self._testcase, "__name__", self.name)
|
||||
|
||||
def teardown(self):
|
||||
if hasattr(self._testcase, 'teardown_method'):
|
||||
self._testcase.teardown_method(self._obj)
|
||||
@@ -86,7 +100,7 @@ class TestCaseFunction(pytest.Function):
|
||||
# unwrap potential exception info (see twisted trial support below)
|
||||
rawexcinfo = getattr(rawexcinfo, '_rawexcinfo', rawexcinfo)
|
||||
try:
|
||||
excinfo = py.code.ExceptionInfo(rawexcinfo)
|
||||
excinfo = _pytest._code.ExceptionInfo(rawexcinfo)
|
||||
except TypeError:
|
||||
try:
|
||||
try:
|
||||
@@ -102,7 +116,7 @@ class TestCaseFunction(pytest.Function):
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except pytest.fail.Exception:
|
||||
excinfo = py.code.ExceptionInfo()
|
||||
excinfo = _pytest._code.ExceptionInfo()
|
||||
self.__dict__.setdefault('_excinfo', []).append(excinfo)
|
||||
|
||||
def addError(self, testcase, rawexcinfo):
|
||||
|
||||
@@ -573,7 +573,7 @@ class _MultiCall:
|
||||
|
||||
# XXX note that the __multicall__ argument is supported only
|
||||
# for pytest compatibility reasons. It was never officially
|
||||
# supported there and is explicitely deprecated since 2.8
|
||||
# supported there and is explicitly deprecated since 2.8
|
||||
# so we can remove it soon, allowing to avoid the below recursion
|
||||
# in execute() and simplify/speed up the execute loop.
|
||||
|
||||
|
||||
108
appveyor.yml
108
appveyor.yml
@@ -1,98 +1,28 @@
|
||||
environment:
|
||||
matrix:
|
||||
|
||||
# Pre-installed Python versions, which Appveyor may upgrade to
|
||||
# a later point release.
|
||||
|
||||
- PYTHON: "C:\\Python27"
|
||||
PYTHON_VERSION: "2.7.x" # currently 2.7.9
|
||||
PYTHON_ARCH: "32"
|
||||
TESTENV: "py27"
|
||||
|
||||
- PYTHON: "C:\\Python27-x64"
|
||||
PYTHON_VERSION: "2.7.x" # currently 2.7.9
|
||||
PYTHON_ARCH: "64"
|
||||
TESTENV: "py27"
|
||||
|
||||
- PYTHON: "C:\\Python33"
|
||||
PYTHON_VERSION: "3.3.x" # currently 3.3.5
|
||||
PYTHON_ARCH: "32"
|
||||
TESTENV: "py33"
|
||||
|
||||
- PYTHON: "C:\\Python33-x64"
|
||||
PYTHON_VERSION: "3.3.x" # currently 3.3.5
|
||||
PYTHON_ARCH: "64"
|
||||
TESTENV: "py33"
|
||||
|
||||
- PYTHON: "C:\\Python34"
|
||||
PYTHON_VERSION: "3.4.x" # currently 3.4.3
|
||||
PYTHON_ARCH: "32"
|
||||
TESTENV: "py34"
|
||||
|
||||
- PYTHON: "C:\\Python34-x64"
|
||||
PYTHON_VERSION: "3.4.x" # currently 3.4.3
|
||||
PYTHON_ARCH: "64"
|
||||
TESTENV: "py34"
|
||||
|
||||
- PYTHON: "C:\\Python35"
|
||||
PYTHON_VERSION: "3.5.x" # currently 3.5.0
|
||||
PYTHON_ARCH: "32"
|
||||
TESTENV: "py35"
|
||||
|
||||
- PYTHON: "C:\\Python35-x64"
|
||||
PYTHON_VERSION: "3.5.x" # currently 3.5.0
|
||||
PYTHON_ARCH: "64"
|
||||
TESTENV: "py35"
|
||||
|
||||
# Also test a Python version not pre-installed
|
||||
# See: https://github.com/ogrisel/python-appveyor-demo/issues/10
|
||||
|
||||
- PYTHON: "C:\\Python266"
|
||||
PYTHON_VERSION: "2.6.6"
|
||||
PYTHON_ARCH: "32"
|
||||
TESTENV: "py26"
|
||||
|
||||
# xdist testing
|
||||
|
||||
- PYTHON: "C:\\Python27"
|
||||
PYTHON_VERSION: "2.7.x" # currently 2.7.9
|
||||
PYTHON_ARCH: "32"
|
||||
TESTENV: "py27-xdist"
|
||||
|
||||
- PYTHON: "C:\\Python35"
|
||||
PYTHON_VERSION: "3.5.x" # currently 3.5.0
|
||||
PYTHON_ARCH: "32"
|
||||
TESTENV: "py35-xdist"
|
||||
|
||||
COVERALLS_REPO_TOKEN:
|
||||
secure: 2NJ5Ct55cHJ9WEg3xbSqCuv0rdgzzb6pnzOIG5OkMbTndw3wOBrXntWFoQrXiMFi
|
||||
# this is pytest's token in coveralls.io, encrypted
|
||||
# using pytestbot account as detailed here:
|
||||
# https://www.appveyor.com/docs/build-configuration#secure-variables
|
||||
|
||||
install:
|
||||
- ECHO "Filesystem root:"
|
||||
- ps: "ls \"C:/\""
|
||||
- echo Installed Pythons
|
||||
- dir c:\Python*
|
||||
|
||||
- ECHO "Installed SDKs:"
|
||||
- ps: "ls \"C:/Program Files/Microsoft SDKs/Windows\""
|
||||
# install pypy using choco (redirect to a file and write to console in case
|
||||
# choco install returns non-zero, because choco install python.pypy is too
|
||||
# noisy)
|
||||
- choco install python.pypy > pypy-inst.log 2>&1 || (type pypy-inst.log & exit /b 1)
|
||||
- set PATH=C:\tools\pypy\pypy;%PATH% # so tox can find pypy
|
||||
- echo PyPy installed
|
||||
- pypy --version
|
||||
|
||||
# Install Python (from the official .msi of http://python.org) and pip when
|
||||
# not already installed.
|
||||
- ps: if (-not(Test-Path($env:PYTHON))) { & appveyor\install.ps1 }
|
||||
|
||||
# Prepend newly installed Python to the PATH of this build (this cannot be
|
||||
# done from inside the powershell script as it would require to restart
|
||||
# the parent CMD process).
|
||||
- "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
|
||||
|
||||
# Check that we have the expected version and architecture for Python
|
||||
- "python --version"
|
||||
- "python -c \"import struct; print(struct.calcsize('P') * 8)\""
|
||||
|
||||
# Install the build dependencies of the project. If some dependencies contain
|
||||
# compiled extensions and are not provided as pre-built wheel packages,
|
||||
# pip will build them from source using the MSVC compiler matching the
|
||||
# target Python version and architecture
|
||||
- C:\Python27\python -m pip install tox
|
||||
- C:\Python35\python -m pip install tox
|
||||
|
||||
build: false # Not a C# project, build stuff at the test step instead.
|
||||
|
||||
test_script:
|
||||
# Build the compiled extension and run the project tests
|
||||
- C:\Python27\python -m tox -e %TESTENV%
|
||||
- C:\Python35\python -m tox
|
||||
# coveralls is not in tox's envlist, plus for PRs the secure variable
|
||||
# is not defined so we have to check for it
|
||||
- if defined COVERALLS_REPO_TOKEN C:\Python35\python -m tox -e coveralls
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
# Sample script to install Python and pip under Windows
|
||||
# Authors: Olivier Grisel, Jonathan Helmus and Kyle Kastner
|
||||
# License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
$MINICONDA_URL = "http://repo.continuum.io/miniconda/"
|
||||
$BASE_URL = "https://www.python.org/ftp/python/"
|
||||
$GET_PIP_URL = "https://bootstrap.pypa.io/get-pip.py"
|
||||
$GET_PIP_PATH = "C:\get-pip.py"
|
||||
|
||||
|
||||
function DownloadPython ($python_version, $platform_suffix) {
|
||||
$webclient = New-Object System.Net.WebClient
|
||||
$filename = "python-" + $python_version + $platform_suffix + ".msi"
|
||||
$url = $BASE_URL + $python_version + "/" + $filename
|
||||
|
||||
$basedir = $pwd.Path + "\"
|
||||
$filepath = $basedir + $filename
|
||||
if (Test-Path $filename) {
|
||||
Write-Host "Reusing" $filepath
|
||||
return $filepath
|
||||
}
|
||||
|
||||
# Download and retry up to 3 times in case of network transient errors.
|
||||
Write-Host "Downloading" $filename "from" $url
|
||||
$retry_attempts = 2
|
||||
for($i=0; $i -lt $retry_attempts; $i++){
|
||||
try {
|
||||
$webclient.DownloadFile($url, $filepath)
|
||||
break
|
||||
}
|
||||
Catch [Exception]{
|
||||
Start-Sleep 1
|
||||
}
|
||||
}
|
||||
if (Test-Path $filepath) {
|
||||
Write-Host "File saved at" $filepath
|
||||
} else {
|
||||
# Retry once to get the error message if any at the last try
|
||||
$webclient.DownloadFile($url, $filepath)
|
||||
}
|
||||
return $filepath
|
||||
}
|
||||
|
||||
|
||||
function InstallPython ($python_version, $architecture, $python_home) {
|
||||
Write-Host "Installing Python" $python_version "for" $architecture "bit architecture to" $python_home
|
||||
if (Test-Path $python_home) {
|
||||
Write-Host $python_home "already exists, skipping."
|
||||
return $false
|
||||
}
|
||||
if ($architecture -eq "32") {
|
||||
$platform_suffix = ""
|
||||
} else {
|
||||
$platform_suffix = ".amd64"
|
||||
}
|
||||
$msipath = DownloadPython $python_version $platform_suffix
|
||||
Write-Host "Installing" $msipath "to" $python_home
|
||||
$install_log = $python_home + ".log"
|
||||
$install_args = "/qn /log $install_log /i $msipath TARGETDIR=$python_home"
|
||||
$uninstall_args = "/qn /x $msipath"
|
||||
RunCommand "msiexec.exe" $install_args
|
||||
if (-not(Test-Path $python_home)) {
|
||||
Write-Host "Python seems to be installed else-where, reinstalling."
|
||||
RunCommand "msiexec.exe" $uninstall_args
|
||||
RunCommand "msiexec.exe" $install_args
|
||||
}
|
||||
if (Test-Path $python_home) {
|
||||
Write-Host "Python $python_version ($architecture) installation complete"
|
||||
} else {
|
||||
Write-Host "Failed to install Python in $python_home"
|
||||
Get-Content -Path $install_log
|
||||
Exit 1
|
||||
}
|
||||
}
|
||||
|
||||
function RunCommand ($command, $command_args) {
|
||||
Write-Host $command $command_args
|
||||
Start-Process -FilePath $command -ArgumentList $command_args -Wait -Passthru
|
||||
}
|
||||
|
||||
|
||||
function InstallPip ($python_home) {
|
||||
$pip_path = $python_home + "\Scripts\pip.exe"
|
||||
$python_path = $python_home + "\python.exe"
|
||||
if (-not(Test-Path $pip_path)) {
|
||||
Write-Host "Installing pip..."
|
||||
$webclient = New-Object System.Net.WebClient
|
||||
$webclient.DownloadFile($GET_PIP_URL, $GET_PIP_PATH)
|
||||
Write-Host "Executing:" $python_path $GET_PIP_PATH
|
||||
Start-Process -FilePath "$python_path" -ArgumentList "$GET_PIP_PATH" -Wait -Passthru
|
||||
} else {
|
||||
Write-Host "pip already installed."
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function DownloadMiniconda ($python_version, $platform_suffix) {
|
||||
$webclient = New-Object System.Net.WebClient
|
||||
if ($python_version -eq "3.4") {
|
||||
$filename = "Miniconda3-3.5.5-Windows-" + $platform_suffix + ".exe"
|
||||
} else {
|
||||
$filename = "Miniconda-3.5.5-Windows-" + $platform_suffix + ".exe"
|
||||
}
|
||||
$url = $MINICONDA_URL + $filename
|
||||
|
||||
$basedir = $pwd.Path + "\"
|
||||
$filepath = $basedir + $filename
|
||||
if (Test-Path $filename) {
|
||||
Write-Host "Reusing" $filepath
|
||||
return $filepath
|
||||
}
|
||||
|
||||
# Download and retry up to 3 times in case of network transient errors.
|
||||
Write-Host "Downloading" $filename "from" $url
|
||||
$retry_attempts = 2
|
||||
for($i=0; $i -lt $retry_attempts; $i++){
|
||||
try {
|
||||
$webclient.DownloadFile($url, $filepath)
|
||||
break
|
||||
}
|
||||
Catch [Exception]{
|
||||
Start-Sleep 1
|
||||
}
|
||||
}
|
||||
if (Test-Path $filepath) {
|
||||
Write-Host "File saved at" $filepath
|
||||
} else {
|
||||
# Retry once to get the error message if any at the last try
|
||||
$webclient.DownloadFile($url, $filepath)
|
||||
}
|
||||
return $filepath
|
||||
}
|
||||
|
||||
|
||||
function InstallMiniconda ($python_version, $architecture, $python_home) {
|
||||
Write-Host "Installing Python" $python_version "for" $architecture "bit architecture to" $python_home
|
||||
if (Test-Path $python_home) {
|
||||
Write-Host $python_home "already exists, skipping."
|
||||
return $false
|
||||
}
|
||||
if ($architecture -eq "32") {
|
||||
$platform_suffix = "x86"
|
||||
} else {
|
||||
$platform_suffix = "x86_64"
|
||||
}
|
||||
$filepath = DownloadMiniconda $python_version $platform_suffix
|
||||
Write-Host "Installing" $filepath "to" $python_home
|
||||
$install_log = $python_home + ".log"
|
||||
$args = "/S /D=$python_home"
|
||||
Write-Host $filepath $args
|
||||
Start-Process -FilePath $filepath -ArgumentList $args -Wait -Passthru
|
||||
if (Test-Path $python_home) {
|
||||
Write-Host "Python $python_version ($architecture) installation complete"
|
||||
} else {
|
||||
Write-Host "Failed to install Python in $python_home"
|
||||
Get-Content -Path $install_log
|
||||
Exit 1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function InstallMinicondaPip ($python_home) {
|
||||
$pip_path = $python_home + "\Scripts\pip.exe"
|
||||
$conda_path = $python_home + "\Scripts\conda.exe"
|
||||
if (-not(Test-Path $pip_path)) {
|
||||
Write-Host "Installing pip..."
|
||||
$args = "install --yes pip"
|
||||
Write-Host $conda_path $args
|
||||
Start-Process -FilePath "$conda_path" -ArgumentList $args -Wait -Passthru
|
||||
} else {
|
||||
Write-Host "pip already installed."
|
||||
}
|
||||
}
|
||||
|
||||
function main () {
|
||||
InstallPython $env:PYTHON_VERSION $env:PYTHON_ARCH $env:PYTHON
|
||||
InstallPip $env:PYTHON
|
||||
}
|
||||
|
||||
main
|
||||
@@ -9,6 +9,7 @@
|
||||
<li><a href="{{ pathto('contact') }}">Contact</a></li>
|
||||
<li><a href="{{ pathto('talks') }}">Talks/Posts</a></li>
|
||||
<li><a href="{{ pathto('changelog') }}">Changelog</a></li>
|
||||
<li><a href="{{ pathto('license') }}">License</a></li>
|
||||
</ul>
|
||||
|
||||
{%- if display_toc %}
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
{% extends "!layout.html" %}
|
||||
{% block header %}
|
||||
<div align="center" xmlns="http://www.w3.org/1999/html" style="background-color: lightgreen; padding: .5em">
|
||||
<h4>
|
||||
Want to help improve pytest? Please
|
||||
<a href="https://www.indiegogo.com/projects/python-testing-sprint-mid-2016#/">
|
||||
contribute to
|
||||
</a>
|
||||
or
|
||||
<a href="announce/sprint2016.html">
|
||||
join
|
||||
</a>
|
||||
our upcoming sprint in June 2016!
|
||||
|
||||
</h4>
|
||||
</div>
|
||||
{{super()}}
|
||||
{% endblock %}
|
||||
{% block footer %}
|
||||
{{ super() }}
|
||||
<script type="text/javascript">
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
<h3>Useful Links</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://www.indiegogo.com/projects/python-testing-sprint-mid-2016#/">
|
||||
<b>Sprint funding campaign</b>
|
||||
</a>
|
||||
</li>
|
||||
<li><a href="{{ pathto('index') }}">The pytest Website</a></li>
|
||||
<li><a href="{{ pathto('contributing') }}">Contribution Guide</a></li>
|
||||
<li><a href="https://pypi.python.org/pypi/pytest">pytest @ PyPI</a></li>
|
||||
<li><a href="https://github.com/pytest-dev/pytest/">pytest @ GitHub</a></li>
|
||||
<li><a href="http://pytest.org/latest/plugins_index/index.html">3rd party plugins</a></li>
|
||||
<li><a href="http://plugincompat.herokuapp.com/">3rd party plugins</a></li>
|
||||
<li><a href="https://github.com/pytest-dev/pytest/issues">Issue Tracker</a></li>
|
||||
<li><a href="http://pytest.org/latest/pytest.pdf">PDF Documentation</a>
|
||||
</ul>
|
||||
|
||||
@@ -5,6 +5,17 @@ Release announcements
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
|
||||
sprint2016
|
||||
release-2.9.1
|
||||
release-2.9.1
|
||||
release-2.9.0
|
||||
release-2.8.7
|
||||
release-2.8.6
|
||||
release-2.8.5
|
||||
release-2.8.4
|
||||
release-2.8.3
|
||||
release-2.8.2
|
||||
release-2.7.2
|
||||
release-2.7.1
|
||||
release-2.7.0
|
||||
@@ -36,4 +47,3 @@ Release announcements
|
||||
release-2.0.2
|
||||
release-2.0.1
|
||||
release-2.0.0
|
||||
|
||||
|
||||
44
doc/en/announce/release-2.8.2.rst
Normal file
44
doc/en/announce/release-2.8.2.rst
Normal file
@@ -0,0 +1,44 @@
|
||||
pytest-2.8.2: bug fixes
|
||||
=======================
|
||||
|
||||
pytest is a mature Python testing tool with more than a 1100 tests
|
||||
against itself, passing on many different interpreters and platforms.
|
||||
This release is supposed to be drop-in compatible to 2.8.1.
|
||||
|
||||
See below for the changes and see docs at:
|
||||
|
||||
http://pytest.org
|
||||
|
||||
As usual, you can upgrade from pypi via::
|
||||
|
||||
pip install -U pytest
|
||||
|
||||
Thanks to all who contributed to this release, among them:
|
||||
|
||||
Bruno Oliveira
|
||||
Demian Brecht
|
||||
Florian Bruhin
|
||||
Ionel Cristian Mărieș
|
||||
Raphael Pierzina
|
||||
Ronny Pfannschmidt
|
||||
holger krekel
|
||||
|
||||
Happy testing,
|
||||
The py.test Development Team
|
||||
|
||||
|
||||
2.8.2 (compared to 2.7.2)
|
||||
-----------------------------
|
||||
|
||||
- fix #1085: proper handling of encoding errors when passing encoded byte
|
||||
strings to pytest.parametrize in Python 2.
|
||||
Thanks Themanwithoutaplan for the report and Bruno Oliveira for the PR.
|
||||
|
||||
- fix #1087: handling SystemError when passing empty byte strings to
|
||||
pytest.parametrize in Python 3.
|
||||
Thanks Paul Kehrer for the report and Bruno Oliveira for the PR.
|
||||
|
||||
- fix #995: fixed internal error when filtering tracebacks where one entry
|
||||
was generated by an exec() statement.
|
||||
Thanks Daniel Hahler, Ashley C Straw, Philippe Gauthier and Pavel Savchenko
|
||||
for contributing and Bruno Oliveira for the PR.
|
||||
59
doc/en/announce/release-2.8.3.rst
Normal file
59
doc/en/announce/release-2.8.3.rst
Normal file
@@ -0,0 +1,59 @@
|
||||
pytest-2.8.3: bug fixes
|
||||
=======================
|
||||
|
||||
pytest is a mature Python testing tool with more than a 1100 tests
|
||||
against itself, passing on many different interpreters and platforms.
|
||||
This release is supposed to be drop-in compatible to 2.8.2.
|
||||
|
||||
See below for the changes and see docs at:
|
||||
|
||||
http://pytest.org
|
||||
|
||||
As usual, you can upgrade from pypi via::
|
||||
|
||||
pip install -U pytest
|
||||
|
||||
Thanks to all who contributed to this release, among them:
|
||||
|
||||
Bruno Oliveira
|
||||
Florian Bruhin
|
||||
Gabe Hollombe
|
||||
Gabriel Reis
|
||||
Hartmut Goebel
|
||||
John Vandenberg
|
||||
Lee Kamentsky
|
||||
Michael Birtwell
|
||||
Raphael Pierzina
|
||||
Ronny Pfannschmidt
|
||||
William Martin Stewart
|
||||
|
||||
Happy testing,
|
||||
The py.test Development Team
|
||||
|
||||
|
||||
2.8.3 (compared to 2.8.2)
|
||||
-----------------------------
|
||||
|
||||
- fix #1169: add __name__ attribute to testcases in TestCaseFunction to
|
||||
support the @unittest.skip decorator on functions and methods.
|
||||
Thanks Lee Kamentsky for the PR.
|
||||
|
||||
- fix #1035: collecting tests if test module level obj has __getattr__().
|
||||
Thanks Suor for the report and Bruno Oliveira / Tom Viner for the PR.
|
||||
|
||||
- fix #331: don't collect tests if their failure cannot be reported correctly
|
||||
e.g. they are a callable instance of a class.
|
||||
|
||||
- fix #1133: fixed internal error when filtering tracebacks where one entry
|
||||
belongs to a file which is no longer available.
|
||||
Thanks Bruno Oliveira for the PR.
|
||||
|
||||
- enhancement made to highlight in red the name of the failing tests so
|
||||
they stand out in the output.
|
||||
Thanks Gabriel Reis for the PR.
|
||||
|
||||
- add more talks to the documentation
|
||||
- extend documentation on the --ignore cli option
|
||||
- use pytest-runner for setuptools integration
|
||||
- minor fixes for interaction with OS X El Capitan system integrity protection (thanks Florian)
|
||||
|
||||
52
doc/en/announce/release-2.8.4.rst
Normal file
52
doc/en/announce/release-2.8.4.rst
Normal file
@@ -0,0 +1,52 @@
|
||||
pytest-2.8.4
|
||||
============
|
||||
|
||||
pytest is a mature Python testing tool with more than a 1100 tests
|
||||
against itself, passing on many different interpreters and platforms.
|
||||
This release is supposed to be drop-in compatible to 2.8.2.
|
||||
|
||||
See below for the changes and see docs at:
|
||||
|
||||
http://pytest.org
|
||||
|
||||
As usual, you can upgrade from pypi via::
|
||||
|
||||
pip install -U pytest
|
||||
|
||||
Thanks to all who contributed to this release, among them:
|
||||
|
||||
Bruno Oliveira
|
||||
Florian Bruhin
|
||||
Jeff Widman
|
||||
Mehdy Khoshnoody
|
||||
Nicholas Chammas
|
||||
Ronny Pfannschmidt
|
||||
Tim Chan
|
||||
|
||||
|
||||
Happy testing,
|
||||
The py.test Development Team
|
||||
|
||||
|
||||
2.8.4 (compared to 2.8.3)
|
||||
-----------------------------
|
||||
|
||||
- fix #1190: ``deprecated_call()`` now works when the deprecated
|
||||
function has been already called by another test in the same
|
||||
module. Thanks Mikhail Chernykh for the report and Bruno Oliveira for the
|
||||
PR.
|
||||
|
||||
- fix #1198: ``--pastebin`` option now works on Python 3. Thanks
|
||||
Mehdy Khoshnoody for the PR.
|
||||
|
||||
- fix #1219: ``--pastebin`` now works correctly when captured output contains
|
||||
non-ascii characters. Thanks Bruno Oliveira for the PR.
|
||||
|
||||
- fix #1204: another error when collecting with a nasty __getattr__().
|
||||
Thanks Florian Bruhin for the PR.
|
||||
|
||||
- fix the summary printed when no tests did run.
|
||||
Thanks Florian Bruhin for the PR.
|
||||
|
||||
- a number of documentation modernizations wrt good practices.
|
||||
Thanks Bruno Oliveira for the PR.
|
||||
39
doc/en/announce/release-2.8.5.rst
Normal file
39
doc/en/announce/release-2.8.5.rst
Normal file
@@ -0,0 +1,39 @@
|
||||
pytest-2.8.5
|
||||
============
|
||||
|
||||
pytest is a mature Python testing tool with more than a 1100 tests
|
||||
against itself, passing on many different interpreters and platforms.
|
||||
This release is supposed to be drop-in compatible to 2.8.4.
|
||||
|
||||
See below for the changes and see docs at:
|
||||
|
||||
http://pytest.org
|
||||
|
||||
As usual, you can upgrade from pypi via::
|
||||
|
||||
pip install -U pytest
|
||||
|
||||
Thanks to all who contributed to this release, among them:
|
||||
|
||||
Alex Gaynor
|
||||
aselus-hub
|
||||
Bruno Oliveira
|
||||
Ronny Pfannschmidt
|
||||
|
||||
|
||||
Happy testing,
|
||||
The py.test Development Team
|
||||
|
||||
|
||||
2.8.5 (compared to 2.8.4)
|
||||
-------------------------
|
||||
|
||||
- fix #1243: fixed issue where class attributes injected during collection could break pytest.
|
||||
PR by Alexei Kozlenok, thanks Ronny Pfannschmidt and Bruno Oliveira for the review and help.
|
||||
|
||||
- fix #1074: precompute junitxml chunks instead of storing the whole tree in objects
|
||||
Thanks Bruno Oliveira for the report and Ronny Pfannschmidt for the PR
|
||||
|
||||
- fix #1238: fix ``pytest.deprecated_call()`` receiving multiple arguments
|
||||
(Regression introduced in 2.8.4). Thanks Alex Gaynor for the report and
|
||||
Bruno Oliveira for the PR.
|
||||
67
doc/en/announce/release-2.8.6.rst
Normal file
67
doc/en/announce/release-2.8.6.rst
Normal file
@@ -0,0 +1,67 @@
|
||||
pytest-2.8.6
|
||||
============
|
||||
|
||||
pytest is a mature Python testing tool with more than a 1100 tests
|
||||
against itself, passing on many different interpreters and platforms.
|
||||
This release is supposed to be drop-in compatible to 2.8.5.
|
||||
|
||||
See below for the changes and see docs at:
|
||||
|
||||
http://pytest.org
|
||||
|
||||
As usual, you can upgrade from pypi via::
|
||||
|
||||
pip install -U pytest
|
||||
|
||||
Thanks to all who contributed to this release, among them:
|
||||
|
||||
AMiT Kumar
|
||||
Bruno Oliveira
|
||||
Erik M. Bray
|
||||
Florian Bruhin
|
||||
Georgy Dyuldin
|
||||
Jeff Widman
|
||||
Kartik Singhal
|
||||
Loïc Estève
|
||||
Manu Phatak
|
||||
Peter Demin
|
||||
Rick van Hattem
|
||||
Ronny Pfannschmidt
|
||||
Ulrich Petri
|
||||
foxx
|
||||
|
||||
|
||||
Happy testing,
|
||||
The py.test Development Team
|
||||
|
||||
|
||||
2.8.6 (compared to 2.8.5)
|
||||
-------------------------
|
||||
|
||||
- fix #1259: allow for double nodeids in junitxml,
|
||||
this was a regression failing plugins combinations
|
||||
like pytest-pep8 + pytest-flakes
|
||||
|
||||
- Workaround for exception that occurs in pyreadline when using
|
||||
``--pdb`` with standard I/O capture enabled.
|
||||
Thanks Erik M. Bray for the PR.
|
||||
|
||||
- fix #900: Better error message in case the target of a ``monkeypatch`` call
|
||||
raises an ``ImportError``.
|
||||
|
||||
- fix #1292: monkeypatch calls (setattr, setenv, etc.) are now O(1).
|
||||
Thanks David R. MacIver for the report and Bruno Oliveira for the PR.
|
||||
|
||||
- fix #1223: captured stdout and stderr are now properly displayed before
|
||||
entering pdb when ``--pdb`` is used instead of being thrown away.
|
||||
Thanks Cal Leeming for the PR.
|
||||
|
||||
- fix #1305: pytest warnings emitted during ``pytest_terminal_summary`` are now
|
||||
properly displayed.
|
||||
Thanks Ionel Maries Cristian for the report and Bruno Oliveira for the PR.
|
||||
|
||||
- fix #628: fixed internal UnicodeDecodeError when doctests contain unicode.
|
||||
Thanks Jason R. Coombs for the report and Bruno Oliveira for the PR.
|
||||
|
||||
- fix #1334: Add captured stdout to jUnit XML report on setup error.
|
||||
Thanks Georgy Dyuldin for the PR.
|
||||
31
doc/en/announce/release-2.8.7.rst
Normal file
31
doc/en/announce/release-2.8.7.rst
Normal file
@@ -0,0 +1,31 @@
|
||||
pytest-2.8.7
|
||||
============
|
||||
|
||||
This is a hotfix release to solve a regression
|
||||
in the builtin monkeypatch plugin that got introduced in 2.8.6.
|
||||
|
||||
pytest is a mature Python testing tool with more than a 1100 tests
|
||||
against itself, passing on many different interpreters and platforms.
|
||||
This release is supposed to be drop-in compatible to 2.8.5.
|
||||
|
||||
See below for the changes and see docs at:
|
||||
|
||||
http://pytest.org
|
||||
|
||||
As usual, you can upgrade from pypi via::
|
||||
|
||||
pip install -U pytest
|
||||
|
||||
Thanks to all who contributed to this release, among them:
|
||||
|
||||
Ronny Pfannschmidt
|
||||
|
||||
|
||||
Happy testing,
|
||||
The py.test Development Team
|
||||
|
||||
|
||||
2.8.7 (compared to 2.8.6)
|
||||
-------------------------
|
||||
|
||||
- fix #1338: use predictable object resolution for monkeypatch
|
||||
159
doc/en/announce/release-2.9.0.rst
Normal file
159
doc/en/announce/release-2.9.0.rst
Normal file
@@ -0,0 +1,159 @@
|
||||
pytest-2.9.0
|
||||
============
|
||||
|
||||
pytest is a mature Python testing tool with more than a 1100 tests
|
||||
against itself, passing on many different interpreters and platforms.
|
||||
|
||||
See below for the changes and see docs at:
|
||||
|
||||
http://pytest.org
|
||||
|
||||
As usual, you can upgrade from pypi via::
|
||||
|
||||
pip install -U pytest
|
||||
|
||||
Thanks to all who contributed to this release, among them:
|
||||
|
||||
Anatoly Bubenkov
|
||||
Bruno Oliveira
|
||||
Buck Golemon
|
||||
David Vierra
|
||||
Florian Bruhin
|
||||
Galaczi Endre
|
||||
Georgy Dyuldin
|
||||
Lukas Bednar
|
||||
Luke Murphy
|
||||
Marcin Biernat
|
||||
Matt Williams
|
||||
Michael Aquilina
|
||||
Raphael Pierzina
|
||||
Ronny Pfannschmidt
|
||||
Ryan Wooden
|
||||
Tiemo Kieft
|
||||
TomV
|
||||
holger krekel
|
||||
jab
|
||||
|
||||
|
||||
Happy testing,
|
||||
The py.test Development Team
|
||||
|
||||
|
||||
2.9.0 (compared to 2.8.7)
|
||||
-------------------------
|
||||
|
||||
**New Features**
|
||||
|
||||
* New ``pytest.mark.skip`` mark, which unconditionally skips marked tests.
|
||||
Thanks `@MichaelAquilina`_ for the complete PR (`#1040`_).
|
||||
|
||||
* ``--doctest-glob`` may now be passed multiple times in the command-line.
|
||||
Thanks `@jab`_ and `@nicoddemus`_ for the PR.
|
||||
|
||||
* New ``-rp`` and ``-rP`` reporting options give the summary and full output
|
||||
of passing tests, respectively. Thanks to `@codewarrior0`_ for the PR.
|
||||
|
||||
* ``pytest.mark.xfail`` now has a ``strict`` option which makes ``XPASS``
|
||||
tests to fail the test suite, defaulting to ``False``. There's also a
|
||||
``xfail_strict`` ini option that can be used to configure it project-wise.
|
||||
Thanks `@rabbbit`_ for the request and `@nicoddemus`_ for the PR (`#1355`_).
|
||||
|
||||
* ``Parser.addini`` now supports options of type ``bool``. Thanks
|
||||
`@nicoddemus`_ for the PR.
|
||||
|
||||
* New ``ALLOW_BYTES`` doctest option strips ``b`` prefixes from byte strings
|
||||
in doctest output (similar to ``ALLOW_UNICODE``).
|
||||
Thanks `@jaraco`_ for the request and `@nicoddemus`_ for the PR (`#1287`_).
|
||||
|
||||
* give a hint on KeyboardInterrupt to use the --fulltrace option to show the errors,
|
||||
this fixes `#1366`_.
|
||||
Thanks to `@hpk42`_ for the report and `@RonnyPfannschmidt`_ for the PR.
|
||||
|
||||
* catch IndexError exceptions when getting exception source location. This fixes
|
||||
pytest internal error for dynamically generated code (fixtures and tests)
|
||||
where source lines are fake by intention
|
||||
|
||||
**Changes**
|
||||
|
||||
* **Important**: `py.code <https://pylib.readthedocs.io/en/latest/code.html>`_ has been
|
||||
merged into the ``pytest`` repository as ``pytest._code``. This decision
|
||||
was made because ``py.code`` had very few uses outside ``pytest`` and the
|
||||
fact that it was in a different repository made it difficult to fix bugs on
|
||||
its code in a timely manner. The team hopes with this to be able to better
|
||||
refactor out and improve that code.
|
||||
This change shouldn't affect users, but it is useful to let users aware
|
||||
if they encounter any strange behavior.
|
||||
|
||||
Keep in mind that the code for ``pytest._code`` is **private** and
|
||||
**experimental**, so you definitely should not import it explicitly!
|
||||
|
||||
Please note that the original ``py.code`` is still available in
|
||||
`pylib <https://pylib.readthedocs.io>`_.
|
||||
|
||||
* ``pytest_enter_pdb`` now optionally receives the pytest config object.
|
||||
Thanks `@nicoddemus`_ for the PR.
|
||||
|
||||
* Removed code and documentation for Python 2.5 or lower versions,
|
||||
including removal of the obsolete ``_pytest.assertion.oldinterpret`` module.
|
||||
Thanks `@nicoddemus`_ for the PR (`#1226`_).
|
||||
|
||||
* Comparisons now always show up in full when ``CI`` or ``BUILD_NUMBER`` is
|
||||
found in the environment, even when -vv isn't used.
|
||||
Thanks `@The-Compiler`_ for the PR.
|
||||
|
||||
* ``--lf`` and ``--ff`` now support long names: ``--last-failed`` and
|
||||
``--failed-first`` respectively.
|
||||
Thanks `@MichaelAquilina`_ for the PR.
|
||||
|
||||
* Added expected exceptions to pytest.raises fail message
|
||||
|
||||
* Collection only displays progress ("collecting X items") when in a terminal.
|
||||
This avoids cluttering the output when using ``--color=yes`` to obtain
|
||||
colors in CI integrations systems (`#1397`_).
|
||||
|
||||
**Bug Fixes**
|
||||
|
||||
* The ``-s`` and ``-c`` options should now work under ``xdist``;
|
||||
``Config.fromdictargs`` now represents its input much more faithfully.
|
||||
Thanks to `@bukzor`_ for the complete PR (`#680`_).
|
||||
|
||||
* Fix (`#1290`_): support Python 3.5's ``@`` operator in assertion rewriting.
|
||||
Thanks `@Shinkenjoe`_ for report with test case and `@tomviner`_ for the PR.
|
||||
|
||||
* Fix formatting utf-8 explanation messages (`#1379`_).
|
||||
Thanks `@biern`_ for the PR.
|
||||
|
||||
* Fix `traceback style docs`_ to describe all of the available options
|
||||
(auto/long/short/line/native/no), with `auto` being the default since v2.6.
|
||||
Thanks `@hackebrot`_ for the PR.
|
||||
|
||||
* Fix (`#1422`_): junit record_xml_property doesn't allow multiple records
|
||||
with same name.
|
||||
|
||||
|
||||
.. _`traceback style docs`: https://pytest.org/latest/usage.html#modifying-python-traceback-printing
|
||||
|
||||
.. _#1422: https://github.com/pytest-dev/pytest/issues/1422
|
||||
.. _#1379: https://github.com/pytest-dev/pytest/issues/1379
|
||||
.. _#1366: https://github.com/pytest-dev/pytest/issues/1366
|
||||
.. _#1040: https://github.com/pytest-dev/pytest/pull/1040
|
||||
.. _#680: https://github.com/pytest-dev/pytest/issues/680
|
||||
.. _#1287: https://github.com/pytest-dev/pytest/pull/1287
|
||||
.. _#1226: https://github.com/pytest-dev/pytest/pull/1226
|
||||
.. _#1290: https://github.com/pytest-dev/pytest/pull/1290
|
||||
.. _#1355: https://github.com/pytest-dev/pytest/pull/1355
|
||||
.. _#1397: https://github.com/pytest-dev/pytest/issues/1397
|
||||
.. _@biern: https://github.com/biern
|
||||
.. _@MichaelAquilina: https://github.com/MichaelAquilina
|
||||
.. _@bukzor: https://github.com/bukzor
|
||||
.. _@hpk42: https://github.com/hpk42
|
||||
.. _@nicoddemus: https://github.com/nicoddemus
|
||||
.. _@jab: https://github.com/jab
|
||||
.. _@codewarrior0: https://github.com/codewarrior0
|
||||
.. _@jaraco: https://github.com/jaraco
|
||||
.. _@The-Compiler: https://github.com/The-Compiler
|
||||
.. _@Shinkenjoe: https://github.com/Shinkenjoe
|
||||
.. _@tomviner: https://github.com/tomviner
|
||||
.. _@RonnyPfannschmidt: https://github.com/RonnyPfannschmidt
|
||||
.. _@rabbbit: https://github.com/rabbbit
|
||||
.. _@hackebrot: https://github.com/hackebrot
|
||||
65
doc/en/announce/release-2.9.1.rst
Normal file
65
doc/en/announce/release-2.9.1.rst
Normal file
@@ -0,0 +1,65 @@
|
||||
pytest-2.9.1
|
||||
============
|
||||
|
||||
pytest is a mature Python testing tool with more than a 1100 tests
|
||||
against itself, passing on many different interpreters and platforms.
|
||||
|
||||
See below for the changes and see docs at:
|
||||
|
||||
http://pytest.org
|
||||
|
||||
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
|
||||
Dmitry Malinovsky
|
||||
Florian Bruhin
|
||||
Floris Bruynooghe
|
||||
Matt Bachmann
|
||||
Ronny Pfannschmidt
|
||||
TomV
|
||||
Vladimir Bolshakov
|
||||
Zearin
|
||||
palaviv
|
||||
|
||||
|
||||
Happy testing,
|
||||
The py.test Development Team
|
||||
|
||||
|
||||
2.9.1 (compared to 2.9.0)
|
||||
-------------------------
|
||||
|
||||
**Bug Fixes**
|
||||
|
||||
* Improve error message when a plugin fails to load.
|
||||
Thanks `@nicoddemus`_ for the PR.
|
||||
|
||||
* Fix (`#1178 <https://github.com/pytest-dev/pytest/issues/1178>`_):
|
||||
``pytest.fail`` with non-ascii characters raises an internal pytest error.
|
||||
Thanks `@nicoddemus`_ for the PR.
|
||||
|
||||
* Fix (`#469`_): junit parses report.nodeid incorrectly, when params IDs
|
||||
contain ``::``. Thanks `@tomviner`_ for the PR (`#1431`_).
|
||||
|
||||
* Fix (`#578 <https://github.com/pytest-dev/pytest/issues/578>`_): SyntaxErrors
|
||||
containing non-ascii lines at the point of failure generated an internal
|
||||
py.test error.
|
||||
Thanks `@asottile`_ for the report and `@nicoddemus`_ for the PR.
|
||||
|
||||
* Fix (`#1437`_): When passing in a bytestring regex pattern to parameterize
|
||||
attempt to decode it as utf-8 ignoring errors.
|
||||
|
||||
* Fix (`#649`_): parametrized test nodes cannot be specified to run on the command line.
|
||||
|
||||
|
||||
.. _#1437: https://github.com/pytest-dev/pytest/issues/1437
|
||||
.. _#469: https://github.com/pytest-dev/pytest/issues/469
|
||||
.. _#1431: https://github.com/pytest-dev/pytest/pull/1431
|
||||
.. _#649: https://github.com/pytest-dev/pytest/issues/649
|
||||
|
||||
.. _@asottile: https://github.com/asottile
|
||||
73
doc/en/announce/release-2.9.2.rst
Normal file
73
doc/en/announce/release-2.9.2.rst
Normal file
@@ -0,0 +1,73 @@
|
||||
pytest-2.9.1
|
||||
============
|
||||
|
||||
pytest is a mature Python testing tool with more than a 1100 tests
|
||||
against itself, passing on many different interpreters and platforms.
|
||||
|
||||
See below for the changes and see docs at:
|
||||
|
||||
http://pytest.org
|
||||
|
||||
As usual, you can upgrade from pypi via::
|
||||
|
||||
pip install -U pytest
|
||||
|
||||
Thanks to all who contributed to this release, among them:
|
||||
|
||||
Adam Chainz
|
||||
Benjamin Dopplinger
|
||||
Bruno Oliveira
|
||||
Florian Bruhin
|
||||
John Towler
|
||||
Martin Prusse
|
||||
Meng Jue
|
||||
MengJueM
|
||||
Omar Kohl
|
||||
Quentin Pradet
|
||||
Ronny Pfannschmidt
|
||||
Thomas Güttler
|
||||
TomV
|
||||
Tyler Goodlet
|
||||
|
||||
|
||||
Happy testing,
|
||||
The py.test Development Team
|
||||
|
||||
|
||||
2.9.2 (compared to 2.9.1)
|
||||
---------------------------
|
||||
|
||||
**Bug Fixes**
|
||||
|
||||
* fix `#510`_: skip tests where one parameterize dimension was empty
|
||||
thanks Alex Stapleton for the Report and `@RonnyPfannschmidt`_ for the PR
|
||||
|
||||
* Fix Xfail does not work with condition keyword argument.
|
||||
Thanks `@astraw38`_ for reporting the issue (`#1496`_) and `@tomviner`_
|
||||
for PR the (`#1524`_).
|
||||
|
||||
* Fix win32 path issue when puttinging custom config file with absolute path
|
||||
in ``pytest.main("-c your_absolute_path")``.
|
||||
|
||||
* Fix maximum recursion depth detection when raised error class is not aware
|
||||
of unicode/encoded bytes.
|
||||
Thanks `@prusse-martin`_ for the PR (`#1506`_).
|
||||
|
||||
* Fix ``pytest.mark.skip`` mark when used in strict mode.
|
||||
Thanks `@pquentin`_ for the PR and `@RonnyPfannschmidt`_ for
|
||||
showing how to fix the bug.
|
||||
|
||||
* Minor improvements and fixes to the documentation.
|
||||
Thanks `@omarkohl`_ for the PR.
|
||||
|
||||
* Fix ``--fixtures`` to show all fixture definitions as opposed to just
|
||||
one per fixture name.
|
||||
Thanks to `@hackebrot`_ for the PR.
|
||||
|
||||
.. _#510: https://github.com/pytest-dev/pytest/issues/510
|
||||
.. _#1506: https://github.com/pytest-dev/pytest/pull/1506
|
||||
.. _#1496: https://github.com/pytest-dev/pytest/issue/1496
|
||||
.. _#1524: https://github.com/pytest-dev/pytest/issue/1524
|
||||
|
||||
.. _@prusse-martin: https://github.com/prusse-martin
|
||||
.. _@astraw38: https://github.com/astraw38
|
||||
105
doc/en/announce/sprint2016.rst
Normal file
105
doc/en/announce/sprint2016.rst
Normal file
@@ -0,0 +1,105 @@
|
||||
python testing sprint June 20th-26th 2016
|
||||
======================================================
|
||||
|
||||
.. image:: ../img/freiburg2.jpg
|
||||
:width: 400
|
||||
|
||||
The pytest core group is heading towards the biggest sprint
|
||||
in its history, to take place in the black forest town Freiburg
|
||||
in Germany. As of February 2016 we have started a `funding
|
||||
campaign on Indiegogo to cover expenses
|
||||
<http://igg.me/at/pytest-sprint/x/4034848>`_ The page also mentions
|
||||
some preliminary topics:
|
||||
|
||||
- improving pytest-xdist test scheduling to take into account
|
||||
fixture setups and explicit user hints.
|
||||
|
||||
- provide info on fixture dependencies during --collect-only
|
||||
|
||||
- tying pytest-xdist to tox so that you can do "py.test -e py34"
|
||||
to run tests in a particular tox-managed virtualenv. Also
|
||||
look into making pytest-xdist use tox environments on
|
||||
remote ssh-sides so that remote dependency management becomes
|
||||
easier.
|
||||
|
||||
- refactoring the fixture system so more people understand it :)
|
||||
|
||||
- integrating PyUnit setup methods as autouse fixtures.
|
||||
possibly adding ways to influence ordering of same-scoped
|
||||
fixtures (so you can make a choice of which fixtures come
|
||||
before others)
|
||||
|
||||
- fixing bugs and issues from the tracker, really an endless source :)
|
||||
|
||||
|
||||
Participants
|
||||
--------------
|
||||
|
||||
Here are preliminary participants who said they are likely to come,
|
||||
given some expenses funding::
|
||||
|
||||
Anatoly Bubenkoff, Netherlands
|
||||
Andreas Pelme, Personalkollen, Sweden
|
||||
Anthony Wang, Splunk, US
|
||||
Brianna Laugher, Australia
|
||||
Bruno Oliveira, Brazil
|
||||
Danielle Jenkins, Splunk, US
|
||||
Dave Hunt, UK
|
||||
Florian Bruhin, Switzerland
|
||||
Floris Bruynooghe, Cobe.io, UK
|
||||
Holger Krekel, merlinux, Germany
|
||||
Oliver Bestwalter, Avira, Germany
|
||||
Omar Kohl, Germany
|
||||
Raphael Pierzina, FanDuel, UK
|
||||
Tom Viner, UK
|
||||
|
||||
<your name here?>
|
||||
|
||||
Other contributors and experienced newcomers are invited to join as well
|
||||
but please send a mail to the pytest-dev mailing list if you intend to
|
||||
do so somewhat soon, also how much funding you need if so. And if you
|
||||
are working for a company and using pytest heavily you are welcome to
|
||||
join and we encourage your company to provide some funding for the
|
||||
sprint. They may see it, and rightfully so, as a very cheap and deep
|
||||
training which brings you together with the experts in the field :)
|
||||
|
||||
|
||||
Sprint organisation, schedule
|
||||
-------------------------------
|
||||
|
||||
tentative schedule:
|
||||
|
||||
- 19/20th arrival in Freiburg
|
||||
- 20th social get together, initial hacking
|
||||
- 21/22th full sprint days
|
||||
- 23rd break day, hiking
|
||||
- 24/25th full sprint days
|
||||
- 26th departure
|
||||
|
||||
We might adjust according to weather to make sure that if
|
||||
we do some hiking or excursion we'll have good weather.
|
||||
Freiburg is one of the sunniest places in Germany so
|
||||
it shouldn't be too much of a constraint.
|
||||
|
||||
|
||||
Accomodation
|
||||
----------------
|
||||
|
||||
We'll see to arrange for renting a flat with multiple
|
||||
beds/rooms. Hotels are usually below 100 per night.
|
||||
The earlier we book the better.
|
||||
|
||||
Money / funding
|
||||
---------------
|
||||
|
||||
The Indiegogo campaign asks for 11000 USD which should cover
|
||||
the costs for flights and accomodation, renting a sprint place
|
||||
and maybe a bit of food as well.
|
||||
|
||||
If your organisation wants to support the sprint but prefers
|
||||
to give money according to an invoice, get in contact with
|
||||
holger at http://merlinux.eu who can invoice your organisation
|
||||
properly.
|
||||
|
||||
If we have excess money we'll use for further sprint/travel
|
||||
funding for pytest/tox contributors.
|
||||
@@ -26,7 +26,7 @@ you will see the return value of the function call::
|
||||
|
||||
$ py.test test_assert1.py
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 items
|
||||
|
||||
@@ -81,13 +81,10 @@ and if you need to have access to the actual exception info you may use::
|
||||
f()
|
||||
assert 'maximum recursion' in str(excinfo.value)
|
||||
|
||||
``excinfo`` is a `py.code.ExceptionInfo`_ instance, which is a wrapper around
|
||||
``excinfo`` is a ``ExceptionInfo`` instance, which is a wrapper around
|
||||
the actual exception raised. The main attributes of interest are
|
||||
``.type``, ``.value`` and ``.traceback``.
|
||||
|
||||
.. _py.code.ExceptionInfo:
|
||||
http://pylib.readthedocs.org/en/latest/code.html#py-code-exceptioninfo
|
||||
|
||||
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::
|
||||
|
||||
@@ -146,7 +143,7 @@ if you run this module::
|
||||
|
||||
$ py.test test_assert2.py
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 items
|
||||
|
||||
@@ -159,7 +156,7 @@ if you run this module::
|
||||
set1 = set("1308")
|
||||
set2 = set("8035")
|
||||
> assert set1 == set2
|
||||
E assert set(['0', '1', '3', '8']) == set(['0', '3', '5', '8'])
|
||||
E assert {'0', '1', '3', '8'} == {'0', '3', '5', '8'}
|
||||
E Extra items in the left set:
|
||||
E '1'
|
||||
E Extra items in the right set:
|
||||
@@ -192,8 +189,8 @@ provides an alternative explanation for ``Foo`` objects::
|
||||
from test_foocompare import Foo
|
||||
def pytest_assertrepr_compare(op, left, right):
|
||||
if isinstance(left, Foo) and isinstance(right, Foo) and op == "==":
|
||||
return ['Comparing Foo instances:',
|
||||
' vals: %s != %s' % (left.val, right.val)]
|
||||
return ['Comparing Foo instances:',
|
||||
' vals: %s != %s' % (left.val, right.val)]
|
||||
|
||||
now, given this test module::
|
||||
|
||||
@@ -243,10 +240,9 @@ recording the intermediate values. Which technique is used depends on the
|
||||
location of the assert, ``pytest`` configuration, and Python version being used
|
||||
to run ``pytest``.
|
||||
|
||||
By default, if the Python version is greater than or equal to 2.6, ``pytest``
|
||||
rewrites assert statements in test modules. Rewritten assert statements put
|
||||
introspection information into the assertion failure message. ``pytest`` only
|
||||
rewrites test modules directly discovered by its test collection process, so
|
||||
By default, ``pytest`` rewrites assert statements in test modules.
|
||||
Rewritten assert statements put introspection information into the assertion failure message.
|
||||
``pytest`` only rewrites test modules directly discovered by its test collection process, so
|
||||
asserts in supporting modules which are not themselves test modules will not be
|
||||
rewritten.
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ Setting up bash completion
|
||||
==========================
|
||||
|
||||
When using bash as your shell, ``pytest`` can use argcomplete
|
||||
(https://argcomplete.readthedocs.org/) for auto-completion.
|
||||
(https://argcomplete.readthedocs.io/) for auto-completion.
|
||||
For this ``argcomplete`` needs to be installed **and** enabled.
|
||||
|
||||
Install argcomplete using::
|
||||
|
||||
@@ -79,8 +79,8 @@ You can ask for available builtin or project-custom
|
||||
cache.get(key, default)
|
||||
cache.set(key, value)
|
||||
|
||||
Keys must be strings not containing a "/" separator. Add a unique identifier
|
||||
(such as plugin/app name) to avoid clashes with other cache users.
|
||||
Keys must be a ``/`` separated value, where the first part is usually the
|
||||
name of your plugin or application to avoid clashes with other cache users.
|
||||
|
||||
Values can be any object handled by the json stdlib module.
|
||||
capsys
|
||||
@@ -131,4 +131,4 @@ You can ask for available builtin or project-custom
|
||||
directory. The returned object is a `py.path.local`_
|
||||
path object.
|
||||
|
||||
in 0.12 seconds
|
||||
no tests ran in 0.12 seconds
|
||||
|
||||
@@ -5,19 +5,20 @@ Cache: working with cross-testrun state
|
||||
|
||||
.. warning::
|
||||
|
||||
The functionality of this core plugin was previosuly distributed
|
||||
The functionality of this core plugin was previously distributed
|
||||
as a third party plugin named ``pytest-cache``. The core plugin
|
||||
is compatible regarding command line options and API usage except that you
|
||||
can only store/receive data between test runs that is json-serializable.
|
||||
|
||||
|
||||
Usage
|
||||
---------
|
||||
|
||||
The plugin provides two command line options to rerun failures from the
|
||||
last ``py.test`` invocation:
|
||||
|
||||
* ``--lf`` (last failures) - to only re-run the failures.
|
||||
* ``--ff`` (failures first) - to run the failures first and then the rest of
|
||||
* ``--lf``, ``--last-failed`` - to only re-run the failures.
|
||||
* ``--ff``, ``--failed-first`` - to run the failures first and then the rest of
|
||||
the tests.
|
||||
|
||||
For cleanup (usually not needed), a ``--cache-clear`` option allows to remove
|
||||
@@ -26,6 +27,12 @@ all cross-session cache contents ahead of a test run.
|
||||
Other plugins may access the `config.cache`_ object to set/get
|
||||
**json encodable** values between ``py.test`` invocations.
|
||||
|
||||
.. note::
|
||||
|
||||
This plugin is enabled by default, but can be disabled if needed: see
|
||||
:ref:`cmdunregister` (the internal name for this plugin is
|
||||
``cacheprovider``).
|
||||
|
||||
|
||||
Rerunning only failures or failures first
|
||||
-----------------------------------------------
|
||||
@@ -73,7 +80,7 @@ If you then run it with ``--lf``::
|
||||
|
||||
$ py.test --lf
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
run-last-failure: rerun last 2 failures
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 50 items
|
||||
@@ -114,7 +121,7 @@ of ``FF`` and dots)::
|
||||
|
||||
$ py.test --ff
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
run-last-failure: rerun last 2 failures first
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 50 items
|
||||
@@ -219,7 +226,7 @@ You can always peek at the content of the cache using the
|
||||
|
||||
$ py.test --cache-clear
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 items
|
||||
|
||||
@@ -255,7 +262,7 @@ than speed.
|
||||
config.cache API
|
||||
------------------
|
||||
|
||||
The `config.cache`` object allows other plugins,
|
||||
The ``config.cache`` object allows other plugins,
|
||||
including ``conftest.py`` files,
|
||||
to safely and flexibly store and retrieve values across
|
||||
test runs because the ``config`` object is available
|
||||
|
||||
@@ -64,7 +64,7 @@ of the failing function and hide the other one::
|
||||
|
||||
$ py.test
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 2 items
|
||||
|
||||
|
||||
@@ -4,4 +4,4 @@
|
||||
Changelog history
|
||||
=================================
|
||||
|
||||
.. include:: ../../CHANGELOG
|
||||
.. include:: ../../CHANGELOG.rst
|
||||
|
||||
@@ -13,19 +13,25 @@ Full pytest documentation
|
||||
overview
|
||||
apiref
|
||||
example/index
|
||||
plugins
|
||||
monkeypatch
|
||||
tmpdir
|
||||
capture
|
||||
recwarn
|
||||
cache
|
||||
plugins
|
||||
|
||||
contributing
|
||||
plugins_index/index
|
||||
talks
|
||||
|
||||
.. only:: html
|
||||
|
||||
.. toctree::
|
||||
|
||||
funcarg_compare
|
||||
announce/index
|
||||
|
||||
.. only:: html
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
||||
|
||||
@@ -162,7 +162,7 @@ Builtin configuration file options
|
||||
.. versionadded:: 2.8
|
||||
|
||||
Sets list of directories that should be searched for tests when
|
||||
no specific directories or files are given in the command line when
|
||||
no specific directories, files or test ids are given in the command line when
|
||||
executing pytest from the :ref:`rootdir <rootdir>` directory.
|
||||
Useful when all project tests are in a known location to speed up
|
||||
test collection and to avoid picking up undesired tests by accident.
|
||||
|
||||
@@ -8,7 +8,10 @@ can change the pattern by issuing::
|
||||
|
||||
py.test --doctest-glob='*.rst'
|
||||
|
||||
on the command line. You can also trigger running of doctests
|
||||
on the command line. Since version ``2.9``, ``--doctest-glob``
|
||||
can be given multiple times in the command-line.
|
||||
|
||||
You can also trigger running of doctests
|
||||
from docstrings in all python modules (including regular
|
||||
python test modules)::
|
||||
|
||||
@@ -46,7 +49,7 @@ then you can just invoke ``py.test`` without command line options::
|
||||
|
||||
$ py.test
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
|
||||
collected 1 items
|
||||
|
||||
@@ -67,19 +70,32 @@ when executing text doctest files.
|
||||
The standard ``doctest`` module provides some setting flags to configure the
|
||||
strictness of doctest tests. In py.test You can enable those flags those flags
|
||||
using the configuration file. To make pytest ignore trailing whitespaces and
|
||||
ignore lengthy exception stack traces you can just write::
|
||||
ignore lengthy exception stack traces you can just write:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
# content of pytest.ini
|
||||
[pytest]
|
||||
doctest_optionflags= NORMALIZE_WHITESPACE IGNORE_EXCEPTION_DETAIL
|
||||
|
||||
py.test also introduces new options to allow doctests to run in Python 2 and
|
||||
Python 3 unchanged:
|
||||
|
||||
py.test also introduces a new ``ALLOW_UNICODE`` option flag: when enabled, the
|
||||
``u`` prefix is stripped from unicode strings in expected doctest output. This
|
||||
allows doctests which use unicode to run in Python 2 and 3 unchanged.
|
||||
* ``ALLOW_UNICODE``: when enabled, the ``u`` prefix is stripped from unicode
|
||||
strings in expected doctest output.
|
||||
|
||||
As with any other option flag, this flag can be enabled in ``pytest.ini`` using
|
||||
the ``doctest_optionflags`` ini option or by an inline comment in the doc test
|
||||
* ``ALLOW_BYTES``: when enabled, the ``b`` prefix is stripped from byte strings
|
||||
in expected doctest output.
|
||||
|
||||
As with any other option flag, these flags can be enabled in ``pytest.ini`` using
|
||||
the ``doctest_optionflags`` ini option:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[pytest]
|
||||
doctest_optionflags = ALLOW_UNICODE ALLOW_BYTES
|
||||
|
||||
|
||||
Alternatively, it can be enabled by an inline comment in the doc test
|
||||
itself::
|
||||
|
||||
# content of example.rst
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from pytest import raises
|
||||
import _pytest._code
|
||||
import py
|
||||
|
||||
def otherfunc(a,b):
|
||||
@@ -159,7 +160,7 @@ def test_dynamic_compile_shows_nicely():
|
||||
src = 'def foo():\n assert 1 == 0\n'
|
||||
name = 'abc-123'
|
||||
module = py.std.imp.new_module(name)
|
||||
code = py.code.compile(src, name, 'exec')
|
||||
code = _pytest._code.compile(src, name, 'exec')
|
||||
py.builtin.exec_(code, module.__dict__)
|
||||
py.std.sys.modules[name] = module
|
||||
module.foo()
|
||||
|
||||
@@ -31,7 +31,7 @@ You can then restrict a test run to only run tests marked with ``webtest``::
|
||||
|
||||
$ py.test -v -m webtest
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.5
|
||||
cachedir: .cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 4 items
|
||||
@@ -45,7 +45,7 @@ Or the inverse, running all tests except the webtest ones::
|
||||
|
||||
$ py.test -v -m "not webtest"
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.5
|
||||
cachedir: .cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 4 items
|
||||
@@ -66,7 +66,7 @@ tests based on their module, class, method, or function name::
|
||||
|
||||
$ py.test -v test_server.py::TestClass::test_method
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.5
|
||||
cachedir: .cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 5 items
|
||||
@@ -79,7 +79,7 @@ You can also select on the class::
|
||||
|
||||
$ py.test -v test_server.py::TestClass
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.5
|
||||
cachedir: .cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 4 items
|
||||
@@ -92,7 +92,7 @@ Or select multiple nodes::
|
||||
|
||||
$ py.test -v test_server.py::TestClass test_server.py::test_send_http
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.5
|
||||
cachedir: .cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 8 items
|
||||
@@ -130,7 +130,7 @@ select tests based on their names::
|
||||
|
||||
$ py.test -v -k http # running with the above defined example module
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.5
|
||||
cachedir: .cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 4 items
|
||||
@@ -144,7 +144,7 @@ And you can also run all tests except the ones that match the keyword::
|
||||
|
||||
$ py.test -k "not send_http" -v
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.5
|
||||
cachedir: .cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 4 items
|
||||
@@ -160,7 +160,7 @@ Or to select "http" and "quick" tests::
|
||||
|
||||
$ py.test -k "http or quick" -v
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.5
|
||||
cachedir: .cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 4 items
|
||||
@@ -201,9 +201,11 @@ You can ask which markers exist for your test suite - the list includes our just
|
||||
$ py.test --markers
|
||||
@pytest.mark.webtest: mark a test as a webtest.
|
||||
|
||||
@pytest.mark.skip(reason=None): skip the given test function with an optional reason. Example: skip(reason="no way of currently testing this") skips the test.
|
||||
|
||||
@pytest.mark.skipif(condition): skip the given test function if eval(condition) results in a True value. Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform. see http://pytest.org/latest/skipping.html
|
||||
|
||||
@pytest.mark.xfail(condition, reason=None, run=True, raises=None): mark the the test function as an expected failure if eval(condition) has a True value. Optionally specify a reason for better reporting and run=False if you don't even want to execute the test function. If only specific exception(s) are expected, you can list them in raises, and if the test fails in other ways, it will be reported as a true failure. See http://pytest.org/latest/skipping.html
|
||||
@pytest.mark.xfail(condition, reason=None, run=True, raises=None, strict=False): mark the the test function as an expected failure if eval(condition) has a True value. Optionally specify a reason for better reporting and run=False if you don't even want to execute the test function. If only specific exception(s) are expected, you can list them in raises, and if the test fails in other ways, it will be reported as a true failure. See http://pytest.org/latest/skipping.html
|
||||
|
||||
@pytest.mark.parametrize(argnames, argvalues): call a test function multiple times passing in different arguments in turn. argvalues generally needs to be a list of values if argnames specifies only one name or a list of tuples of values if argnames specifies multiple names. Example: @parametrize('arg1', [1,2]) would lead to two calls of the decorated test function, one with arg1=1 and another with arg1=2.see http://pytest.org/latest/parametrize.html for more info and examples.
|
||||
|
||||
@@ -219,7 +221,7 @@ For an example on how to add and work with markers from a plugin, see
|
||||
|
||||
.. note::
|
||||
|
||||
It is recommended to explicitely register markers so that:
|
||||
It is recommended to explicitly register markers so that:
|
||||
|
||||
* there is one place in your test suite defining your markers
|
||||
|
||||
@@ -234,8 +236,8 @@ For an example on how to add and work with markers from a plugin, see
|
||||
Marking whole classes or modules
|
||||
----------------------------------------------------
|
||||
|
||||
If you are programming with Python 2.6 or later you may use ``pytest.mark``
|
||||
decorators with classes to apply markers to all of its test methods::
|
||||
You may use ``pytest.mark`` decorators with classes to apply markers to all of
|
||||
its test methods::
|
||||
|
||||
# content of test_mark_classlevel.py
|
||||
import pytest
|
||||
@@ -350,7 +352,7 @@ the test needs::
|
||||
|
||||
$ py.test -E stage2
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 items
|
||||
|
||||
@@ -362,7 +364,7 @@ and here is one that specifies exactly the environment needed::
|
||||
|
||||
$ py.test -E stage1
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 items
|
||||
|
||||
@@ -375,9 +377,11 @@ The ``--markers`` option always gives you a list of available markers::
|
||||
$ py.test --markers
|
||||
@pytest.mark.env(name): mark test to run only on named environment
|
||||
|
||||
@pytest.mark.skip(reason=None): skip the given test function with an optional reason. Example: skip(reason="no way of currently testing this") skips the test.
|
||||
|
||||
@pytest.mark.skipif(condition): skip the given test function if eval(condition) results in a True value. Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform. see http://pytest.org/latest/skipping.html
|
||||
|
||||
@pytest.mark.xfail(condition, reason=None, run=True, raises=None): mark the the test function as an expected failure if eval(condition) has a True value. Optionally specify a reason for better reporting and run=False if you don't even want to execute the test function. If only specific exception(s) are expected, you can list them in raises, and if the test fails in other ways, it will be reported as a true failure. See http://pytest.org/latest/skipping.html
|
||||
@pytest.mark.xfail(condition, reason=None, run=True, raises=None, strict=False): mark the the test function as an expected failure if eval(condition) has a True value. Optionally specify a reason for better reporting and run=False if you don't even want to execute the test function. If only specific exception(s) are expected, you can list them in raises, and if the test fails in other ways, it will be reported as a true failure. See http://pytest.org/latest/skipping.html
|
||||
|
||||
@pytest.mark.parametrize(argnames, argvalues): call a test function multiple times passing in different arguments in turn. argvalues generally needs to be a list of values if argnames specifies only one name or a list of tuples of values if argnames specifies multiple names. Example: @parametrize('arg1', [1,2]) would lead to two calls of the decorated test function, one with arg1=1 and another with arg1=2.see http://pytest.org/latest/parametrize.html for more info and examples.
|
||||
|
||||
@@ -436,7 +440,7 @@ marking platform specific tests with pytest
|
||||
.. regendoc:wipe
|
||||
|
||||
Consider you have a test suite which marks tests for particular platforms,
|
||||
namely ``pytest.mark.osx``, ``pytest.mark.win32`` etc. and you
|
||||
namely ``pytest.mark.darwin``, ``pytest.mark.win32`` etc. and you
|
||||
also have tests that run on all platforms and have no specific
|
||||
marker. If you now want to have a way to only run the tests
|
||||
for your particular platform, you could use the following plugin::
|
||||
@@ -446,7 +450,7 @@ for your particular platform, you could use the following plugin::
|
||||
import sys
|
||||
import pytest
|
||||
|
||||
ALL = set("osx linux2 win32".split())
|
||||
ALL = set("darwin linux2 win32".split())
|
||||
|
||||
def pytest_runtest_setup(item):
|
||||
if isinstance(item, item.Function):
|
||||
@@ -462,7 +466,7 @@ Let's do a little test file to show how this looks like::
|
||||
|
||||
import pytest
|
||||
|
||||
@pytest.mark.osx
|
||||
@pytest.mark.darwin
|
||||
def test_if_apple_is_evil():
|
||||
pass
|
||||
|
||||
@@ -481,7 +485,7 @@ then you will see two test skipped and two executed tests as expected::
|
||||
|
||||
$ py.test -rs # this option reports skip reasons
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 4 items
|
||||
|
||||
@@ -495,7 +499,7 @@ Note that if you specify a platform via the marker-command line option like this
|
||||
|
||||
$ py.test -m linux2
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 4 items
|
||||
|
||||
@@ -547,7 +551,7 @@ We can now use the ``-m option`` to select one set::
|
||||
|
||||
$ py.test -m interface --tb=short
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 4 items
|
||||
|
||||
@@ -569,7 +573,7 @@ or to select both "event" and "interface" tests::
|
||||
|
||||
$ py.test -m "interface or event" --tb=short
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 4 items
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ serialization via the pickle module.
|
||||
"""
|
||||
import py
|
||||
import pytest
|
||||
import _pytest._code
|
||||
|
||||
pythonlist = ['python2.6', 'python2.7', 'python3.3']
|
||||
@pytest.fixture(params=pythonlist)
|
||||
@@ -23,7 +24,7 @@ class Python:
|
||||
self.picklefile = picklefile
|
||||
def dumps(self, obj):
|
||||
dumpfile = self.picklefile.dirpath("dump.py")
|
||||
dumpfile.write(py.code.Source("""
|
||||
dumpfile.write(_pytest._code.Source("""
|
||||
import pickle
|
||||
f = open(%r, 'wb')
|
||||
s = pickle.dump(%r, f, protocol=2)
|
||||
@@ -33,7 +34,7 @@ class Python:
|
||||
|
||||
def load_and_is_true(self, expression):
|
||||
loadfile = self.picklefile.dirpath("load.py")
|
||||
loadfile.write(py.code.Source("""
|
||||
loadfile.write(_pytest._code.Source("""
|
||||
import pickle
|
||||
f = open(%r, 'rb')
|
||||
obj = pickle.load(f)
|
||||
|
||||
@@ -27,11 +27,11 @@ now execute the test specification::
|
||||
|
||||
nonpython $ py.test test_simple.yml
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
|
||||
collected 2 items
|
||||
|
||||
test_simple.yml F.
|
||||
test_simple.yml .F
|
||||
|
||||
======= FAILURES ========
|
||||
_______ usecase: hello ________
|
||||
@@ -59,13 +59,13 @@ consulted when reporting in ``verbose`` mode::
|
||||
|
||||
nonpython $ py.test -v
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.5
|
||||
cachedir: .cache
|
||||
rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
|
||||
collecting ... collected 2 items
|
||||
|
||||
test_simple.yml::hello FAILED
|
||||
test_simple.yml::ok PASSED
|
||||
test_simple.yml::hello FAILED
|
||||
|
||||
======= FAILURES ========
|
||||
_______ usecase: hello ________
|
||||
@@ -81,11 +81,11 @@ interesting to just look at the collection tree::
|
||||
|
||||
nonpython $ py.test --collect-only
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
|
||||
collected 2 items
|
||||
<YamlFile 'test_simple.yml'>
|
||||
<YamlItem 'hello'>
|
||||
<YamlItem 'ok'>
|
||||
<YamlItem 'hello'>
|
||||
|
||||
======= in 0.12 seconds ========
|
||||
======= no tests ran in 0.12 seconds ========
|
||||
|
||||
@@ -130,7 +130,7 @@ objects, they are still using the default pytest representation::
|
||||
|
||||
$ py.test test_time.py --collect-only
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 6 items
|
||||
<Module 'test_time.py'>
|
||||
@@ -141,7 +141,7 @@ objects, they are still using the default pytest representation::
|
||||
<Function 'test_timedistance_v2[20011212-20011211-expected0]'>
|
||||
<Function 'test_timedistance_v2[20011211-20011212-expected1]'>
|
||||
|
||||
======= in 0.12 seconds ========
|
||||
======= no tests ran in 0.12 seconds ========
|
||||
|
||||
A quick port of "testscenarios"
|
||||
------------------------------------
|
||||
@@ -181,7 +181,7 @@ this is a fully self-contained example which you can run with::
|
||||
|
||||
$ py.test test_scenarios.py
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 4 items
|
||||
|
||||
@@ -194,7 +194,7 @@ If you just collect tests you'll also nicely see 'advanced' and 'basic' as varia
|
||||
|
||||
$ py.test --collect-only test_scenarios.py
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 4 items
|
||||
<Module 'test_scenarios.py'>
|
||||
@@ -205,7 +205,7 @@ If you just collect tests you'll also nicely see 'advanced' and 'basic' as varia
|
||||
<Function 'test_demo1[advanced]'>
|
||||
<Function 'test_demo2[advanced]'>
|
||||
|
||||
======= in 0.12 seconds ========
|
||||
======= no tests ran in 0.12 seconds ========
|
||||
|
||||
Note that we told ``metafunc.parametrize()`` that your scenario values
|
||||
should be considered class-scoped. With pytest-2.3 this leads to a
|
||||
@@ -259,14 +259,14 @@ Let's first see how it looks like at collection time::
|
||||
|
||||
$ py.test test_backends.py --collect-only
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 2 items
|
||||
<Module 'test_backends.py'>
|
||||
<Function 'test_db_initialized[d1]'>
|
||||
<Function 'test_db_initialized[d2]'>
|
||||
|
||||
======= in 0.12 seconds ========
|
||||
======= no tests ran in 0.12 seconds ========
|
||||
|
||||
And then when we run the test::
|
||||
|
||||
@@ -320,25 +320,25 @@ The result of this test will be successful::
|
||||
|
||||
$ py.test test_indirect_list.py --collect-only
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 items
|
||||
<Module 'test_indirect_list.py'>
|
||||
<Function 'test_indirect[a-b]'>
|
||||
|
||||
======= in 0.12 seconds ========
|
||||
======= no tests ran in 0.12 seconds ========
|
||||
|
||||
.. regendoc:wipe
|
||||
|
||||
Parametrizing test methods through per-class configuration
|
||||
--------------------------------------------------------------
|
||||
|
||||
.. _`unittest parameterizer`: http://code.google.com/p/unittest-ext/source/browse/trunk/params.py
|
||||
.. _`unittest parametrizer`: https://github.com/testing-cabal/unittest-ext/blob/master/params.py
|
||||
|
||||
|
||||
Here is an example ``pytest_generate_function`` function implementing a
|
||||
parametrization scheme similar to Michael Foord's `unittest
|
||||
parameterizer`_ but in a lot less code::
|
||||
parametrizer`_ but in a lot less code::
|
||||
|
||||
# content of ./test_parametrize.py
|
||||
import pytest
|
||||
@@ -369,7 +369,7 @@ argument sets to use for each test function. Let's run it::
|
||||
$ py.test -q
|
||||
F..
|
||||
======= FAILURES ========
|
||||
_______ TestClass.test_equals[2-1] ________
|
||||
_______ TestClass.test_equals[1-2] ________
|
||||
|
||||
self = <test_parametrize.TestClass object at 0xdeadbeef>, a = 1, b = 2
|
||||
|
||||
@@ -445,7 +445,7 @@ If you run this with reporting for skips enabled::
|
||||
|
||||
$ py.test -rs test_module.py
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 2 items
|
||||
|
||||
|
||||
@@ -1,6 +1,45 @@
|
||||
Changing standard (Python) test discovery
|
||||
===============================================
|
||||
|
||||
Ignore paths during test collection
|
||||
-----------------------------------
|
||||
|
||||
You can easily ignore certain test directories and modules during collection
|
||||
by passing the ``--ignore=path`` option on the cli. ``pytest`` allows multiple
|
||||
``--ignore`` options. Example::
|
||||
|
||||
tests/
|
||||
|-- example
|
||||
| |-- test_example_01.py
|
||||
| |-- test_example_02.py
|
||||
| '-- test_example_03.py
|
||||
|-- foobar
|
||||
| |-- test_foobar_01.py
|
||||
| |-- test_foobar_02.py
|
||||
| '-- test_foobar_03.py
|
||||
'-- hello
|
||||
'-- world
|
||||
|-- test_world_01.py
|
||||
|-- test_world_02.py
|
||||
'-- 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::
|
||||
|
||||
========= test session starts ==========
|
||||
platform darwin -- Python 2.7.10, pytest-2.8.2, py-1.4.30, pluggy-0.3.1
|
||||
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 .
|
||||
|
||||
======= 5 passed in 0.02 seconds =======
|
||||
|
||||
|
||||
Changing directory recursion
|
||||
-----------------------------------------------------
|
||||
|
||||
@@ -43,7 +82,7 @@ then the test collection looks like this::
|
||||
|
||||
$ py.test --collect-only
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile: setup.cfg
|
||||
collected 2 items
|
||||
<Module 'check_myapp.py'>
|
||||
@@ -52,7 +91,7 @@ then the test collection looks like this::
|
||||
<Function 'simple_check'>
|
||||
<Function 'complex_check'>
|
||||
|
||||
======= in 0.12 seconds ========
|
||||
======= no tests ran in 0.12 seconds ========
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -89,7 +128,7 @@ You can always peek at the collection tree without running tests like this::
|
||||
|
||||
. $ py.test --collect-only pythoncollection.py
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
|
||||
collected 3 items
|
||||
<Module 'CWD/pythoncollection.py'>
|
||||
@@ -99,7 +138,7 @@ You can always peek at the collection tree without running tests like this::
|
||||
<Function 'test_method'>
|
||||
<Function 'test_anothermethod'>
|
||||
|
||||
======= in 0.12 seconds ========
|
||||
======= no tests ran in 0.12 seconds ========
|
||||
|
||||
customizing test collection to find all .py files
|
||||
---------------------------------------------------------
|
||||
@@ -136,18 +175,28 @@ And then if you have a module file like this::
|
||||
and a setup.py dummy file like this::
|
||||
|
||||
# content of setup.py
|
||||
0/0 # will raise exeption if imported
|
||||
0/0 # will raise exception if imported
|
||||
|
||||
then a pytest run on python2 will find the one test when run with a python2
|
||||
interpreters and will leave out the setup.py file::
|
||||
then a pytest run on Python2 will find the one test and will leave out the
|
||||
setup.py file::
|
||||
|
||||
#$ py.test --collect-only
|
||||
====== test session starts ======
|
||||
platform linux2 -- Python 2.7.10, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
|
||||
collected 1 items
|
||||
<Module 'pkg/module_py2.py'>
|
||||
<Function 'test_only_on_python2'>
|
||||
|
||||
====== no tests ran in 0.04 seconds ======
|
||||
|
||||
If you run with a Python3 interpreter both the one test and the setup.py file
|
||||
will be left out::
|
||||
|
||||
$ py.test --collect-only
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
|
||||
collected 0 items
|
||||
|
||||
======= in 0.12 seconds ========
|
||||
|
||||
If you run with a Python3 interpreter the moduled added through the conftest.py file will not be considered for test collection.
|
||||
|
||||
======= no tests ran in 0.12 seconds ========
|
||||
|
||||
@@ -13,7 +13,7 @@ get on the terminal - we are working on that):
|
||||
|
||||
assertion $ py.test failure_demo.py
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR/assertion, inifile:
|
||||
collected 42 items
|
||||
|
||||
@@ -28,7 +28,7 @@ get on the terminal - we are working on that):
|
||||
> assert param1 * 2 < param2
|
||||
E assert (3 * 2) < 6
|
||||
|
||||
failure_demo.py:15: AssertionError
|
||||
failure_demo.py:16: AssertionError
|
||||
_______ TestFailing.test_simple ________
|
||||
|
||||
self = <failure_demo.TestFailing object at 0xdeadbeef>
|
||||
@@ -44,7 +44,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:28: AssertionError
|
||||
failure_demo.py:29: AssertionError
|
||||
_______ TestFailing.test_simple_multiline ________
|
||||
|
||||
self = <failure_demo.TestFailing object at 0xdeadbeef>
|
||||
@@ -54,7 +54,7 @@ get on the terminal - we are working on that):
|
||||
42,
|
||||
> 6*9)
|
||||
|
||||
failure_demo.py:33:
|
||||
failure_demo.py:34:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
a = 42, b = 54
|
||||
@@ -64,7 +64,7 @@ get on the terminal - we are working on that):
|
||||
b)
|
||||
E assert 42 == 54
|
||||
|
||||
failure_demo.py:11: AssertionError
|
||||
failure_demo.py:12: AssertionError
|
||||
_______ TestFailing.test_not ________
|
||||
|
||||
self = <failure_demo.TestFailing object at 0xdeadbeef>
|
||||
@@ -76,7 +76,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:38: AssertionError
|
||||
failure_demo.py:39: AssertionError
|
||||
_______ TestSpecialisedExplanations.test_eq_text ________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -87,7 +87,7 @@ get on the terminal - we are working on that):
|
||||
E - spam
|
||||
E + eggs
|
||||
|
||||
failure_demo.py:42: AssertionError
|
||||
failure_demo.py:43: AssertionError
|
||||
_______ TestSpecialisedExplanations.test_eq_similar_text ________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -100,7 +100,7 @@ get on the terminal - we are working on that):
|
||||
E + foo 2 bar
|
||||
E ? ^
|
||||
|
||||
failure_demo.py:45: AssertionError
|
||||
failure_demo.py:46: AssertionError
|
||||
_______ TestSpecialisedExplanations.test_eq_multiline_text ________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -113,7 +113,7 @@ get on the terminal - we are working on that):
|
||||
E + eggs
|
||||
E bar
|
||||
|
||||
failure_demo.py:48: AssertionError
|
||||
failure_demo.py:49: AssertionError
|
||||
_______ TestSpecialisedExplanations.test_eq_long_text ________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -130,7 +130,7 @@ get on the terminal - we are working on that):
|
||||
E + 1111111111b222222222
|
||||
E ? ^
|
||||
|
||||
failure_demo.py:53: AssertionError
|
||||
failure_demo.py:54: AssertionError
|
||||
_______ TestSpecialisedExplanations.test_eq_long_text_multiline ________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -154,7 +154,7 @@ get on the terminal - we are working on that):
|
||||
E 2
|
||||
E 2
|
||||
|
||||
failure_demo.py:58: AssertionError
|
||||
failure_demo.py:59: AssertionError
|
||||
_______ TestSpecialisedExplanations.test_eq_list ________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -165,7 +165,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:61: AssertionError
|
||||
failure_demo.py:62: AssertionError
|
||||
_______ TestSpecialisedExplanations.test_eq_list_long ________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -178,7 +178,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:66: AssertionError
|
||||
failure_demo.py:67: AssertionError
|
||||
_______ TestSpecialisedExplanations.test_eq_dict ________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -195,14 +195,14 @@ get on the terminal - we are working on that):
|
||||
E {'d': 0}
|
||||
E Use -v to get the full diff
|
||||
|
||||
failure_demo.py:69: AssertionError
|
||||
failure_demo.py:70: AssertionError
|
||||
_______ TestSpecialisedExplanations.test_eq_set ________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
|
||||
def test_eq_set(self):
|
||||
> assert set([0, 10, 11, 12]) == set([0, 20, 21])
|
||||
E assert set([0, 10, 11, 12]) == set([0, 20, 21])
|
||||
E assert {0, 10, 11, 12} == {0, 20, 21}
|
||||
E Extra items in the left set:
|
||||
E 10
|
||||
E 11
|
||||
@@ -212,7 +212,7 @@ get on the terminal - we are working on that):
|
||||
E 21
|
||||
E Use -v to get the full diff
|
||||
|
||||
failure_demo.py:72: AssertionError
|
||||
failure_demo.py:73: AssertionError
|
||||
_______ TestSpecialisedExplanations.test_eq_longer_list ________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -223,7 +223,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:75: AssertionError
|
||||
failure_demo.py:76: AssertionError
|
||||
_______ TestSpecialisedExplanations.test_in_list ________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -232,7 +232,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:78: AssertionError
|
||||
failure_demo.py:79: AssertionError
|
||||
_______ TestSpecialisedExplanations.test_not_in_text_multiline ________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -250,7 +250,7 @@ get on the terminal - we are working on that):
|
||||
E and a
|
||||
E tail
|
||||
|
||||
failure_demo.py:82: AssertionError
|
||||
failure_demo.py:83: AssertionError
|
||||
_______ TestSpecialisedExplanations.test_not_in_text_single ________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -263,7 +263,7 @@ get on the terminal - we are working on that):
|
||||
E single foo line
|
||||
E ? +++
|
||||
|
||||
failure_demo.py:86: AssertionError
|
||||
failure_demo.py:87: AssertionError
|
||||
_______ TestSpecialisedExplanations.test_not_in_text_single_long ________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -276,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:90: AssertionError
|
||||
failure_demo.py:91: AssertionError
|
||||
______ TestSpecialisedExplanations.test_not_in_text_single_long_term _______
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||
@@ -289,7 +289,7 @@ get on the terminal - we are working on that):
|
||||
E head head fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffftail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail
|
||||
E ? ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
failure_demo.py:94: AssertionError
|
||||
failure_demo.py:95: AssertionError
|
||||
_______ test_attribute ________
|
||||
|
||||
def test_attribute():
|
||||
@@ -300,7 +300,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:101: AssertionError
|
||||
failure_demo.py:102: AssertionError
|
||||
_______ test_attribute_instance ________
|
||||
|
||||
def test_attribute_instance():
|
||||
@@ -311,7 +311,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:107: AssertionError
|
||||
failure_demo.py:108: AssertionError
|
||||
_______ test_attribute_failure ________
|
||||
|
||||
def test_attribute_failure():
|
||||
@@ -322,7 +322,7 @@ get on the terminal - we are working on that):
|
||||
i = Foo()
|
||||
> assert i.b == 2
|
||||
|
||||
failure_demo.py:116:
|
||||
failure_demo.py:117:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
self = <failure_demo.test_attribute_failure.<locals>.Foo object at 0xdeadbeef>
|
||||
@@ -331,7 +331,7 @@ get on the terminal - we are working on that):
|
||||
> raise Exception('Failed to get attrib')
|
||||
E Exception: Failed to get attrib
|
||||
|
||||
failure_demo.py:113: Exception
|
||||
failure_demo.py:114: Exception
|
||||
_______ test_attribute_multiple ________
|
||||
|
||||
def test_attribute_multiple():
|
||||
@@ -346,7 +346,7 @@ get on the terminal - we are working on that):
|
||||
E + and 2 = <failure_demo.test_attribute_multiple.<locals>.Bar object at 0xdeadbeef>.b
|
||||
E + where <failure_demo.test_attribute_multiple.<locals>.Bar object at 0xdeadbeef> = <class 'failure_demo.test_attribute_multiple.<locals>.Bar'>()
|
||||
|
||||
failure_demo.py:124: AssertionError
|
||||
failure_demo.py:125: AssertionError
|
||||
_______ TestRaises.test_raises ________
|
||||
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||
@@ -355,22 +355,22 @@ get on the terminal - we are working on that):
|
||||
s = 'qwe'
|
||||
> raises(TypeError, "int(s)")
|
||||
|
||||
failure_demo.py:133:
|
||||
failure_demo.py:134:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
> int(s)
|
||||
E ValueError: invalid literal for int() with base 10: 'qwe'
|
||||
|
||||
<0-codegen $PYTHON_PREFIX/lib/python3.4/site-packages/_pytest/python.py:1247>:1: ValueError
|
||||
<0-codegen $PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/python.py:1309>:1: ValueError
|
||||
_______ TestRaises.test_raises_doesnt ________
|
||||
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||
|
||||
def test_raises_doesnt(self):
|
||||
> raises(IOError, "int('3')")
|
||||
E Failed: DID NOT RAISE
|
||||
E Failed: DID NOT RAISE <class 'OSError'>
|
||||
|
||||
failure_demo.py:136: Failed
|
||||
failure_demo.py:137: Failed
|
||||
_______ TestRaises.test_raise ________
|
||||
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||
@@ -379,16 +379,16 @@ get on the terminal - we are working on that):
|
||||
> raise ValueError("demo error")
|
||||
E ValueError: demo error
|
||||
|
||||
failure_demo.py:139: ValueError
|
||||
failure_demo.py:140: ValueError
|
||||
_______ TestRaises.test_tupleerror ________
|
||||
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||
|
||||
def test_tupleerror(self):
|
||||
> a,b = [1]
|
||||
E ValueError: need more than 1 value to unpack
|
||||
E ValueError: not enough values to unpack (expected 2, got 1)
|
||||
|
||||
failure_demo.py:142: ValueError
|
||||
failure_demo.py:143: ValueError
|
||||
______ TestRaises.test_reinterpret_fails_with_print_for_the_fun_of_it ______
|
||||
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||
@@ -399,7 +399,7 @@ get on the terminal - we are working on that):
|
||||
> a,b = l.pop()
|
||||
E TypeError: 'int' object is not iterable
|
||||
|
||||
failure_demo.py:147: TypeError
|
||||
failure_demo.py:148: TypeError
|
||||
--------------------------- Captured stdout call ---------------------------
|
||||
l is [1, 2, 3]
|
||||
_______ TestRaises.test_some_error ________
|
||||
@@ -410,26 +410,26 @@ get on the terminal - we are working on that):
|
||||
> if namenotexi:
|
||||
E NameError: name 'namenotexi' is not defined
|
||||
|
||||
failure_demo.py:150: NameError
|
||||
failure_demo.py:151: NameError
|
||||
_______ test_dynamic_compile_shows_nicely ________
|
||||
|
||||
def test_dynamic_compile_shows_nicely():
|
||||
src = 'def foo():\n assert 1 == 0\n'
|
||||
name = 'abc-123'
|
||||
module = py.std.imp.new_module(name)
|
||||
code = py.code.compile(src, name, 'exec')
|
||||
code = _pytest._code.compile(src, name, 'exec')
|
||||
py.builtin.exec_(code, module.__dict__)
|
||||
py.std.sys.modules[name] = module
|
||||
> module.foo()
|
||||
|
||||
failure_demo.py:165:
|
||||
failure_demo.py:166:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
def foo():
|
||||
> assert 1 == 0
|
||||
E assert 1 == 0
|
||||
|
||||
<2-codegen 'abc-123' $REGENDOC_TMPDIR/assertion/failure_demo.py:162>:2: AssertionError
|
||||
<2-codegen 'abc-123' $REGENDOC_TMPDIR/assertion/failure_demo.py:163>:2: AssertionError
|
||||
_______ TestMoreErrors.test_complex_error ________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -441,9 +441,9 @@ get on the terminal - we are working on that):
|
||||
return 43
|
||||
> somefunc(f(), g())
|
||||
|
||||
failure_demo.py:175:
|
||||
failure_demo.py:176:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
failure_demo.py:8: in somefunc
|
||||
failure_demo.py:9: in somefunc
|
||||
otherfunc(x,y)
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
@@ -453,7 +453,7 @@ get on the terminal - we are working on that):
|
||||
> assert a==b
|
||||
E assert 44 == 43
|
||||
|
||||
failure_demo.py:5: AssertionError
|
||||
failure_demo.py:6: AssertionError
|
||||
_______ TestMoreErrors.test_z1_unpack_error ________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -461,9 +461,9 @@ get on the terminal - we are working on that):
|
||||
def test_z1_unpack_error(self):
|
||||
l = []
|
||||
> a,b = l
|
||||
E ValueError: need more than 0 values to unpack
|
||||
E ValueError: not enough values to unpack (expected 2, got 0)
|
||||
|
||||
failure_demo.py:179: ValueError
|
||||
failure_demo.py:180: ValueError
|
||||
_______ TestMoreErrors.test_z2_type_error ________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -473,7 +473,7 @@ get on the terminal - we are working on that):
|
||||
> a,b = l
|
||||
E TypeError: 'int' object is not iterable
|
||||
|
||||
failure_demo.py:183: TypeError
|
||||
failure_demo.py:184: TypeError
|
||||
_______ TestMoreErrors.test_startswith ________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -485,7 +485,7 @@ get on the terminal - we are working on that):
|
||||
E assert <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:188: AssertionError
|
||||
failure_demo.py:189: AssertionError
|
||||
_______ TestMoreErrors.test_startswith_nested ________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -501,7 +501,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:195: AssertionError
|
||||
failure_demo.py:196: AssertionError
|
||||
_______ TestMoreErrors.test_global_func ________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -511,7 +511,7 @@ get on the terminal - we are working on that):
|
||||
E assert isinstance(43, float)
|
||||
E + where 43 = globf(42)
|
||||
|
||||
failure_demo.py:198: AssertionError
|
||||
failure_demo.py:199: AssertionError
|
||||
_______ TestMoreErrors.test_instance ________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -522,7 +522,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:202: AssertionError
|
||||
failure_demo.py:203: AssertionError
|
||||
_______ TestMoreErrors.test_compare ________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -532,7 +532,7 @@ get on the terminal - we are working on that):
|
||||
E assert 11 < 5
|
||||
E + where 11 = globf(10)
|
||||
|
||||
failure_demo.py:205: AssertionError
|
||||
failure_demo.py:206: AssertionError
|
||||
_______ TestMoreErrors.test_try_finally ________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
@@ -543,7 +543,7 @@ get on the terminal - we are working on that):
|
||||
> assert x == 0
|
||||
E assert 1 == 0
|
||||
|
||||
failure_demo.py:210: AssertionError
|
||||
failure_demo.py:211: AssertionError
|
||||
_______ TestCustomAssertMsg.test_single_line ________
|
||||
|
||||
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
|
||||
@@ -557,7 +557,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:221: AssertionError
|
||||
failure_demo.py:222: AssertionError
|
||||
_______ TestCustomAssertMsg.test_multiline ________
|
||||
|
||||
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
|
||||
@@ -574,7 +574,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:227: AssertionError
|
||||
failure_demo.py:228: AssertionError
|
||||
_______ TestCustomAssertMsg.test_custom_repr ________
|
||||
|
||||
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
|
||||
@@ -594,5 +594,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:237: AssertionError
|
||||
failure_demo.py:238: AssertionError
|
||||
======= 42 failed in 0.12 seconds ========
|
||||
|
||||
@@ -108,11 +108,11 @@ directory with the above conftest.py::
|
||||
|
||||
$ py.test
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 0 items
|
||||
|
||||
======= in 0.12 seconds ========
|
||||
======= no tests ran in 0.12 seconds ========
|
||||
|
||||
.. _`excontrolskip`:
|
||||
|
||||
@@ -131,20 +131,23 @@ line option to control skipping of ``slow`` marked tests::
|
||||
parser.addoption("--runslow", action="store_true",
|
||||
help="run slow tests")
|
||||
|
||||
def pytest_runtest_setup(item):
|
||||
if 'slow' in item.keywords and not item.config.getoption("--runslow"):
|
||||
pytest.skip("need --runslow option to run")
|
||||
|
||||
We can now write a test module like this::
|
||||
|
||||
# content of test_module.py
|
||||
|
||||
import pytest
|
||||
slow = pytest.mark.slow
|
||||
|
||||
|
||||
slow = pytest.mark.skipif(
|
||||
not pytest.config.getoption("--runslow"),
|
||||
reason="need --runslow option to run"
|
||||
)
|
||||
|
||||
|
||||
def test_func_fast():
|
||||
pass
|
||||
|
||||
|
||||
@slow
|
||||
def test_func_slow():
|
||||
pass
|
||||
@@ -153,13 +156,13 @@ and when running it will see a skipped "slow" test::
|
||||
|
||||
$ py.test -rs # "-rs" means report details on the little 's'
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 2 items
|
||||
|
||||
test_module.py .s
|
||||
======= short test summary info ========
|
||||
SKIP [1] $REGENDOC_TMPDIR/conftest.py:9: need --runslow option to run
|
||||
SKIP [1] test_module.py:14: need --runslow option to run
|
||||
|
||||
======= 1 passed, 1 skipped in 0.12 seconds ========
|
||||
|
||||
@@ -167,7 +170,7 @@ Or run it including the ``slow`` marked test::
|
||||
|
||||
$ py.test --runslow
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 2 items
|
||||
|
||||
@@ -198,7 +201,7 @@ Example::
|
||||
|
||||
The ``__tracebackhide__`` setting influences ``pytest`` showing
|
||||
of tracebacks: the ``checkconfig`` function will not be shown
|
||||
unless the ``--fulltrace`` command line option is specified.
|
||||
unless the ``--full-trace`` command line option is specified.
|
||||
Let's run our little function::
|
||||
|
||||
$ py.test -q test_checkconfig.py
|
||||
@@ -259,12 +262,12 @@ which will add the string to the test header accordingly::
|
||||
|
||||
$ py.test
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
project deps: mylib-1.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 0 items
|
||||
|
||||
======= in 0.12 seconds ========
|
||||
======= no tests ran in 0.12 seconds ========
|
||||
|
||||
.. regendoc:wipe
|
||||
|
||||
@@ -283,24 +286,24 @@ which will add info only when run with "--v"::
|
||||
|
||||
$ py.test -v
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.5
|
||||
cachedir: .cache
|
||||
info1: did you know that ...
|
||||
did you?
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 0 items
|
||||
|
||||
======= in 0.12 seconds ========
|
||||
======= no tests ran in 0.12 seconds ========
|
||||
|
||||
and nothing when run plainly::
|
||||
|
||||
$ py.test
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 0 items
|
||||
|
||||
======= in 0.12 seconds ========
|
||||
======= no tests ran in 0.12 seconds ========
|
||||
|
||||
profiling test duration
|
||||
--------------------------
|
||||
@@ -310,7 +313,7 @@ profiling test duration
|
||||
.. versionadded: 2.2
|
||||
|
||||
If you have a slow running large test suite you might want to find
|
||||
out which tests are the slowest. Let's make an artifical test suite::
|
||||
out which tests are the slowest. Let's make an artificial test suite::
|
||||
|
||||
# content of test_some_are_slow.py
|
||||
|
||||
@@ -329,7 +332,7 @@ Now we can profile which test functions execute the slowest::
|
||||
|
||||
$ py.test --durations=3
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 3 items
|
||||
|
||||
@@ -338,7 +341,7 @@ Now we can profile which test functions execute the slowest::
|
||||
======= slowest 3 test durations ========
|
||||
0.20s call test_some_are_slow.py::test_funcslow2
|
||||
0.10s call test_some_are_slow.py::test_funcslow1
|
||||
0.00s setup test_some_are_slow.py::test_funcslow2
|
||||
0.00s teardown test_some_are_slow.py::test_funcslow2
|
||||
======= 3 passed in 0.12 seconds ========
|
||||
|
||||
incremental testing - test steps
|
||||
@@ -391,11 +394,14 @@ If we run this::
|
||||
|
||||
$ py.test -rx
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 4 items
|
||||
|
||||
test_step.py .Fx.
|
||||
======= short test summary info ========
|
||||
XFAIL test_step.py::TestUserHandling::()::test_deletion
|
||||
reason: previous test failed (test_modification)
|
||||
|
||||
======= FAILURES ========
|
||||
_______ TestUserHandling.test_modification ________
|
||||
@@ -407,9 +413,6 @@ If we run this::
|
||||
E assert 0
|
||||
|
||||
test_step.py:9: AssertionError
|
||||
======= short test summary info ========
|
||||
XFAIL test_step.py::TestUserHandling::()::test_deletion
|
||||
reason: previous test failed (test_modification)
|
||||
======= 1 failed, 2 passed, 1 xfailed in 0.12 seconds ========
|
||||
|
||||
We'll see that ``test_deletion`` was not executed because ``test_modification``
|
||||
@@ -424,7 +427,7 @@ by placing fixture functions in a ``conftest.py`` file in that directory
|
||||
You can use all types of fixtures including :ref:`autouse fixtures
|
||||
<autouse fixtures>` which are the equivalent of xUnit's setup/teardown
|
||||
concept. It's however recommended to have explicit fixture references in your
|
||||
tests or test classes rather than relying on implicitely executing
|
||||
tests or test classes rather than relying on implicitly executing
|
||||
setup/teardown functions, especially if they are far away from the actual tests.
|
||||
|
||||
Here is a an example for making a ``db`` fixture available in a directory::
|
||||
@@ -462,7 +465,7 @@ We can run this::
|
||||
|
||||
$ py.test
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 7 items
|
||||
|
||||
@@ -476,7 +479,7 @@ We can run this::
|
||||
file $REGENDOC_TMPDIR/b/test_error.py, line 1
|
||||
def test_root(db): # no db here, will error out
|
||||
fixture 'db' not found
|
||||
available fixtures: tmpdir, pytestconfig, record_xml_property, monkeypatch, recwarn, tmpdir_factory, capsys, capfd, cache
|
||||
available fixtures: tmpdir_factory, cache, tmpdir, pytestconfig, recwarn, monkeypatch, capfd, record_xml_property, capsys
|
||||
use 'py.test --fixtures [testpath]' for help on them.
|
||||
|
||||
$REGENDOC_TMPDIR/b/test_error.py:1
|
||||
@@ -566,7 +569,7 @@ and run them::
|
||||
|
||||
$ py.test test_module.py
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 2 items
|
||||
|
||||
@@ -657,7 +660,7 @@ and run it::
|
||||
|
||||
$ py.test -s test_module.py
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 3 items
|
||||
|
||||
@@ -700,7 +703,7 @@ Integrating pytest runner and cx_freeze
|
||||
-----------------------------------------------------------
|
||||
|
||||
If you freeze your application using a tool like
|
||||
`cx_freeze <http://cx-freeze.readthedocs.org>`_ in order to distribute it
|
||||
`cx_freeze <https://cx-freeze.readthedocs.io>`_ in order to distribute it
|
||||
to your end-users, it is a good idea to also package your test runner and run
|
||||
your tests using the frozen application.
|
||||
|
||||
@@ -745,4 +748,4 @@ over to ``pytest`` instead. For example::
|
||||
This makes it convenient to execute your tests from within your frozen
|
||||
application, using standard ``py.test`` command-line options::
|
||||
|
||||
./app_main --pytest --verbose --tb=long --junit-xml=results.xml test-suite/
|
||||
./app_main --pytest --verbose --tb=long --junitxml=results.xml test-suite/
|
||||
|
||||
@@ -60,15 +60,14 @@ and customizable testing framework for Python. Note, however, that
|
||||
thus likely not something for Python beginners.
|
||||
|
||||
A second "magic" issue was the assert statement debugging feature.
|
||||
Nowadays, ``pytest`` explicitely rewrites assert statements in test modules
|
||||
Nowadays, ``pytest`` explicitly rewrites assert statements in test modules
|
||||
in order to provide more useful :ref:`assert feedback <assertfeedback>`.
|
||||
This completely avoids previous issues of confusing assertion-reporting.
|
||||
It also means, that you can use Python's ``-O`` optimization without losing
|
||||
assertions in test modules.
|
||||
|
||||
``pytest`` contains a second, mostly obsolete, assert debugging technique,
|
||||
invoked via ``--assert=reinterpret``, activated by default on
|
||||
Python-2.5: When an ``assert`` statement fails, ``pytest`` re-interprets
|
||||
``pytest`` contains a second, mostly obsolete, assert debugging technique
|
||||
invoked via ``--assert=reinterpret``: When an ``assert`` statement fails, ``pytest`` re-interprets
|
||||
the expression part to show intermediate values. This technique suffers
|
||||
from a caveat that the rewriting does not: If your expression has side
|
||||
effects (better to avoid them anyway!) the intermediate values may not
|
||||
@@ -76,7 +75,7 @@ be the same, confusing the reinterpreter and obfuscating the initial
|
||||
error (this is also explained at the command line if it happens).
|
||||
|
||||
You can also turn off all assertion interaction using the
|
||||
``--assertmode=off`` option.
|
||||
``--assert=plain`` option.
|
||||
|
||||
.. _`py namespaces`: index.html
|
||||
.. _`py/__init__.py`: http://bitbucket.org/hpk42/py-trunk/src/trunk/py/__init__.py
|
||||
@@ -121,7 +120,7 @@ in a managed class/module/function scope.
|
||||
|
||||
.. _`Convention over Configuration`: http://en.wikipedia.org/wiki/Convention_over_Configuration
|
||||
|
||||
Can I yield multiple values from a fixture function function?
|
||||
Can I yield multiple values from a fixture function?
|
||||
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
There are two conceptual reasons why yielding from a factory function
|
||||
@@ -141,10 +140,10 @@ However, with pytest-2.3 you can use the :ref:`@pytest.fixture` decorator
|
||||
and specify ``params`` so that all tests depending on the factory-created
|
||||
resource will run multiple times with different parameters.
|
||||
|
||||
You can also use the `pytest_generate_tests`_ hook to
|
||||
implement the `parametrization scheme of your choice`_.
|
||||
You can also use the ``pytest_generate_tests`` hook to
|
||||
implement the `parametrization scheme of your choice`_. See also
|
||||
:ref:`paramexamples` for more examples.
|
||||
|
||||
.. _`pytest_generate_tests`: test/funcargs.html#parametrizing-tests
|
||||
.. _`parametrization scheme of your choice`: http://tetamap.wordpress.com/2009/05/13/parametrizing-python-tests-generalized/
|
||||
|
||||
pytest interaction with other packages
|
||||
|
||||
@@ -36,9 +36,9 @@ style <unittest.TestCase>` or :ref:`nose based <nosestyle>` projects.
|
||||
|
||||
.. note::
|
||||
|
||||
pytest-2.4 introduced an additional experimental
|
||||
:ref:`yield fixture mechanism <yieldfixture>` for easier context manager
|
||||
integration and more linear writing of teardown code.
|
||||
pytest-2.4 introduced an additional :ref:`yield fixture mechanism
|
||||
<yieldfixture>` for easier context manager integration and more linear
|
||||
writing of teardown code.
|
||||
|
||||
.. _`funcargs`:
|
||||
.. _`funcarg mechanism`:
|
||||
@@ -75,7 +75,7 @@ marked ``smtp`` fixture function. Running the test looks like this::
|
||||
|
||||
$ py.test test_smtpsimple.py
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 items
|
||||
|
||||
@@ -193,7 +193,7 @@ inspect what is going on and can now run the tests::
|
||||
|
||||
$ py.test test_module.py
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 2 items
|
||||
|
||||
@@ -283,6 +283,14 @@ module itself does not need to change or know about these details
|
||||
of fixture setup.
|
||||
|
||||
|
||||
Finalization/teardown with yield fixtures
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Another alternative to the *request.addfinalizer()* method is to use *yield
|
||||
fixtures*. All the code after the *yield* statement serves as the teardown
|
||||
code. See the :ref:`yield fixture documentation <yieldfixture>`.
|
||||
|
||||
|
||||
.. _`request-context`:
|
||||
|
||||
Fixtures can introspect the requesting test context
|
||||
@@ -480,7 +488,7 @@ Running the above tests results in the following test IDs being used::
|
||||
|
||||
$ py.test --collect-only
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 10 items
|
||||
<Module 'test_anothersmtp.py'>
|
||||
@@ -497,7 +505,7 @@ Running the above tests results in the following test IDs being used::
|
||||
<Function 'test_ehlo[mail.python.org]'>
|
||||
<Function 'test_noop[mail.python.org]'>
|
||||
|
||||
======= in 0.12 seconds ========
|
||||
======= no tests ran in 0.12 seconds ========
|
||||
|
||||
.. _`interdependent fixtures`:
|
||||
|
||||
@@ -531,7 +539,7 @@ Here we declare an ``app`` fixture which receives the previously defined
|
||||
|
||||
$ py.test -v test_appsetup.py
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.5
|
||||
cachedir: .cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 2 items
|
||||
@@ -577,55 +585,84 @@ to show the setup/teardown flow::
|
||||
@pytest.fixture(scope="module", params=["mod1", "mod2"])
|
||||
def modarg(request):
|
||||
param = request.param
|
||||
print ("create", param)
|
||||
print (" SETUP modarg %s" % param)
|
||||
def fin():
|
||||
print ("fin %s" % param)
|
||||
print (" TEARDOWN modarg %s" % param)
|
||||
request.addfinalizer(fin)
|
||||
return param
|
||||
|
||||
@pytest.fixture(scope="function", params=[1,2])
|
||||
def otherarg(request):
|
||||
return request.param
|
||||
param = request.param
|
||||
print (" SETUP otherarg %s" % param)
|
||||
def fin():
|
||||
print (" TEARDOWN otherarg %s" % param)
|
||||
request.addfinalizer(fin)
|
||||
return param
|
||||
|
||||
def test_0(otherarg):
|
||||
print (" test0", otherarg)
|
||||
print (" RUN test0 with otherarg %s" % otherarg)
|
||||
def test_1(modarg):
|
||||
print (" test1", modarg)
|
||||
print (" RUN test1 with modarg %s" % modarg)
|
||||
def test_2(otherarg, modarg):
|
||||
print (" test2", 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::
|
||||
|
||||
$ py.test -v -s test_module.py
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.5
|
||||
cachedir: .cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 8 items
|
||||
|
||||
test_module.py::test_0[1] test0 1
|
||||
test_module.py::test_0[1] SETUP otherarg 1
|
||||
RUN test0 with otherarg 1
|
||||
PASSED TEARDOWN otherarg 1
|
||||
|
||||
test_module.py::test_0[2] SETUP otherarg 2
|
||||
RUN test0 with otherarg 2
|
||||
PASSED TEARDOWN otherarg 2
|
||||
|
||||
test_module.py::test_1[mod1] SETUP modarg mod1
|
||||
RUN test1 with modarg mod1
|
||||
PASSED
|
||||
test_module.py::test_0[2] test0 2
|
||||
PASSED
|
||||
test_module.py::test_1[mod1] create mod1
|
||||
test1 mod1
|
||||
PASSED
|
||||
test_module.py::test_2[1-mod1] test2 1 mod1
|
||||
PASSED
|
||||
test_module.py::test_2[2-mod1] test2 2 mod1
|
||||
PASSED
|
||||
test_module.py::test_1[mod2] create mod2
|
||||
test1 mod2
|
||||
PASSED
|
||||
test_module.py::test_2[1-mod2] test2 1 mod2
|
||||
PASSED
|
||||
test_module.py::test_2[2-mod2] test2 2 mod2
|
||||
test_module.py::test_2[1-mod1] SETUP otherarg 1
|
||||
RUN test2 with otherarg 1 and modarg mod1
|
||||
PASSED TEARDOWN otherarg 1
|
||||
|
||||
test_module.py::test_2[2-mod1] SETUP otherarg 2
|
||||
RUN test2 with otherarg 2 and modarg mod1
|
||||
PASSED TEARDOWN otherarg 2
|
||||
|
||||
test_module.py::test_1[mod2] TEARDOWN modarg mod1
|
||||
SETUP modarg mod2
|
||||
RUN test1 with modarg mod2
|
||||
PASSED
|
||||
test_module.py::test_2[1-mod2] SETUP otherarg 1
|
||||
RUN test2 with otherarg 1 and modarg mod2
|
||||
PASSED TEARDOWN otherarg 1
|
||||
|
||||
test_module.py::test_2[2-mod2] SETUP otherarg 2
|
||||
RUN test2 with otherarg 2 and modarg mod2
|
||||
PASSED TEARDOWN otherarg 2
|
||||
TEARDOWN modarg mod2
|
||||
|
||||
|
||||
======= 8 passed in 0.12 seconds ========
|
||||
|
||||
You can see that the parametrized module-scoped ``modarg`` resource caused
|
||||
an ordering of test execution that lead to the fewest possible "active" resources. The finalizer for the ``mod1`` parametrized resource was executed
|
||||
before the ``mod2`` resource was setup.
|
||||
You can see that the parametrized module-scoped ``modarg`` resource caused an
|
||||
ordering of test execution that lead to the fewest possible "active" resources.
|
||||
The finalizer for the ``mod1`` parametrized resource was executed before the
|
||||
``mod2`` resource was setup.
|
||||
|
||||
In particular notice that test_0 is completely independent and finishes first.
|
||||
Then test_1 is executed with ``mod1``, then test_2 with ``mod1``, then test_1
|
||||
with ``mod2`` and finally test_2 with ``mod2``.
|
||||
|
||||
The ``otherarg`` parametrized resource (having function scope) was set up before
|
||||
and teared down after every test that used it.
|
||||
|
||||
|
||||
.. _`usefixtures`:
|
||||
|
||||
@@ -209,7 +209,7 @@ fixtures:
|
||||
and let pytest figure things out for you.
|
||||
|
||||
* if you used parametrization and funcarg factories which made use of
|
||||
``request.cached_setup()`` it is recommeneded to invest a few minutes
|
||||
``request.cached_setup()`` it is recommended to invest a few minutes
|
||||
and simplify your fixture function code to use the :ref:`@pytest.fixture`
|
||||
decorator instead. This will also allow to take advantage of
|
||||
the automatic per-resource grouping of tests.
|
||||
|
||||
@@ -27,7 +27,7 @@ Installation options::
|
||||
To check your installation has installed the correct version::
|
||||
|
||||
$ py.test --version
|
||||
This is pytest version 2.8.1, imported from $PYTHON_PREFIX/lib/python3.4/site-packages/pytest.py
|
||||
This is pytest version 2.9.2, imported from $PYTHON_PREFIX/lib/python3.5/site-packages/pytest.py
|
||||
|
||||
If you get an error checkout :ref:`installation issues`.
|
||||
|
||||
@@ -49,7 +49,7 @@ That's it. You can execute the test function now::
|
||||
|
||||
$ py.test
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 items
|
||||
|
||||
@@ -106,8 +106,6 @@ Running it with, this time in "quiet" reporting mode::
|
||||
.
|
||||
1 passed in 0.12 seconds
|
||||
|
||||
.. todo:: For further ways to assert exceptions see the `raises`
|
||||
|
||||
Grouping multiple tests in a class
|
||||
--------------------------------------------------------------
|
||||
|
||||
@@ -195,7 +193,7 @@ Where to go next
|
||||
Here are a few suggestions where to go next:
|
||||
|
||||
* :ref:`cmdline` for command line invocation examples
|
||||
* :ref:`good practises <goodpractises>` for virtualenv, test layout, genscript support
|
||||
* :ref:`good practices <goodpractices>` for virtualenv, test layout, genscript support
|
||||
* :ref:`fixtures` for providing a functional baseline to your tests
|
||||
* :ref:`apiref` for documentation and examples on using ``pytest``
|
||||
* :ref:`plugins` managing and writing plugins
|
||||
|
||||
@@ -1,32 +1,31 @@
|
||||
.. highlightlang:: python
|
||||
.. _`goodpractises`:
|
||||
.. _`goodpractices`:
|
||||
|
||||
Good Integration Practices
|
||||
=================================================
|
||||
|
||||
Work with virtual environments
|
||||
-----------------------------------------------------------
|
||||
|
||||
We recommend to use virtualenv_ environments and use pip_
|
||||
(or easy_install_) for installing your application and any dependencies
|
||||
as well as the ``pytest`` package itself. This way you will get an isolated
|
||||
and reproducible environment. Given you have installed virtualenv_
|
||||
and execute it from the command line, here is an example session for unix
|
||||
or windows::
|
||||
.. _`test discovery`:
|
||||
.. _`Python test discovery`:
|
||||
|
||||
virtualenv . # create a virtualenv directory in the current directory
|
||||
Conventions for Python test discovery
|
||||
-------------------------------------------------
|
||||
|
||||
source bin/activate # on unix
|
||||
``pytest`` implements the following standard test discovery:
|
||||
|
||||
scripts/activate # on Windows
|
||||
* If no arguments are specified then collection starts from :confval:`testpaths`
|
||||
(if configured) or the current directory. Alternatively, command line arguments
|
||||
can be used in any combination of directories, file names or node ids.
|
||||
* recurse into directories, unless they match :confval:`norecursedirs`
|
||||
* ``test_*.py`` or ``*_test.py`` files, imported by their `test package name`_.
|
||||
* ``Test`` prefixed test classes (without an ``__init__`` method)
|
||||
* ``test_`` prefixed test functions or methods are test items
|
||||
|
||||
We can now install pytest::
|
||||
For examples of how to customize your test discovery :doc:`example/pythoncollection`.
|
||||
|
||||
pip install pytest
|
||||
Within Python modules, ``pytest`` also discovers tests using the standard
|
||||
:ref:`unittest.TestCase <unittest.TestCase>` subclassing technique.
|
||||
|
||||
Due to the ``activate`` step above the ``pip`` will come from
|
||||
the virtualenv directory and install any package into the isolated
|
||||
virtual environment.
|
||||
|
||||
Choosing a test layout / import rules
|
||||
------------------------------------------
|
||||
@@ -135,8 +134,13 @@ required configurations.
|
||||
|
||||
.. _`use tox`:
|
||||
|
||||
Use tox and Continuous Integration servers
|
||||
-------------------------------------------------
|
||||
Tox
|
||||
------
|
||||
|
||||
For development, we recommend to use virtualenv_ environments and pip_
|
||||
for installing your application and any dependencies
|
||||
as well as the ``pytest`` package itself. This ensures your code and
|
||||
dependencies are isolated from the system Python installation.
|
||||
|
||||
If you frequently release code and want to make sure that your actual
|
||||
package passes all tests you may want to look into `tox`_, the
|
||||
@@ -148,89 +152,58 @@ options. It will run tests against the installed package and not
|
||||
against your source code checkout, helping to detect packaging
|
||||
glitches.
|
||||
|
||||
If you want to use Jenkins_ you can use the ``--junitxml=PATH`` option
|
||||
to create a JUnitXML file that Jenkins_ can pick up and generate reports.
|
||||
|
||||
.. _standalone:
|
||||
.. _`genscript method`:
|
||||
|
||||
(deprecated) Create a pytest standalone script
|
||||
-----------------------------------------------
|
||||
|
||||
If you are a maintainer or application developer and want people
|
||||
who don't deal with python much to easily run tests you may generate
|
||||
a standalone ``pytest`` script::
|
||||
|
||||
py.test --genscript=runtests.py
|
||||
|
||||
This generates a ``runtests.py`` script which is a fully functional basic
|
||||
``pytest`` script, running unchanged under Python2 and Python3.
|
||||
You can tell people to download the script and then e.g. run it like this::
|
||||
|
||||
python runtests.py
|
||||
|
||||
.. note::
|
||||
|
||||
You must have pytest and its dependencies installed as an sdist, not
|
||||
as wheels because genscript need the source code for generating a
|
||||
standalone script.
|
||||
Continuous integration services such as Jenkins_ can make use of the
|
||||
``--junitxml=PATH`` option to create a JUnitXML file and generate reports (e.g.
|
||||
by publishing the results in a nice format with the `Jenkins xUnit Plugin
|
||||
<https://wiki.jenkins-ci.org/display/JENKINS/xUnit+Plugin>`_).
|
||||
|
||||
|
||||
Integrating with setuptools / ``python setup.py test`` / ``pytest-runner``
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
You can integrate test runs into your setuptools based project
|
||||
with the `pytest-runner <https://pypi.python.org/pypi/pytest-runner>`_ plugin.
|
||||
|
||||
Integrating with setuptools / ``python setup.py test``
|
||||
------------------------------------------------------
|
||||
Add this to ``setup.py`` file:
|
||||
|
||||
You can integrate test runs into your
|
||||
setuptools based project. Use the `genscript method`_
|
||||
to generate a standalone ``pytest`` script::
|
||||
|
||||
py.test --genscript=runtests.py
|
||||
|
||||
and make this script part of your distribution and then add
|
||||
this to your ``setup.py`` file::
|
||||
|
||||
from distutils.core import setup, Command
|
||||
# you can also import from setuptools
|
||||
|
||||
class PyTest(Command):
|
||||
user_options = []
|
||||
def initialize_options(self):
|
||||
pass
|
||||
|
||||
def finalize_options(self):
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
import subprocess
|
||||
import sys
|
||||
errno = subprocess.call([sys.executable, 'runtests.py'])
|
||||
raise SystemExit(errno)
|
||||
.. code-block:: python
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
#...,
|
||||
cmdclass = {'test': PyTest},
|
||||
setup_requires=['pytest-runner', ...],
|
||||
tests_require=['pytest', ...],
|
||||
#...,
|
||||
)
|
||||
|
||||
|
||||
And create an alias into ``setup.cfg`` file:
|
||||
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[aliases]
|
||||
test=pytest
|
||||
|
||||
If you now type::
|
||||
|
||||
python setup.py test
|
||||
|
||||
this will execute your tests using ``runtests.py``. As this is a
|
||||
this will execute your tests using ``pytest-runner``. As this is a
|
||||
standalone version of ``pytest`` no prior installation whatsoever is
|
||||
required for calling the test command. You can also pass additional
|
||||
arguments to the subprocess-calls such as your test directory or other
|
||||
options.
|
||||
arguments to py.test such as your test directory or other
|
||||
options using ``--addopts``.
|
||||
|
||||
|
||||
Integration with setuptools test commands
|
||||
----------------------------------------------------
|
||||
Manual Integration
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Setuptools supports writing our own Test command for invoking pytest.
|
||||
Most often it is better to use tox_ instead, but here is how you can
|
||||
get started with setuptools integration::
|
||||
If for some reason you don't want/can't use ``pytest-runner``, you can write
|
||||
your own setuptools Test command for invoking pytest.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import sys
|
||||
|
||||
@@ -244,11 +217,6 @@ get started with setuptools integration::
|
||||
TestCommand.initialize_options(self)
|
||||
self.pytest_args = []
|
||||
|
||||
def finalize_options(self):
|
||||
TestCommand.finalize_options(self)
|
||||
self.test_args = []
|
||||
self.test_suite = True
|
||||
|
||||
def run_tests(self):
|
||||
#import here, cause outside the eggs aren't loaded
|
||||
import pytest
|
||||
@@ -274,32 +242,39 @@ using the ``--pytest-args`` or ``-a`` command-line option. For example::
|
||||
|
||||
is equivalent to running ``py.test --durations=5``.
|
||||
|
||||
.. seealso::
|
||||
|
||||
For a more powerful solution, take a look at the
|
||||
`pytest-runner <https://pypi.python.org/pypi/pytest-runner>`_ plugin.
|
||||
.. _standalone:
|
||||
.. _`genscript method`:
|
||||
|
||||
.. _`test discovery`:
|
||||
.. _`Python test discovery`:
|
||||
(deprecated) Create a pytest standalone script
|
||||
-----------------------------------------------
|
||||
|
||||
Conventions for Python test discovery
|
||||
-------------------------------------------------
|
||||
.. deprecated:: 2.8
|
||||
|
||||
``pytest`` implements the following standard test discovery:
|
||||
.. note::
|
||||
|
||||
* collection starts from paths specified in :confval:`testpaths` if configured,
|
||||
otherwise from initial command line arguments which may be directories,
|
||||
filenames or test ids. If :confval:`testpaths` is not configured and no
|
||||
directories or files were given in the command line, start collection from
|
||||
the current directory.
|
||||
* recurse into directories, unless they match :confval:`norecursedirs`
|
||||
* ``test_*.py`` or ``*_test.py`` files, imported by their `test package name`_.
|
||||
* ``Test`` prefixed test classes (without an ``__init__`` method)
|
||||
* ``test_`` prefixed test functions or methods are test items
|
||||
``genscript`` has been deprecated because:
|
||||
|
||||
For examples of how to customize your test discovery :doc:`example/pythoncollection`.
|
||||
* It cannot support plugins, rendering its usefulness extremely limited;
|
||||
* Tooling has become much better since ``genscript`` was introduced;
|
||||
* It is possible to build a zipped ``pytest`` application without the
|
||||
shortcomings above.
|
||||
|
||||
There's no planned version in which this command will be removed
|
||||
at the moment of this writing, but its use is discouraged for new
|
||||
applications.
|
||||
|
||||
If you are a maintainer or application developer and want people
|
||||
who don't deal with python much to easily run tests you may generate
|
||||
a standalone ``pytest`` script::
|
||||
|
||||
py.test --genscript=runtests.py
|
||||
|
||||
This generates a ``runtests.py`` script which is a fully functional basic
|
||||
``pytest`` script, running unchanged under Python2 and Python3.
|
||||
You can tell people to download the script and then e.g. run it like this::
|
||||
|
||||
python runtests.py
|
||||
|
||||
Within Python modules, ``pytest`` also discovers tests using the standard
|
||||
:ref:`unittest.TestCase <unittest.TestCase>` subclassing technique.
|
||||
|
||||
.. include:: links.inc
|
||||
BIN
doc/en/img/freiburg2.jpg
Normal file
BIN
doc/en/img/freiburg2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 102 KiB |
@@ -7,6 +7,7 @@ pytest: helps you write better programs
|
||||
**a mature full-featured Python testing tool**
|
||||
|
||||
- runs on Posix/Windows, Python 2.6-3.5, PyPy and (possibly still) Jython-2.5.1
|
||||
- free and open source software, distributed under the terms of the :ref:`MIT license <license>`
|
||||
- **well tested** with more than a thousand tests against itself
|
||||
- **strict backward compatibility policy** for safe pytest upgrades
|
||||
- :ref:`comprehensive online <toc>` and `PDF documentation <pytest.pdf>`_
|
||||
@@ -40,7 +41,7 @@ pytest: helps you write better programs
|
||||
- multi-paradigm: pytest can run ``nose``, ``unittest`` and
|
||||
``doctest`` style test suites, including running testcases made for
|
||||
Django and trial
|
||||
- supports :ref:`good integration practises <goodpractises>`
|
||||
- supports :ref:`good integration practices <goodpractices>`
|
||||
- supports extended :ref:`xUnit style setup <xunitsetup>`
|
||||
- supports domain-specific :ref:`non-python tests`
|
||||
- supports generating `test coverage reports
|
||||
|
||||
32
doc/en/license.rst
Normal file
32
doc/en/license.rst
Normal file
@@ -0,0 +1,32 @@
|
||||
.. _license:
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
Distributed under the terms of the `MIT`_ license, pytest is free and open source software.
|
||||
|
||||
::
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2004-2016 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
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
.. _`MIT`: https://github.com/pytest-dev/pytest/blob/master/LICENSE
|
||||
@@ -6,7 +6,7 @@
|
||||
.. _`pytest_nose`: plugin/nose.html
|
||||
.. _`reStructured Text`: http://docutils.sourceforge.net
|
||||
.. _`Python debugger`: http://docs.python.org/lib/module-pdb.html
|
||||
.. _nose: https://nose.readthedocs.org/en/latest/
|
||||
.. _nose: https://nose.readthedocs.io/en/latest/
|
||||
.. _pytest: http://pypi.python.org/pypi/pytest
|
||||
.. _mercurial: http://mercurial.selenic.com/wiki/
|
||||
.. _`setuptools`: http://pypi.python.org/pypi/setuptools
|
||||
@@ -18,4 +18,4 @@
|
||||
.. _hudson: http://hudson-ci.org/
|
||||
.. _jenkins: http://jenkins-ci.org/
|
||||
.. _tox: http://testrun.org/tox
|
||||
.. _pylib: http://py.readthedocs.org/en/latest/
|
||||
.. _pylib: https://py.readthedocs.io/en/latest/
|
||||
|
||||
@@ -46,7 +46,7 @@ Unsupported idioms / known issues
|
||||
(e.g. ``tests/test_mode.py`` and ``other/tests/test_mode.py``)
|
||||
by extending sys.path/import semantics. pytest does not do that
|
||||
but there is discussion in `issue268 <https://github.com/pytest-dev/pytest/issues/268>`_ for adding some support. Note that
|
||||
`nose2 choose to avoid this sys.path/import hackery <https://nose2.readthedocs.org/en/latest/differences.html#test-discovery-and-loading>`_.
|
||||
`nose2 choose to avoid this sys.path/import hackery <https://nose2.readthedocs.io/en/latest/differences.html#test-discovery-and-loading>`_.
|
||||
|
||||
- nose-style doctests are not collected and executed correctly,
|
||||
also doctest fixtures don't work.
|
||||
|
||||
@@ -5,10 +5,9 @@ Getting started basics
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
index
|
||||
getting-started
|
||||
usage
|
||||
goodpractises
|
||||
goodpractices
|
||||
projects
|
||||
faq
|
||||
|
||||
|
||||
@@ -41,21 +41,21 @@ to an expected output::
|
||||
|
||||
# content of test_expectation.py
|
||||
import pytest
|
||||
@pytest.mark.parametrize("input,expected", [
|
||||
@pytest.mark.parametrize("test_input,expected", [
|
||||
("3+5", 8),
|
||||
("2+4", 6),
|
||||
("6*9", 42),
|
||||
])
|
||||
def test_eval(input, expected):
|
||||
assert eval(input) == expected
|
||||
def test_eval(test_input, expected):
|
||||
assert eval(test_input) == expected
|
||||
|
||||
Here, the ``@parametrize`` decorator defines three different ``(input,expected)``
|
||||
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::
|
||||
|
||||
$ py.test
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 3 items
|
||||
|
||||
@@ -64,15 +64,15 @@ them in turn::
|
||||
======= FAILURES ========
|
||||
_______ test_eval[6*9-42] ________
|
||||
|
||||
input = '6*9', expected = 42
|
||||
test_input = '6*9', expected = 42
|
||||
|
||||
@pytest.mark.parametrize("input,expected", [
|
||||
@pytest.mark.parametrize("test_input,expected", [
|
||||
("3+5", 8),
|
||||
("2+4", 6),
|
||||
("6*9", 42),
|
||||
])
|
||||
def test_eval(input, expected):
|
||||
> assert eval(input) == expected
|
||||
def test_eval(test_input, expected):
|
||||
> assert eval(test_input) == expected
|
||||
E assert 54 == 42
|
||||
E + where 54 = eval('6*9')
|
||||
|
||||
@@ -91,19 +91,19 @@ for example with the builtin ``mark.xfail``::
|
||||
|
||||
# content of test_expectation.py
|
||||
import pytest
|
||||
@pytest.mark.parametrize("input,expected", [
|
||||
@pytest.mark.parametrize("test_input,expected", [
|
||||
("3+5", 8),
|
||||
("2+4", 6),
|
||||
pytest.mark.xfail(("6*9", 42)),
|
||||
])
|
||||
def test_eval(input, expected):
|
||||
assert eval(input) == expected
|
||||
def test_eval(test_input, expected):
|
||||
assert eval(test_input) == expected
|
||||
|
||||
Let's run this::
|
||||
|
||||
$ py.test
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 3 items
|
||||
|
||||
@@ -142,7 +142,7 @@ Sometimes you may want to implement your own parametrization scheme
|
||||
or implement some dynamism for determining the parameters or scope
|
||||
of a fixture. For this, you can use the ``pytest_generate_tests`` hook
|
||||
which is called when collecting a test function. Through the passed in
|
||||
`metafunc` object you can inspect the requesting test context and, most
|
||||
``metafunc`` object you can inspect the requesting test context and, most
|
||||
importantly, you can call ``metafunc.parametrize()`` to cause
|
||||
parametrization.
|
||||
|
||||
@@ -196,12 +196,12 @@ 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
|
||||
listlist::
|
||||
list::
|
||||
|
||||
$ py.test -q -rs test_strings.py
|
||||
s
|
||||
======= short test summary info ========
|
||||
SKIP [1] $PYTHON_PREFIX/lib/python3.4/site-packages/_pytest/python.py:1364: got empty parameter set, function test_valid_string at $REGENDOC_TMPDIR/test_strings.py:1
|
||||
SKIP [1] test_strings.py:1: got empty parameter set ['stringinput'], function test_valid_string at $REGENDOC_TMPDIR/test_strings.py:1
|
||||
1 skipped in 0.12 seconds
|
||||
|
||||
For further examples, you might want to look at :ref:`more
|
||||
|
||||
@@ -14,10 +14,9 @@ Installing a third party plugin can be easily done with ``pip``::
|
||||
pip uninstall pytest-NAME
|
||||
|
||||
If a plugin is installed, ``pytest`` automatically finds and integrates it,
|
||||
there is no need to activate it. We have a :doc:`page listing
|
||||
all 3rd party plugins and their status against the latest py.test version
|
||||
<plugins_index/index>` and here is a little annotated list
|
||||
for some popular plugins:
|
||||
there is no need to activate it.
|
||||
|
||||
Here is a little annotated list for some popular plugins:
|
||||
|
||||
.. _`django`: https://www.djangoproject.com/
|
||||
|
||||
@@ -28,7 +27,7 @@ for some popular plugins:
|
||||
for `twisted <http://twistedmatrix.com>`_ apps, starting a reactor and
|
||||
processing deferreds from test functions.
|
||||
|
||||
* `pytest-capturelog <http://pypi.python.org/pypi/pytest-capturelog>`_:
|
||||
* `pytest-catchlog <http://pypi.python.org/pypi/pytest-catchlog>`_:
|
||||
to capture and assert about messages from the logging module
|
||||
|
||||
* `pytest-cov <http://pypi.python.org/pypi/pytest-cov>`_:
|
||||
@@ -50,15 +49,14 @@ for some popular plugins:
|
||||
* `pytest-timeout <http://pypi.python.org/pypi/pytest-timeout>`_:
|
||||
to timeout tests based on function marks or global definitions.
|
||||
|
||||
* `pytest-cache <http://pypi.python.org/pypi/pytest-cache>`_:
|
||||
to interactively re-run failing tests and help other plugins to
|
||||
store test run information across invocations.
|
||||
|
||||
* `pytest-pep8 <http://pypi.python.org/pypi/pytest-pep8>`_:
|
||||
a ``--pep8`` option to enable PEP8 compliance checking.
|
||||
|
||||
* `pytest-flakes <https://pypi.python.org/pypi/pytest-flakes>`_:
|
||||
check source code with pyflakes.
|
||||
|
||||
* `oejskit <http://pypi.python.org/pypi/oejskit>`_:
|
||||
a plugin to run javascript unittests in life browsers
|
||||
a plugin to run javascript unittests in live browsers.
|
||||
|
||||
To see a complete list of all plugins with their latest testing
|
||||
status against different py.test and Python versions, please visit
|
||||
@@ -92,7 +90,7 @@ Finding out which plugins are active
|
||||
If you want to find out which plugins are active in your
|
||||
environment you can type::
|
||||
|
||||
py.test --traceconfig
|
||||
py.test --trace-config
|
||||
|
||||
and will get an extended test header which shows activated plugins
|
||||
and their names. It will also print local plugins aka
|
||||
@@ -108,8 +106,21 @@ You can prevent plugins from loading or unregister them::
|
||||
py.test -p no:NAME
|
||||
|
||||
This means that any subsequent try to activate/load the named
|
||||
plugin will it already existing. See :ref:`findpluginname` for
|
||||
how to obtain the name of a plugin.
|
||||
plugin will not work.
|
||||
|
||||
If you want to unconditionally disable a plugin for a project, you can add
|
||||
this option to your ``pytest.ini`` file:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[pytest]
|
||||
addopts = -p no:NAME
|
||||
|
||||
Alternatively to disable it only in certain environments (for example in a
|
||||
CI server), you can set ``PYTEST_ADDOPTS`` environment variable to
|
||||
``-p no:name``.
|
||||
|
||||
See :ref:`findpluginname` for how to obtain the name of a plugin.
|
||||
|
||||
.. _`builtin plugins`:
|
||||
|
||||
@@ -123,6 +134,7 @@ in the `pytest repository <https://github.com/pytest-dev/pytest>`_.
|
||||
.. autosummary::
|
||||
|
||||
_pytest.assertion
|
||||
_pytest.cacheprovider
|
||||
_pytest.capture
|
||||
_pytest.config
|
||||
_pytest.doctest
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 679 B |
Binary file not shown.
|
Before Width: | Height: | Size: 734 B |
@@ -1,290 +0,0 @@
|
||||
.. _plugins_index:
|
||||
|
||||
List of Third-Party Plugins
|
||||
===========================
|
||||
|
||||
The table below contains a listing of plugins found in PyPI and
|
||||
their status when tested when using latest py.test and python versions.
|
||||
|
||||
A complete listing can also be found at
|
||||
`plugincompat <http://plugincompat.herokuapp.com/>`_, which contains tests
|
||||
status against other py.test releases.
|
||||
|
||||
|
||||
============================================================================================ ===================================================================================================================== ===================================================================================================================== =========================================================================== =============================================================================================================================================
|
||||
Name Py27 Py34 Home Summary
|
||||
============================================================================================ ===================================================================================================================== ===================================================================================================================== =========================================================================== =============================================================================================================================================
|
||||
`pytest-allure-adaptor <http://pypi.python.org/pypi/pytest-allure-adaptor>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-allure-adaptor-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-allure-adaptor-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Plugin for py.test to generate allure xml reports
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-allure-adaptor-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-allure-adaptor-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/allure-framework/allure-python
|
||||
`pytest-ansible <http://pypi.python.org/pypi/pytest-ansible>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-ansible-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-ansible-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Plugin for py.test to allow running ansible
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-ansible-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-ansible-latest?py=py34&pytest=2.8.0.dev4 :target: http://github.com/jlaska/pytest-ansible
|
||||
`pytest-asyncio <http://pypi.python.org/pypi/pytest-asyncio>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-asyncio-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-asyncio-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Pytest support for asyncio.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-asyncio-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-asyncio-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/pytest-dev/pytest-asyncio
|
||||
`pytest-autochecklog <http://pypi.python.org/pypi/pytest-autochecklog>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-autochecklog-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-autochecklog-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png automatically check condition and log all the checks
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-autochecklog-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-autochecklog-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/steven004/python-autochecklog
|
||||
`pytest-bdd <http://pypi.python.org/pypi/pytest-bdd>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-bdd-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-bdd-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png BDD for pytest
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-bdd-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-bdd-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/pytest-dev/pytest-bdd
|
||||
`pytest-beakerlib <http://pypi.python.org/pypi/pytest-beakerlib>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-beakerlib-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-beakerlib-latest?py=py34&pytest=2.8.0.dev4 `link <https://fedorahosted.org/python-pytest-beakerlib/>`_ A pytest plugin that reports test results to the BeakerLib framework
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-beakerlib-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-beakerlib-latest?py=py34&pytest=2.8.0.dev4
|
||||
`pytest-beds <http://pypi.python.org/pypi/pytest-beds>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-beds-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-beds-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Fixtures for testing Google Appengine (GAE) apps
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-beds-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-beds-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/kaste/pytest-beds
|
||||
`pytest-bench <http://pypi.python.org/pypi/pytest-bench>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-bench-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-bench-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Benchmark utility that plugs into pytest.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-bench-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-bench-latest?py=py34&pytest=2.8.0.dev4 :target: http://github.com/concordusapps/pytest-bench
|
||||
`pytest-benchmark <http://pypi.python.org/pypi/pytest-benchmark>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-benchmark-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-benchmark-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png py.test fixture for benchmarking code
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-benchmark-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-benchmark-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/ionelmc/pytest-benchmark
|
||||
`pytest-blockage <http://pypi.python.org/pypi/pytest-blockage>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-blockage-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-blockage-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Disable network requests during a test run.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-blockage-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-blockage-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/rob-b/pytest-blockage
|
||||
`pytest-bpdb <http://pypi.python.org/pypi/pytest-bpdb>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-bpdb-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-bpdb-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png A py.test plug-in to enable drop to bpdb debugger on test failure.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-bpdb-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-bpdb-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/slafs/pytest-bpdb
|
||||
`pytest-browsermob-proxy <http://pypi.python.org/pypi/pytest-browsermob-proxy>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-browsermob-proxy-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-browsermob-proxy-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png BrowserMob proxy plugin for py.test.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-browsermob-proxy-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-browsermob-proxy-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/davehunt/pytest-browsermob-proxy
|
||||
`pytest-bugzilla <http://pypi.python.org/pypi/pytest-bugzilla>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-bugzilla-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-bugzilla-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png py.test bugzilla integration plugin
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-bugzilla-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-bugzilla-latest?py=py34&pytest=2.8.0.dev4 :target: http://github.com/nibrahim/pytest_bugzilla
|
||||
`pytest-marker-bugzilla <http://pypi.python.org/pypi/pytest-marker-bugzilla>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-marker-bugzilla-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-marker-bugzilla-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png py.test bugzilla integration plugin, using markers
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-marker-bugzilla-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-marker-bugzilla-latest?py=py34&pytest=2.8.0.dev4 :target: http://github.com/eanxgeek/pytest_marker_bugzilla
|
||||
`pytest-remove-stale-bytecode <http://pypi.python.org/pypi/pytest-remove-stale-bytecode>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-remove-stale-bytecode-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-remove-stale-bytecode-latest?py=py34&pytest=2.8.0.dev4 .. image:: bitbucket.png py.test plugin to remove stale byte code files.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-remove-stale-bytecode-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-remove-stale-bytecode-latest?py=py34&pytest=2.8.0.dev4 :target: https://bitbucket.org/gocept/pytest-remove-stale-bytecode/
|
||||
`pytest-cache <http://pypi.python.org/pypi/pytest-cache>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-cache-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-cache-latest?py=py34&pytest=2.8.0.dev4 .. image:: bitbucket.png pytest plugin with mechanisms for caching across test runs
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-cache-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-cache-latest?py=py34&pytest=2.8.0.dev4 :target: http://bitbucket.org/hpk42/pytest-cache/
|
||||
`pytest-cagoule <http://pypi.python.org/pypi/pytest-cagoule>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-cagoule-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-cagoule-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Pytest plugin to only run tests affected by changes
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-cagoule-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-cagoule-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/davidszotten/pytest-cagoule
|
||||
`pytest-capturelog <http://pypi.python.org/pypi/pytest-capturelog>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-capturelog-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-capturelog-latest?py=py34&pytest=2.8.0.dev4 .. image:: bitbucket.png py.test plugin to capture log messages
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-capturelog-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-capturelog-latest?py=py34&pytest=2.8.0.dev4 :target: http://bitbucket.org/memedough/pytest-capturelog/overview
|
||||
`pytest-django-casperjs <http://pypi.python.org/pypi/pytest-django-casperjs>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-django-casperjs-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-django-casperjs-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Integrate CasperJS with your django tests as a pytest fixture.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-django-casperjs-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-django-casperjs-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/EnTeQuAk/pytest-django-casperjs/
|
||||
`pytest-catchlog <http://pypi.python.org/pypi/pytest-catchlog>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-catchlog-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-catchlog-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png py.test plugin to catch log messages. This is a fork of pytest-capturelog.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-catchlog-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-catchlog-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/eisensheng/pytest-catchlog
|
||||
`pytest-circleci <http://pypi.python.org/pypi/pytest-circleci>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-circleci-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-circleci-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png py.test plugin for CircleCI
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-circleci-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-circleci-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/micktwomey/pytest-circleci
|
||||
`pytest-cloud <http://pypi.python.org/pypi/pytest-cloud>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-cloud-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-cloud-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Distributed tests planner plugin for pytest testing framework.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-cloud-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-cloud-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/pytest-dev/pytest-cloud
|
||||
`pytest-codecheckers <http://pypi.python.org/pypi/pytest-codecheckers>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-codecheckers-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-codecheckers-latest?py=py34&pytest=2.8.0.dev4 .. image:: bitbucket.png pytest plugin to add source code sanity checks (pep8 and friends)
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-codecheckers-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-codecheckers-latest?py=py34&pytest=2.8.0.dev4 :target: http://bitbucket.org/RonnyPfannschmidt/pytest-codecheckers/
|
||||
`pytest-colordots <http://pypi.python.org/pypi/pytest-colordots>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-colordots-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-colordots-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Colorizes the progress indicators
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-colordots-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-colordots-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/svenstaro/pytest-colordots
|
||||
`pytest-paste-config <http://pypi.python.org/pypi/pytest-paste-config>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-paste-config-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-paste-config-latest?py=py34&pytest=2.8.0.dev4 ? Allow setting the path to a paste config file
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-paste-config-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-paste-config-latest?py=py34&pytest=2.8.0.dev4
|
||||
`pytest-config <http://pypi.python.org/pypi/pytest-config>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-config-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-config-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Base configurations and utilities for developing your Python project test suite with pytest.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-config-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-config-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/buzzfeed/pytest_config
|
||||
`pytest-contextfixture <http://pypi.python.org/pypi/pytest-contextfixture>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-contextfixture-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-contextfixture-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Define pytest fixtures as context managers.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-contextfixture-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-contextfixture-latest?py=py34&pytest=2.8.0.dev4 :target: http://github.com/pelme/pytest-contextfixture/
|
||||
`pytest-couchdbkit <http://pypi.python.org/pypi/pytest-couchdbkit>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-couchdbkit-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-couchdbkit-latest?py=py34&pytest=2.8.0.dev4 .. image:: bitbucket.png py.test extension for per-test couchdb databases using couchdbkit
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-couchdbkit-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-couchdbkit-latest?py=py34&pytest=2.8.0.dev4 :target: http://bitbucket.org/RonnyPfannschmidt/pytest-couchdbkit
|
||||
`pytest-cov <http://pypi.python.org/pypi/pytest-cov>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-cov-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-cov-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png py.test plugin for coverage reporting with support for both centralised and distributed testing, including subprocesses and multiprocessing
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-cov-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-cov-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/schlamar/pytest-cov
|
||||
`pytest-cover <http://pypi.python.org/pypi/pytest-cover>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-cover-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-cover-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Pytest plugin for measuring coverage. Forked from `pytest-cov`.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-cover-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-cover-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/ionelmc/pytest-cover
|
||||
`pytest-coverage <http://pypi.python.org/pypi/pytest-coverage>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-coverage-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-coverage-latest?py=py34&pytest=2.8.0.dev4 `link <https://pypi.python.org/pypi/pytest-cover/>`_ Pytest plugin for measuring coverage. Forked from `pytest-cov`.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-coverage-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-coverage-latest?py=py34&pytest=2.8.0.dev4
|
||||
`pytest-cpp <http://pypi.python.org/pypi/pytest-cpp>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-cpp-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-cpp-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Use pytest's runner to discover and execute C++ tests
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-cpp-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-cpp-latest?py=py34&pytest=2.8.0.dev4 :target: http://github.com/pytest-dev/pytest-cpp
|
||||
`pytest-curl-report <http://pypi.python.org/pypi/pytest-curl-report>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-curl-report-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-curl-report-latest?py=py34&pytest=2.8.0.dev4 .. image:: bitbucket.png pytest plugin to generate curl command line report
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-curl-report-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-curl-report-latest?py=py34&pytest=2.8.0.dev4 :target: https://bitbucket.org/pytest-dev/pytest-curl-report
|
||||
`pytest-dbfixtures <http://pypi.python.org/pypi/pytest-dbfixtures>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-dbfixtures-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-dbfixtures-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Databases fixtures plugin for py.test.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-dbfixtures-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-dbfixtures-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/ClearcodeHQ/pytest-dbfixtures
|
||||
`pytest-dbus-notification <http://pypi.python.org/pypi/pytest-dbus-notification>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-dbus-notification-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-dbus-notification-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png D-BUS notifications for pytest results.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-dbus-notification-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-dbus-notification-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/bmathieu33/pytest-dbus-notification
|
||||
`pytest-describe <http://pypi.python.org/pypi/pytest-describe>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-describe-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-describe-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Describe-style plugin for pytest
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-describe-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-describe-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/ropez/pytest-describe
|
||||
`pytest-diffeo <http://pypi.python.org/pypi/pytest-diffeo>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-diffeo-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-diffeo-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Common py.test support for Diffeo packages
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-diffeo-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-diffeo-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/diffeo/pytest-diffeo
|
||||
`pytest-django-sqlcounts <http://pypi.python.org/pypi/pytest-django-sqlcounts>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-django-sqlcounts-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-django-sqlcounts-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png py.test plugin for reporting the number of SQLs executed per django testcase.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-django-sqlcounts-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-django-sqlcounts-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/stj/pytest-django-sqlcount
|
||||
`pytest-django-sqlcount <http://pypi.python.org/pypi/pytest-django-sqlcount>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-django-sqlcount-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-django-sqlcount-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png py.test plugin for reporting the number of SQLs executed per django testcase.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-django-sqlcount-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-django-sqlcount-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/stj/pytest-django-sqlcount
|
||||
`pytest-django-haystack <http://pypi.python.org/pypi/pytest-django-haystack>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-django-haystack-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-django-haystack-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Cleanup your Haystack indexes between tests
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-django-haystack-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-django-haystack-latest?py=py34&pytest=2.8.0.dev4 :target: http://github.com/rouge8/pytest-django-haystack
|
||||
`pytest-django-lite <http://pypi.python.org/pypi/pytest-django-lite>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-django-lite-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-django-lite-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png The bare minimum to integrate py.test with Django.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-django-lite-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-django-lite-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/dcramer/pytest-django-lite
|
||||
`pytest-django <http://pypi.python.org/pypi/pytest-django>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-django-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-django-latest?py=py34&pytest=2.8.0.dev4 `link <http://pytest-django.readthedocs.org/>`_ A Django plugin for py.test.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-django-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-django-latest?py=py34&pytest=2.8.0.dev4
|
||||
`pytest-doc <http://pypi.python.org/pypi/pytest-doc>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-doc-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-doc-latest?py=py34&pytest=2.8.0.dev4 `link <http://pytest-doc.readthedocs.org/>`_ A documentation plugin for py.test.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-doc-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-doc-latest?py=py34&pytest=2.8.0.dev4
|
||||
`pytest-dump2json <http://pypi.python.org/pypi/pytest-dump2json>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-dump2json-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-dump2json-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png A pytest plugin for dumping test results to json.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-dump2json-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-dump2json-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/d6e/pytest-dump2json
|
||||
`pytest-echo <http://pypi.python.org/pypi/pytest-echo>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-echo-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-echo-latest?py=py34&pytest=2.8.0.dev4 `link <http://pypi.python.org/pypi/pytest-echo/>`_ pytest plugin with mechanisms for echoing environment variables, package version and generic attributes
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-echo-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-echo-latest?py=py34&pytest=2.8.0.dev4
|
||||
`pytest-env <http://pypi.python.org/pypi/pytest-env>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-env-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-env-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png py.test plugin that allows you to add environment variables.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-env-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-env-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/MobileDynasty/pytest-env
|
||||
`pytest-eradicate <http://pypi.python.org/pypi/pytest-eradicate>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-eradicate-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-eradicate-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png pytest plugin to check for commented out code
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-eradicate-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-eradicate-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/spil-johan/pytest-eradicate
|
||||
`pytest-factoryboy <http://pypi.python.org/pypi/pytest-factoryboy>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-factoryboy-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-factoryboy-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Factory Boy support for pytest.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-factoryboy-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-factoryboy-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/pytest-dev/pytest-factoryboy
|
||||
`pytest-poo-fail <http://pypi.python.org/pypi/pytest-poo-fail>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-poo-fail-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-poo-fail-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Visualize your failed tests with poo
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-poo-fail-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-poo-fail-latest?py=py34&pytest=2.8.0.dev4 :target: http://github.com/alyssa.barela/pytest-poo-fail
|
||||
`pytest-faker <http://pypi.python.org/pypi/pytest-faker>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-faker-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-faker-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Faker integration for pytest framework.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-faker-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-faker-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/pytest-dev/pytest-faker
|
||||
`pytest-faulthandler <http://pypi.python.org/pypi/pytest-faulthandler>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-faulthandler-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-faulthandler-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png py.test plugin that activates the fault handler module for tests
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-faulthandler-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-faulthandler-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/pytest-dev/pytest-faulthandler
|
||||
`pytest-fauxfactory <http://pypi.python.org/pypi/pytest-fauxfactory>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-fauxfactory-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-fauxfactory-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Integration of fauxfactory into pytest.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-fauxfactory-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-fauxfactory-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/mfalesni/pytest-fauxfactory
|
||||
`pytest-figleaf <http://pypi.python.org/pypi/pytest-figleaf>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-figleaf-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-figleaf-latest?py=py34&pytest=2.8.0.dev4 .. image:: bitbucket.png py.test figleaf coverage plugin
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-figleaf-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-figleaf-latest?py=py34&pytest=2.8.0.dev4 :target: http://bitbucket.org/hpk42/pytest-figleaf
|
||||
`pytest-fixture-tools <http://pypi.python.org/pypi/pytest-fixture-tools>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-fixture-tools-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-fixture-tools-latest?py=py34&pytest=2.8.0.dev4 ? Plugin for pytest which provides tools for fixtures
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-fixture-tools-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-fixture-tools-latest?py=py34&pytest=2.8.0.dev4
|
||||
`pytest-flake8 <http://pypi.python.org/pypi/pytest-flake8>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-flake8-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-flake8-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png pytest plugin to check FLAKE8 requirements
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-flake8-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-flake8-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/tholo/pytest-flake8
|
||||
`pytest-flakes <http://pypi.python.org/pypi/pytest-flakes>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-flakes-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-flakes-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png pytest plugin to check source code with pyflakes
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-flakes-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-flakes-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/fschulze/pytest-flakes
|
||||
`pytest-flask <http://pypi.python.org/pypi/pytest-flask>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-flask-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-flask-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png A set of py.test fixtures to test Flask applications.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-flask-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-flask-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/vitalk/pytest-flask
|
||||
`pytest-gitignore <http://pypi.python.org/pypi/pytest-gitignore>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-gitignore-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-gitignore-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png py.test plugin to ignore the same files as git
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-gitignore-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-gitignore-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/tgs/pytest-gitignore
|
||||
`pytest-greendots <http://pypi.python.org/pypi/pytest-greendots>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-greendots-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-greendots-latest?py=py34&pytest=2.8.0.dev4 ? Green progress dots
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-greendots-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-greendots-latest?py=py34&pytest=2.8.0.dev4
|
||||
`pytest-growl <http://pypi.python.org/pypi/pytest-growl>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-growl-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-growl-latest?py=py34&pytest=2.8.0.dev4 ? Growl notifications for pytest results.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-growl-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-growl-latest?py=py34&pytest=2.8.0.dev4
|
||||
`pytest-html <http://pypi.python.org/pypi/pytest-html>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-html-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-html-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png pytest plugin for generating HTML reports
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-html-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-html-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/davehunt/pytest-html
|
||||
`pytest-httpbin <http://pypi.python.org/pypi/pytest-httpbin>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-httpbin-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-httpbin-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Easily test your HTTP library against a local copy of httpbin
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-httpbin-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-httpbin-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/kevin1024/pytest-httpbin
|
||||
`pytest-httpretty <http://pypi.python.org/pypi/pytest-httpretty>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-httpretty-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-httpretty-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png A thin wrapper of HTTPretty for pytest
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-httpretty-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-httpretty-latest?py=py34&pytest=2.8.0.dev4 :target: http://github.com/papaeye/pytest-httpretty
|
||||
`pytest-incremental <http://pypi.python.org/pypi/pytest-incremental>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-incremental-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-incremental-latest?py=py34&pytest=2.8.0.dev4 `link <http://pytest-incremental.readthedocs.org>`_ an incremental test runner (pytest plugin)
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-incremental-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-incremental-latest?py=py34&pytest=2.8.0.dev4
|
||||
`pytest-instafail <http://pypi.python.org/pypi/pytest-instafail>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-instafail-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-instafail-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png py.test plugin to show failures instantly
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-instafail-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-instafail-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/jpvanhal/pytest-instafail
|
||||
`pytest-ipdb <http://pypi.python.org/pypi/pytest-ipdb>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-ipdb-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-ipdb-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png A py.test plug-in to enable drop to ipdb debugger on test failure.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-ipdb-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-ipdb-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/mverteuil/pytest-ipdb
|
||||
`pytest-ipynb <http://pypi.python.org/pypi/pytest-ipynb>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-ipynb-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-ipynb-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Use pytest's runner to discover and execute tests as cells of IPython notebooks
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-ipynb-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-ipynb-latest?py=py34&pytest=2.8.0.dev4 :target: http://github.com/zonca/pytest-ipynb
|
||||
`pytest-isort <http://pypi.python.org/pypi/pytest-isort>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-isort-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-isort-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png pytest plugin to perform isort checks (import ordering)
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-isort-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-isort-latest?py=py34&pytest=2.8.0.dev4 :target: http://github.com/moccu/pytest-isort/
|
||||
`pytest-jira <http://pypi.python.org/pypi/pytest-jira>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-jira-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-jira-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png py.test JIRA integration plugin, using markers
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-jira-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-jira-latest?py=py34&pytest=2.8.0.dev4 :target: http://github.com/jlaska/pytest_jira
|
||||
`pytest-knows <http://pypi.python.org/pypi/pytest-knows>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-knows-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-knows-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png A pytest plugin that can automaticly skip test case based on dependence info calculated by trace
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-knows-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-knows-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/mapix/ptknows
|
||||
`pytest-konira <http://pypi.python.org/pypi/pytest-konira>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-konira-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-konira-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Run Konira DSL tests with py.test
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-konira-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-konira-latest?py=py34&pytest=2.8.0.dev4 :target: http://github.com/alfredodeza/pytest-konira
|
||||
`pytest-localserver <http://pypi.python.org/pypi/pytest-localserver>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-localserver-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-localserver-latest?py=py34&pytest=2.8.0.dev4 .. image:: bitbucket.png py.test plugin to test server connections locally.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-localserver-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-localserver-latest?py=py34&pytest=2.8.0.dev4 :target: http://bitbucket.org/basti/pytest-localserver/
|
||||
`pytest-markfiltration <http://pypi.python.org/pypi/pytest-markfiltration>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-markfiltration-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-markfiltration-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png UNKNOWN
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-markfiltration-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-markfiltration-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/adamgoucher/pytest-markfiltration
|
||||
`pytest-marks <http://pypi.python.org/pypi/pytest-marks>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-marks-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-marks-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png UNKNOWN
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-marks-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-marks-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/adamgoucher/pytest-marks
|
||||
`pytest-mccabe <http://pypi.python.org/pypi/pytest-mccabe>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-mccabe-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-mccabe-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png pytest plugin to run the mccabe code complexity checker.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-mccabe-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-mccabe-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/The-Compiler/pytest-mccabe
|
||||
`pytest-mock <http://pypi.python.org/pypi/pytest-mock>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-mock-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-mock-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Thin-wrapper around the mock package for easier use with py.test
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-mock-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-mock-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/pytest-dev/pytest-mock/
|
||||
`pytest-monkeyplus <http://pypi.python.org/pypi/pytest-monkeyplus>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-monkeyplus-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-monkeyplus-latest?py=py34&pytest=2.8.0.dev4 .. image:: bitbucket.png pytest's monkeypatch subclass with extra functionalities
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-monkeyplus-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-monkeyplus-latest?py=py34&pytest=2.8.0.dev4 :target: http://bitbucket.org/hsoft/pytest-monkeyplus/
|
||||
`pytest-mozwebqa <http://pypi.python.org/pypi/pytest-mozwebqa>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-mozwebqa-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-mozwebqa-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Mozilla WebQA plugin for py.test.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-mozwebqa-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-mozwebqa-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/mozilla/pytest-mozwebqa
|
||||
`pytest-mpl <http://pypi.python.org/pypi/pytest-mpl>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-mpl-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-mpl-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png pytest plugin to help with testing figures output from Matplotlib
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-mpl-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-mpl-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/astrofrog/pytest-mpl
|
||||
`pytest-multihost <http://pypi.python.org/pypi/pytest-multihost>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-multihost-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-multihost-latest?py=py34&pytest=2.8.0.dev4 `link <https://fedorahosted.org/python-pytest-multihost/>`_ Utility for writing multi-host tests for pytest
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-multihost-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-multihost-latest?py=py34&pytest=2.8.0.dev4
|
||||
`pytest-oerp <http://pypi.python.org/pypi/pytest-oerp>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-oerp-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-oerp-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png pytest plugin to test OpenERP modules
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-oerp-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-oerp-latest?py=py34&pytest=2.8.0.dev4 :target: http://github.com/santagada/pytest-oerp/
|
||||
`pytest-oot <http://pypi.python.org/pypi/pytest-oot>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-oot-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-oot-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Run object-oriented tests in a simple format
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-oot-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-oot-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/steven004/pytest_oot
|
||||
`pytest-optional <http://pypi.python.org/pypi/pytest-optional>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-optional-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-optional-latest?py=py34&pytest=2.8.0.dev4 .. image:: bitbucket.png include/exclude values of fixtures in pytest
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-optional-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-optional-latest?py=py34&pytest=2.8.0.dev4 :target: http://bitbucket.org/maho/pytest-optional
|
||||
`pytest-ordering <http://pypi.python.org/pypi/pytest-ordering>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-ordering-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-ordering-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png pytest plugin to run your tests in a specific order
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-ordering-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-ordering-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/ftobia/pytest-ordering
|
||||
`pytest-osxnotify <http://pypi.python.org/pypi/pytest-osxnotify>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-osxnotify-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-osxnotify-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png OS X notifications for py.test results.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-osxnotify-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-osxnotify-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/dbader/pytest-osxnotify
|
||||
`pytest-pep257 <http://pypi.python.org/pypi/pytest-pep257>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-pep257-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-pep257-latest?py=py34&pytest=2.8.0.dev4 ? py.test plugin for pep257
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-pep257-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-pep257-latest?py=py34&pytest=2.8.0.dev4
|
||||
`pytest-pep8 <http://pypi.python.org/pypi/pytest-pep8>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-pep8-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-pep8-latest?py=py34&pytest=2.8.0.dev4 .. image:: bitbucket.png pytest plugin to check PEP8 requirements
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-pep8-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-pep8-latest?py=py34&pytest=2.8.0.dev4 :target: http://bitbucket.org/hpk42/pytest-pep8/
|
||||
`pytest-pipeline <http://pypi.python.org/pypi/pytest-pipeline>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-pipeline-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-pipeline-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Pytest plugin for functional testing of data analysis pipelines
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-pipeline-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-pipeline-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/bow/pytest-pipeline
|
||||
`pytest-poo <http://pypi.python.org/pypi/pytest-poo>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-poo-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-poo-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Visualize your crappy tests
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-poo-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-poo-latest?py=py34&pytest=2.8.0.dev4 :target: http://github.com/pelme/pytest-poo
|
||||
`pytest-proper-wheel <http://pypi.python.org/pypi/pytest-proper-wheel>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-proper-wheel-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-proper-wheel-latest?py=py34&pytest=2.8.0.dev4 `link <http://pytest.org>`_ pytest: simple powerful testing with Python
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-proper-wheel-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-proper-wheel-latest?py=py34&pytest=2.8.0.dev4
|
||||
`pytest-purkinje <http://pypi.python.org/pypi/pytest-purkinje>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-purkinje-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-purkinje-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png py.test plugin for purkinje test runner
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-purkinje-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-purkinje-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/bbiskup
|
||||
`pytest-pycharm <http://pypi.python.org/pypi/pytest-pycharm>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-pycharm-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-pycharm-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Plugin for py.test to enter PyCharm debugger on uncaught exceptions
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-pycharm-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-pycharm-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/jlubcke/pytest-pycharm
|
||||
`pytest-pydev <http://pypi.python.org/pypi/pytest-pydev>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-pydev-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-pydev-latest?py=py34&pytest=2.8.0.dev4 .. image:: bitbucket.png py.test plugin to connect to a remote debug server with PyDev or PyCharm.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-pydev-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-pydev-latest?py=py34&pytest=2.8.0.dev4 :target: http://bitbucket.org/basti/pytest-pydev/
|
||||
`pytest-pylint <http://pypi.python.org/pypi/pytest-pylint>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-pylint-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-pylint-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png pytest plugin to check source code with pylint
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-pylint-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-pylint-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/carsongee/pytest-pylint
|
||||
`pytest-pyq <http://pypi.python.org/pypi/pytest-pyq>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-pyq-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-pyq-latest?py=py34&pytest=2.8.0.dev4 `link <http://pyq.enlnt.com>`_ Pytest fixture "q" for pyq
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-pyq-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-pyq-latest?py=py34&pytest=2.8.0.dev4
|
||||
`pytest-sftpserver <http://pypi.python.org/pypi/pytest-sftpserver>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-sftpserver-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-sftpserver-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png py.test plugin to locally test sftp server connections.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-sftpserver-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-sftpserver-latest?py=py34&pytest=2.8.0.dev4 :target: http://github.com/ulope/pytest-sftpserver/
|
||||
`pytest-rage <http://pypi.python.org/pypi/pytest-rage>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-rage-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-rage-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png pytest plugin to implement PEP712
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-rage-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-rage-latest?py=py34&pytest=2.8.0.dev4 :target: http://github.com/santagada/pytest-rage/
|
||||
`pytest-smartcov <http://pypi.python.org/pypi/pytest-smartcov>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-smartcov-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-smartcov-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Smart coverage plugin for pytest.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-smartcov-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-smartcov-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/carljm/pytest-smartcov/
|
||||
`pytest-variables <http://pypi.python.org/pypi/pytest-variables>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-variables-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-variables-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png pytest plugin for providing variables to tests/fixtures
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-variables-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-variables-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/davehunt/pytest-variables
|
||||
`pytest-selenium <http://pypi.python.org/pypi/pytest-selenium>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-selenium-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-selenium-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png A selenium plugin for pytest
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-selenium-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-selenium-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/codingjoe/pytest-selenium
|
||||
`pytest-readme <http://pypi.python.org/pypi/pytest-readme>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-readme-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-readme-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Test your README.md file
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-readme-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-readme-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/boxed/pytest-readme
|
||||
`pytest-translations <http://pypi.python.org/pypi/pytest-translations>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-translations-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-translations-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Test your translation files
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-translations-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-translations-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/thermondo/pytest-translations
|
||||
`pytest-xprocess <http://pypi.python.org/pypi/pytest-xprocess>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-xprocess-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-xprocess-latest?py=py34&pytest=2.8.0.dev4 .. image:: bitbucket.png pytest plugin to manage external processes across test runs
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-xprocess-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-xprocess-latest?py=py34&pytest=2.8.0.dev4 :target: http://bitbucket.org/hpk42/pytest-xprocess/
|
||||
`pytest-random <http://pypi.python.org/pypi/pytest-random>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-random-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-random-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png py.test plugin to randomize tests
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-random-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-random-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/klrmn/pytest-random
|
||||
`pytest-sourceorder <http://pypi.python.org/pypi/pytest-sourceorder>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-sourceorder-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-sourceorder-latest?py=py34&pytest=2.8.0.dev4 `link <https://fedorahosted.org/python-pytest-sourceorder/>`_ Test-ordering plugin for pytest
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-sourceorder-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-sourceorder-latest?py=py34&pytest=2.8.0.dev4
|
||||
`pytest-zap <http://pypi.python.org/pypi/pytest-zap>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-zap-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-zap-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png OWASP ZAP plugin for py.test.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-zap-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-zap-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/davehunt/pytest-zap
|
||||
`pytest-raisesregexp <http://pypi.python.org/pypi/pytest-raisesregexp>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-raisesregexp-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-raisesregexp-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Simple pytest plugin to look for regex in Exceptions
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-raisesregexp-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-raisesregexp-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/Walkman/pytest_raisesregexp
|
||||
`pytest-trialtemp <http://pypi.python.org/pypi/pytest-trialtemp>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-trialtemp-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-trialtemp-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png py.test plugin for using the same _trial_temp working directory as trial
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-trialtemp-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-trialtemp-latest?py=py34&pytest=2.8.0.dev4 :target: http://github.com/jerith/pytest-trialtemp
|
||||
`pytest-sftpserver <http://pypi.python.org/pypi/pytest-sftpserver>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-sftpserver-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-sftpserver-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png py.test plugin to locally test sftp server connections.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-sftpserver-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-sftpserver-latest?py=py34&pytest=2.8.0.dev4 :target: http://github.com/ulope/pytest-sftpserver/
|
||||
`pytest-rerunfailures <http://pypi.python.org/pypi/pytest-rerunfailures>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-rerunfailures-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-rerunfailures-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png py.test plugin to re-run tests to eliminate flakey failures
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-rerunfailures-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-rerunfailures-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/klrmn/pytest-rerunfailures
|
||||
`pytest-spec <http://pypi.python.org/pypi/pytest-spec>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-spec-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-spec-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png pytest plugin to display test execution output like a SPECIFICATION
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-spec-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-spec-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/pchomik/pytest-spec
|
||||
`pytest-testmon <http://pypi.python.org/pypi/pytest-testmon>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-testmon-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-testmon-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png take TDD to a new level with py.test and testmon
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-testmon-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-testmon-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/tarpas/pytest-testmon/
|
||||
`pytest-sftpserver <http://pypi.python.org/pypi/pytest-sftpserver>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-sftpserver-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-sftpserver-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png py.test plugin to locally test sftp server connections.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-sftpserver-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-sftpserver-latest?py=py34&pytest=2.8.0.dev4 :target: http://github.com/ulope/pytest-sftpserver/
|
||||
`pytest-stepwise <http://pypi.python.org/pypi/pytest-stepwise>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-stepwise-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-stepwise-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Run a test suite one failing test at a time.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-stepwise-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-stepwise-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/nip3o/pytest-stepwise
|
||||
`pytest-runfailed <http://pypi.python.org/pypi/pytest-runfailed>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-runfailed-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-runfailed-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png implement a --failed option for pytest
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-runfailed-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-runfailed-latest?py=py34&pytest=2.8.0.dev4 :target: http://github.com/dmerejkowsky/pytest-runfailed
|
||||
`pytest-tornado <http://pypi.python.org/pypi/pytest-tornado>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-tornado-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-tornado-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png A py.test plugin providing fixtures and markers to simplify testing of asynchronous tornado applications.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-tornado-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-tornado-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/eugeniy/pytest-tornado
|
||||
`pytest-sftpserver <http://pypi.python.org/pypi/pytest-sftpserver>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-sftpserver-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-sftpserver-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png py.test plugin to locally test sftp server connections.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-sftpserver-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-sftpserver-latest?py=py34&pytest=2.8.0.dev4 :target: http://github.com/ulope/pytest-sftpserver/
|
||||
`pytest-timeout <http://pypi.python.org/pypi/pytest-timeout>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-timeout-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-timeout-latest?py=py34&pytest=2.8.0.dev4 .. image:: bitbucket.png py.test plugin to abort hanging tests
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-timeout-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-timeout-latest?py=py34&pytest=2.8.0.dev4 :target: http://bitbucket.org/flub/pytest-timeout/
|
||||
`pytest-sftpserver <http://pypi.python.org/pypi/pytest-sftpserver>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-sftpserver-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-sftpserver-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png py.test plugin to locally test sftp server connections.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-sftpserver-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-sftpserver-latest?py=py34&pytest=2.8.0.dev4 :target: http://github.com/ulope/pytest-sftpserver/
|
||||
`pytest-ubersmith <http://pypi.python.org/pypi/pytest-ubersmith>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-ubersmith-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-ubersmith-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Easily mock calls to ubersmith at the `requests` level.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-ubersmith-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-ubersmith-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/hivelocity/pytest-ubersmith
|
||||
`pytest-services <http://pypi.python.org/pypi/pytest-services>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-services-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-services-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Services plugin for pytest testing framework
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-services-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-services-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/pytest-dev/pytest-services
|
||||
`pytest-pythonpath <http://pypi.python.org/pypi/pytest-pythonpath>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-pythonpath-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-pythonpath-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png pytest plugin for adding to the PYTHONPATH from command line or configs.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-pythonpath-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-pythonpath-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/bigsassy/pytest-pythonpath
|
||||
`pytest-yamlwsgi <http://pypi.python.org/pypi/pytest-yamlwsgi>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-yamlwsgi-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-yamlwsgi-latest?py=py34&pytest=2.8.0.dev4 ? Run tests against wsgi apps defined in yaml
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-yamlwsgi-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-yamlwsgi-latest?py=py34&pytest=2.8.0.dev4
|
||||
`pytest-trello <http://pypi.python.org/pypi/pytest-trello>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-trello-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-trello-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Plugin for py.test that integrates trello using markers
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-trello-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-trello-latest?py=py34&pytest=2.8.0.dev4 :target: http://github.com/jlaska/pytest-trello
|
||||
`pytest-quickcheck <http://pypi.python.org/pypi/pytest-quickcheck>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-quickcheck-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-quickcheck-latest?py=py34&pytest=2.8.0.dev4 .. image:: bitbucket.png pytest plugin to generate random data inspired by QuickCheck
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-quickcheck-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-quickcheck-latest?py=py34&pytest=2.8.0.dev4 :target: https://bitbucket.org/pytest-dev/pytest-quickcheck
|
||||
`pytest-twisted <http://pypi.python.org/pypi/pytest-twisted>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-twisted-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-twisted-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png A twisted plugin for py.test.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-twisted-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-twisted-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/schmir/pytest-twisted
|
||||
`pytest-sftpserver <http://pypi.python.org/pypi/pytest-sftpserver>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-sftpserver-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-sftpserver-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png py.test plugin to locally test sftp server connections.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-sftpserver-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-sftpserver-latest?py=py34&pytest=2.8.0.dev4 :target: http://github.com/ulope/pytest-sftpserver/
|
||||
`pytest-watch <http://pypi.python.org/pypi/pytest-watch>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-watch-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-watch-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Local continuous test runner with pytest and watchdog.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-watch-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-watch-latest?py=py34&pytest=2.8.0.dev4 :target: http://github.com/joeyespo/pytest-watch
|
||||
`pytest-unmarked <http://pypi.python.org/pypi/pytest-unmarked>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-unmarked-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-unmarked-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Run only unmarked tests
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-unmarked-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-unmarked-latest?py=py34&pytest=2.8.0.dev4 :target: http://github.com/alyssa.barela/pytest-unmarked
|
||||
`pytest-regtest <http://pypi.python.org/pypi/pytest-regtest>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-regtest-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-regtest-latest?py=py34&pytest=2.8.0.dev4 `link <https://sissource.ethz.ch/uweschmitt/pytest-regtest/tree/master>`_ py.test plugin for regression tests
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-regtest-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-regtest-latest?py=py34&pytest=2.8.0.dev4
|
||||
`pytest-xdist <http://pypi.python.org/pypi/pytest-xdist>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-xdist-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-xdist-latest?py=py34&pytest=2.8.0.dev4 .. image:: bitbucket.png py.test xdist plugin for distributed testing and loop-on-failing modes
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-xdist-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-xdist-latest?py=py34&pytest=2.8.0.dev4 :target: http://bitbucket.org/hpk42/pytest-xdist
|
||||
`pytest-sugar <http://pypi.python.org/pypi/pytest-sugar>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-sugar-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-sugar-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png py.test is a plugin for py.test that changes the default look and feel of py.test (e.g. progressbar, show tests that fail instantly).
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-sugar-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-sugar-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/Frozenball/pytest-sugar
|
||||
`pytest-qt <http://pypi.python.org/pypi/pytest-qt>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-qt-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-qt-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png pytest support for PyQt and PySide applications
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-qt-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-qt-latest?py=py34&pytest=2.8.0.dev4 :target: http://github.com/pytest-dev/pytest-qt
|
||||
`pytest-runner <http://pypi.python.org/pypi/pytest-runner>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-runner-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-runner-latest?py=py34&pytest=2.8.0.dev4 .. image:: bitbucket.png Invoke py.test as distutils command with dependency resolution.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-runner-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-runner-latest?py=py34&pytest=2.8.0.dev4 :target: https://bitbucket.org/pytest-dev/pytest-runner
|
||||
`pytest-splinter <http://pypi.python.org/pypi/pytest-splinter>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-splinter-latest?py=py27&pytest=2.8.0.dev4 .. image:: http://plugincompat.herokuapp.com/status/pytest-splinter-latest?py=py34&pytest=2.8.0.dev4 .. image:: github.png Splinter plugin for pytest testing framework
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-splinter-latest?py=py27&pytest=2.8.0.dev4 :target: http://plugincompat.herokuapp.com/output/pytest-splinter-latest?py=py34&pytest=2.8.0.dev4 :target: https://github.com/pytest-dev/pytest-splinter
|
||||
|
||||
============================================================================================ ===================================================================================================================== ===================================================================================================================== =========================================================================== =============================================================================================================================================
|
||||
|
||||
*(Updated on 2015-06-30)*
|
||||
@@ -1,307 +0,0 @@
|
||||
"""
|
||||
Script to generate the file `index.txt` with information about
|
||||
pytest plugins taken directly from PyPI.
|
||||
|
||||
Usage:
|
||||
python plugins_index.py
|
||||
|
||||
This command will update `index.txt` in the same directory found as this script.
|
||||
This should be issued before every major documentation release to obtain latest
|
||||
versions from PyPI.
|
||||
|
||||
Also includes plugin compatibility between different python and pytest versions,
|
||||
obtained from http://plugincompat.herokuapp.com.
|
||||
"""
|
||||
from __future__ import print_function
|
||||
from collections import namedtuple
|
||||
import datetime
|
||||
from distutils.version import LooseVersion
|
||||
import itertools
|
||||
from optparse import OptionParser
|
||||
import os
|
||||
import sys
|
||||
import pytest
|
||||
|
||||
|
||||
def get_proxy(url):
|
||||
"""
|
||||
wrapper function to obtain a xmlrpc proxy, taking in account import
|
||||
differences between python 2.X and 3.X
|
||||
|
||||
:param url: url to bind the proxy to
|
||||
:return: a ServerProxy instance
|
||||
"""
|
||||
if sys.version_info < (3, 0):
|
||||
from xmlrpclib import ServerProxy
|
||||
else:
|
||||
from xmlrpc.client import ServerProxy
|
||||
return ServerProxy(url)
|
||||
|
||||
|
||||
def iter_plugins(client):
|
||||
"""
|
||||
Returns an iterator of (name, version) from PyPI.
|
||||
|
||||
:param client: ServerProxy
|
||||
:param search: package names to search for
|
||||
"""
|
||||
for plug_data in client.search({'name': 'pytest'}):
|
||||
if plug_data['name'].startswith('pytest-'):
|
||||
yield plug_data['name'], plug_data['version']
|
||||
|
||||
|
||||
def get_latest_versions(plugins):
|
||||
"""
|
||||
Returns an iterator of (name, version) from the given list of (name,
|
||||
version), but returning only the latest version of the package. Uses
|
||||
distutils.LooseVersion to ensure compatibility with PEP386.
|
||||
"""
|
||||
plugins = [(name, LooseVersion(version)) for (name, version) in plugins]
|
||||
for name, grouped_plugins in itertools.groupby(plugins, key=lambda x: x[0]):
|
||||
name, loose_version = list(grouped_plugins)[-1]
|
||||
yield name, str(loose_version)
|
||||
|
||||
|
||||
def obtain_plugins_table(plugins, client, verbose, pytest_ver):
|
||||
"""
|
||||
Returns information to populate a table of plugins, their versions,
|
||||
authors, etc.
|
||||
|
||||
The returned information is a list of columns of `ColumnData`
|
||||
namedtuples(text, link). Link can be None if the text for that column
|
||||
should not be linked to anything.
|
||||
|
||||
:param plugins: list of (name, version)
|
||||
:param client: ServerProxy
|
||||
:param verbose: print plugin name and version as they are fetch
|
||||
:param pytest_ver: pytest version to use.
|
||||
"""
|
||||
if pytest_ver is None:
|
||||
pytest_ver = pytest.__version__
|
||||
|
||||
def get_repo_markup(repo):
|
||||
"""
|
||||
obtains appropriate markup for the given repository, as two lines
|
||||
that should be output in the same table row. We use this to display an icon
|
||||
for known repository hosts (github, etc), just a "?" char when
|
||||
repository is not registered in pypi or a simple link otherwise.
|
||||
"""
|
||||
target = repo
|
||||
if 'github.com' in repo:
|
||||
image = 'github.png'
|
||||
elif 'bitbucket.org' in repo:
|
||||
image = 'bitbucket.png'
|
||||
elif repo.lower() == 'unknown':
|
||||
return '?', ''
|
||||
else:
|
||||
image = None
|
||||
|
||||
if image is not None:
|
||||
image_markup = '.. image:: %s' % image
|
||||
target_markup = ' :target: %s' % repo
|
||||
pad_right = ('%-' + str(len(target_markup)) + 's')
|
||||
return pad_right % image_markup, target_markup
|
||||
else:
|
||||
return ('`link <%s>`_' % target), ''
|
||||
|
||||
def sanitize_summary(summary):
|
||||
"""Make sure summaries don't break our table formatting.
|
||||
"""
|
||||
return summary.replace('\n', ' ')
|
||||
|
||||
rows = []
|
||||
ColumnData = namedtuple('ColumnData', 'text link')
|
||||
headers = ['Name', 'Py27', 'Py34', 'Home', 'Summary']
|
||||
repositories = obtain_override_repositories()
|
||||
print('Generating plugins_index page (pytest-{0})'.format(pytest_ver))
|
||||
plugins = list(plugins)
|
||||
for index, (package_name, version) in enumerate(plugins):
|
||||
if verbose:
|
||||
print(package_name, version, '...', end='')
|
||||
|
||||
release_data = client.release_data(package_name, version)
|
||||
|
||||
common_params = dict(
|
||||
site='http://plugincompat.herokuapp.com',
|
||||
name=package_name,
|
||||
version=version)
|
||||
|
||||
repository = repositories.get(package_name, release_data['home_page'])
|
||||
repo_markup_1, repo_markup_2 = get_repo_markup(repository)
|
||||
|
||||
# first row: name, images and simple links
|
||||
url = '.. image:: {site}/status/{name}-latest'
|
||||
image_url = url.format(**common_params)
|
||||
image_url += '?py={py}&pytest={pytest}'
|
||||
row = (
|
||||
ColumnData(package_name, release_data['package_url']),
|
||||
ColumnData(image_url.format(py='py27', pytest=pytest_ver),
|
||||
None),
|
||||
ColumnData(image_url.format(py='py34', pytest=pytest_ver),
|
||||
None),
|
||||
ColumnData(
|
||||
repo_markup_1,
|
||||
None),
|
||||
ColumnData(sanitize_summary(release_data['summary']), None),
|
||||
)
|
||||
assert len(row) == len(headers)
|
||||
rows.append(row)
|
||||
|
||||
# second row: links for images (they should be in their own line)
|
||||
url = ' :target: {site}/output/{name}-latest'
|
||||
output_url = url.format(**common_params)
|
||||
output_url += '?py={py}&pytest={pytest}'
|
||||
|
||||
row = (
|
||||
ColumnData('', None),
|
||||
ColumnData(output_url.format(py='py27', pytest=pytest_ver),
|
||||
None),
|
||||
ColumnData(output_url.format(py='py34', pytest=pytest_ver),
|
||||
None),
|
||||
ColumnData(repo_markup_2, None),
|
||||
ColumnData('', None),
|
||||
|
||||
)
|
||||
assert len(row) == len(headers)
|
||||
rows.append(row)
|
||||
|
||||
if verbose:
|
||||
print('OK (%d%%)' % ((index + 1) * 100 / len(plugins)))
|
||||
|
||||
print('Done: %d plugins' % len(plugins))
|
||||
|
||||
return headers, rows
|
||||
|
||||
|
||||
def obtain_override_repositories():
|
||||
"""
|
||||
Used to override the "home_page" obtained from pypi to known
|
||||
package repositories. Used when the author didn't fill the "home_page"
|
||||
field in setup.py.
|
||||
|
||||
:return: dict of {package_name: repository_url}
|
||||
"""
|
||||
return {
|
||||
'pytest-blockage': 'https://github.com/rob-b/pytest-blockage',
|
||||
'pytest-konira': 'http://github.com/alfredodeza/pytest-konira',
|
||||
'pytest-sugar': 'https://github.com/Frozenball/pytest-sugar',
|
||||
}
|
||||
|
||||
|
||||
def generate_plugins_index_from_table(filename, headers, rows, pytest_ver):
|
||||
"""
|
||||
Generates a RST file with the table data given.
|
||||
|
||||
:param filename: output filename
|
||||
:param headers: see `obtain_plugins_table`
|
||||
:param rows: see `obtain_plugins_table`
|
||||
:param pytest_ver: see `obtain_plugins_table`
|
||||
"""
|
||||
# creates a list of rows, each being a str containing appropriate column
|
||||
# text and link
|
||||
table_texts = []
|
||||
for row in rows:
|
||||
column_texts = []
|
||||
for i, col_data in enumerate(row):
|
||||
text = '`%s <%s>`_' % (
|
||||
col_data.text,
|
||||
col_data.link) if col_data.link else col_data.text
|
||||
column_texts.append(text)
|
||||
table_texts.append(column_texts)
|
||||
|
||||
# compute max length of each column so we can build the rst table
|
||||
column_lengths = [len(x) for x in headers]
|
||||
for column_texts in table_texts:
|
||||
for i, row_text in enumerate(column_texts):
|
||||
column_lengths[i] = max(column_lengths[i], len(row_text) + 2)
|
||||
|
||||
def get_row_limiter(char):
|
||||
return ' '.join(char * length for length in column_lengths)
|
||||
|
||||
with open(filename, 'w') as f:
|
||||
# header
|
||||
print(HEADER, file=f)
|
||||
print(file=f)
|
||||
|
||||
# table
|
||||
print(get_row_limiter('='), file=f)
|
||||
formatted_headers = [
|
||||
'{0:^{fill}}'.format(header, fill=column_lengths[i])
|
||||
for i, header in enumerate(headers)]
|
||||
print(*formatted_headers, file=f)
|
||||
print(get_row_limiter('='), file=f)
|
||||
|
||||
for column_texts in table_texts:
|
||||
formatted_rows = [
|
||||
'{0:^{fill}}'.format(row_text, fill=column_lengths[i])
|
||||
for i, row_text in enumerate(column_texts)
|
||||
]
|
||||
print(*formatted_rows, file=f)
|
||||
print(file=f)
|
||||
print(get_row_limiter('='), file=f)
|
||||
print(file=f)
|
||||
today = datetime.date.today().strftime('%Y-%m-%d')
|
||||
print('*(Updated on %s)*' % today, file=f)
|
||||
|
||||
|
||||
def generate_plugins_index(client, filename, verbose, pytest_ver):
|
||||
"""
|
||||
Generates an RST file with a table of the latest pytest plugins found in
|
||||
PyPI.
|
||||
|
||||
:param client: ServerProxy
|
||||
:param filename: output filename
|
||||
:param verbose: print name and version of each plugin as they are fetch
|
||||
:param pytest_ver: pytest version to use; if not given, use current pytest
|
||||
version.
|
||||
"""
|
||||
plugins = get_latest_versions(iter_plugins(client))
|
||||
headers, rows = obtain_plugins_table(plugins, client, verbose, pytest_ver)
|
||||
generate_plugins_index_from_table(filename, headers, rows, pytest_ver)
|
||||
|
||||
|
||||
def main(argv):
|
||||
"""
|
||||
Script entry point. Configures an option parser and calls the appropriate
|
||||
internal function.
|
||||
"""
|
||||
filename = os.path.join(os.path.dirname(__file__), 'index.txt')
|
||||
url = 'http://pypi.python.org/pypi'
|
||||
|
||||
parser = OptionParser(
|
||||
description='Generates a restructured document of pytest plugins from PyPI')
|
||||
parser.add_option('-f', '--filename', default=filename,
|
||||
help='output filename [default: %default]')
|
||||
parser.add_option('-u', '--url', default=url,
|
||||
help='url of PyPI server to obtain data from [default: %default]')
|
||||
parser.add_option('-v', '--verbose', default=False, action='store_true',
|
||||
help='verbose output')
|
||||
parser.add_option('--pytest-ver', default=None, action='store',
|
||||
help='generate index for this pytest version (default current version)')
|
||||
(options, _) = parser.parse_args(argv[1:])
|
||||
|
||||
client = get_proxy(options.url)
|
||||
generate_plugins_index(client, options.filename, options.verbose, options.pytest_ver)
|
||||
|
||||
print()
|
||||
print('%s updated.' % options.filename)
|
||||
return 0
|
||||
|
||||
|
||||
# header for the plugins_index page
|
||||
HEADER = '''.. _plugins_index:
|
||||
|
||||
List of Third-Party Plugins
|
||||
===========================
|
||||
|
||||
The table below contains a listing of plugins found in PyPI and
|
||||
their status when tested when using latest py.test and python versions.
|
||||
|
||||
A complete listing can also be found at
|
||||
`plugincompat <http://plugincompat.herokuapp.com/>`_, which contains tests
|
||||
status against other py.test releases.
|
||||
'''
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv))
|
||||
@@ -57,7 +57,7 @@ Here are some examples of projects using ``pytest`` (please send notes via :ref:
|
||||
* `bu <http://packages.python.org/bu/>`_ a microscopic build system
|
||||
* `katcp <https://bitbucket.org/hodgestar/katcp>`_ Telescope communication protocol over Twisted
|
||||
* `kss plugin timer <http://pypi.python.org/pypi/kss.plugin.timer>`_
|
||||
* `pyudev <http://pyudev.readthedocs.org/en/latest/tests/plugins.html>`_ a pure Python binding to the Linux library libudev
|
||||
* `pyudev <https://pyudev.readthedocs.io/en/latest/tests/plugins.html>`_ a pure Python binding to the Linux library libudev
|
||||
* `pytest-localserver <https://bitbucket.org/basti/pytest-localserver/>`_ a plugin for pytest that provides a httpserver and smtpserver
|
||||
* `pytest-monkeyplus <http://pypi.python.org/pypi/pytest-monkeyplus/>`_ a plugin that extends monkeypatch
|
||||
|
||||
|
||||
@@ -41,6 +41,10 @@ additional information::
|
||||
Alternatively, you can examine raised warnings in detail using the
|
||||
:ref:`recwarn <recwarn>` fixture (see below).
|
||||
|
||||
.. note::
|
||||
``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated
|
||||
differently; see :ref:`ensuring_function_triggers`.
|
||||
|
||||
.. _recwarn:
|
||||
|
||||
Recording warnings
|
||||
@@ -87,6 +91,9 @@ Each recorded warning has the attributes ``message``, ``category``,
|
||||
class of the warning. The ``message`` is the warning itself; calling
|
||||
``str(message)`` will return the actual message of the warning.
|
||||
|
||||
.. note::
|
||||
``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated
|
||||
differently; see :ref:`ensuring_function_triggers`.
|
||||
|
||||
.. _ensuring_function_triggers:
|
||||
|
||||
@@ -94,16 +101,17 @@ Ensuring a function triggers a deprecation warning
|
||||
-------------------------------------------------------
|
||||
|
||||
You can also call a global helper for checking
|
||||
that a certain function call triggers a ``DeprecationWarning``::
|
||||
that a certain function call triggers a ``DeprecationWarning`` or
|
||||
``PendingDeprecationWarning``::
|
||||
|
||||
import pytest
|
||||
|
||||
def test_global():
|
||||
pytest.deprecated_call(myfunction, 17)
|
||||
|
||||
By default, deprecation warnings will not be caught when using ``pytest.warns``
|
||||
or ``recwarn``, since the default Python warnings filters hide
|
||||
DeprecationWarnings. If you wish to record them in your own code, use the
|
||||
By default, ``DeprecationWarning`` and ``PendingDeprecationWarning`` will not be
|
||||
caught when using ``pytest.warns`` or ``recwarn`` because default Python warnings filters hide
|
||||
them. If you wish to record them in your own code, use the
|
||||
command ``warnings.simplefilter('always')``::
|
||||
|
||||
import warnings
|
||||
@@ -114,3 +122,9 @@ command ``warnings.simplefilter('always')``::
|
||||
warnings.warn("deprecated", DeprecationWarning)
|
||||
assert len(recwarn) == 1
|
||||
assert recwarn.pop(DeprecationWarning)
|
||||
|
||||
You can also use it as a contextmanager::
|
||||
|
||||
def test_global():
|
||||
with pytest.deprecated_call():
|
||||
myobject.deprecated_method()
|
||||
|
||||
@@ -29,8 +29,23 @@ corresponding to the "short" letters shown in the test progress::
|
||||
Marking a test function to be skipped
|
||||
-------------------------------------------
|
||||
|
||||
.. versionadded:: 2.9
|
||||
|
||||
The simplest way to skip a test function is to mark it with the ``skip`` decorator
|
||||
which may be passed an optional ``reason``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.skip(reason="no way of currently testing this")
|
||||
def test_the_unknown():
|
||||
...
|
||||
|
||||
``skipif``
|
||||
~~~~~~~~~~
|
||||
|
||||
.. versionadded:: 2.0, 2.4
|
||||
|
||||
If you wish to skip something conditionally then you can use ``skipif`` instead.
|
||||
Here is an example of marking a test function to be skipped
|
||||
when run on a Python3.3 interpreter::
|
||||
|
||||
@@ -79,12 +94,10 @@ between test modules so it's no longer advertised as the primary method.
|
||||
Skip all test functions of a class or module
|
||||
---------------------------------------------
|
||||
|
||||
As with all function :ref:`marking <mark>` you can skip test functions at the
|
||||
`whole class- or module level`_. If your code targets python2.6 or above you
|
||||
use the skipif decorator (and any other marker) on classes::
|
||||
You can use the ``skipif`` decorator (and any other marker) on classes::
|
||||
|
||||
@pytest.mark.skipif(sys.platform != 'win32',
|
||||
reason="requires windows")
|
||||
@pytest.mark.skipif(sys.platform == 'win32',
|
||||
reason="does not run on windows")
|
||||
class TestPosixCalls:
|
||||
|
||||
def test_function(self):
|
||||
@@ -93,19 +106,6 @@ use the skipif decorator (and any other marker) on classes::
|
||||
If the condition is true, this marker will produce a skip result for
|
||||
each of the test methods.
|
||||
|
||||
If your code targets python2.5 where class-decorators are not available,
|
||||
you can set the ``pytestmark`` attribute of a class::
|
||||
|
||||
class TestPosixCalls:
|
||||
pytestmark = pytest.mark.skipif(sys.platform != 'win32',
|
||||
reason="requires Windows")
|
||||
|
||||
def test_function(self):
|
||||
"will not be setup or run under 'win32' platform"
|
||||
|
||||
As with the class-decorator, the ``pytestmark`` special name tells
|
||||
``pytest`` to apply it to each test function in the class.
|
||||
|
||||
If you want to skip all test functions of a module, you must use
|
||||
the ``pytestmark`` name on the global level:
|
||||
|
||||
@@ -125,7 +125,7 @@ Mark a test function as expected to fail
|
||||
-------------------------------------------------------
|
||||
|
||||
You can use the ``xfail`` marker to indicate that you
|
||||
expect the test to fail::
|
||||
expect a test to fail::
|
||||
|
||||
@pytest.mark.xfail
|
||||
def test_function():
|
||||
@@ -133,14 +133,36 @@ expect the test to fail::
|
||||
|
||||
This test will be run but no traceback will be reported
|
||||
when it fails. Instead terminal reporting will list it in the
|
||||
"expected to fail" or "unexpectedly passing" sections.
|
||||
"expected to fail" (``XFAIL``) or "unexpectedly passing" (``XPASS``) sections.
|
||||
|
||||
By specifying on the commandline::
|
||||
``strict`` parameter
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
pytest --runxfail
|
||||
.. versionadded:: 2.9
|
||||
|
||||
you can force the running and reporting of an ``xfail`` marked test
|
||||
as if it weren't marked at all.
|
||||
Both ``XFAIL`` and ``XPASS`` don't fail the test suite, unless the ``strict`` keyword-only
|
||||
parameter is passed as ``True``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.xfail(strict=True)
|
||||
def test_function():
|
||||
...
|
||||
|
||||
|
||||
This will make ``XPASS`` ("unexpectedly passing") results from this test to fail the test suite.
|
||||
|
||||
You can change the default value of the ``strict`` parameter using the
|
||||
``xfail_strict`` ini option:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[pytest]
|
||||
xfail_strict=true
|
||||
|
||||
|
||||
``reason`` parameter
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
As with skipif_ you can also mark your expectation of a failure
|
||||
on a particular platform::
|
||||
@@ -150,14 +172,51 @@ on a particular platform::
|
||||
def test_function():
|
||||
...
|
||||
|
||||
|
||||
``raises`` parameter
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you want to be more specific as to why the test is failing, you can specify
|
||||
a single exception, or a list of exceptions, in the ``raises`` argument. Then
|
||||
the test will be reported as a regular failure if it fails with an
|
||||
a single exception, or a list of exceptions, in the ``raises`` argument.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.xfail(raises=RuntimeError)
|
||||
def test_function():
|
||||
...
|
||||
|
||||
Then the test will be reported as a regular failure if it fails with an
|
||||
exception not mentioned in ``raises``.
|
||||
|
||||
You can furthermore prevent the running of an "xfail" test or
|
||||
specify a reason such as a bug ID or similar. Here is
|
||||
a simple test file with the several usages:
|
||||
``run`` parameter
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
If a test should be marked as xfail and reported as such but should not be
|
||||
even executed, use the ``run`` parameter as ``False``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.xfail(run=False)
|
||||
def test_function():
|
||||
...
|
||||
|
||||
This is specially useful for marking crashing tests for later inspection.
|
||||
|
||||
|
||||
Ignoring xfail marks
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
By specifying on the commandline::
|
||||
|
||||
pytest --runxfail
|
||||
|
||||
you can force the running and reporting of an ``xfail`` marked test
|
||||
as if it weren't marked at all.
|
||||
|
||||
Examples
|
||||
~~~~~~~~
|
||||
|
||||
Here is a simple test file with the several usages:
|
||||
|
||||
.. literalinclude:: example/xfail_demo.py
|
||||
|
||||
@@ -165,7 +224,7 @@ Running it with the report-on-xfail option gives this output::
|
||||
|
||||
example $ py.test -rx xfail_demo.py
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.1, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR/example, inifile:
|
||||
collected 7 items
|
||||
|
||||
@@ -186,6 +245,18 @@ Running it with the report-on-xfail option gives this output::
|
||||
|
||||
======= 7 xfailed in 0.12 seconds ========
|
||||
|
||||
xfail signature summary
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Here's the signature of the ``xfail`` marker, using Python 3 keyword-only
|
||||
arguments syntax:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def xfail(condition=None, *, reason=None, raises=None, run=True, strict=False):
|
||||
|
||||
|
||||
|
||||
.. _`skip/xfail with parametrize`:
|
||||
|
||||
Skip/xfail with parametrize
|
||||
@@ -194,19 +265,19 @@ Skip/xfail with parametrize
|
||||
It is possible to apply markers like skip and xfail to individual
|
||||
test instances when using parametrize::
|
||||
|
||||
import pytest
|
||||
import pytest
|
||||
|
||||
@pytest.mark.parametrize(("n", "expected"), [
|
||||
(1, 2),
|
||||
pytest.mark.xfail((1, 0)),
|
||||
pytest.mark.xfail(reason="some bug")((1, 3)),
|
||||
(2, 3),
|
||||
(3, 4),
|
||||
(4, 5),
|
||||
pytest.mark.skipif("sys.version_info >= (3,0)")((10, 11)),
|
||||
])
|
||||
def test_increment(n, expected):
|
||||
assert n + 1 == expected
|
||||
@pytest.mark.parametrize(("n", "expected"), [
|
||||
(1, 2),
|
||||
pytest.mark.xfail((1, 0)),
|
||||
pytest.mark.xfail(reason="some bug")((1, 3)),
|
||||
(2, 3),
|
||||
(3, 4),
|
||||
(4, 5),
|
||||
pytest.mark.skipif("sys.version_info >= (3,0)")((10, 11)),
|
||||
])
|
||||
def test_increment(n, expected):
|
||||
assert n + 1 == expected
|
||||
|
||||
|
||||
Imperative xfail from within a test or setup function
|
||||
|
||||
@@ -4,7 +4,7 @@ Talks and Tutorials
|
||||
|
||||
.. sidebar:: Next Open Trainings
|
||||
|
||||
`professional testing with pytest and tox <http://www.python-academy.com/courses/specialtopics/python_course_testing.html>`_, 24-26th November 2014, Freiburg, Germany
|
||||
`professional testing with pytest and tox <http://www.python-academy.com/courses/specialtopics/python_course_testing.html>`_, 27-29th June 2016, Freiburg, Germany
|
||||
|
||||
.. _`funcargs`: funcargs.html
|
||||
|
||||
@@ -14,6 +14,12 @@ Talks and blog postings
|
||||
.. _`tutorial1 repository`: http://bitbucket.org/pytest-dev/pytest-tutorial1/
|
||||
.. _`pycon 2010 tutorial PDF`: http://bitbucket.org/pytest-dev/pytest-tutorial1/raw/tip/pytest-basic.pdf
|
||||
|
||||
- `pytest - Rapid Simple Testing, Florian Bruhin, Swiss Python Summit 2016
|
||||
<https://www.youtube.com/watch?v=rCBHkQ_LVIs>`_.
|
||||
|
||||
- `Improve your testing with Pytest and Mock, Gabe Hollombe, PyCon SG 2015
|
||||
<https://www.youtube.com/watch?v=RcN26hznmk4>`_.
|
||||
|
||||
- `Introduction to pytest, Andreas Pelme, EuroPython 2014
|
||||
<https://www.youtube.com/watch?v=LdVJj65ikRY>`_.
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ but note that project specific settings will be considered
|
||||
first. There is a flag that helps you debugging your
|
||||
conftest.py configurations::
|
||||
|
||||
py.test --traceconfig
|
||||
py.test --trace-config
|
||||
|
||||
|
||||
customizing the collecting and running process
|
||||
|
||||
@@ -16,7 +16,7 @@ command line options
|
||||
display py lib version and import information.
|
||||
``-p name``
|
||||
early-load given plugin (multi-allowed).
|
||||
``--traceconfig``
|
||||
``--trace-config``
|
||||
trace considerations of conftest.py files.
|
||||
``--nomagic``
|
||||
don't reinterpret asserts, no traceback cutting.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user