Compare commits
369 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
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 | ||
|
|
cee828130c | ||
|
|
67236d6de3 | ||
|
|
470e4f9e91 | ||
|
|
0e55a8793f | ||
|
|
49d46a0059 | ||
|
|
616d8251f3 | ||
|
|
a24126effb | ||
|
|
8984177448 | ||
|
|
750442909c | ||
|
|
df874db817 | ||
|
|
00d0c74657 | ||
|
|
122980ecad | ||
|
|
fc0bd9412c | ||
|
|
5ff9a0ff54 | ||
|
|
25d74a5919 | ||
|
|
213dbe7a5f | ||
|
|
9e57954b03 | ||
|
|
1b5aa2868d | ||
|
|
eee24138b0 | ||
|
|
04545f8a54 | ||
|
|
d1628944a6 | ||
|
|
abc27f56fc | ||
|
|
771aef9ddb | ||
|
|
dc7153e33c | ||
|
|
5ec08d3081 | ||
|
|
61b8443723 | ||
|
|
ad0b8e31b8 | ||
|
|
f144666f8b | ||
|
|
4e94135d36 | ||
|
|
b71add27da | ||
|
|
cb58eaa611 | ||
|
|
0c05ca1fd5 | ||
|
|
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)
|
||||
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
|
||||
|
||||
|
||||
11
AUTHORS
11
AUTHORS
@@ -26,14 +26,18 @@ Daniel Grana
|
||||
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
|
||||
@@ -43,16 +47,19 @@ Jaap Broekhuizen
|
||||
Jan Balster
|
||||
Janne Vanhala
|
||||
Jason R. Coombs
|
||||
Joshua Bronson
|
||||
Jurko Gospodnetić
|
||||
Katarzyna Jachim
|
||||
Kevin Cox
|
||||
Lee Kamentsky
|
||||
Lukas Bednar
|
||||
Maciek Fijalkowski
|
||||
Maho
|
||||
Marc Schlaich
|
||||
Mark Abramowitz
|
||||
Markus Unterwaditzer
|
||||
Martijn Faassen
|
||||
Michael Aquilina
|
||||
Michael Birtwell
|
||||
Michael Droettboom
|
||||
Nicolas Delaby
|
||||
@@ -60,8 +67,10 @@ Pieter Mulder
|
||||
Piotr Banaszkiewicz
|
||||
Punyashloka Biswal
|
||||
Ralf Schmitt
|
||||
Raphael Pierzina
|
||||
Ronny Pfannschmidt
|
||||
Ross Lawley
|
||||
Ryan Wooden
|
||||
Samuele Pedroni
|
||||
Tom Viner
|
||||
Trevor Bekolay
|
||||
@@ -71,3 +80,5 @@ Eric Hunsberger
|
||||
Simon Gomizelj
|
||||
Russel Winder
|
||||
Ben Webb
|
||||
Alexei Kozlenok
|
||||
Cal Leeming
|
||||
|
||||
@@ -1,6 +1,199 @@
|
||||
2.8.3
|
||||
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 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 <http://pylib.readthedocs.org/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 <http://pylib.readthedocs.org>`_.
|
||||
|
||||
* ``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
|
||||
|
||||
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.
|
||||
@@ -26,10 +219,8 @@
|
||||
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.
|
||||
@@ -49,7 +240,7 @@
|
||||
Oliveira for the PR.
|
||||
|
||||
2.8.1
|
||||
-----
|
||||
=====
|
||||
|
||||
- fix #1034: Add missing nodeid on pytest_logwarning call in
|
||||
addhook. Thanks Simon Gomizelj for the PR.
|
||||
@@ -88,6 +279,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.
|
||||
@@ -95,7 +287,7 @@
|
||||
- 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
|
||||
@@ -152,10 +344,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.
|
||||
|
||||
@@ -184,7 +376,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
|
||||
@@ -280,20 +472,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.
|
||||
@@ -328,7 +520,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
|
||||
@@ -357,7 +549,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
|
||||
@@ -390,7 +582,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.
|
||||
@@ -449,7 +641,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).
|
||||
|
||||
@@ -460,7 +652,7 @@
|
||||
via postmortem debugging (almarklein).
|
||||
|
||||
2.6.4
|
||||
----------
|
||||
==========
|
||||
|
||||
- Improve assertion failure reporting on iterables, by using ndiff and
|
||||
pprint.
|
||||
@@ -489,7 +681,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.
|
||||
@@ -516,7 +708,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.
|
||||
@@ -545,7 +737,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.
|
||||
@@ -577,7 +769,7 @@
|
||||
Thanks Bruno Oliveira.
|
||||
|
||||
2.6
|
||||
-----------------------------------
|
||||
===================================
|
||||
|
||||
- Cache exceptions from fixtures according to their scope (issue 467).
|
||||
|
||||
@@ -682,7 +874,7 @@
|
||||
|
||||
|
||||
2.5.2
|
||||
-----------------------------------
|
||||
===================================
|
||||
|
||||
- fix issue409 -- better interoperate with cx_freeze by not
|
||||
trying to import from collections.abc which causes problems
|
||||
@@ -710,7 +902,7 @@
|
||||
|
||||
|
||||
2.5.1
|
||||
-----------------------------------
|
||||
===================================
|
||||
|
||||
- merge new documentation styling PR from Tobias Bieniek.
|
||||
|
||||
@@ -731,7 +923,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
|
||||
@@ -867,7 +1059,7 @@
|
||||
- fix verbose reporting for @mock'd test functions
|
||||
|
||||
v2.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)
|
||||
@@ -898,7 +1090,7 @@ v2.4.2
|
||||
config.do_configure() for plugin-compatibility
|
||||
|
||||
v2.4.1
|
||||
-----------------------------------
|
||||
===================================
|
||||
|
||||
- When using parser.addoption() unicode arguments to the
|
||||
"type" keyword should also be converted to the respective types.
|
||||
@@ -914,7 +1106,7 @@ v2.4.1
|
||||
- merge doc typo fixes, thanks Andy Dirnberger
|
||||
|
||||
v2.4
|
||||
-----------------------------------
|
||||
===================================
|
||||
|
||||
known incompatibilities:
|
||||
|
||||
@@ -1083,7 +1275,7 @@ Bug fixes:
|
||||
information at the end of a test run.
|
||||
|
||||
v2.3.5
|
||||
-----------------------------------
|
||||
===================================
|
||||
|
||||
- fix issue169: respect --tb=style with setup/teardown errors as well.
|
||||
|
||||
@@ -1148,7 +1340,7 @@ v2.3.5
|
||||
- fix issue266 - accept unicode in MarkEvaluator expressions
|
||||
|
||||
v2.3.4
|
||||
-----------------------------------
|
||||
===================================
|
||||
|
||||
- yielded test functions will now have autouse-fixtures active but
|
||||
cannot accept fixtures as funcargs - it's anyway recommended to
|
||||
@@ -1168,7 +1360,7 @@ v2.3.4
|
||||
method in a certain test class.
|
||||
|
||||
v2.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
|
||||
@@ -1200,7 +1392,7 @@ v2.3.3
|
||||
add a ``config.getoption(name)`` helper function for consistency.
|
||||
|
||||
v2.3.2
|
||||
-----------------------------------
|
||||
===================================
|
||||
|
||||
- fix issue208 and fix issue29 use new py version to avoid long pauses
|
||||
when printing tracebacks in long modules
|
||||
@@ -1233,7 +1425,7 @@ v2.3.2
|
||||
bits are properly distributed for maintainers who run pytest-own tests
|
||||
|
||||
v2.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
|
||||
@@ -1246,7 +1438,7 @@ v2.3.1
|
||||
pytest.mark.* usage.
|
||||
|
||||
v2.3.0
|
||||
-----------------------------------
|
||||
===================================
|
||||
|
||||
- fix issue202 - better automatic names for parametrized test functions
|
||||
- fix issue139 - introduce @pytest.fixture which allows direct scoping
|
||||
@@ -1325,7 +1517,7 @@ v2.3.0
|
||||
- py.test -vv will show all of assert comparisations instead of truncating
|
||||
|
||||
v2.2.4
|
||||
-----------------------------------
|
||||
===================================
|
||||
|
||||
- fix error message for rewritten assertions involving the % operator
|
||||
- fix issue 126: correctly match all invalid xml characters for junitxml
|
||||
@@ -1342,12 +1534,12 @@ v2.2.4
|
||||
- upgrade distribute_setup.py to 0.6.27
|
||||
|
||||
v2.2.3
|
||||
----------------------------------------
|
||||
========================================
|
||||
|
||||
- fix uploaded package to only include neccesary files
|
||||
|
||||
v2.2.2
|
||||
----------------------------------------
|
||||
========================================
|
||||
|
||||
- fix issue101: wrong args to unittest.TestCase test function now
|
||||
produce better output
|
||||
@@ -1367,7 +1559,7 @@ v2.2.2
|
||||
with distributed testing (no upgrade of pytest-xdist needed)
|
||||
|
||||
v2.2.1
|
||||
----------------------------------------
|
||||
========================================
|
||||
|
||||
- fix issue99 (in pytest and py) internallerrors with resultlog now
|
||||
produce better output - fixed by normalizing pytest_internalerror
|
||||
@@ -1384,7 +1576,7 @@ v2.2.1
|
||||
to Ralf Schmitt (fixed by depending on a more recent pylib)
|
||||
|
||||
v2.2.0
|
||||
----------------------------------------
|
||||
========================================
|
||||
|
||||
- fix issue90: introduce eager tearing down of test items so that
|
||||
teardown function are called earlier.
|
||||
@@ -1419,7 +1611,7 @@ v2.2.0
|
||||
- add support for skip properties on unittest classes and functions
|
||||
|
||||
v2.1.3
|
||||
----------------------------------------
|
||||
========================================
|
||||
|
||||
- fix issue79: assertion rewriting failed on some comparisons in boolops
|
||||
- correctly handle zero length arguments (a la pytest '')
|
||||
@@ -1428,7 +1620,7 @@ v2.1.3
|
||||
- fix issue77 / Allow assertrepr_compare hook to apply to a subset of tests
|
||||
|
||||
v2.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
|
||||
@@ -1438,7 +1630,7 @@ v2.1.2
|
||||
- don't try assertion rewriting on Jython, use reinterp
|
||||
|
||||
v2.1.1
|
||||
----------------------------------------------
|
||||
==============================================
|
||||
|
||||
- fix issue64 / pytest.set_trace now works within pytest_generate_tests hooks
|
||||
- fix issue60 / fix error conditions involving the creation of __pycache__
|
||||
@@ -1451,7 +1643,7 @@ v2.1.1
|
||||
- you can now build a man page with "cd doc ; make man"
|
||||
|
||||
v2.1.0
|
||||
----------------------------------------------
|
||||
==============================================
|
||||
|
||||
- fix issue53 call nosestyle setup functions with correct ordering
|
||||
- fix issue58 and issue59: new assertion code fixes
|
||||
@@ -1471,7 +1663,7 @@ v2.1.0
|
||||
- fix issue 35 - provide PDF doc version and download link from index page
|
||||
|
||||
v2.0.3
|
||||
----------------------------------------------
|
||||
==============================================
|
||||
|
||||
- fix issue38: nicer tracebacks on calls to hooks, particularly early
|
||||
configure/sessionstart ones
|
||||
@@ -1491,7 +1683,7 @@ v2.0.3
|
||||
- fix issue37: avoid invalid characters in junitxml's output
|
||||
|
||||
v2.0.2
|
||||
----------------------------------------------
|
||||
==============================================
|
||||
|
||||
- tackle issue32 - speed up test runs of very quick test functions
|
||||
by reducing the relative overhead
|
||||
@@ -1543,7 +1735,7 @@ v2.0.2
|
||||
- avoid std unittest assertion helper code in tracebacks (thanks Ronny)
|
||||
|
||||
v2.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
|
||||
@@ -1592,7 +1784,7 @@ v2.0.1
|
||||
mechanism, see the docs.
|
||||
|
||||
v2.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
|
||||
@@ -1637,7 +1829,7 @@ v2.0.0
|
||||
- fix strangeness: mark.* objects are now immutable, create new instances
|
||||
|
||||
v1.3.4
|
||||
----------------------------------------------
|
||||
==============================================
|
||||
|
||||
- fix issue111: improve install documentation for windows
|
||||
- fix issue119: fix custom collectability of __init__.py as a module
|
||||
@@ -1646,7 +1838,7 @@ v1.3.4
|
||||
- fix issue118: new --tb=native for presenting cpython-standard exceptions
|
||||
|
||||
v1.3.3
|
||||
----------------------------------------------
|
||||
==============================================
|
||||
|
||||
- fix issue113: assertion representation problem with triple-quoted strings
|
||||
(and possibly other cases)
|
||||
@@ -1661,10 +1853,9 @@ v1.3.3
|
||||
- remove trailing whitespace in all py/text distribution files
|
||||
|
||||
v1.3.2
|
||||
----------------------------------------------
|
||||
==============================================
|
||||
|
||||
New features
|
||||
++++++++++++++++++
|
||||
**New features**
|
||||
|
||||
- fix issue103: introduce py.test.raises as context manager, examples::
|
||||
|
||||
@@ -1699,8 +1890,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
|
||||
@@ -1736,10 +1926,9 @@ Bug fixes / Maintenance
|
||||
- ship distribute_setup.py version 0.6.13
|
||||
|
||||
v1.3.1
|
||||
---------------------------------------------
|
||||
=============================================
|
||||
|
||||
New features
|
||||
++++++++++++++++++
|
||||
**New features**
|
||||
|
||||
- issue91: introduce new py.test.xfail(reason) helper
|
||||
to imperatively mark a test as expected to fail. Can
|
||||
@@ -1777,8 +1966,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
|
||||
@@ -1808,7 +1996,7 @@ Fixes / Maintenance
|
||||
|
||||
|
||||
v1.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
|
||||
@@ -1873,7 +2061,7 @@ v1.3.0
|
||||
|
||||
|
||||
v1.2.0
|
||||
---------------------------------------------
|
||||
=============================================
|
||||
|
||||
- refined usage and options for "py.cleanup"::
|
||||
|
||||
@@ -1912,7 +2100,7 @@ v1.2.0
|
||||
- fix plugin links
|
||||
|
||||
v1.1.1
|
||||
---------------------------------------------
|
||||
=============================================
|
||||
|
||||
- moved dist/looponfailing from py.test core into a new
|
||||
separately released pytest-xdist plugin.
|
||||
@@ -1932,7 +2120,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
|
||||
@@ -1996,7 +2184,7 @@ v1.1.1
|
||||
|
||||
|
||||
v1.1.0
|
||||
---------------------------------------------
|
||||
=============================================
|
||||
|
||||
- introduce automatic plugin registration via 'pytest11'
|
||||
entrypoints via setuptools' pkg_resources.iter_entry_points
|
||||
@@ -2014,8 +2202,8 @@ v1.1.0
|
||||
- try harder to have deprecation warnings for py.compat.* accesses
|
||||
report a correct location
|
||||
|
||||
v1.0.2
|
||||
---------------------------------------------
|
||||
v1.0.3
|
||||
=============================================
|
||||
|
||||
* adjust and improve docs
|
||||
|
||||
@@ -2100,7 +2288,7 @@ v1.0.2
|
||||
* simplified internal localpath implementation
|
||||
|
||||
v1.0.2
|
||||
-------------------------------------------
|
||||
===========================================
|
||||
|
||||
* fixing packaging issues, triggered by fedora redhat packaging,
|
||||
also added doc, examples and contrib dirs to the tarball.
|
||||
@@ -2108,7 +2296,7 @@ v1.0.2
|
||||
* added a documentation link to the new django plugin.
|
||||
|
||||
v1.0.1
|
||||
-------------------------------------------
|
||||
===========================================
|
||||
|
||||
* added a 'pytest_nose' plugin which handles nose.SkipTest,
|
||||
nose-style function/method/generator setup/teardown and
|
||||
@@ -2142,13 +2330,13 @@ v1.0.1
|
||||
renamed some internal methods and argnames
|
||||
|
||||
v1.0.0
|
||||
-------------------------------------------
|
||||
===========================================
|
||||
|
||||
* more terse reporting try to show filesystem path relatively to current dir
|
||||
* improve xfail output a bit
|
||||
|
||||
v1.0.0b9
|
||||
-------------------------------------------
|
||||
===========================================
|
||||
|
||||
* cleanly handle and report final teardown of test setup
|
||||
|
||||
@@ -2182,7 +2370,7 @@ v1.0.0b9
|
||||
|
||||
|
||||
v1.0.0b8
|
||||
-------------------------------------------
|
||||
===========================================
|
||||
|
||||
* pytest_unittest-plugin is now enabled by default
|
||||
|
||||
@@ -2211,7 +2399,7 @@ v1.0.0b8
|
||||
thanks Radomir.
|
||||
|
||||
v1.0.0b7
|
||||
-------------------------------------------
|
||||
===========================================
|
||||
|
||||
* renamed py.test.xfail back to py.test.mark.xfail to avoid
|
||||
two ways to decorate for xfail
|
||||
@@ -2236,7 +2424,7 @@ v1.0.0b7
|
||||
* make __name__ == "__channelexec__" for remote_exec code
|
||||
|
||||
v1.0.0b3
|
||||
-------------------------------------------
|
||||
===========================================
|
||||
|
||||
* plugin classes are removed: one now defines
|
||||
hooks directly in conftest.py or global pytest_*.py
|
||||
@@ -2253,7 +2441,7 @@ v1.0.0b3
|
||||
|
||||
|
||||
v1.0.0b1
|
||||
-------------------------------------------
|
||||
===========================================
|
||||
|
||||
* introduced new "funcarg" setup method,
|
||||
see doc/test/funcarg.txt
|
||||
@@ -2277,7 +2465,7 @@ v1.0.0b1
|
||||
XXX lots of things missing here XXX
|
||||
|
||||
v0.9.2
|
||||
-------------------------------------------
|
||||
===========================================
|
||||
|
||||
* refined installation and metadata, created new setup.py,
|
||||
now based on setuptools/ez_setup (thanks to Ralf Schmitt
|
||||
@@ -2310,7 +2498,7 @@ v0.9.2
|
||||
* there now is a py.__version__ attribute
|
||||
|
||||
v0.9.1
|
||||
-------------------------------------------
|
||||
===========================================
|
||||
|
||||
This is a fairly complete list of v0.9.1, which can
|
||||
serve as a reference for developers.
|
||||
142
CONTRIBUTING.rst
142
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:
|
||||
|
||||
@@ -125,12 +81,88 @@ 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,6 +3,10 @@ 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
|
||||
@@ -27,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::
|
||||
|
||||
@@ -41,8 +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
|
||||
python plugins_index/plugins_index.py
|
||||
cd doc/en
|
||||
make html
|
||||
|
||||
Commit any changes before tagging the release.
|
||||
@@ -57,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:
|
||||
@@ -86,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.3'
|
||||
__version__ = '2.9.0'
|
||||
|
||||
@@ -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
|
||||
|
||||
79
_pytest/_code/_py2traceback.py
Normal file
79
_pytest/_code/_py2traceback.py
Normal file
@@ -0,0 +1,79 @@
|
||||
# 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:
|
||||
lines.append(' %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__
|
||||
795
_pytest/_code/code.py
Normal file
795
_pytest/_code/code.py
Normal file
@@ -0,0 +1,795 @@
|
||||
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 TracebackItem
|
||||
instance, and should return True when the item should be added
|
||||
to the Traceback, False when not
|
||||
|
||||
by default this removes all the TracebackItems 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/TracebackItem 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 excinfo.errisinstance(RuntimeError):
|
||||
if "maximum recursion depth exceeded" in str(excinfo.value):
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -155,11 +155,11 @@ class LFPlugin:
|
||||
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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
@@ -455,11 +456,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 +478,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 +781,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 +883,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 +902,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 +923,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 +951,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 +1012,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 +1167,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,12 @@
|
||||
""" 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):
|
||||
@@ -15,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",
|
||||
@@ -29,11 +32,20 @@ def pytest_collect_file(path, parent):
|
||||
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):
|
||||
@@ -79,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))
|
||||
@@ -98,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)
|
||||
@@ -118,7 +130,9 @@ 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):
|
||||
@@ -147,7 +161,7 @@ class DoctestTextfile(DoctestItem, pytest.Module):
|
||||
|
||||
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)
|
||||
@@ -182,7 +196,7 @@ class DoctestModule(pytest.Module):
|
||||
finder = doctest.DocTestFinder()
|
||||
optionflags = get_optionflags(self)
|
||||
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
|
||||
checker=_get_unicode_checker())
|
||||
checker=_get_checker())
|
||||
for test in finder.find(module, module.__name__):
|
||||
if test.examples: # skip empty doctests
|
||||
yield DoctestItem(test.name, self, runner, test)
|
||||
@@ -204,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,
|
||||
@@ -233,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():
|
||||
@@ -258,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')
|
||||
|
||||
@@ -282,13 +282,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,19 @@ 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
|
||||
|
||||
|
||||
def bin_xml_escape(arg):
|
||||
def repl(matchobj):
|
||||
i = ord(matchobj.group())
|
||||
@@ -52,122 +54,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_testnames(testreport.nodeid.split("::"))
|
||||
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 +145,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 +173,120 @@ 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_testnames(names):
|
||||
names = [x.replace(".py", "") for x in names if x != '()']
|
||||
names[0] = names[0].replace("/", '.')
|
||||
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 +312,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 +357,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']
|
||||
|
||||
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):
|
||||
|
||||
@@ -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,14 +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
|
||||
@@ -86,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
|
||||
|
||||
@@ -331,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
|
||||
@@ -384,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()
|
||||
@@ -405,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):
|
||||
@@ -447,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
|
||||
@@ -606,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(
|
||||
@@ -712,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)
|
||||
@@ -1198,10 +1203,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::
|
||||
@@ -1217,19 +1222,19 @@ def raises(expected_exception, *args, **kwargs):
|
||||
Lines of code after that, within the scope of the context manager will
|
||||
not be executed. For example::
|
||||
|
||||
>>> with raises(OSError) as err:
|
||||
>>> with raises(OSError) as exc_info:
|
||||
assert 1 == 1 # this will execute as expected
|
||||
raise OSError(errno.EEXISTS, 'directory exists')
|
||||
assert err.errno == errno.EEXISTS # this will not execute
|
||||
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 err:
|
||||
>>> with raises(OSError) as exc_info:
|
||||
assert 1 == 1 # this will execute as expected
|
||||
raise OSError(errno.EEXISTS, 'directory exists')
|
||||
|
||||
assert err.errno == errno.EEXISTS # this will now execute
|
||||
assert exc_info.value.errno == errno.EEXISTS # this will now execute
|
||||
|
||||
Or you can specify a callable by passing a to-be-called lambda::
|
||||
|
||||
@@ -1250,21 +1255,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
|
||||
@@ -1293,19 +1299,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):
|
||||
@@ -1313,7 +1319,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):
|
||||
@@ -1761,13 +1767,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:
|
||||
@@ -2019,7 +2026,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)
|
||||
|
||||
@@ -2162,14 +2169,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):
|
||||
@@ -469,7 +474,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
|
||||
@@ -38,18 +47,22 @@ def pytest_configure(config):
|
||||
"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
|
||||
@@ -147,23 +160,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 +279,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 +318,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')
|
||||
@@ -544,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):
|
||||
@@ -100,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:
|
||||
@@ -116,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,13 @@ Release announcements
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
|
||||
sprint2016
|
||||
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
|
||||
|
||||
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 <http://pylib.readthedocs.org/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 <http://pylib.readthedocs.org>`_.
|
||||
|
||||
* ``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
|
||||
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, 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.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, 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.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 items
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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,7 +5,7 @@ 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.
|
||||
@@ -17,8 +17,8 @@ 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
|
||||
@@ -80,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.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1
|
||||
run-last-failure: rerun last 2 failures
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 50 items
|
||||
@@ -121,7 +121,7 @@ of ``FF`` and dots)::
|
||||
|
||||
$ py.test --ff
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1
|
||||
run-last-failure: rerun last 2 failures first
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 50 items
|
||||
@@ -226,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.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 items
|
||||
|
||||
@@ -262,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.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, 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,10 +13,14 @@ Full pytest documentation
|
||||
overview
|
||||
apiref
|
||||
example/index
|
||||
plugins
|
||||
monkeypatch
|
||||
tmpdir
|
||||
capture
|
||||
recwarn
|
||||
cache
|
||||
plugins
|
||||
|
||||
contributing
|
||||
plugins_index/index
|
||||
talks
|
||||
|
||||
.. only:: html
|
||||
|
||||
@@ -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.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, 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.3, py-1.4.30, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
|
||||
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.3, py-1.4.30, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
|
||||
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.3, py-1.4.30, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
|
||||
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.3, py-1.4.30, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
|
||||
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.3, py-1.4.30, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
|
||||
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.3, py-1.4.30, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
|
||||
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.3, py-1.4.30, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
|
||||
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.3, py-1.4.30, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
|
||||
cachedir: .cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 4 items
|
||||
@@ -219,7 +219,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 +234,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 +350,7 @@ the test needs::
|
||||
|
||||
$ py.test -E stage2
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 items
|
||||
|
||||
@@ -362,7 +362,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.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 items
|
||||
|
||||
@@ -436,7 +436,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 +446,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 +462,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 +481,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.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 4 items
|
||||
|
||||
@@ -495,7 +495,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.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 4 items
|
||||
|
||||
@@ -547,7 +547,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.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 4 items
|
||||
|
||||
@@ -569,7 +569,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.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, 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,7 +27,7 @@ now execute the test specification::
|
||||
|
||||
nonpython $ py.test test_simple.yml
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
|
||||
collected 2 items
|
||||
|
||||
@@ -59,7 +59,7 @@ consulted when reporting in ``verbose`` mode::
|
||||
|
||||
nonpython $ py.test -v
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.3, py-1.4.30, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
|
||||
cachedir: .cache
|
||||
rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
|
||||
collecting ... collected 2 items
|
||||
@@ -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.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
|
||||
collected 2 items
|
||||
<YamlFile 'test_simple.yml'>
|
||||
<YamlItem 'hello'>
|
||||
<YamlItem 'ok'>
|
||||
|
||||
======= 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.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, 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.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, 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.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, 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.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, 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.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, 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
|
||||
|
||||
@@ -399,8 +399,8 @@ Running it results in some skips if we don't have all the python interpreters in
|
||||
. $ py.test -rs -q multipython.py
|
||||
ssssssssssss...ssssssssssss
|
||||
======= short test summary info ========
|
||||
SKIP [12] $REGENDOC_TMPDIR/CWD/multipython.py:22: 'python3.3' not found
|
||||
SKIP [12] $REGENDOC_TMPDIR/CWD/multipython.py:22: 'python2.6' not found
|
||||
SKIP [12] $REGENDOC_TMPDIR/CWD/multipython.py:23: 'python2.6' not found
|
||||
SKIP [12] $REGENDOC_TMPDIR/CWD/multipython.py:23: 'python3.3' not found
|
||||
3 passed, 24 skipped in 0.12 seconds
|
||||
|
||||
Indirect parametrization of optional implementations/imports
|
||||
@@ -448,7 +448,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.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 2 items
|
||||
|
||||
|
||||
@@ -82,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.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile: setup.cfg
|
||||
collected 2 items
|
||||
<Module 'check_myapp.py'>
|
||||
@@ -91,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::
|
||||
|
||||
@@ -128,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.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
|
||||
collected 3 items
|
||||
<Module 'CWD/pythoncollection.py'>
|
||||
@@ -138,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
|
||||
---------------------------------------------------------
|
||||
@@ -175,18 +175,18 @@ 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::
|
||||
|
||||
$ py.test --collect-only
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
|
||||
collected 0 items
|
||||
|
||||
======= in 0.12 seconds ========
|
||||
======= no tests ran 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.
|
||||
|
||||
|
||||
@@ -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.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, 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,7 +195,7 @@ 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>
|
||||
@@ -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:1296>:1: ValueError
|
||||
<0-codegen $PYTHON_PREFIX/lib/python3.4/site-packages/_pytest/python.py:1302>: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,7 +379,7 @@ 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>
|
||||
@@ -388,7 +388,7 @@ get on the terminal - we are working on that):
|
||||
> a,b = [1]
|
||||
E ValueError: need more than 1 value to unpack
|
||||
|
||||
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>
|
||||
@@ -463,7 +463,7 @@ get on the terminal - we are working on that):
|
||||
> a,b = l
|
||||
E ValueError: need more than 0 values to unpack
|
||||
|
||||
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.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, 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`:
|
||||
|
||||
@@ -156,7 +156,7 @@ 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.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 2 items
|
||||
|
||||
@@ -170,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.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 2 items
|
||||
|
||||
@@ -262,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.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, 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
|
||||
|
||||
@@ -286,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.3, py-1.4.30, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
|
||||
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.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, 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
|
||||
--------------------------
|
||||
@@ -313,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
|
||||
|
||||
@@ -332,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.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 3 items
|
||||
|
||||
@@ -341,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 setup test_some_are_slow.py::test_funcfast
|
||||
======= 3 passed in 0.12 seconds ========
|
||||
|
||||
incremental testing - test steps
|
||||
@@ -394,11 +394,14 @@ If we run this::
|
||||
|
||||
$ py.test -rx
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, 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 ________
|
||||
@@ -410,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``
|
||||
@@ -427,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::
|
||||
@@ -465,7 +465,7 @@ We can run this::
|
||||
|
||||
$ py.test
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 7 items
|
||||
|
||||
@@ -479,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: monkeypatch, recwarn, pytestconfig, record_xml_property, capsys, cache, tmpdir, capfd, tmpdir_factory
|
||||
available fixtures: cache, tmpdir_factory, capsys, pytestconfig, capfd, record_xml_property, recwarn, tmpdir, monkeypatch
|
||||
use 'py.test --fixtures [testpath]' for help on them.
|
||||
|
||||
$REGENDOC_TMPDIR/b/test_error.py:1
|
||||
@@ -569,7 +569,7 @@ and run them::
|
||||
|
||||
$ py.test test_module.py
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 2 items
|
||||
|
||||
@@ -660,7 +660,7 @@ and run it::
|
||||
|
||||
$ py.test -s test_module.py
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 3 items
|
||||
|
||||
@@ -748,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
|
||||
|
||||
@@ -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.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, 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.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 2 items
|
||||
|
||||
@@ -480,7 +480,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.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 10 items
|
||||
<Module 'test_anothersmtp.py'>
|
||||
@@ -497,7 +497,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 +531,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.3, py-1.4.30, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
|
||||
cachedir: .cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 2 items
|
||||
@@ -597,7 +597,7 @@ 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.3, py-1.4.30, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
|
||||
cachedir: .cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 8 items
|
||||
|
||||
@@ -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.3, imported from $PYTHON_PREFIX/lib/python3.4/site-packages/pytest.py
|
||||
This is pytest version 2.9.0, imported from $PYTHON_PREFIX/lib/python3.4/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.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 items
|
||||
|
||||
@@ -193,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,45 +152,21 @@ 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.
|
||||
|
||||
|
||||
Integrating with setuptools / ``python setup.py test`` / ``pytest-runner``
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
You can integrate test runs into your setuptools based project
|
||||
with pytest-runner.
|
||||
with the `pytest-runner <https://pypi.python.org/pypi/pytest-runner>`_ plugin.
|
||||
|
||||
Add this to ``setup.py`` file::
|
||||
Add this to ``setup.py`` file:
|
||||
|
||||
from distutils.core import setup
|
||||
# you can also import from setuptools
|
||||
.. code-block:: python
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
#...,
|
||||
@@ -195,7 +175,11 @@ Add this to ``setup.py`` file::
|
||||
#...,
|
||||
)
|
||||
|
||||
And create an alias into ``setup.cfg``file::
|
||||
|
||||
And create an alias into ``setup.cfg`` file:
|
||||
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[aliases]
|
||||
test=pytest
|
||||
@@ -210,59 +194,14 @@ required for calling the test command. You can also pass additional
|
||||
arguments to py.test such as your test directory or other
|
||||
options using ``--addopts``.
|
||||
|
||||
Integrating with setuptools / ``python setup.py test`` / ``genscript``
|
||||
----------------------------------------------------------------------
|
||||
|
||||
You can integrate test runs into your
|
||||
setuptools based project. Use the `genscript method`_
|
||||
to generate a standalone ``pytest`` script::
|
||||
Manual Integration
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
py.test --genscript=runtests.py
|
||||
If for some reason you don't want/can't use ``pytest-runner``, you can write
|
||||
your own setuptools Test command for invoking pytest.
|
||||
|
||||
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)
|
||||
|
||||
|
||||
setup(
|
||||
#...,
|
||||
cmdclass = {'test': PyTest},
|
||||
#...,
|
||||
)
|
||||
|
||||
If you now type::
|
||||
|
||||
python setup.py test
|
||||
|
||||
this will execute your tests using ``runtests.py``. 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.
|
||||
|
||||
|
||||
Integration with setuptools test commands
|
||||
----------------------------------------------------
|
||||
|
||||
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::
|
||||
.. code-block:: python
|
||||
|
||||
import sys
|
||||
|
||||
@@ -276,11 +215,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
|
||||
@@ -306,32 +240,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
|
||||
@@ -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.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, 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.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, 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.
|
||||
|
||||
@@ -201,7 +201,7 @@ 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:1413: got empty parameter set, function test_valid_string at $REGENDOC_TMPDIR/test_strings.py:1
|
||||
SKIP [1] $PYTHON_PREFIX/lib/python3.4/site-packages/_pytest/python.py:1419: got empty parameter set, 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
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 679 B |
Binary file not shown.
|
Before Width: | Height: | Size: 734 B |
@@ -1,336 +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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-allure-adaptor-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-allure-adaptor-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-ansible-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-ansible-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-asyncio-latest?py=py34&pytest=2.8.3 .. image:: github.png Pytest support for asyncio.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-asyncio-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-asyncio-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-autochecklog-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-autochecklog-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-bdd-latest?py=py34&pytest=2.8.3 .. image:: github.png BDD for pytest
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-bdd-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-bdd-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-beakerlib-latest?py=py34&pytest=2.8.3 `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.3 :target: http://plugincompat.herokuapp.com/output/pytest-beakerlib-latest?py=py34&pytest=2.8.3
|
||||
`pytest-beds <http://pypi.python.org/pypi/pytest-beds>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-beds-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-beds-latest?py=py34&pytest=2.8.3 .. image:: github.png Fixtures for testing Google Appengine (GAE) apps
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-beds-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-beds-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-bench-latest?py=py34&pytest=2.8.3 .. image:: github.png Benchmark utility that plugs into pytest.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-bench-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-bench-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-benchmark-latest?py=py34&pytest=2.8.3 .. image:: github.png A ``py.test`` fixture for benchmarking code.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-benchmark-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-benchmark-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-blockage-latest?py=py34&pytest=2.8.3 .. image:: github.png Disable network requests during a test run.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-blockage-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-blockage-latest?py=py34&pytest=2.8.3 :target: https://github.com/rob-b/pytest-blockage
|
||||
`pytest-blocker <http://pypi.python.org/pypi/pytest-blocker>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-blocker-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-blocker-latest?py=py34&pytest=2.8.3 .. image:: github.png pytest plugin to mark a test as blocker and skip all other tests
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-blocker-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-blocker-latest?py=py34&pytest=2.8.3 :target: http://github.com/EverythingMe/pytest-blocker
|
||||
`pytest-bpdb <http://pypi.python.org/pypi/pytest-bpdb>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-bpdb-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-bpdb-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-bpdb-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-browsermob-proxy-latest?py=py34&pytest=2.8.3 .. image:: github.png BrowserMob proxy plugin for py.test.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-browsermob-proxy-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-browsermob-proxy-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-bugzilla-latest?py=py34&pytest=2.8.3 .. image:: github.png py.test bugzilla integration plugin
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-bugzilla-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-bugzilla-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-marker-bugzilla-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-marker-bugzilla-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-remove-stale-bytecode-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-remove-stale-bytecode-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-cache-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-cache-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-cagoule-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-cagoule-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-capturelog-latest?py=py34&pytest=2.8.3 .. image:: bitbucket.png py.test plugin to capture log messages
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-capturelog-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-capturelog-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-django-casperjs-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-django-casperjs-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-catchlog-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-catchlog-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-circleci-latest?py=py34&pytest=2.8.3 .. image:: github.png py.test plugin for CircleCI
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-circleci-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-circleci-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-cloud-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-cloud-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-codecheckers-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-codecheckers-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-colordots-latest?py=py34&pytest=2.8.3 .. image:: github.png Colorizes the progress indicators
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-colordots-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-colordots-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-paste-config-latest?py=py34&pytest=2.8.3 ? Allow setting the path to a paste config file
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-paste-config-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-paste-config-latest?py=py34&pytest=2.8.3
|
||||
`pytest-config <http://pypi.python.org/pypi/pytest-config>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-config-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-config-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-config-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-contextfixture-latest?py=py34&pytest=2.8.3 .. image:: github.png Define pytest fixtures as context managers.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-contextfixture-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-contextfixture-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-couchdbkit-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-couchdbkit-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-cov-latest?py=py34&pytest=2.8.3 .. image:: github.png Pytest plugin for measuring coverage.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-cov-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-cov-latest?py=py34&pytest=2.8.3 :target: https://github.com/pytest-dev/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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-cover-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-cover-latest?py=py34&pytest=2.8.3 :target: https://github.com/ionelmc/pytest-cover
|
||||
`pytest-cpp <http://pypi.python.org/pypi/pytest-cpp>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-cpp-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-cpp-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-cpp-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-curl-report-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-curl-report-latest?py=py34&pytest=2.8.3 :target: https://bitbucket.org/pytest-dev/pytest-curl-report
|
||||
`pytest-data <http://pypi.python.org/pypi/pytest-data>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-data-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-data-latest?py=py34&pytest=2.8.3 .. image:: github.png Useful functions for managing data for pytest fixtures
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-data-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-data-latest?py=py34&pytest=2.8.3 :target: https://github.com/horejsek/python-pytest-data
|
||||
`pytest-datadir <http://pypi.python.org/pypi/pytest-datadir>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-datadir-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-datadir-latest?py=py34&pytest=2.8.3 .. image:: github.png pytest plugin for test data directories and files
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-datadir-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-datadir-latest?py=py34&pytest=2.8.3 :target: http://github.com/gabrielcnr/pytest-datadir
|
||||
`pytest-datafiles <http://pypi.python.org/pypi/pytest-datafiles>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-datafiles-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-datafiles-latest?py=py34&pytest=2.8.3 .. image:: github.png py.test plugin to create a 'tmpdir' containing predefined files/directories.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-datafiles-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-datafiles-latest?py=py34&pytest=2.8.3 :target: https://github.com/omarkohl/pytest-datafiles
|
||||
`pytest-dbfixtures <http://pypi.python.org/pypi/pytest-dbfixtures>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-dbfixtures-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-dbfixtures-latest?py=py34&pytest=2.8.3 .. image:: github.png Databases fixtures plugin for py.test.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-dbfixtures-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-dbfixtures-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-dbus-notification-latest?py=py34&pytest=2.8.3 .. image:: github.png D-BUS notifications for pytest results.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-dbus-notification-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-dbus-notification-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-describe-latest?py=py34&pytest=2.8.3 .. image:: github.png Describe-style plugin for pytest
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-describe-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-describe-latest?py=py34&pytest=2.8.3 :target: https://github.com/ropez/pytest-describe
|
||||
`pytest-diamond <http://pypi.python.org/pypi/pytest-diamond>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-diamond-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-diamond-latest?py=py34&pytest=2.8.3 .. image:: github.png pytest plugin for diamond
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-diamond-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-diamond-latest?py=py34&pytest=2.8.3 :target: https://github.com/python-diamond/pytest-diamond
|
||||
`pytest-diffeo <http://pypi.python.org/pypi/pytest-diffeo>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-diffeo-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-diffeo-latest?py=py34&pytest=2.8.3 .. image:: github.png Common py.test support for Diffeo packages
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-diffeo-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-diffeo-latest?py=py34&pytest=2.8.3 :target: https://github.com/diffeo/pytest-diffeo
|
||||
`pytest-disable <http://pypi.python.org/pypi/pytest-disable>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-disable-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-disable-latest?py=py34&pytest=2.8.3 .. image:: github.png pytest plugin to disable a test and skip it from testrun
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-disable-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-disable-latest?py=py34&pytest=2.8.3 :target: http://github.com/EverythingMe/pytest-disable
|
||||
`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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-django-sqlcounts-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-django-sqlcounts-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-django-sqlcount-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-django-sqlcount-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-django-haystack-latest?py=py34&pytest=2.8.3 .. image:: github.png Cleanup your Haystack indexes between tests
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-django-haystack-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-django-haystack-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-django-lite-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-django-lite-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-django-latest?py=py34&pytest=2.8.3 `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.3 :target: http://plugincompat.herokuapp.com/output/pytest-django-latest?py=py34&pytest=2.8.3
|
||||
`pytest-doc <http://pypi.python.org/pypi/pytest-doc>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-doc-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-doc-latest?py=py34&pytest=2.8.3 `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.3 :target: http://plugincompat.herokuapp.com/output/pytest-doc-latest?py=py34&pytest=2.8.3
|
||||
`pytest-docker-pexpect <http://pypi.python.org/pypi/pytest-docker-pexpect>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-docker-pexpect-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-docker-pexpect-latest?py=py34&pytest=2.8.3 .. image:: github.png pytest plugin for writing functional tests with pexpect and docker
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-docker-pexpect-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-docker-pexpect-latest?py=py34&pytest=2.8.3 :target: https://github.com/nvbn/pytest-docker-pexpect
|
||||
`pytest-dump2json <http://pypi.python.org/pypi/pytest-dump2json>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-dump2json-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-dump2json-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-dump2json-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-echo-latest?py=py34&pytest=2.8.3 `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.3 :target: http://plugincompat.herokuapp.com/output/pytest-echo-latest?py=py34&pytest=2.8.3
|
||||
`pytest-env <http://pypi.python.org/pypi/pytest-env>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-env-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-env-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-env-latest?py=py34&pytest=2.8.3 :target: https://github.com/MobileDynasty/pytest-env
|
||||
`pytest-envfiles <http://pypi.python.org/pypi/pytest-envfiles>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-envfiles-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-envfiles-latest?py=py34&pytest=2.8.3 .. image:: github.png A py.test plugin that parses environment files before running tests
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-envfiles-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-envfiles-latest?py=py34&pytest=2.8.3 :target: https://github.com/JonnyFunFun/pytest-envfiles
|
||||
`pytest-eradicate <http://pypi.python.org/pypi/pytest-eradicate>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-eradicate-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-eradicate-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-eradicate-latest?py=py34&pytest=2.8.3 :target: https://github.com/aequitas/pytest-eradicate
|
||||
`pytest-expect <http://pypi.python.org/pypi/pytest-expect>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-expect-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-expect-latest?py=py34&pytest=2.8.3 .. image:: github.png py.test plugin to store test expectations and mark tests based on them
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-expect-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-expect-latest?py=py34&pytest=2.8.3 :target: https://github.com/gsnedders/pytest-expect
|
||||
`pytest-factoryboy <http://pypi.python.org/pypi/pytest-factoryboy>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-factoryboy-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-factoryboy-latest?py=py34&pytest=2.8.3 .. image:: github.png Factory Boy support for pytest.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-factoryboy-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-factoryboy-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-poo-fail-latest?py=py34&pytest=2.8.3 .. image:: github.png Visualize your failed tests with poo
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-poo-fail-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-poo-fail-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-faker-latest?py=py34&pytest=2.8.3 .. image:: github.png Faker integration for pytest framework.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-faker-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-faker-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-faulthandler-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-faulthandler-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-fauxfactory-latest?py=py34&pytest=2.8.3 .. image:: github.png Integration of fauxfactory into pytest.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-fauxfactory-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-fauxfactory-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-figleaf-latest?py=py34&pytest=2.8.3 .. image:: bitbucket.png py.test figleaf coverage plugin
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-figleaf-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-figleaf-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-fixture-tools-latest?py=py34&pytest=2.8.3 ? Plugin for pytest which provides tools for fixtures
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-fixture-tools-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-fixture-tools-latest?py=py34&pytest=2.8.3
|
||||
`pytest-flake8 <http://pypi.python.org/pypi/pytest-flake8>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-flake8-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-flake8-latest?py=py34&pytest=2.8.3 .. image:: github.png pytest plugin to check FLAKE8 requirements
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-flake8-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-flake8-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-flakes-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-flakes-latest?py=py34&pytest=2.8.3 :target: https://github.com/fschulze/pytest-flakes
|
||||
`pytest-ignore-flaky <http://pypi.python.org/pypi/pytest-ignore-flaky>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-ignore-flaky-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-ignore-flaky-latest?py=py34&pytest=2.8.3 `link <http://pypi.python.org/pypi/pytest-ignore-flaky>`_ ignore failures from flaky tests (pytest plugin)
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-ignore-flaky-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-ignore-flaky-latest?py=py34&pytest=2.8.3
|
||||
`pytest-flask <http://pypi.python.org/pypi/pytest-flask>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-flask-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-flask-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-flask-latest?py=py34&pytest=2.8.3 :target: https://github.com/vitalk/pytest-flask
|
||||
`pytest-travis-fold <http://pypi.python.org/pypi/pytest-travis-fold>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-travis-fold-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-travis-fold-latest?py=py34&pytest=2.8.3 .. image:: github.png Folds captured output sections in Travis CI build log
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-travis-fold-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-travis-fold-latest?py=py34&pytest=2.8.3 :target: https://github.com/abusalimov/pytest-travis-fold
|
||||
`pytest-gitignore <http://pypi.python.org/pypi/pytest-gitignore>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-gitignore-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-gitignore-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-gitignore-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-greendots-latest?py=py34&pytest=2.8.3 ? Green progress dots
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-greendots-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-greendots-latest?py=py34&pytest=2.8.3
|
||||
`pytest-growl <http://pypi.python.org/pypi/pytest-growl>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-growl-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-growl-latest?py=py34&pytest=2.8.3 ? Growl notifications for pytest results.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-growl-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-growl-latest?py=py34&pytest=2.8.3
|
||||
`pytest-html <http://pypi.python.org/pypi/pytest-html>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-html-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-html-latest?py=py34&pytest=2.8.3 .. image:: github.png pytest plugin for generating HTML reports
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-html-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-html-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-httpbin-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-httpbin-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-httpretty-latest?py=py34&pytest=2.8.3 .. image:: github.png A thin wrapper of HTTPretty for pytest
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-httpretty-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-httpretty-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-incremental-latest?py=py34&pytest=2.8.3 `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.3 :target: http://plugincompat.herokuapp.com/output/pytest-incremental-latest?py=py34&pytest=2.8.3
|
||||
`pytest-instafail <http://pypi.python.org/pypi/pytest-instafail>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-instafail-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-instafail-latest?py=py34&pytest=2.8.3 .. image:: github.png py.test plugin to show failures instantly
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-instafail-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-instafail-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-ipdb-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-ipdb-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-ipynb-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-ipynb-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-isort-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-isort-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-jira-latest?py=py34&pytest=2.8.3 .. image:: github.png py.test JIRA integration plugin, using markers
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-jira-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-jira-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-knows-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-knows-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-konira-latest?py=py34&pytest=2.8.3 .. image:: github.png Run Konira DSL tests with py.test
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-konira-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-konira-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-localserver-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-localserver-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-markfiltration-latest?py=py34&pytest=2.8.3 .. image:: github.png UNKNOWN
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-markfiltration-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-markfiltration-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-marks-latest?py=py34&pytest=2.8.3 .. image:: github.png UNKNOWN
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-marks-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-marks-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-mccabe-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-mccabe-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-mock-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-mock-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-monkeyplus-latest?py=py34&pytest=2.8.3 .. image:: bitbucket.png pytest's monkeypatch subclass with extra functionalities
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-monkeyplus-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-monkeyplus-latest?py=py34&pytest=2.8.3 :target: http://bitbucket.org/hsoft/pytest-monkeyplus/
|
||||
`pytest-moto <http://pypi.python.org/pypi/pytest-moto>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-moto-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-moto-latest?py=py34&pytest=2.8.3 .. image:: github.png Fixtures for integration tests of AWS services,uses moto mocking library.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-moto-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-moto-latest?py=py34&pytest=2.8.3 :target: https://github.com/jotes/pytest-moto
|
||||
`pytest-mozwebqa <http://pypi.python.org/pypi/pytest-mozwebqa>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-mozwebqa-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-mozwebqa-latest?py=py34&pytest=2.8.3 .. image:: github.png Mozilla WebQA plugin for py.test.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-mozwebqa-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-mozwebqa-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-mpl-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-mpl-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-multihost-latest?py=py34&pytest=2.8.3 `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.3 :target: http://plugincompat.herokuapp.com/output/pytest-multihost-latest?py=py34&pytest=2.8.3
|
||||
`pytest-nocustom <http://pypi.python.org/pypi/pytest-nocustom>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-nocustom-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-nocustom-latest?py=py34&pytest=2.8.3 .. image:: github.png Run all tests without custom markers
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-nocustom-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-nocustom-latest?py=py34&pytest=2.8.3 :target: http://github.com/alyssabarela/pytest-nocustom
|
||||
`pytest-oerp <http://pypi.python.org/pypi/pytest-oerp>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-oerp-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-oerp-latest?py=py34&pytest=2.8.3 .. image:: github.png pytest plugin to test OpenERP modules
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-oerp-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-oerp-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-oot-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-oot-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-optional-latest?py=py34&pytest=2.8.3 .. image:: bitbucket.png include/exclude values of fixtures in pytest
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-optional-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-optional-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-ordering-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-ordering-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-osxnotify-latest?py=py34&pytest=2.8.3 .. image:: github.png OS X notifications for py.test results.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-osxnotify-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-osxnotify-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-pep257-latest?py=py34&pytest=2.8.3 ? py.test plugin for pep257
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-pep257-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-pep257-latest?py=py34&pytest=2.8.3
|
||||
`pytest-pep8 <http://pypi.python.org/pypi/pytest-pep8>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-pep8-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-pep8-latest?py=py34&pytest=2.8.3 .. image:: bitbucket.png pytest plugin to check PEP8 requirements
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-pep8-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-pep8-latest?py=py34&pytest=2.8.3 :target: https://bitbucket.org/pytest-dev/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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-pipeline-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-pipeline-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-poo-latest?py=py34&pytest=2.8.3 .. image:: github.png Visualize your crappy tests
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-poo-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-poo-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-proper-wheel-latest?py=py34&pytest=2.8.3 `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.3 :target: http://plugincompat.herokuapp.com/output/pytest-proper-wheel-latest?py=py34&pytest=2.8.3
|
||||
`pytest-purkinje <http://pypi.python.org/pypi/pytest-purkinje>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-purkinje-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-purkinje-latest?py=py34&pytest=2.8.3 .. image:: github.png py.test plugin for purkinje test runner
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-purkinje-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-purkinje-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-pycharm-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-pycharm-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-pydev-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-pydev-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-pylint-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-pylint-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-pyq-latest?py=py34&pytest=2.8.3 `link <http://pyq.enlnt.com>`_ Pytest fixture "q" for pyq
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-pyq-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-pyq-latest?py=py34&pytest=2.8.3
|
||||
`pytest-readme <http://pypi.python.org/pypi/pytest-readme>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-readme-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-readme-latest?py=py34&pytest=2.8.3 .. image:: github.png Test your README.md file
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-readme-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-readme-latest?py=py34&pytest=2.8.3 :target: https://github.com/boxed/pytest-readme
|
||||
`pytest-trepan <http://pypi.python.org/pypi/pytest-trepan>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-trepan-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-trepan-latest?py=py34&pytest=2.8.3 .. image:: github.png Pytest plugin for trepan debugger.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-trepan-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-trepan-latest?py=py34&pytest=2.8.3 :target: http://github.com/rocky/pytest-trepan
|
||||
`pytest-vw <http://pypi.python.org/pypi/pytest-vw>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-vw-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-vw-latest?py=py34&pytest=2.8.3 .. image:: github.png pytest-vw makes your failing test cases succeed under CI tools scrutiny
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-vw-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-vw-latest?py=py34&pytest=2.8.3 :target: https://github.com/The-Compiler/pytest-vw
|
||||
`pytest-rage <http://pypi.python.org/pypi/pytest-rage>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-rage-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-rage-latest?py=py34&pytest=2.8.3 .. image:: github.png pytest plugin to implement PEP712
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-rage-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-rage-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-smartcov-latest?py=py34&pytest=2.8.3 .. image:: github.png Smart coverage plugin for pytest.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-smartcov-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-smartcov-latest?py=py34&pytest=2.8.3 :target: https://github.com/carljm/pytest-smartcov/
|
||||
`pytest-spawner <http://pypi.python.org/pypi/pytest-spawner>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-spawner-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-spawner-latest?py=py34&pytest=2.8.3 `link <https://git.qe-infra.yandex-team.ru/users/dldmitry/repos/pytest-spawner/browse>`_ py.test plugin to spawn process and communicate with them.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-spawner-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-spawner-latest?py=py34&pytest=2.8.3
|
||||
`pytest-session2file <http://pypi.python.org/pypi/pytest-session2file>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-session2file-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-session2file-latest?py=py34&pytest=2.8.3 .. image:: github.png pytest-session2file (aka: pytest-session_to_file for v0.1.0 - v0.1.2) is a py.test plugin for capturing and saving to file the stdout of py.test.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-session2file-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-session2file-latest?py=py34&pytest=2.8.3 :target: https://github.com/BuhtigithuB/pytest-session2file
|
||||
`pytest-selenium <http://pypi.python.org/pypi/pytest-selenium>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-selenium-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-selenium-latest?py=py34&pytest=2.8.3 .. image:: github.png pytest plugin for Selenium
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-selenium-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-selenium-latest?py=py34&pytest=2.8.3 :target: https://github.com/davehunt/pytest-selenium
|
||||
`pytest-random <http://pypi.python.org/pypi/pytest-random>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-random-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-random-latest?py=py34&pytest=2.8.3 .. image:: github.png py.test plugin to randomize tests
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-random-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-random-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-sourceorder-latest?py=py34&pytest=2.8.3 `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.3 :target: http://plugincompat.herokuapp.com/output/pytest-sourceorder-latest?py=py34&pytest=2.8.3
|
||||
`pytest-wholenodeid <http://pypi.python.org/pypi/pytest-wholenodeid>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-wholenodeid-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-wholenodeid-latest?py=py34&pytest=2.8.3 .. image:: github.png pytest addon for displaying the whole node id for failures
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-wholenodeid-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-wholenodeid-latest?py=py34&pytest=2.8.3 :target: https://github.com/willkg/pytest-wholenodeid
|
||||
`pytest-translations <http://pypi.python.org/pypi/pytest-translations>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-translations-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-translations-latest?py=py34&pytest=2.8.3 .. image:: github.png Test your translation files
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-translations-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-translations-latest?py=py34&pytest=2.8.3 :target: https://github.com/thermondo/pytest-translations
|
||||
`pytest-raisesregexp <http://pypi.python.org/pypi/pytest-raisesregexp>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-raisesregexp-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-raisesregexp-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-raisesregexp-latest?py=py34&pytest=2.8.3 :target: https://github.com/Walkman/pytest_raisesregexp
|
||||
`pytest-rerunfailures <http://pypi.python.org/pypi/pytest-rerunfailures>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-rerunfailures-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-rerunfailures-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-rerunfailures-latest?py=py34&pytest=2.8.3 :target: https://github.com/klrmn/pytest-rerunfailures
|
||||
`pytest-trialtemp <http://pypi.python.org/pypi/pytest-trialtemp>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-trialtemp-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-trialtemp-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-trialtemp-latest?py=py34&pytest=2.8.3 :target: http://github.com/jerith/pytest-trialtemp
|
||||
`pytest-zap <http://pypi.python.org/pypi/pytest-zap>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-zap-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-zap-latest?py=py34&pytest=2.8.3 .. image:: github.png OWASP ZAP plugin for py.test.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-zap-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-zap-latest?py=py34&pytest=2.8.3 :target: https://github.com/davehunt/pytest-zap
|
||||
`pytest-stepwise <http://pypi.python.org/pypi/pytest-stepwise>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-stepwise-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-stepwise-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-stepwise-latest?py=py34&pytest=2.8.3 :target: https://github.com/nip3o/pytest-stepwise
|
||||
`pytest-session_to_file <http://pypi.python.org/pypi/pytest-session_to_file>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-session_to_file-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-session_to_file-latest?py=py34&pytest=2.8.3 `link <https://pypi.python.org/pypi/pytest-session_to_file>`_ pytest-session_to_file is a py.test plugin for capturing and saving to file the stdout of py.test.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-session_to_file-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-session_to_file-latest?py=py34&pytest=2.8.3
|
||||
`pytest-spec <http://pypi.python.org/pypi/pytest-spec>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-spec-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-spec-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-spec-latest?py=py34&pytest=2.8.3 :target: https://github.com/pchomik/pytest-spec
|
||||
`pytest-selenium <http://pypi.python.org/pypi/pytest-selenium>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-selenium-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-selenium-latest?py=py34&pytest=2.8.3 .. image:: github.png pytest plugin for Selenium
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-selenium-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-selenium-latest?py=py34&pytest=2.8.3 :target: https://github.com/davehunt/pytest-selenium
|
||||
`pytest-testmon <http://pypi.python.org/pypi/pytest-testmon>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-testmon-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-testmon-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-testmon-latest?py=py34&pytest=2.8.3 :target: https://github.com/tarpas/pytest-testmon/
|
||||
`pytest-variables <http://pypi.python.org/pypi/pytest-variables>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-variables-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-variables-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-variables-latest?py=py34&pytest=2.8.3 :target: https://github.com/davehunt/pytest-variables
|
||||
`pytest-runfailed <http://pypi.python.org/pypi/pytest-runfailed>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-runfailed-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-runfailed-latest?py=py34&pytest=2.8.3 .. image:: github.png implement a --failed option for pytest
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-runfailed-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-runfailed-latest?py=py34&pytest=2.8.3 :target: http://github.com/dmerejkowsky/pytest-runfailed
|
||||
`pytest-testmon <http://pypi.python.org/pypi/pytest-testmon>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-testmon-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-testmon-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-testmon-latest?py=py34&pytest=2.8.3 :target: https://github.com/tarpas/pytest-testmon/
|
||||
`pytest-stepwise <http://pypi.python.org/pypi/pytest-stepwise>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-stepwise-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-stepwise-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-stepwise-latest?py=py34&pytest=2.8.3 :target: https://github.com/nip3o/pytest-stepwise
|
||||
`pytest-xprocess <http://pypi.python.org/pypi/pytest-xprocess>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-xprocess-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-xprocess-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-xprocess-latest?py=py34&pytest=2.8.3 :target: http://bitbucket.org/hpk42/pytest-xprocess/
|
||||
`pytest-session2file <http://pypi.python.org/pypi/pytest-session2file>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-session2file-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-session2file-latest?py=py34&pytest=2.8.3 .. image:: github.png pytest-session2file (aka: pytest-session_to_file for v0.1.0 - v0.1.2) is a py.test plugin for capturing and saving to file the stdout of py.test.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-session2file-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-session2file-latest?py=py34&pytest=2.8.3 :target: https://github.com/BuhtigithuB/pytest_session2file
|
||||
`pytest-selenium <http://pypi.python.org/pypi/pytest-selenium>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-selenium-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-selenium-latest?py=py34&pytest=2.8.3 .. image:: github.png pytest plugin for Selenium
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-selenium-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-selenium-latest?py=py34&pytest=2.8.3 :target: https://github.com/davehunt/pytest-selenium
|
||||
`pytest-tornado <http://pypi.python.org/pypi/pytest-tornado>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-tornado-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-tornado-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-tornado-latest?py=py34&pytest=2.8.3 :target: https://github.com/eugeniy/pytest-tornado
|
||||
`pytest-ubersmith <http://pypi.python.org/pypi/pytest-ubersmith>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-ubersmith-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-ubersmith-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-ubersmith-latest?py=py34&pytest=2.8.3 :target: https://github.com/hivelocity/pytest-ubersmith
|
||||
`pytest-session2file <http://pypi.python.org/pypi/pytest-session2file>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-session2file-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-session2file-latest?py=py34&pytest=2.8.3 .. image:: github.png pytest-session2file (aka: pytest-session_to_file for v0.1.0 - v0.1.2) is a py.test plugin for capturing and saving to file the stdout of py.test.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-session2file-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-session2file-latest?py=py34&pytest=2.8.3 :target: https://github.com/BuhtigithuB/pytest_session2file
|
||||
`pytest-timeout <http://pypi.python.org/pypi/pytest-timeout>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-timeout-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-timeout-latest?py=py34&pytest=2.8.3 .. image:: bitbucket.png py.test plugin to abort hanging tests
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-timeout-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-timeout-latest?py=py34&pytest=2.8.3 :target: http://bitbucket.org/pytest-dev/pytest-timeout/
|
||||
`pytest-quickcheck <http://pypi.python.org/pypi/pytest-quickcheck>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-quickcheck-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-quickcheck-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-quickcheck-latest?py=py34&pytest=2.8.3 :target: https://bitbucket.org/pytest-dev/pytest-quickcheck
|
||||
`pytest-trello <http://pypi.python.org/pypi/pytest-trello>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-trello-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-trello-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-trello-latest?py=py34&pytest=2.8.3 :target: http://github.com/jlaska/pytest-trello
|
||||
`pytest-twisted <http://pypi.python.org/pypi/pytest-twisted>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-twisted-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-twisted-latest?py=py34&pytest=2.8.3 .. image:: github.png A twisted plugin for py.test.
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-twisted-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-twisted-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-sftpserver-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-sftpserver-latest?py=py34&pytest=2.8.3 :target: http://github.com/ulope/pytest-sftpserver/
|
||||
`pytest-yamlwsgi <http://pypi.python.org/pypi/pytest-yamlwsgi>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-yamlwsgi-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-yamlwsgi-latest?py=py34&pytest=2.8.3 ? Run tests against wsgi apps defined in yaml
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-yamlwsgi-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-yamlwsgi-latest?py=py34&pytest=2.8.3
|
||||
`pytest-watch <http://pypi.python.org/pypi/pytest-watch>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-watch-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-watch-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-watch-latest?py=py34&pytest=2.8.3 :target: http://github.com/joeyespo/pytest-watch
|
||||
`pytest-pythonpath <http://pypi.python.org/pypi/pytest-pythonpath>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-pythonpath-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-pythonpath-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-pythonpath-latest?py=py34&pytest=2.8.3 :target: https://github.com/bigsassy/pytest-pythonpath
|
||||
`pytest-watch <http://pypi.python.org/pypi/pytest-watch>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-watch-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-watch-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-watch-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-unmarked-latest?py=py34&pytest=2.8.3 .. image:: github.png Run only unmarked tests
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-unmarked-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-unmarked-latest?py=py34&pytest=2.8.3 :target: http://github.com/alyssa.barela/pytest-unmarked
|
||||
`pytest-sugar <http://pypi.python.org/pypi/pytest-sugar>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-sugar-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-sugar-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-sugar-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-qt-latest?py=py34&pytest=2.8.3 .. image:: github.png pytest support for PyQt and PySide applications
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-qt-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-qt-latest?py=py34&pytest=2.8.3 :target: http://github.com/pytest-dev/pytest-qt
|
||||
`pytest-xdist <http://pypi.python.org/pypi/pytest-xdist>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-xdist-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-xdist-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-xdist-latest?py=py34&pytest=2.8.3 :target: https://bitbucket.org/pytest-dev/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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-sugar-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-sugar-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-qt-latest?py=py34&pytest=2.8.3 .. image:: github.png pytest support for PyQt and PySide applications
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-qt-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-qt-latest?py=py34&pytest=2.8.3 :target: http://github.com/pytest-dev/pytest-qt
|
||||
`pytest-regtest <http://pypi.python.org/pypi/pytest-regtest>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-regtest-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-regtest-latest?py=py34&pytest=2.8.3 `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.3 :target: http://plugincompat.herokuapp.com/output/pytest-regtest-latest?py=py34&pytest=2.8.3
|
||||
`pytest-qt <http://pypi.python.org/pypi/pytest-qt>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-qt-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-qt-latest?py=py34&pytest=2.8.3 .. image:: github.png pytest support for PyQt and PySide applications
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-qt-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-qt-latest?py=py34&pytest=2.8.3 :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.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-runner-latest?py=py34&pytest=2.8.3 .. 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.3 :target: http://plugincompat.herokuapp.com/output/pytest-runner-latest?py=py34&pytest=2.8.3 :target: https://bitbucket.org/pytest-dev/pytest-runner
|
||||
`pytest-services <http://pypi.python.org/pypi/pytest-services>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-services-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-services-latest?py=py34&pytest=2.8.3 .. image:: github.png Services plugin for pytest testing framework
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-services-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-services-latest?py=py34&pytest=2.8.3 :target: https://github.com/pytest-dev/pytest-services
|
||||
`pytest-splinter <http://pypi.python.org/pypi/pytest-splinter>`_ .. image:: http://plugincompat.herokuapp.com/status/pytest-splinter-latest?py=py27&pytest=2.8.3 .. image:: http://plugincompat.herokuapp.com/status/pytest-splinter-latest?py=py34&pytest=2.8.3 .. image:: github.png Splinter plugin for pytest testing framework
|
||||
:target: http://plugincompat.herokuapp.com/output/pytest-splinter-latest?py=py27&pytest=2.8.3 :target: http://plugincompat.herokuapp.com/output/pytest-splinter-latest?py=py34&pytest=2.8.3 :target: https://github.com/pytest-dev/pytest-splinter
|
||||
|
||||
============================================================================================ ================================================================================================================ ================================================================================================================ ========================================================================================== ===================================================================================================================================================
|
||||
|
||||
*(Updated on 2015-10-17)*
|
||||
@@ -1,308 +0,0 @@
|
||||
"""
|
||||
Script to generate the file `index.rst` with information about
|
||||
pytest plugins taken directly from PyPI.
|
||||
|
||||
Usage:
|
||||
python plugins_index.py
|
||||
|
||||
This command will update `index.rst` 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 argparse import ArgumentParser
|
||||
from collections import namedtuple
|
||||
import datetime
|
||||
from distutils.version import LooseVersion
|
||||
import itertools
|
||||
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.rst')
|
||||
url = 'http://pypi.python.org/pypi'
|
||||
|
||||
parser = ArgumentParser(
|
||||
description='Generates a document with all pytest plugins from PyPI')
|
||||
parser.add_argument('-f', '--filename', default=filename,
|
||||
help='output filename [default: %default]')
|
||||
parser.add_argument('-u', '--url', default=url,
|
||||
help='url of PyPI server to obtain data from [default: %default]')
|
||||
parser.add_argument('-v', '--verbose', default=False, action='store_true',
|
||||
help='verbose output')
|
||||
parser.add_argument('version', default=None, action='store',
|
||||
help='generate index for this pytest version')
|
||||
options = parser.parse_args()
|
||||
|
||||
client = get_proxy(options.url)
|
||||
generate_plugins_index(client, options.filename, options.verbose,
|
||||
options.version)
|
||||
|
||||
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))
|
||||
@@ -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.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, 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
|
||||
|
||||
@@ -126,7 +126,7 @@ Specifying test exec environments in a conftest.py
|
||||
Instead of specifying command line options, you can
|
||||
put options values in a ``conftest.py`` file like this::
|
||||
|
||||
option_tx = ['ssh=myhost//python=python2.5', 'popen//python=python2.5']
|
||||
option_tx = ['ssh=myhost//python=python2.7', 'popen//python=python2.7']
|
||||
option_dist = True
|
||||
|
||||
Any commandline ``--tx`` specifications will add to the list of
|
||||
@@ -163,7 +163,7 @@ command line options
|
||||
|
||||
(default) no: run tests inprocess, don't distribute.
|
||||
``--tx=xspec``
|
||||
add a test execution environment. some examples: --tx popen//python=python2.5 --tx socket=192.168.1.102:8888 --tx ssh=user@codespeak.net//chdir=testcache
|
||||
add a test execution environment. some examples: --tx popen//python=python2.7 --tx socket=192.168.1.102:8888 --tx ssh=user@codespeak.net//chdir=testcache
|
||||
``-d``
|
||||
load-balance tests. shortcut for '--dist=load'
|
||||
``--rsyncdir=dir1``
|
||||
|
||||
@@ -29,7 +29,7 @@ Running this would result in a passed test except for the last
|
||||
|
||||
$ py.test test_tmpdir.py
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 items
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ the ``self.db`` values in the traceback::
|
||||
|
||||
$ py.test test_unittest_db.py
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.4.3, pytest-2.8.3, py-1.4.30, pluggy-0.3.1
|
||||
platform linux -- Python 3.4.0, pytest-2.9.0, py-1.4.31, pluggy-0.3.1
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 2 items
|
||||
|
||||
@@ -126,7 +126,7 @@ when writing the class-scoped fixture function above.
|
||||
autouse fixtures and accessing other fixtures
|
||||
-------------------------------------------------------------------
|
||||
|
||||
Although it's usually better to explicitely declare use of fixtures you need
|
||||
Although it's usually better to explicitly declare use of fixtures you need
|
||||
for a given test, you may sometimes want to have fixtures that are
|
||||
automatically used in a given context. After all, the traditional
|
||||
style of unittest-setup mandates the use of this implicit fixture writing
|
||||
|
||||
@@ -12,8 +12,7 @@ Calling pytest through ``python -m pytest``
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
If you use Python-2.5 or later you can invoke testing through the
|
||||
Python interpreter from the command line::
|
||||
You can invoke testing through the Python interpreter from the command line::
|
||||
|
||||
python -m pytest [...]
|
||||
|
||||
@@ -67,10 +66,13 @@ Examples for modifying traceback printing::
|
||||
py.test --showlocals # show local variables in tracebacks
|
||||
py.test -l # show local variables (shortcut)
|
||||
|
||||
py.test --tb=long # the default informative traceback formatting
|
||||
py.test --tb=native # the Python standard library formatting
|
||||
py.test --tb=short # a shorter traceback format
|
||||
py.test --tb=auto # (default) 'long' tracebacks for the first and last
|
||||
# entry, but 'short' style for the other entries
|
||||
py.test --tb=long # exhaustive, informative traceback formatting
|
||||
py.test --tb=short # shorter traceback format
|
||||
py.test --tb=line # only one line per failure
|
||||
py.test --tb=native # Python standard library formatting
|
||||
py.test --tb=no # no traceback at all
|
||||
|
||||
Dropping to PDB_ (Python Debugger) on failures
|
||||
-----------------------------------------------
|
||||
@@ -123,7 +125,7 @@ automatically disables its output capture when you enter PDB_ tracing:
|
||||
such.
|
||||
* Any later output produced within the same test will not be captured and will
|
||||
instead get sent directly to ``sys.stdout``. Note that this holds true even
|
||||
for test output occuring after you exit the interactive PDB_ tracing session
|
||||
for test output occurring after you exit the interactive PDB_ tracing session
|
||||
and continue with the regular test run.
|
||||
|
||||
.. versionadded: 2.4.0
|
||||
|
||||
@@ -16,7 +16,7 @@ reporting by calling `well specified hooks`_ of the following plugins:
|
||||
|
||||
* :ref:`builtin plugins`: loaded from pytest's internal ``_pytest`` directory.
|
||||
|
||||
* :ref:`external plugins <plugins_index>`: modules discovered through
|
||||
* :ref:`external plugins <extplugins>`: modules discovered through
|
||||
`setuptools entry points`_
|
||||
|
||||
* `conftest.py plugins`_: modules auto-discovered in test directories
|
||||
@@ -95,7 +95,7 @@ Here is how you might run it::
|
||||
python package directory (i.e. one containing an ``__init__.py``) then
|
||||
"import conftest" can be ambiguous because there might be other
|
||||
``conftest.py`` files as well on your PYTHONPATH or ``sys.path``.
|
||||
It is thus good practise for projects to either put ``conftest.py``
|
||||
It is thus good practice for projects to either put ``conftest.py``
|
||||
under a package scope or to never import anything from a
|
||||
conftest.py file.
|
||||
|
||||
@@ -109,8 +109,8 @@ If you want to write a plugin, there are many real-life examples
|
||||
you can copy from:
|
||||
|
||||
* a custom collection example plugin: :ref:`yaml plugin`
|
||||
* around 20 doc:`builtin plugins` which provide pytest's own functionality
|
||||
* many :ref:`external plugins <plugins_index>` providing additional features
|
||||
* around 20 :ref:`builtin plugins` which provide pytest's own functionality
|
||||
* many `external plugins <http://plugincompat.herokuapp.com>`_ providing additional features
|
||||
|
||||
All of these plugins implement the documented `well specified hooks`_
|
||||
to extend and add functionality.
|
||||
@@ -204,7 +204,7 @@ plugin. Given that you have an installed plugin you can enable the
|
||||
:py:class:`testdir <_pytest.pytester.Testdir>` fixture via specifying a
|
||||
command line option to include the pytester plugin (``-p pytester``) or
|
||||
by putting ``pytest_plugins = "pytester"`` into your test or
|
||||
``conftest.py`` file. You then will have a ``testdir`` fixure which you
|
||||
``conftest.py`` file. You then will have a ``testdir`` fixture which you
|
||||
can use like this::
|
||||
|
||||
# content of test_myplugin.py
|
||||
@@ -386,7 +386,7 @@ are expected.
|
||||
|
||||
For an example, see `newhooks.py`_ from :ref:`xdist`.
|
||||
|
||||
.. _`newhooks.py`: https://bitbucket.org/pytest-dev/pytest-xdist/src/52082f70e7dd04b00361091b8af906c60fd6700f/xdist/newhooks.py?at=default
|
||||
.. _`newhooks.py`: https://github.com/pytest-dev/pytest-xdist/blob/974bd566c599dc6a9ea291838c6f226197208b46/xdist/newhooks.py
|
||||
|
||||
|
||||
Optionally using hooks from 3rd party plugins
|
||||
@@ -485,12 +485,20 @@ Session related reporting hooks:
|
||||
.. autofunction:: pytest_itemcollected
|
||||
.. autofunction:: pytest_collectreport
|
||||
.. autofunction:: pytest_deselected
|
||||
.. autofunction:: pytest_report_header
|
||||
.. autofunction:: pytest_report_teststatus
|
||||
.. autofunction:: pytest_terminal_summary
|
||||
|
||||
And here is the central hook for reporting about
|
||||
test execution:
|
||||
|
||||
.. autofunction:: pytest_runtest_logreport
|
||||
|
||||
You can also use this hook to customize assertion representation for some
|
||||
types:
|
||||
|
||||
.. autofunction:: pytest_assertrepr_compare
|
||||
|
||||
|
||||
Debugging/Interaction hooks
|
||||
---------------------------
|
||||
@@ -501,7 +509,7 @@ reporting or interaction with exceptions:
|
||||
.. autofunction:: pytest_internalerror
|
||||
.. autofunction:: pytest_keyboard_interrupt
|
||||
.. autofunction:: pytest_exception_interact
|
||||
|
||||
.. autofunction:: pytest_enter_pdb
|
||||
|
||||
|
||||
Reference of objects involved in hooks
|
||||
@@ -542,7 +550,7 @@ Reference of objects involved in hooks
|
||||
.. autoclass:: _pytest.runner.TestReport()
|
||||
:members:
|
||||
|
||||
.. autoclass:: _pytest.core.CallOutcome()
|
||||
.. autoclass:: _pytest.vendored_packages.pluggy._CallOutcome()
|
||||
:members:
|
||||
|
||||
.. autofunction:: _pytest.config.get_plugin_manager()
|
||||
|
||||
@@ -61,16 +61,16 @@ a lot of I/O this can lead to considerable speed ups.
|
||||
Running tests in a Python subprocess
|
||||
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
To instantiate a Python-2.4 subprocess and send tests to it, you may type::
|
||||
To instantiate a Python-2.7 subprocess and send tests to it, you may type::
|
||||
|
||||
py.test -d --tx popen//python=python2.4
|
||||
py.test -d --tx popen//python=python2.7
|
||||
|
||||
This will start a subprocess which is run with the "python2.4"
|
||||
This will start a subprocess which is run with the "python2.7"
|
||||
Python interpreter, found in your system binary lookup path.
|
||||
|
||||
If you prefix the --tx option value like this::
|
||||
|
||||
py.test -d --tx 3*popen//python=python2.4
|
||||
py.test -d --tx 3*popen//python=python2.7
|
||||
|
||||
then three subprocesses would be created and the tests
|
||||
will be distributed to three subprocesses and run simultanously.
|
||||
@@ -170,7 +170,7 @@ For example, you could make running with three subprocesses your default::
|
||||
You can also add default environments like this::
|
||||
|
||||
[pytest]
|
||||
addopts = --tx ssh=myhost//python=python2.5 --tx ssh=myhost//python=python2.6
|
||||
addopts = --tx ssh=myhost//python=python2.7 --tx ssh=myhost//python=python2.6
|
||||
|
||||
and then just type::
|
||||
|
||||
|
||||
2
setup.py
2
setup.py
@@ -75,7 +75,7 @@ def main():
|
||||
# the following should be enabled for release
|
||||
install_requires=install_requires,
|
||||
extras_require=extras_require,
|
||||
packages=['_pytest', '_pytest.assertion', '_pytest.vendored_packages'],
|
||||
packages=['_pytest', '_pytest.assertion', '_pytest._code', '_pytest.vendored_packages'],
|
||||
py_modules=['pytest'],
|
||||
zip_safe=False,
|
||||
)
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import sys
|
||||
import py, pytest
|
||||
|
||||
import _pytest._code
|
||||
import py
|
||||
import pytest
|
||||
from _pytest.main import EXIT_NOTESTSCOLLECTED, EXIT_USAGEERROR
|
||||
|
||||
|
||||
@@ -197,7 +200,7 @@ class TestGeneralUsage:
|
||||
def test_chdir(self, testdir):
|
||||
testdir.tmpdir.join("py").mksymlinkto(py._pydir)
|
||||
p = testdir.tmpdir.join("main.py")
|
||||
p.write(py.code.Source("""
|
||||
p.write(_pytest._code.Source("""
|
||||
import sys, os
|
||||
sys.path.insert(0, '')
|
||||
import py
|
||||
@@ -450,19 +453,16 @@ class TestInvocationVariants:
|
||||
"*1 passed*",
|
||||
])
|
||||
|
||||
@pytest.mark.skipif("sys.version_info < (2,5)")
|
||||
def test_python_minus_m_invocation_ok(self, testdir):
|
||||
p1 = testdir.makepyfile("def test_hello(): pass")
|
||||
res = testdir.run(py.std.sys.executable, "-m", "pytest", str(p1))
|
||||
assert res.ret == 0
|
||||
|
||||
@pytest.mark.skipif("sys.version_info < (2,5)")
|
||||
def test_python_minus_m_invocation_fail(self, testdir):
|
||||
p1 = testdir.makepyfile("def test_fail(): 0/0")
|
||||
res = testdir.run(py.std.sys.executable, "-m", "pytest", str(p1))
|
||||
assert res.ret == 1
|
||||
|
||||
@pytest.mark.skipif("sys.version_info < (2,5)")
|
||||
def test_python_pytest_package(self, testdir):
|
||||
p1 = testdir.makepyfile("def test_pass(): pass")
|
||||
res = testdir.run(py.std.sys.executable, "-m", "pytest", str(p1))
|
||||
|
||||
163
testing/code/test_code.py
Normal file
163
testing/code/test_code.py
Normal file
@@ -0,0 +1,163 @@
|
||||
import sys
|
||||
|
||||
import _pytest._code
|
||||
import py
|
||||
import pytest
|
||||
|
||||
|
||||
def test_ne():
|
||||
code1 = _pytest._code.Code(compile('foo = "bar"', '', 'exec'))
|
||||
assert code1 == code1
|
||||
code2 = _pytest._code.Code(compile('foo = "baz"', '', 'exec'))
|
||||
assert code2 != code1
|
||||
|
||||
def test_code_gives_back_name_for_not_existing_file():
|
||||
name = 'abc-123'
|
||||
co_code = compile("pass\n", name, 'exec')
|
||||
assert co_code.co_filename == name
|
||||
code = _pytest._code.Code(co_code)
|
||||
assert str(code.path) == name
|
||||
assert code.fullsource is None
|
||||
|
||||
def test_code_with_class():
|
||||
class A:
|
||||
pass
|
||||
pytest.raises(TypeError, "_pytest._code.Code(A)")
|
||||
|
||||
if True:
|
||||
def x():
|
||||
pass
|
||||
|
||||
def test_code_fullsource():
|
||||
code = _pytest._code.Code(x)
|
||||
full = code.fullsource
|
||||
assert 'test_code_fullsource()' in str(full)
|
||||
|
||||
def test_code_source():
|
||||
code = _pytest._code.Code(x)
|
||||
src = code.source()
|
||||
expected = """def x():
|
||||
pass"""
|
||||
assert str(src) == expected
|
||||
|
||||
def test_frame_getsourcelineno_myself():
|
||||
def func():
|
||||
return sys._getframe(0)
|
||||
f = func()
|
||||
f = _pytest._code.Frame(f)
|
||||
source, lineno = f.code.fullsource, f.lineno
|
||||
assert source[lineno].startswith(" return sys._getframe(0)")
|
||||
|
||||
def test_getstatement_empty_fullsource():
|
||||
def func():
|
||||
return sys._getframe(0)
|
||||
f = func()
|
||||
f = _pytest._code.Frame(f)
|
||||
prop = f.code.__class__.fullsource
|
||||
try:
|
||||
f.code.__class__.fullsource = None
|
||||
assert f.statement == _pytest._code.Source("")
|
||||
finally:
|
||||
f.code.__class__.fullsource = prop
|
||||
|
||||
def test_code_from_func():
|
||||
co = _pytest._code.Code(test_frame_getsourcelineno_myself)
|
||||
assert co.firstlineno
|
||||
assert co.path
|
||||
|
||||
|
||||
|
||||
def test_builtin_patch_unpatch(monkeypatch):
|
||||
cpy_builtin = py.builtin.builtins
|
||||
comp = cpy_builtin.compile
|
||||
def mycompile(*args, **kwargs):
|
||||
return comp(*args, **kwargs)
|
||||
class Sub(AssertionError):
|
||||
pass
|
||||
monkeypatch.setattr(cpy_builtin, 'AssertionError', Sub)
|
||||
monkeypatch.setattr(cpy_builtin, 'compile', mycompile)
|
||||
_pytest._code.patch_builtins()
|
||||
assert cpy_builtin.AssertionError != Sub
|
||||
assert cpy_builtin.compile != mycompile
|
||||
_pytest._code.unpatch_builtins()
|
||||
assert cpy_builtin.AssertionError is Sub
|
||||
assert cpy_builtin.compile == mycompile
|
||||
|
||||
|
||||
def test_unicode_handling():
|
||||
value = py.builtin._totext('\xc4\x85\xc4\x87\n', 'utf-8').encode('utf8')
|
||||
def f():
|
||||
raise Exception(value)
|
||||
excinfo = pytest.raises(Exception, f)
|
||||
str(excinfo)
|
||||
if sys.version_info[0] < 3:
|
||||
unicode(excinfo)
|
||||
|
||||
def test_code_getargs():
|
||||
def f1(x):
|
||||
pass
|
||||
c1 = _pytest._code.Code(f1)
|
||||
assert c1.getargs(var=True) == ('x',)
|
||||
|
||||
def f2(x, *y):
|
||||
pass
|
||||
c2 = _pytest._code.Code(f2)
|
||||
assert c2.getargs(var=True) == ('x', 'y')
|
||||
|
||||
def f3(x, **z):
|
||||
pass
|
||||
c3 = _pytest._code.Code(f3)
|
||||
assert c3.getargs(var=True) == ('x', 'z')
|
||||
|
||||
def f4(x, *y, **z):
|
||||
pass
|
||||
c4 = _pytest._code.Code(f4)
|
||||
assert c4.getargs(var=True) == ('x', 'y', 'z')
|
||||
|
||||
|
||||
def test_frame_getargs():
|
||||
def f1(x):
|
||||
return sys._getframe(0)
|
||||
fr1 = _pytest._code.Frame(f1('a'))
|
||||
assert fr1.getargs(var=True) == [('x', 'a')]
|
||||
|
||||
def f2(x, *y):
|
||||
return sys._getframe(0)
|
||||
fr2 = _pytest._code.Frame(f2('a', 'b', 'c'))
|
||||
assert fr2.getargs(var=True) == [('x', 'a'), ('y', ('b', 'c'))]
|
||||
|
||||
def f3(x, **z):
|
||||
return sys._getframe(0)
|
||||
fr3 = _pytest._code.Frame(f3('a', b='c'))
|
||||
assert fr3.getargs(var=True) == [('x', 'a'), ('z', {'b': 'c'})]
|
||||
|
||||
def f4(x, *y, **z):
|
||||
return sys._getframe(0)
|
||||
fr4 = _pytest._code.Frame(f4('a', 'b', c='d'))
|
||||
assert fr4.getargs(var=True) == [('x', 'a'), ('y', ('b',)),
|
||||
('z', {'c': 'd'})]
|
||||
|
||||
|
||||
class TestExceptionInfo:
|
||||
|
||||
def test_bad_getsource(self):
|
||||
try:
|
||||
if False: pass
|
||||
else: assert False
|
||||
except AssertionError:
|
||||
exci = _pytest._code.ExceptionInfo()
|
||||
assert exci.getrepr()
|
||||
|
||||
|
||||
class TestTracebackEntry:
|
||||
|
||||
def test_getsource(self):
|
||||
try:
|
||||
if False: pass
|
||||
else: assert False
|
||||
except AssertionError:
|
||||
exci = _pytest._code.ExceptionInfo()
|
||||
entry = exci.traceback[0]
|
||||
source = entry.getsource()
|
||||
assert len(source) == 4
|
||||
assert 'else: assert False' in source[3]
|
||||
911
testing/code/test_excinfo.py
Normal file
911
testing/code/test_excinfo.py
Normal file
@@ -0,0 +1,911 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import _pytest
|
||||
import py
|
||||
import pytest
|
||||
from _pytest._code.code import FormattedExcinfo, ReprExceptionInfo
|
||||
|
||||
queue = py.builtin._tryimport('queue', 'Queue')
|
||||
|
||||
failsonjython = pytest.mark.xfail("sys.platform.startswith('java')")
|
||||
from test_source import astonly
|
||||
|
||||
try:
|
||||
import importlib
|
||||
except ImportError:
|
||||
invalidate_import_caches = None
|
||||
else:
|
||||
invalidate_import_caches = getattr(importlib, "invalidate_caches", None)
|
||||
|
||||
import pytest
|
||||
pytest_version_info = tuple(map(int, pytest.__version__.split(".")[:3]))
|
||||
|
||||
class TWMock:
|
||||
def __init__(self):
|
||||
self.lines = []
|
||||
def sep(self, sep, line=None):
|
||||
self.lines.append((sep, line))
|
||||
def line(self, line, **kw):
|
||||
self.lines.append(line)
|
||||
def markup(self, text, **kw):
|
||||
return text
|
||||
|
||||
fullwidth = 80
|
||||
|
||||
def test_excinfo_simple():
|
||||
try:
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
info = _pytest._code.ExceptionInfo()
|
||||
assert info.type == ValueError
|
||||
|
||||
def test_excinfo_getstatement():
|
||||
def g():
|
||||
raise ValueError
|
||||
def f():
|
||||
g()
|
||||
try:
|
||||
f()
|
||||
except ValueError:
|
||||
excinfo = _pytest._code.ExceptionInfo()
|
||||
linenumbers = [_pytest._code.getrawcode(f).co_firstlineno - 1 + 3,
|
||||
_pytest._code.getrawcode(f).co_firstlineno - 1 + 1,
|
||||
_pytest._code.getrawcode(g).co_firstlineno - 1 + 1, ]
|
||||
l = list(excinfo.traceback)
|
||||
foundlinenumbers = [x.lineno for x in l]
|
||||
assert foundlinenumbers == linenumbers
|
||||
#for x in info:
|
||||
# print "%s:%d %s" %(x.path.relto(root), x.lineno, x.statement)
|
||||
#xxx
|
||||
|
||||
# testchain for getentries test below
|
||||
def f():
|
||||
#
|
||||
raise ValueError
|
||||
#
|
||||
def g():
|
||||
#
|
||||
__tracebackhide__ = True
|
||||
f()
|
||||
#
|
||||
def h():
|
||||
#
|
||||
g()
|
||||
#
|
||||
|
||||
class TestTraceback_f_g_h:
|
||||
def setup_method(self, method):
|
||||
try:
|
||||
h()
|
||||
except ValueError:
|
||||
self.excinfo = _pytest._code.ExceptionInfo()
|
||||
|
||||
def test_traceback_entries(self):
|
||||
tb = self.excinfo.traceback
|
||||
entries = list(tb)
|
||||
assert len(tb) == 4 # maybe fragile test
|
||||
assert len(entries) == 4 # maybe fragile test
|
||||
names = ['f', 'g', 'h']
|
||||
for entry in entries:
|
||||
try:
|
||||
names.remove(entry.frame.code.name)
|
||||
except ValueError:
|
||||
pass
|
||||
assert not names
|
||||
|
||||
def test_traceback_entry_getsource(self):
|
||||
tb = self.excinfo.traceback
|
||||
s = str(tb[-1].getsource() )
|
||||
assert s.startswith("def f():")
|
||||
assert s.endswith("raise ValueError")
|
||||
|
||||
@astonly
|
||||
@failsonjython
|
||||
def test_traceback_entry_getsource_in_construct(self):
|
||||
source = _pytest._code.Source("""\
|
||||
def xyz():
|
||||
try:
|
||||
raise ValueError
|
||||
except somenoname:
|
||||
pass
|
||||
xyz()
|
||||
""")
|
||||
try:
|
||||
exec (source.compile())
|
||||
except NameError:
|
||||
tb = _pytest._code.ExceptionInfo().traceback
|
||||
print (tb[-1].getsource())
|
||||
s = str(tb[-1].getsource())
|
||||
assert s.startswith("def xyz():\n try:")
|
||||
assert s.strip().endswith("except somenoname:")
|
||||
|
||||
def test_traceback_cut(self):
|
||||
co = _pytest._code.Code(f)
|
||||
path, firstlineno = co.path, co.firstlineno
|
||||
traceback = self.excinfo.traceback
|
||||
newtraceback = traceback.cut(path=path, firstlineno=firstlineno)
|
||||
assert len(newtraceback) == 1
|
||||
newtraceback = traceback.cut(path=path, lineno=firstlineno+2)
|
||||
assert len(newtraceback) == 1
|
||||
|
||||
def test_traceback_cut_excludepath(self, testdir):
|
||||
p = testdir.makepyfile("def f(): raise ValueError")
|
||||
excinfo = pytest.raises(ValueError, "p.pyimport().f()")
|
||||
basedir = py.path.local(pytest.__file__).dirpath()
|
||||
newtraceback = excinfo.traceback.cut(excludepath=basedir)
|
||||
for x in newtraceback:
|
||||
if hasattr(x, 'path'):
|
||||
assert not py.path.local(x.path).relto(basedir)
|
||||
assert newtraceback[-1].frame.code.path == p
|
||||
|
||||
def test_traceback_filter(self):
|
||||
traceback = self.excinfo.traceback
|
||||
ntraceback = traceback.filter()
|
||||
assert len(ntraceback) == len(traceback) - 1
|
||||
|
||||
def test_traceback_recursion_index(self):
|
||||
def f(n):
|
||||
if n < 10:
|
||||
n += 1
|
||||
f(n)
|
||||
excinfo = pytest.raises(RuntimeError, f, 8)
|
||||
traceback = excinfo.traceback
|
||||
recindex = traceback.recursionindex()
|
||||
assert recindex == 3
|
||||
|
||||
def test_traceback_only_specific_recursion_errors(self, monkeypatch):
|
||||
def f(n):
|
||||
if n == 0:
|
||||
raise RuntimeError("hello")
|
||||
f(n-1)
|
||||
|
||||
excinfo = pytest.raises(RuntimeError, f, 100)
|
||||
monkeypatch.delattr(excinfo.traceback.__class__, "recursionindex")
|
||||
repr = excinfo.getrepr()
|
||||
assert "RuntimeError: hello" in str(repr.reprcrash)
|
||||
|
||||
def test_traceback_no_recursion_index(self):
|
||||
def do_stuff():
|
||||
raise RuntimeError
|
||||
def reraise_me():
|
||||
import sys
|
||||
exc, val, tb = sys.exc_info()
|
||||
py.builtin._reraise(exc, val, tb)
|
||||
def f(n):
|
||||
try:
|
||||
do_stuff()
|
||||
except:
|
||||
reraise_me()
|
||||
excinfo = pytest.raises(RuntimeError, f, 8)
|
||||
traceback = excinfo.traceback
|
||||
recindex = traceback.recursionindex()
|
||||
assert recindex is None
|
||||
|
||||
def test_traceback_messy_recursion(self):
|
||||
#XXX: simplified locally testable version
|
||||
decorator = pytest.importorskip('decorator').decorator
|
||||
|
||||
def log(f, *k, **kw):
|
||||
print('%s %s' % (k, kw))
|
||||
f(*k, **kw)
|
||||
log = decorator(log)
|
||||
|
||||
def fail():
|
||||
raise ValueError('')
|
||||
|
||||
fail = log(log(fail))
|
||||
|
||||
excinfo = pytest.raises(ValueError, fail)
|
||||
assert excinfo.traceback.recursionindex() is None
|
||||
|
||||
|
||||
|
||||
def test_traceback_getcrashentry(self):
|
||||
def i():
|
||||
__tracebackhide__ = True
|
||||
raise ValueError
|
||||
def h():
|
||||
i()
|
||||
def g():
|
||||
__tracebackhide__ = True
|
||||
h()
|
||||
def f():
|
||||
g()
|
||||
|
||||
excinfo = pytest.raises(ValueError, f)
|
||||
tb = excinfo.traceback
|
||||
entry = tb.getcrashentry()
|
||||
co = _pytest._code.Code(h)
|
||||
assert entry.frame.code.path == co.path
|
||||
assert entry.lineno == co.firstlineno + 1
|
||||
assert entry.frame.code.name == 'h'
|
||||
|
||||
def test_traceback_getcrashentry_empty(self):
|
||||
def g():
|
||||
__tracebackhide__ = True
|
||||
raise ValueError
|
||||
def f():
|
||||
__tracebackhide__ = True
|
||||
g()
|
||||
|
||||
excinfo = pytest.raises(ValueError, f)
|
||||
tb = excinfo.traceback
|
||||
entry = tb.getcrashentry()
|
||||
co = _pytest._code.Code(g)
|
||||
assert entry.frame.code.path == co.path
|
||||
assert entry.lineno == co.firstlineno + 2
|
||||
assert entry.frame.code.name == 'g'
|
||||
|
||||
def hello(x):
|
||||
x + 5
|
||||
|
||||
def test_tbentry_reinterpret():
|
||||
try:
|
||||
hello("hello")
|
||||
except TypeError:
|
||||
excinfo = _pytest._code.ExceptionInfo()
|
||||
tbentry = excinfo.traceback[-1]
|
||||
msg = tbentry.reinterpret()
|
||||
assert msg.startswith("TypeError: ('hello' + 5)")
|
||||
|
||||
def test_excinfo_exconly():
|
||||
excinfo = pytest.raises(ValueError, h)
|
||||
assert excinfo.exconly().startswith('ValueError')
|
||||
excinfo = pytest.raises(ValueError,
|
||||
"raise ValueError('hello\\nworld')")
|
||||
msg = excinfo.exconly(tryshort=True)
|
||||
assert msg.startswith('ValueError')
|
||||
assert msg.endswith("world")
|
||||
|
||||
def test_excinfo_repr():
|
||||
excinfo = pytest.raises(ValueError, h)
|
||||
s = repr(excinfo)
|
||||
assert s == "<ExceptionInfo ValueError tblen=4>"
|
||||
|
||||
def test_excinfo_str():
|
||||
excinfo = pytest.raises(ValueError, h)
|
||||
s = str(excinfo)
|
||||
assert s.startswith(__file__[:-9]) # pyc file and $py.class
|
||||
assert s.endswith("ValueError")
|
||||
assert len(s.split(":")) >= 3 # on windows it's 4
|
||||
|
||||
def test_excinfo_errisinstance():
|
||||
excinfo = pytest.raises(ValueError, h)
|
||||
assert excinfo.errisinstance(ValueError)
|
||||
|
||||
def test_excinfo_no_sourcecode():
|
||||
try:
|
||||
exec ("raise ValueError()")
|
||||
except ValueError:
|
||||
excinfo = _pytest._code.ExceptionInfo()
|
||||
s = str(excinfo.traceback[-1])
|
||||
if py.std.sys.version_info < (2,5):
|
||||
assert s == " File '<string>':1 in ?\n ???\n"
|
||||
else:
|
||||
assert s == " File '<string>':1 in <module>\n ???\n"
|
||||
|
||||
def test_excinfo_no_python_sourcecode(tmpdir):
|
||||
#XXX: simplified locally testable version
|
||||
tmpdir.join('test.txt').write("{{ h()}}:")
|
||||
|
||||
jinja2 = pytest.importorskip('jinja2')
|
||||
loader = jinja2.FileSystemLoader(str(tmpdir))
|
||||
env = jinja2.Environment(loader=loader)
|
||||
template = env.get_template('test.txt')
|
||||
excinfo = pytest.raises(ValueError,
|
||||
template.render, h=h)
|
||||
for item in excinfo.traceback:
|
||||
print(item) #XXX: for some reason jinja.Template.render is printed in full
|
||||
item.source # shouldnt fail
|
||||
if item.path.basename == 'test.txt':
|
||||
assert str(item.source) == '{{ h()}}:'
|
||||
|
||||
|
||||
def test_entrysource_Queue_example():
|
||||
try:
|
||||
queue.Queue().get(timeout=0.001)
|
||||
except queue.Empty:
|
||||
excinfo = _pytest._code.ExceptionInfo()
|
||||
entry = excinfo.traceback[-1]
|
||||
source = entry.getsource()
|
||||
assert source is not None
|
||||
s = str(source).strip()
|
||||
assert s.startswith("def get")
|
||||
|
||||
def test_codepath_Queue_example():
|
||||
try:
|
||||
queue.Queue().get(timeout=0.001)
|
||||
except queue.Empty:
|
||||
excinfo = _pytest._code.ExceptionInfo()
|
||||
entry = excinfo.traceback[-1]
|
||||
path = entry.path
|
||||
assert isinstance(path, py.path.local)
|
||||
assert path.basename.lower() == "queue.py"
|
||||
assert path.check()
|
||||
|
||||
class TestFormattedExcinfo:
|
||||
def pytest_funcarg__importasmod(self, request):
|
||||
def importasmod(source):
|
||||
source = _pytest._code.Source(source)
|
||||
tmpdir = request.getfuncargvalue("tmpdir")
|
||||
modpath = tmpdir.join("mod.py")
|
||||
tmpdir.ensure("__init__.py")
|
||||
modpath.write(source)
|
||||
if invalidate_import_caches is not None:
|
||||
invalidate_import_caches()
|
||||
return modpath.pyimport()
|
||||
return importasmod
|
||||
|
||||
def excinfo_from_exec(self, source):
|
||||
source = _pytest._code.Source(source).strip()
|
||||
try:
|
||||
exec (source.compile())
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
return _pytest._code.ExceptionInfo()
|
||||
assert 0, "did not raise"
|
||||
|
||||
def test_repr_source(self):
|
||||
pr = FormattedExcinfo()
|
||||
source = _pytest._code.Source("""
|
||||
def f(x):
|
||||
pass
|
||||
""").strip()
|
||||
pr.flow_marker = "|"
|
||||
lines = pr.get_source(source, 0)
|
||||
assert len(lines) == 2
|
||||
assert lines[0] == "| def f(x):"
|
||||
assert lines[1] == " pass"
|
||||
|
||||
def test_repr_source_excinfo(self):
|
||||
""" check if indentation is right """
|
||||
pr = FormattedExcinfo()
|
||||
excinfo = self.excinfo_from_exec("""
|
||||
def f():
|
||||
assert 0
|
||||
f()
|
||||
""")
|
||||
pr = FormattedExcinfo()
|
||||
source = pr._getentrysource(excinfo.traceback[-1])
|
||||
lines = pr.get_source(source, 1, excinfo)
|
||||
assert lines == [
|
||||
' def f():',
|
||||
'> assert 0',
|
||||
'E assert 0'
|
||||
]
|
||||
|
||||
|
||||
def test_repr_source_not_existing(self):
|
||||
pr = FormattedExcinfo()
|
||||
co = compile("raise ValueError()", "", "exec")
|
||||
try:
|
||||
exec (co)
|
||||
except ValueError:
|
||||
excinfo = _pytest._code.ExceptionInfo()
|
||||
repr = pr.repr_excinfo(excinfo)
|
||||
assert repr.reprtraceback.reprentries[1].lines[0] == "> ???"
|
||||
|
||||
def test_repr_many_line_source_not_existing(self):
|
||||
pr = FormattedExcinfo()
|
||||
co = compile("""
|
||||
a = 1
|
||||
raise ValueError()
|
||||
""", "", "exec")
|
||||
try:
|
||||
exec (co)
|
||||
except ValueError:
|
||||
excinfo = _pytest._code.ExceptionInfo()
|
||||
repr = pr.repr_excinfo(excinfo)
|
||||
assert repr.reprtraceback.reprentries[1].lines[0] == "> ???"
|
||||
|
||||
def test_repr_source_failing_fullsource(self):
|
||||
pr = FormattedExcinfo()
|
||||
|
||||
class FakeCode(object):
|
||||
class raw:
|
||||
co_filename = '?'
|
||||
path = '?'
|
||||
firstlineno = 5
|
||||
|
||||
def fullsource(self):
|
||||
return None
|
||||
fullsource = property(fullsource)
|
||||
|
||||
class FakeFrame(object):
|
||||
code = FakeCode()
|
||||
f_locals = {}
|
||||
f_globals = {}
|
||||
|
||||
class FakeTracebackEntry(_pytest._code.Traceback.Entry):
|
||||
def __init__(self, tb):
|
||||
self.lineno = 5+3
|
||||
|
||||
@property
|
||||
def frame(self):
|
||||
return FakeFrame()
|
||||
|
||||
class Traceback(_pytest._code.Traceback):
|
||||
Entry = FakeTracebackEntry
|
||||
|
||||
class FakeExcinfo(_pytest._code.ExceptionInfo):
|
||||
typename = "Foo"
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def exconly(self, tryshort):
|
||||
return "EXC"
|
||||
def errisinstance(self, cls):
|
||||
return False
|
||||
|
||||
excinfo = FakeExcinfo()
|
||||
class FakeRawTB(object):
|
||||
tb_next = None
|
||||
tb = FakeRawTB()
|
||||
excinfo.traceback = Traceback(tb)
|
||||
|
||||
fail = IOError() # noqa
|
||||
repr = pr.repr_excinfo(excinfo)
|
||||
assert repr.reprtraceback.reprentries[0].lines[0] == "> ???"
|
||||
|
||||
fail = py.error.ENOENT # noqa
|
||||
repr = pr.repr_excinfo(excinfo)
|
||||
assert repr.reprtraceback.reprentries[0].lines[0] == "> ???"
|
||||
|
||||
|
||||
def test_repr_local(self):
|
||||
p = FormattedExcinfo(showlocals=True)
|
||||
loc = {'y': 5, 'z': 7, 'x': 3, '@x': 2, '__builtins__': {}}
|
||||
reprlocals = p.repr_locals(loc)
|
||||
assert reprlocals.lines
|
||||
assert reprlocals.lines[0] == '__builtins__ = <builtins>'
|
||||
assert reprlocals.lines[1] == 'x = 3'
|
||||
assert reprlocals.lines[2] == 'y = 5'
|
||||
assert reprlocals.lines[3] == 'z = 7'
|
||||
|
||||
def test_repr_tracebackentry_lines(self, importasmod):
|
||||
mod = importasmod("""
|
||||
def func1():
|
||||
raise ValueError("hello\\nworld")
|
||||
""")
|
||||
excinfo = pytest.raises(ValueError, mod.func1)
|
||||
excinfo.traceback = excinfo.traceback.filter()
|
||||
p = FormattedExcinfo()
|
||||
reprtb = p.repr_traceback_entry(excinfo.traceback[-1])
|
||||
|
||||
# test as intermittent entry
|
||||
lines = reprtb.lines
|
||||
assert lines[0] == ' def func1():'
|
||||
assert lines[1] == '> raise ValueError("hello\\nworld")'
|
||||
|
||||
# test as last entry
|
||||
p = FormattedExcinfo(showlocals=True)
|
||||
repr_entry = p.repr_traceback_entry(excinfo.traceback[-1], excinfo)
|
||||
lines = repr_entry.lines
|
||||
assert lines[0] == ' def func1():'
|
||||
assert lines[1] == '> raise ValueError("hello\\nworld")'
|
||||
assert lines[2] == 'E ValueError: hello'
|
||||
assert lines[3] == 'E world'
|
||||
assert not lines[4:]
|
||||
|
||||
loc = repr_entry.reprlocals is not None
|
||||
loc = repr_entry.reprfileloc
|
||||
assert loc.path == mod.__file__
|
||||
assert loc.lineno == 3
|
||||
#assert loc.message == "ValueError: hello"
|
||||
|
||||
def test_repr_tracebackentry_lines2(self, importasmod):
|
||||
mod = importasmod("""
|
||||
def func1(m, x, y, z):
|
||||
raise ValueError("hello\\nworld")
|
||||
""")
|
||||
excinfo = pytest.raises(ValueError, mod.func1, "m"*90, 5, 13, "z"*120)
|
||||
excinfo.traceback = excinfo.traceback.filter()
|
||||
entry = excinfo.traceback[-1]
|
||||
p = FormattedExcinfo(funcargs=True)
|
||||
reprfuncargs = p.repr_args(entry)
|
||||
assert reprfuncargs.args[0] == ('m', repr("m"*90))
|
||||
assert reprfuncargs.args[1] == ('x', '5')
|
||||
assert reprfuncargs.args[2] == ('y', '13')
|
||||
assert reprfuncargs.args[3] == ('z', repr("z" * 120))
|
||||
|
||||
p = FormattedExcinfo(funcargs=True)
|
||||
repr_entry = p.repr_traceback_entry(entry)
|
||||
assert repr_entry.reprfuncargs.args == reprfuncargs.args
|
||||
tw = TWMock()
|
||||
repr_entry.toterminal(tw)
|
||||
assert tw.lines[0] == "m = " + repr('m' * 90)
|
||||
assert tw.lines[1] == "x = 5, y = 13"
|
||||
assert tw.lines[2] == "z = " + repr('z' * 120)
|
||||
|
||||
def test_repr_tracebackentry_lines_var_kw_args(self, importasmod):
|
||||
mod = importasmod("""
|
||||
def func1(x, *y, **z):
|
||||
raise ValueError("hello\\nworld")
|
||||
""")
|
||||
excinfo = pytest.raises(ValueError, mod.func1, 'a', 'b', c='d')
|
||||
excinfo.traceback = excinfo.traceback.filter()
|
||||
entry = excinfo.traceback[-1]
|
||||
p = FormattedExcinfo(funcargs=True)
|
||||
reprfuncargs = p.repr_args(entry)
|
||||
assert reprfuncargs.args[0] == ('x', repr('a'))
|
||||
assert reprfuncargs.args[1] == ('y', repr(('b',)))
|
||||
assert reprfuncargs.args[2] == ('z', repr({'c': 'd'}))
|
||||
|
||||
p = FormattedExcinfo(funcargs=True)
|
||||
repr_entry = p.repr_traceback_entry(entry)
|
||||
assert repr_entry.reprfuncargs.args == reprfuncargs.args
|
||||
tw = TWMock()
|
||||
repr_entry.toterminal(tw)
|
||||
assert tw.lines[0] == "x = 'a', y = ('b',), z = {'c': 'd'}"
|
||||
|
||||
def test_repr_tracebackentry_short(self, importasmod):
|
||||
mod = importasmod("""
|
||||
def func1():
|
||||
raise ValueError("hello")
|
||||
def entry():
|
||||
func1()
|
||||
""")
|
||||
excinfo = pytest.raises(ValueError, mod.entry)
|
||||
p = FormattedExcinfo(style="short")
|
||||
reprtb = p.repr_traceback_entry(excinfo.traceback[-2])
|
||||
lines = reprtb.lines
|
||||
basename = py.path.local(mod.__file__).basename
|
||||
assert lines[0] == ' func1()'
|
||||
assert basename in str(reprtb.reprfileloc.path)
|
||||
assert reprtb.reprfileloc.lineno == 5
|
||||
|
||||
# test last entry
|
||||
p = FormattedExcinfo(style="short")
|
||||
reprtb = p.repr_traceback_entry(excinfo.traceback[-1], excinfo)
|
||||
lines = reprtb.lines
|
||||
assert lines[0] == ' raise ValueError("hello")'
|
||||
assert lines[1] == 'E ValueError: hello'
|
||||
assert basename in str(reprtb.reprfileloc.path)
|
||||
assert reprtb.reprfileloc.lineno == 3
|
||||
|
||||
def test_repr_tracebackentry_no(self, importasmod):
|
||||
mod = importasmod("""
|
||||
def func1():
|
||||
raise ValueError("hello")
|
||||
def entry():
|
||||
func1()
|
||||
""")
|
||||
excinfo = pytest.raises(ValueError, mod.entry)
|
||||
p = FormattedExcinfo(style="no")
|
||||
p.repr_traceback_entry(excinfo.traceback[-2])
|
||||
|
||||
p = FormattedExcinfo(style="no")
|
||||
reprentry = p.repr_traceback_entry(excinfo.traceback[-1], excinfo)
|
||||
lines = reprentry.lines
|
||||
assert lines[0] == 'E ValueError: hello'
|
||||
assert not lines[1:]
|
||||
|
||||
def test_repr_traceback_tbfilter(self, importasmod):
|
||||
mod = importasmod("""
|
||||
def f(x):
|
||||
raise ValueError(x)
|
||||
def entry():
|
||||
f(0)
|
||||
""")
|
||||
excinfo = pytest.raises(ValueError, mod.entry)
|
||||
p = FormattedExcinfo(tbfilter=True)
|
||||
reprtb = p.repr_traceback(excinfo)
|
||||
assert len(reprtb.reprentries) == 2
|
||||
p = FormattedExcinfo(tbfilter=False)
|
||||
reprtb = p.repr_traceback(excinfo)
|
||||
assert len(reprtb.reprentries) == 3
|
||||
|
||||
def test_traceback_short_no_source(self, importasmod, monkeypatch):
|
||||
mod = importasmod("""
|
||||
def func1():
|
||||
raise ValueError("hello")
|
||||
def entry():
|
||||
func1()
|
||||
""")
|
||||
excinfo = pytest.raises(ValueError, mod.entry)
|
||||
from _pytest._code.code import Code
|
||||
monkeypatch.setattr(Code, 'path', 'bogus')
|
||||
excinfo.traceback[0].frame.code.path = "bogus"
|
||||
p = FormattedExcinfo(style="short")
|
||||
reprtb = p.repr_traceback_entry(excinfo.traceback[-2])
|
||||
lines = reprtb.lines
|
||||
last_p = FormattedExcinfo(style="short")
|
||||
last_reprtb = last_p.repr_traceback_entry(excinfo.traceback[-1], excinfo)
|
||||
last_lines = last_reprtb.lines
|
||||
monkeypatch.undo()
|
||||
assert lines[0] == ' func1()'
|
||||
|
||||
assert last_lines[0] == ' raise ValueError("hello")'
|
||||
assert last_lines[1] == 'E ValueError: hello'
|
||||
|
||||
def test_repr_traceback_and_excinfo(self, importasmod):
|
||||
mod = importasmod("""
|
||||
def f(x):
|
||||
raise ValueError(x)
|
||||
def entry():
|
||||
f(0)
|
||||
""")
|
||||
excinfo = pytest.raises(ValueError, mod.entry)
|
||||
|
||||
for style in ("long", "short"):
|
||||
p = FormattedExcinfo(style=style)
|
||||
reprtb = p.repr_traceback(excinfo)
|
||||
assert len(reprtb.reprentries) == 2
|
||||
assert reprtb.style == style
|
||||
assert not reprtb.extraline
|
||||
repr = p.repr_excinfo(excinfo)
|
||||
assert repr.reprtraceback
|
||||
assert len(repr.reprtraceback.reprentries) == len(reprtb.reprentries)
|
||||
assert repr.reprcrash.path.endswith("mod.py")
|
||||
assert repr.reprcrash.message == "ValueError: 0"
|
||||
|
||||
def test_repr_traceback_with_invalid_cwd(self, importasmod, monkeypatch):
|
||||
mod = importasmod("""
|
||||
def f(x):
|
||||
raise ValueError(x)
|
||||
def entry():
|
||||
f(0)
|
||||
""")
|
||||
excinfo = pytest.raises(ValueError, mod.entry)
|
||||
|
||||
p = FormattedExcinfo()
|
||||
def raiseos():
|
||||
raise OSError(2)
|
||||
monkeypatch.setattr(py.std.os, 'getcwd', raiseos)
|
||||
assert p._makepath(__file__) == __file__
|
||||
p.repr_traceback(excinfo)
|
||||
|
||||
def test_repr_excinfo_addouterr(self, importasmod):
|
||||
mod = importasmod("""
|
||||
def entry():
|
||||
raise ValueError()
|
||||
""")
|
||||
excinfo = pytest.raises(ValueError, mod.entry)
|
||||
repr = excinfo.getrepr()
|
||||
repr.addsection("title", "content")
|
||||
twmock = TWMock()
|
||||
repr.toterminal(twmock)
|
||||
assert twmock.lines[-1] == "content"
|
||||
assert twmock.lines[-2] == ("-", "title")
|
||||
|
||||
def test_repr_excinfo_reprcrash(self, importasmod):
|
||||
mod = importasmod("""
|
||||
def entry():
|
||||
raise ValueError()
|
||||
""")
|
||||
excinfo = pytest.raises(ValueError, mod.entry)
|
||||
repr = excinfo.getrepr()
|
||||
assert repr.reprcrash.path.endswith("mod.py")
|
||||
assert repr.reprcrash.lineno == 3
|
||||
assert repr.reprcrash.message == "ValueError"
|
||||
assert str(repr.reprcrash).endswith("mod.py:3: ValueError")
|
||||
|
||||
def test_repr_traceback_recursion(self, importasmod):
|
||||
mod = importasmod("""
|
||||
def rec2(x):
|
||||
return rec1(x+1)
|
||||
def rec1(x):
|
||||
return rec2(x-1)
|
||||
def entry():
|
||||
rec1(42)
|
||||
""")
|
||||
excinfo = pytest.raises(RuntimeError, mod.entry)
|
||||
|
||||
for style in ("short", "long", "no"):
|
||||
p = FormattedExcinfo(style="short")
|
||||
reprtb = p.repr_traceback(excinfo)
|
||||
assert reprtb.extraline == "!!! Recursion detected (same locals & position)"
|
||||
assert str(reprtb)
|
||||
|
||||
def test_tb_entry_AssertionError(self, importasmod):
|
||||
# probably this test is a bit redundant
|
||||
# as py/magic/testing/test_assertion.py
|
||||
# already tests correctness of
|
||||
# assertion-reinterpretation logic
|
||||
mod = importasmod("""
|
||||
def somefunc():
|
||||
x = 1
|
||||
assert x == 2
|
||||
""")
|
||||
excinfo = pytest.raises(AssertionError, mod.somefunc)
|
||||
|
||||
p = FormattedExcinfo()
|
||||
reprentry = p.repr_traceback_entry(excinfo.traceback[-1], excinfo)
|
||||
lines = reprentry.lines
|
||||
assert lines[-1] == "E assert 1 == 2"
|
||||
|
||||
def test_reprexcinfo_getrepr(self, importasmod):
|
||||
mod = importasmod("""
|
||||
def f(x):
|
||||
raise ValueError(x)
|
||||
def entry():
|
||||
f(0)
|
||||
""")
|
||||
excinfo = pytest.raises(ValueError, mod.entry)
|
||||
|
||||
for style in ("short", "long", "no"):
|
||||
for showlocals in (True, False):
|
||||
repr = excinfo.getrepr(style=style, showlocals=showlocals)
|
||||
assert isinstance(repr, ReprExceptionInfo)
|
||||
assert repr.reprtraceback.style == style
|
||||
|
||||
def test_reprexcinfo_unicode(self):
|
||||
from _pytest._code.code import TerminalRepr
|
||||
class MyRepr(TerminalRepr):
|
||||
def toterminal(self, tw):
|
||||
tw.line(py.builtin._totext("я", "utf-8"))
|
||||
x = py.builtin._totext(MyRepr())
|
||||
assert x == py.builtin._totext("я", "utf-8")
|
||||
|
||||
def test_toterminal_long(self, importasmod):
|
||||
mod = importasmod("""
|
||||
def g(x):
|
||||
raise ValueError(x)
|
||||
def f():
|
||||
g(3)
|
||||
""")
|
||||
excinfo = pytest.raises(ValueError, mod.f)
|
||||
excinfo.traceback = excinfo.traceback.filter()
|
||||
repr = excinfo.getrepr()
|
||||
tw = TWMock()
|
||||
repr.toterminal(tw)
|
||||
assert tw.lines[0] == ""
|
||||
tw.lines.pop(0)
|
||||
assert tw.lines[0] == " def f():"
|
||||
assert tw.lines[1] == "> g(3)"
|
||||
assert tw.lines[2] == ""
|
||||
assert tw.lines[3].endswith("mod.py:5: ")
|
||||
assert tw.lines[4] == ("_ ", None)
|
||||
assert tw.lines[5] == ""
|
||||
assert tw.lines[6] == " def g(x):"
|
||||
assert tw.lines[7] == "> raise ValueError(x)"
|
||||
assert tw.lines[8] == "E ValueError: 3"
|
||||
assert tw.lines[9] == ""
|
||||
assert tw.lines[10].endswith("mod.py:3: ValueError")
|
||||
|
||||
def test_toterminal_long_missing_source(self, importasmod, tmpdir):
|
||||
mod = importasmod("""
|
||||
def g(x):
|
||||
raise ValueError(x)
|
||||
def f():
|
||||
g(3)
|
||||
""")
|
||||
excinfo = pytest.raises(ValueError, mod.f)
|
||||
tmpdir.join('mod.py').remove()
|
||||
excinfo.traceback = excinfo.traceback.filter()
|
||||
repr = excinfo.getrepr()
|
||||
tw = TWMock()
|
||||
repr.toterminal(tw)
|
||||
assert tw.lines[0] == ""
|
||||
tw.lines.pop(0)
|
||||
assert tw.lines[0] == "> ???"
|
||||
assert tw.lines[1] == ""
|
||||
assert tw.lines[2].endswith("mod.py:5: ")
|
||||
assert tw.lines[3] == ("_ ", None)
|
||||
assert tw.lines[4] == ""
|
||||
assert tw.lines[5] == "> ???"
|
||||
assert tw.lines[6] == "E ValueError: 3"
|
||||
assert tw.lines[7] == ""
|
||||
assert tw.lines[8].endswith("mod.py:3: ValueError")
|
||||
|
||||
def test_toterminal_long_incomplete_source(self, importasmod, tmpdir):
|
||||
mod = importasmod("""
|
||||
def g(x):
|
||||
raise ValueError(x)
|
||||
def f():
|
||||
g(3)
|
||||
""")
|
||||
excinfo = pytest.raises(ValueError, mod.f)
|
||||
tmpdir.join('mod.py').write('asdf')
|
||||
excinfo.traceback = excinfo.traceback.filter()
|
||||
repr = excinfo.getrepr()
|
||||
tw = TWMock()
|
||||
repr.toterminal(tw)
|
||||
assert tw.lines[0] == ""
|
||||
tw.lines.pop(0)
|
||||
assert tw.lines[0] == "> ???"
|
||||
assert tw.lines[1] == ""
|
||||
assert tw.lines[2].endswith("mod.py:5: ")
|
||||
assert tw.lines[3] == ("_ ", None)
|
||||
assert tw.lines[4] == ""
|
||||
assert tw.lines[5] == "> ???"
|
||||
assert tw.lines[6] == "E ValueError: 3"
|
||||
assert tw.lines[7] == ""
|
||||
assert tw.lines[8].endswith("mod.py:3: ValueError")
|
||||
|
||||
def test_toterminal_long_filenames(self, importasmod):
|
||||
mod = importasmod("""
|
||||
def f():
|
||||
raise ValueError()
|
||||
""")
|
||||
excinfo = pytest.raises(ValueError, mod.f)
|
||||
tw = TWMock()
|
||||
path = py.path.local(mod.__file__)
|
||||
old = path.dirpath().chdir()
|
||||
try:
|
||||
repr = excinfo.getrepr(abspath=False)
|
||||
repr.toterminal(tw)
|
||||
line = tw.lines[-1]
|
||||
x = py.path.local().bestrelpath(path)
|
||||
if len(x) < len(str(path)):
|
||||
assert line == "mod.py:3: ValueError"
|
||||
|
||||
repr = excinfo.getrepr(abspath=True)
|
||||
repr.toterminal(tw)
|
||||
line = tw.lines[-1]
|
||||
assert line == "%s:3: ValueError" %(path,)
|
||||
finally:
|
||||
old.chdir()
|
||||
|
||||
@pytest.mark.parametrize('reproptions', [
|
||||
{'style': style, 'showlocals': showlocals,
|
||||
'funcargs': funcargs, 'tbfilter': tbfilter
|
||||
} for style in ("long", "short", "no")
|
||||
for showlocals in (True, False)
|
||||
for tbfilter in (True, False)
|
||||
for funcargs in (True, False)])
|
||||
def test_format_excinfo(self, importasmod, reproptions):
|
||||
mod = importasmod("""
|
||||
def g(x):
|
||||
raise ValueError(x)
|
||||
def f():
|
||||
g(3)
|
||||
""")
|
||||
excinfo = pytest.raises(ValueError, mod.f)
|
||||
tw = py.io.TerminalWriter(stringio=True)
|
||||
repr = excinfo.getrepr(**reproptions)
|
||||
repr.toterminal(tw)
|
||||
assert tw.stringio.getvalue()
|
||||
|
||||
|
||||
def test_native_style(self):
|
||||
excinfo = self.excinfo_from_exec("""
|
||||
assert 0
|
||||
""")
|
||||
repr = excinfo.getrepr(style='native')
|
||||
assert "assert 0" in str(repr.reprcrash)
|
||||
s = str(repr)
|
||||
assert s.startswith('Traceback (most recent call last):\n File')
|
||||
assert s.endswith('\nAssertionError: assert 0')
|
||||
assert 'exec (source.compile())' in s
|
||||
# python 2.4 fails to get the source line for the assert
|
||||
if py.std.sys.version_info >= (2, 5):
|
||||
assert s.count('assert 0') == 2
|
||||
|
||||
def test_traceback_repr_style(self, importasmod):
|
||||
mod = importasmod("""
|
||||
def f():
|
||||
g()
|
||||
def g():
|
||||
h()
|
||||
def h():
|
||||
i()
|
||||
def i():
|
||||
raise ValueError()
|
||||
""")
|
||||
excinfo = pytest.raises(ValueError, mod.f)
|
||||
excinfo.traceback = excinfo.traceback.filter()
|
||||
excinfo.traceback[1].set_repr_style("short")
|
||||
excinfo.traceback[2].set_repr_style("short")
|
||||
r = excinfo.getrepr(style="long")
|
||||
tw = TWMock()
|
||||
r.toterminal(tw)
|
||||
for line in tw.lines: print (line)
|
||||
assert tw.lines[0] == ""
|
||||
assert tw.lines[1] == " def f():"
|
||||
assert tw.lines[2] == "> g()"
|
||||
assert tw.lines[3] == ""
|
||||
assert tw.lines[4].endswith("mod.py:3: ")
|
||||
assert tw.lines[5] == ("_ ", None)
|
||||
assert tw.lines[6].endswith("in g")
|
||||
assert tw.lines[7] == " h()"
|
||||
assert tw.lines[8].endswith("in h")
|
||||
assert tw.lines[9] == " i()"
|
||||
assert tw.lines[10] == ("_ ", None)
|
||||
assert tw.lines[11] == ""
|
||||
assert tw.lines[12] == " def i():"
|
||||
assert tw.lines[13] == "> raise ValueError()"
|
||||
assert tw.lines[14] == "E ValueError"
|
||||
assert tw.lines[15] == ""
|
||||
assert tw.lines[16].endswith("mod.py:9: ValueError")
|
||||
659
testing/code/test_source.py
Normal file
659
testing/code/test_source.py
Normal file
@@ -0,0 +1,659 @@
|
||||
# flake8: noqa
|
||||
# disable flake check on this file because some constructs are strange
|
||||
# or redundant on purpose and can't be disable on a line-by-line basis
|
||||
import sys
|
||||
|
||||
import _pytest._code
|
||||
import py
|
||||
import pytest
|
||||
from _pytest._code import Source
|
||||
from _pytest._code.source import _ast
|
||||
|
||||
if _ast is not None:
|
||||
astonly = pytest.mark.nothing
|
||||
else:
|
||||
astonly = pytest.mark.xfail("True", reason="only works with AST-compile")
|
||||
|
||||
failsonjython = pytest.mark.xfail("sys.platform.startswith('java')")
|
||||
|
||||
def test_source_str_function():
|
||||
x = Source("3")
|
||||
assert str(x) == "3"
|
||||
|
||||
x = Source(" 3")
|
||||
assert str(x) == "3"
|
||||
|
||||
x = Source("""
|
||||
3
|
||||
""", rstrip=False)
|
||||
assert str(x) == "\n3\n "
|
||||
|
||||
x = Source("""
|
||||
3
|
||||
""", rstrip=True)
|
||||
assert str(x) == "\n3"
|
||||
|
||||
def test_unicode():
|
||||
try:
|
||||
unicode
|
||||
except NameError:
|
||||
return
|
||||
x = Source(unicode("4"))
|
||||
assert str(x) == "4"
|
||||
co = _pytest._code.compile(unicode('u"\xc3\xa5"', 'utf8'), mode='eval')
|
||||
val = eval(co)
|
||||
assert isinstance(val, unicode)
|
||||
|
||||
def test_source_from_function():
|
||||
source = _pytest._code.Source(test_source_str_function)
|
||||
assert str(source).startswith('def test_source_str_function():')
|
||||
|
||||
def test_source_from_method():
|
||||
class TestClass:
|
||||
def test_method(self):
|
||||
pass
|
||||
source = _pytest._code.Source(TestClass().test_method)
|
||||
assert source.lines == ["def test_method(self):",
|
||||
" pass"]
|
||||
|
||||
def test_source_from_lines():
|
||||
lines = ["a \n", "b\n", "c"]
|
||||
source = _pytest._code.Source(lines)
|
||||
assert source.lines == ['a ', 'b', 'c']
|
||||
|
||||
def test_source_from_inner_function():
|
||||
def f():
|
||||
pass
|
||||
source = _pytest._code.Source(f, deindent=False)
|
||||
assert str(source).startswith(' def f():')
|
||||
source = _pytest._code.Source(f)
|
||||
assert str(source).startswith('def f():')
|
||||
|
||||
def test_source_putaround_simple():
|
||||
source = Source("raise ValueError")
|
||||
source = source.putaround(
|
||||
"try:", """\
|
||||
except ValueError:
|
||||
x = 42
|
||||
else:
|
||||
x = 23""")
|
||||
assert str(source)=="""\
|
||||
try:
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
x = 42
|
||||
else:
|
||||
x = 23"""
|
||||
|
||||
def test_source_putaround():
|
||||
source = Source()
|
||||
source = source.putaround("""
|
||||
if 1:
|
||||
x=1
|
||||
""")
|
||||
assert str(source).strip() == "if 1:\n x=1"
|
||||
|
||||
def test_source_strips():
|
||||
source = Source("")
|
||||
assert source == Source()
|
||||
assert str(source) == ''
|
||||
assert source.strip() == source
|
||||
|
||||
def test_source_strip_multiline():
|
||||
source = Source()
|
||||
source.lines = ["", " hello", " "]
|
||||
source2 = source.strip()
|
||||
assert source2.lines == [" hello"]
|
||||
|
||||
def test_syntaxerror_rerepresentation():
|
||||
ex = pytest.raises(SyntaxError, _pytest._code.compile, 'xyz xyz')
|
||||
assert ex.value.lineno == 1
|
||||
assert ex.value.offset in (4,7) # XXX pypy/jython versus cpython?
|
||||
assert ex.value.text.strip(), 'x x'
|
||||
|
||||
def test_isparseable():
|
||||
assert Source("hello").isparseable()
|
||||
assert Source("if 1:\n pass").isparseable()
|
||||
assert Source(" \nif 1:\n pass").isparseable()
|
||||
assert not Source("if 1:\n").isparseable()
|
||||
assert not Source(" \nif 1:\npass").isparseable()
|
||||
assert not Source(chr(0)).isparseable()
|
||||
|
||||
class TestAccesses:
|
||||
source = Source("""\
|
||||
def f(x):
|
||||
pass
|
||||
def g(x):
|
||||
pass
|
||||
""")
|
||||
def test_getrange(self):
|
||||
x = self.source[0:2]
|
||||
assert x.isparseable()
|
||||
assert len(x.lines) == 2
|
||||
assert str(x) == "def f(x):\n pass"
|
||||
|
||||
def test_getline(self):
|
||||
x = self.source[0]
|
||||
assert x == "def f(x):"
|
||||
|
||||
def test_len(self):
|
||||
assert len(self.source) == 4
|
||||
|
||||
def test_iter(self):
|
||||
l = [x for x in self.source]
|
||||
assert len(l) == 4
|
||||
|
||||
class TestSourceParsingAndCompiling:
|
||||
source = Source("""\
|
||||
def f(x):
|
||||
assert (x ==
|
||||
3 +
|
||||
4)
|
||||
""").strip()
|
||||
|
||||
def test_compile(self):
|
||||
co = _pytest._code.compile("x=3")
|
||||
d = {}
|
||||
exec (co, d)
|
||||
assert d['x'] == 3
|
||||
|
||||
def test_compile_and_getsource_simple(self):
|
||||
co = _pytest._code.compile("x=3")
|
||||
exec (co)
|
||||
source = _pytest._code.Source(co)
|
||||
assert str(source) == "x=3"
|
||||
|
||||
def test_compile_and_getsource_through_same_function(self):
|
||||
def gensource(source):
|
||||
return _pytest._code.compile(source)
|
||||
co1 = gensource("""
|
||||
def f():
|
||||
raise KeyError()
|
||||
""")
|
||||
co2 = gensource("""
|
||||
def f():
|
||||
raise ValueError()
|
||||
""")
|
||||
source1 = py.std.inspect.getsource(co1)
|
||||
assert 'KeyError' in source1
|
||||
source2 = py.std.inspect.getsource(co2)
|
||||
assert 'ValueError' in source2
|
||||
|
||||
def test_getstatement(self):
|
||||
#print str(self.source)
|
||||
ass = str(self.source[1:])
|
||||
for i in range(1, 4):
|
||||
#print "trying start in line %r" % self.source[i]
|
||||
s = self.source.getstatement(i)
|
||||
#x = s.deindent()
|
||||
assert str(s) == ass
|
||||
|
||||
def test_getstatementrange_triple_quoted(self):
|
||||
#print str(self.source)
|
||||
source = Source("""hello('''
|
||||
''')""")
|
||||
s = source.getstatement(0)
|
||||
assert s == str(source)
|
||||
s = source.getstatement(1)
|
||||
assert s == str(source)
|
||||
|
||||
@astonly
|
||||
def test_getstatementrange_within_constructs(self):
|
||||
source = Source("""\
|
||||
try:
|
||||
try:
|
||||
raise ValueError
|
||||
except SomeThing:
|
||||
pass
|
||||
finally:
|
||||
42
|
||||
""")
|
||||
assert len(source) == 7
|
||||
# check all lineno's that could occur in a traceback
|
||||
#assert source.getstatementrange(0) == (0, 7)
|
||||
#assert source.getstatementrange(1) == (1, 5)
|
||||
assert source.getstatementrange(2) == (2, 3)
|
||||
assert source.getstatementrange(3) == (3, 4)
|
||||
assert source.getstatementrange(4) == (4, 5)
|
||||
#assert source.getstatementrange(5) == (0, 7)
|
||||
assert source.getstatementrange(6) == (6, 7)
|
||||
|
||||
def test_getstatementrange_bug(self):
|
||||
source = Source("""\
|
||||
try:
|
||||
x = (
|
||||
y +
|
||||
z)
|
||||
except:
|
||||
pass
|
||||
""")
|
||||
assert len(source) == 6
|
||||
assert source.getstatementrange(2) == (1, 4)
|
||||
|
||||
def test_getstatementrange_bug2(self):
|
||||
source = Source("""\
|
||||
assert (
|
||||
33
|
||||
==
|
||||
[
|
||||
X(3,
|
||||
b=1, c=2
|
||||
),
|
||||
]
|
||||
)
|
||||
""")
|
||||
assert len(source) == 9
|
||||
assert source.getstatementrange(5) == (0, 9)
|
||||
|
||||
def test_getstatementrange_ast_issue58(self):
|
||||
source = Source("""\
|
||||
|
||||
def test_some():
|
||||
for a in [a for a in
|
||||
CAUSE_ERROR]: pass
|
||||
|
||||
x = 3
|
||||
""")
|
||||
assert getstatement(2, source).lines == source.lines[2:3]
|
||||
assert getstatement(3, source).lines == source.lines[3:4]
|
||||
|
||||
@pytest.mark.skipif("sys.version_info < (2,6)")
|
||||
def test_getstatementrange_out_of_bounds_py3(self):
|
||||
source = Source("if xxx:\n from .collections import something")
|
||||
r = source.getstatementrange(1)
|
||||
assert r == (1,2)
|
||||
|
||||
def test_getstatementrange_with_syntaxerror_issue7(self):
|
||||
source = Source(":")
|
||||
pytest.raises(SyntaxError, lambda: source.getstatementrange(0))
|
||||
|
||||
@pytest.mark.skipif("sys.version_info < (2,6)")
|
||||
def test_compile_to_ast(self):
|
||||
import ast
|
||||
source = Source("x = 4")
|
||||
mod = source.compile(flag=ast.PyCF_ONLY_AST)
|
||||
assert isinstance(mod, ast.Module)
|
||||
compile(mod, "<filename>", "exec")
|
||||
|
||||
def test_compile_and_getsource(self):
|
||||
co = self.source.compile()
|
||||
py.builtin.exec_(co, globals())
|
||||
f(7)
|
||||
excinfo = pytest.raises(AssertionError, "f(6)")
|
||||
frame = excinfo.traceback[-1].frame
|
||||
stmt = frame.code.fullsource.getstatement(frame.lineno)
|
||||
#print "block", str(block)
|
||||
assert str(stmt).strip().startswith('assert')
|
||||
|
||||
def test_compilefuncs_and_path_sanity(self):
|
||||
def check(comp, name):
|
||||
co = comp(self.source, name)
|
||||
if not name:
|
||||
expected = "codegen %s:%d>" %(mypath, mylineno+2+1)
|
||||
else:
|
||||
expected = "codegen %r %s:%d>" % (name, mypath, mylineno+2+1)
|
||||
fn = co.co_filename
|
||||
assert fn.endswith(expected)
|
||||
|
||||
mycode = _pytest._code.Code(self.test_compilefuncs_and_path_sanity)
|
||||
mylineno = mycode.firstlineno
|
||||
mypath = mycode.path
|
||||
|
||||
for comp in _pytest._code.compile, _pytest._code.Source.compile:
|
||||
for name in '', None, 'my':
|
||||
yield check, comp, name
|
||||
|
||||
def test_offsetless_synerr(self):
|
||||
pytest.raises(SyntaxError, _pytest._code.compile, "lambda a,a: 0", mode='eval')
|
||||
|
||||
def test_getstartingblock_singleline():
|
||||
class A:
|
||||
def __init__(self, *args):
|
||||
frame = sys._getframe(1)
|
||||
self.source = _pytest._code.Frame(frame).statement
|
||||
|
||||
x = A('x', 'y')
|
||||
|
||||
l = [i for i in x.source.lines if i.strip()]
|
||||
assert len(l) == 1
|
||||
|
||||
def test_getstartingblock_multiline():
|
||||
class A:
|
||||
def __init__(self, *args):
|
||||
frame = sys._getframe(1)
|
||||
self.source = _pytest._code.Frame(frame).statement
|
||||
|
||||
x = A('x',
|
||||
'y' \
|
||||
,
|
||||
'z')
|
||||
|
||||
l = [i for i in x.source.lines if i.strip()]
|
||||
assert len(l) == 4
|
||||
|
||||
def test_getline_finally():
|
||||
def c(): pass
|
||||
excinfo = pytest.raises(TypeError, """
|
||||
teardown = None
|
||||
try:
|
||||
c(1)
|
||||
finally:
|
||||
if teardown:
|
||||
teardown()
|
||||
""")
|
||||
source = excinfo.traceback[-1].statement
|
||||
assert str(source).strip() == 'c(1)'
|
||||
|
||||
def test_getfuncsource_dynamic():
|
||||
source = """
|
||||
def f():
|
||||
raise ValueError
|
||||
|
||||
def g(): pass
|
||||
"""
|
||||
co = _pytest._code.compile(source)
|
||||
py.builtin.exec_(co, globals())
|
||||
assert str(_pytest._code.Source(f)).strip() == 'def f():\n raise ValueError'
|
||||
assert str(_pytest._code.Source(g)).strip() == 'def g(): pass'
|
||||
|
||||
|
||||
def test_getfuncsource_with_multine_string():
|
||||
def f():
|
||||
c = '''while True:
|
||||
pass
|
||||
'''
|
||||
assert str(_pytest._code.Source(f)).strip() == "def f():\n c = '''while True:\n pass\n'''"
|
||||
|
||||
|
||||
def test_deindent():
|
||||
from _pytest._code.source import deindent as deindent
|
||||
assert deindent(['\tfoo', '\tbar', ]) == ['foo', 'bar']
|
||||
|
||||
def f():
|
||||
c = '''while True:
|
||||
pass
|
||||
'''
|
||||
import inspect
|
||||
lines = deindent(inspect.getsource(f).splitlines())
|
||||
assert lines == ["def f():", " c = '''while True:", " pass", "'''"]
|
||||
|
||||
source = """
|
||||
def f():
|
||||
def g():
|
||||
pass
|
||||
"""
|
||||
lines = deindent(source.splitlines())
|
||||
assert lines == ['', 'def f():', ' def g():', ' pass', ' ']
|
||||
|
||||
@pytest.mark.xfail("sys.version_info[:3] < (2,7,0) or "
|
||||
"((3,0) <= sys.version_info[:2] < (3,2))")
|
||||
def test_source_of_class_at_eof_without_newline(tmpdir):
|
||||
# this test fails because the implicit inspect.getsource(A) below
|
||||
# does not return the "x = 1" last line.
|
||||
source = _pytest._code.Source('''
|
||||
class A(object):
|
||||
def method(self):
|
||||
x = 1
|
||||
''')
|
||||
path = tmpdir.join("a.py")
|
||||
path.write(source)
|
||||
s2 = _pytest._code.Source(tmpdir.join("a.py").pyimport().A)
|
||||
assert str(source).strip() == str(s2).strip()
|
||||
|
||||
if True:
|
||||
def x():
|
||||
pass
|
||||
|
||||
def test_getsource_fallback():
|
||||
from _pytest._code.source import getsource
|
||||
expected = """def x():
|
||||
pass"""
|
||||
src = getsource(x)
|
||||
assert src == expected
|
||||
|
||||
def test_idem_compile_and_getsource():
|
||||
from _pytest._code.source import getsource
|
||||
expected = "def x(): pass"
|
||||
co = _pytest._code.compile(expected)
|
||||
src = getsource(co)
|
||||
assert src == expected
|
||||
|
||||
def test_findsource_fallback():
|
||||
from _pytest._code.source import findsource
|
||||
src, lineno = findsource(x)
|
||||
assert 'test_findsource_simple' in str(src)
|
||||
assert src[lineno] == ' def x():'
|
||||
|
||||
def test_findsource():
|
||||
from _pytest._code.source import findsource
|
||||
co = _pytest._code.compile("""if 1:
|
||||
def x():
|
||||
pass
|
||||
""")
|
||||
|
||||
src, lineno = findsource(co)
|
||||
assert 'if 1:' in str(src)
|
||||
|
||||
d = {}
|
||||
eval(co, d)
|
||||
src, lineno = findsource(d['x'])
|
||||
assert 'if 1:' in str(src)
|
||||
assert src[lineno] == " def x():"
|
||||
|
||||
|
||||
def test_getfslineno():
|
||||
from _pytest._code import getfslineno
|
||||
|
||||
def f(x):
|
||||
pass
|
||||
|
||||
fspath, lineno = getfslineno(f)
|
||||
|
||||
assert fspath.basename == "test_source.py"
|
||||
assert lineno == _pytest._code.getrawcode(f).co_firstlineno - 1 # see findsource
|
||||
|
||||
class A(object):
|
||||
pass
|
||||
|
||||
fspath, lineno = getfslineno(A)
|
||||
|
||||
_, A_lineno = py.std.inspect.findsource(A)
|
||||
assert fspath.basename == "test_source.py"
|
||||
assert lineno == A_lineno
|
||||
|
||||
assert getfslineno(3) == ("", -1)
|
||||
class B:
|
||||
pass
|
||||
B.__name__ = "B2"
|
||||
assert getfslineno(B)[1] == -1
|
||||
|
||||
def test_code_of_object_instance_with_call():
|
||||
class A:
|
||||
pass
|
||||
pytest.raises(TypeError, lambda: _pytest._code.Source(A()))
|
||||
class WithCall:
|
||||
def __call__(self):
|
||||
pass
|
||||
|
||||
code = _pytest._code.Code(WithCall())
|
||||
assert 'pass' in str(code.source())
|
||||
|
||||
class Hello(object):
|
||||
def __call__(self):
|
||||
pass
|
||||
pytest.raises(TypeError, lambda: _pytest._code.Code(Hello))
|
||||
|
||||
|
||||
def getstatement(lineno, source):
|
||||
from _pytest._code.source import getstatementrange_ast
|
||||
source = _pytest._code.Source(source, deindent=False)
|
||||
ast, start, end = getstatementrange_ast(lineno, source)
|
||||
return source[start:end]
|
||||
|
||||
def test_oneline():
|
||||
source = getstatement(0, "raise ValueError")
|
||||
assert str(source) == "raise ValueError"
|
||||
|
||||
def test_comment_and_no_newline_at_end():
|
||||
from _pytest._code.source import getstatementrange_ast
|
||||
source = Source(['def test_basic_complex():',
|
||||
' assert 1 == 2',
|
||||
'# vim: filetype=pyopencl:fdm=marker'])
|
||||
ast, start, end = getstatementrange_ast(1, source)
|
||||
assert end == 2
|
||||
|
||||
def test_oneline_and_comment():
|
||||
source = getstatement(0, "raise ValueError\n#hello")
|
||||
assert str(source) == "raise ValueError"
|
||||
|
||||
@pytest.mark.xfail(hasattr(sys, "pypy_version_info"),
|
||||
reason='does not work on pypy')
|
||||
def test_comments():
|
||||
source = '''def test():
|
||||
"comment 1"
|
||||
x = 1
|
||||
# comment 2
|
||||
# comment 3
|
||||
|
||||
assert False
|
||||
|
||||
"""
|
||||
comment 4
|
||||
"""
|
||||
'''
|
||||
for line in range(2,6):
|
||||
assert str(getstatement(line, source)) == ' x = 1'
|
||||
for line in range(6,10):
|
||||
assert str(getstatement(line, source)) == ' assert False'
|
||||
assert str(getstatement(10, source)) == '"""'
|
||||
|
||||
def test_comment_in_statement():
|
||||
source = '''test(foo=1,
|
||||
# comment 1
|
||||
bar=2)
|
||||
'''
|
||||
for line in range(1,3):
|
||||
assert str(getstatement(line, source)) == \
|
||||
'test(foo=1,\n # comment 1\n bar=2)'
|
||||
|
||||
def test_single_line_else():
|
||||
source = getstatement(1, "if False: 2\nelse: 3")
|
||||
assert str(source) == "else: 3"
|
||||
|
||||
def test_single_line_finally():
|
||||
source = getstatement(1, "try: 1\nfinally: 3")
|
||||
assert str(source) == "finally: 3"
|
||||
|
||||
def test_issue55():
|
||||
source = ('def round_trip(dinp):\n assert 1 == dinp\n'
|
||||
'def test_rt():\n round_trip("""\n""")\n')
|
||||
s = getstatement(3, source)
|
||||
assert str(s) == ' round_trip("""\n""")'
|
||||
|
||||
|
||||
def XXXtest_multiline():
|
||||
source = getstatement(0, """\
|
||||
raise ValueError(
|
||||
23
|
||||
)
|
||||
x = 3
|
||||
""")
|
||||
assert str(source) == "raise ValueError(\n 23\n)"
|
||||
|
||||
class TestTry:
|
||||
pytestmark = astonly
|
||||
source = """\
|
||||
try:
|
||||
raise ValueError
|
||||
except Something:
|
||||
raise IndexError(1)
|
||||
else:
|
||||
raise KeyError()
|
||||
"""
|
||||
|
||||
def test_body(self):
|
||||
source = getstatement(1, self.source)
|
||||
assert str(source) == " raise ValueError"
|
||||
|
||||
def test_except_line(self):
|
||||
source = getstatement(2, self.source)
|
||||
assert str(source) == "except Something:"
|
||||
|
||||
def test_except_body(self):
|
||||
source = getstatement(3, self.source)
|
||||
assert str(source) == " raise IndexError(1)"
|
||||
|
||||
def test_else(self):
|
||||
source = getstatement(5, self.source)
|
||||
assert str(source) == " raise KeyError()"
|
||||
|
||||
class TestTryFinally:
|
||||
source = """\
|
||||
try:
|
||||
raise ValueError
|
||||
finally:
|
||||
raise IndexError(1)
|
||||
"""
|
||||
|
||||
def test_body(self):
|
||||
source = getstatement(1, self.source)
|
||||
assert str(source) == " raise ValueError"
|
||||
|
||||
def test_finally(self):
|
||||
source = getstatement(3, self.source)
|
||||
assert str(source) == " raise IndexError(1)"
|
||||
|
||||
|
||||
|
||||
class TestIf:
|
||||
pytestmark = astonly
|
||||
source = """\
|
||||
if 1:
|
||||
y = 3
|
||||
elif False:
|
||||
y = 5
|
||||
else:
|
||||
y = 7
|
||||
"""
|
||||
|
||||
def test_body(self):
|
||||
source = getstatement(1, self.source)
|
||||
assert str(source) == " y = 3"
|
||||
|
||||
def test_elif_clause(self):
|
||||
source = getstatement(2, self.source)
|
||||
assert str(source) == "elif False:"
|
||||
|
||||
def test_elif(self):
|
||||
source = getstatement(3, self.source)
|
||||
assert str(source) == " y = 5"
|
||||
|
||||
def test_else(self):
|
||||
source = getstatement(5, self.source)
|
||||
assert str(source) == " y = 7"
|
||||
|
||||
def test_semicolon():
|
||||
s = """\
|
||||
hello ; pytest.skip()
|
||||
"""
|
||||
source = getstatement(0, s)
|
||||
assert str(source) == s.strip()
|
||||
|
||||
def test_def_online():
|
||||
s = """\
|
||||
def func(): raise ValueError(42)
|
||||
|
||||
def something():
|
||||
pass
|
||||
"""
|
||||
source = getstatement(0, s)
|
||||
assert str(source) == "def func(): raise ValueError(42)"
|
||||
|
||||
def XXX_test_expression_multiline():
|
||||
source = """\
|
||||
something
|
||||
'''
|
||||
'''"""
|
||||
result = getstatement(1, source)
|
||||
assert str(result) == "'''\n'''"
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import sys
|
||||
from textwrap import dedent
|
||||
import pytest, py
|
||||
|
||||
import _pytest._code
|
||||
import py
|
||||
import pytest
|
||||
from _pytest.main import EXIT_NOTESTSCOLLECTED
|
||||
|
||||
|
||||
@@ -494,6 +497,7 @@ class TestFunction:
|
||||
return True
|
||||
config.pluginmanager.register(MyPlugin1())
|
||||
config.pluginmanager.register(MyPlugin2())
|
||||
config.hook.pytest_runtest_setup(item=item)
|
||||
config.hook.pytest_pyfunc_call(pyfuncitem=item)
|
||||
|
||||
def test_multiple_parametrize(self, testdir):
|
||||
@@ -527,6 +531,84 @@ class TestFunction:
|
||||
assert colitems[2].name == 'test2[a-c]'
|
||||
assert colitems[3].name == 'test2[b-c]'
|
||||
|
||||
def test_parametrize_skipif(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
import pytest
|
||||
|
||||
m = pytest.mark.skipif('True')
|
||||
|
||||
@pytest.mark.parametrize('x', [0, 1, m(2)])
|
||||
def test_skip_if(x):
|
||||
assert x < 2
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines('* 2 passed, 1 skipped in *')
|
||||
|
||||
def test_parametrize_skip(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
import pytest
|
||||
|
||||
m = pytest.mark.skip('')
|
||||
|
||||
@pytest.mark.parametrize('x', [0, 1, m(2)])
|
||||
def test_skip(x):
|
||||
assert x < 2
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines('* 2 passed, 1 skipped in *')
|
||||
|
||||
def test_parametrize_skipif_no_skip(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
import pytest
|
||||
|
||||
m = pytest.mark.skipif('False')
|
||||
|
||||
@pytest.mark.parametrize('x', [0, 1, m(2)])
|
||||
def test_skipif_no_skip(x):
|
||||
assert x < 2
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines('* 1 failed, 2 passed in *')
|
||||
|
||||
def test_parametrize_xfail(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
import pytest
|
||||
|
||||
m = pytest.mark.xfail('True')
|
||||
|
||||
@pytest.mark.parametrize('x', [0, 1, m(2)])
|
||||
def test_xfail(x):
|
||||
assert x < 2
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines('* 2 passed, 1 xfailed in *')
|
||||
|
||||
def test_parametrize_passed(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
import pytest
|
||||
|
||||
m = pytest.mark.xfail('True')
|
||||
|
||||
@pytest.mark.parametrize('x', [0, 1, m(2)])
|
||||
def test_xfail(x):
|
||||
pass
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines('* 2 passed, 1 xpassed in *')
|
||||
|
||||
def test_parametrize_xfail_passed(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
import pytest
|
||||
|
||||
m = pytest.mark.xfail('False')
|
||||
|
||||
@pytest.mark.parametrize('x', [0, 1, m(2)])
|
||||
def test_passed(x):
|
||||
pass
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines('* 3 passed in *')
|
||||
|
||||
|
||||
class TestSorting:
|
||||
def test_check_equality(self, testdir):
|
||||
@@ -598,13 +680,13 @@ class TestConftestCustomization:
|
||||
|
||||
def test_customized_pymakemodule_issue205_subdir(self, testdir):
|
||||
b = testdir.mkdir("a").mkdir("b")
|
||||
b.join("conftest.py").write(py.code.Source("""
|
||||
b.join("conftest.py").write(_pytest._code.Source("""
|
||||
def pytest_pycollect_makemodule(__multicall__):
|
||||
mod = __multicall__.execute()
|
||||
mod.obj.hello = "world"
|
||||
return mod
|
||||
"""))
|
||||
b.join("test_module.py").write(py.code.Source("""
|
||||
b.join("test_module.py").write(_pytest._code.Source("""
|
||||
def test_hello():
|
||||
assert hello == "world"
|
||||
"""))
|
||||
@@ -613,7 +695,7 @@ class TestConftestCustomization:
|
||||
|
||||
def test_customized_pymakeitem(self, testdir):
|
||||
b = testdir.mkdir("a").mkdir("b")
|
||||
b.join("conftest.py").write(py.code.Source("""
|
||||
b.join("conftest.py").write(_pytest._code.Source("""
|
||||
import pytest
|
||||
@pytest.hookimpl(hookwrapper=True)
|
||||
def pytest_pycollect_makeitem():
|
||||
@@ -624,7 +706,7 @@ class TestConftestCustomization:
|
||||
for func in result:
|
||||
func._some123 = "world"
|
||||
"""))
|
||||
b.join("test_module.py").write(py.code.Source("""
|
||||
b.join("test_module.py").write(_pytest._code.Source("""
|
||||
import pytest
|
||||
|
||||
@pytest.fixture()
|
||||
@@ -662,7 +744,7 @@ class TestConftestCustomization:
|
||||
def test_setup_only_available_in_subdir(testdir):
|
||||
sub1 = testdir.mkpydir("sub1")
|
||||
sub2 = testdir.mkpydir("sub2")
|
||||
sub1.join("conftest.py").write(py.code.Source("""
|
||||
sub1.join("conftest.py").write(_pytest._code.Source("""
|
||||
import pytest
|
||||
def pytest_runtest_setup(item):
|
||||
assert item.fspath.purebasename == "test_in_sub1"
|
||||
@@ -671,7 +753,7 @@ def test_setup_only_available_in_subdir(testdir):
|
||||
def pytest_runtest_teardown(item):
|
||||
assert item.fspath.purebasename == "test_in_sub1"
|
||||
"""))
|
||||
sub2.join("conftest.py").write(py.code.Source("""
|
||||
sub2.join("conftest.py").write(_pytest._code.Source("""
|
||||
import pytest
|
||||
def pytest_runtest_setup(item):
|
||||
assert item.fspath.purebasename == "test_in_sub2"
|
||||
@@ -787,7 +869,7 @@ class TestTracebackCutting:
|
||||
except ValueError:
|
||||
_, _, tb = sys.exc_info()
|
||||
|
||||
tb = py.code.Traceback(tb)
|
||||
tb = _pytest._code.Traceback(tb)
|
||||
assert isinstance(tb[-1].path, str)
|
||||
assert not filter_traceback(tb[-1])
|
||||
|
||||
@@ -810,7 +892,7 @@ class TestTracebackCutting:
|
||||
_, _, tb = sys.exc_info()
|
||||
|
||||
testdir.tmpdir.join('filter_traceback_entry_as_str.py').remove()
|
||||
tb = py.code.Traceback(tb)
|
||||
tb = _pytest._code.Traceback(tb)
|
||||
assert isinstance(tb[-1].path, str)
|
||||
assert filter_traceback(tb[-1])
|
||||
|
||||
@@ -880,6 +962,21 @@ class TestReportInfo:
|
||||
pass
|
||||
"""
|
||||
|
||||
def test_reportinfo_with_nasty_getattr(self, testdir):
|
||||
# https://github.com/pytest-dev/pytest/issues/1204
|
||||
modcol = testdir.getmodulecol("""
|
||||
# lineno 0
|
||||
class TestClass:
|
||||
def __getattr__(self, name):
|
||||
return "this is not an int"
|
||||
|
||||
def test_foo(self):
|
||||
pass
|
||||
""")
|
||||
classcol = testdir.collect_by_name(modcol, "TestClass")
|
||||
instance = classcol.collect()[0]
|
||||
fspath, lineno, msg = instance.reportinfo()
|
||||
|
||||
|
||||
def test_customized_python_discovery(testdir):
|
||||
testdir.makeini("""
|
||||
@@ -1061,3 +1158,26 @@ def test_dont_collect_non_function_callable(testdir):
|
||||
'WC2 *',
|
||||
'*1 passed, 1 pytest-warnings in *',
|
||||
])
|
||||
|
||||
|
||||
def test_class_injection_does_not_break_collection(testdir):
|
||||
"""Tests whether injection during collection time will terminate testing.
|
||||
|
||||
In this case the error should not occur if the TestClass itself
|
||||
is modified during collection time, and the original method list
|
||||
is still used for collection.
|
||||
"""
|
||||
testdir.makeconftest("""
|
||||
from test_inject import TestClass
|
||||
def pytest_generate_tests(metafunc):
|
||||
TestClass.changed_var = {}
|
||||
""")
|
||||
testdir.makepyfile(test_inject='''
|
||||
class TestClass(object):
|
||||
def test_injection(self):
|
||||
"""Test being parametrized."""
|
||||
pass
|
||||
''')
|
||||
result = testdir.runpytest()
|
||||
assert "RuntimeError: dictionary changed size during iteration" not in result.stdout.str()
|
||||
result.stdout.fnmatch_lines(['*1 passed*'])
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import pytest, py, sys
|
||||
from _pytest import python as funcargs
|
||||
from _pytest.python import FixtureLookupError
|
||||
from _pytest.pytester import get_public_names
|
||||
from textwrap import dedent
|
||||
|
||||
import _pytest._code
|
||||
import pytest
|
||||
import sys
|
||||
from _pytest import python as funcargs
|
||||
from _pytest.pytester import get_public_names
|
||||
from _pytest.python import FixtureLookupError
|
||||
|
||||
|
||||
def test_getfuncargnames():
|
||||
def f(): pass
|
||||
assert not funcargs.getfuncargnames(f)
|
||||
@@ -86,12 +90,12 @@ class TestFillFixtures:
|
||||
def test_conftest_funcargs_only_available_in_subdir(self, testdir):
|
||||
sub1 = testdir.mkpydir("sub1")
|
||||
sub2 = testdir.mkpydir("sub2")
|
||||
sub1.join("conftest.py").write(py.code.Source("""
|
||||
sub1.join("conftest.py").write(_pytest._code.Source("""
|
||||
import pytest
|
||||
def pytest_funcarg__arg1(request):
|
||||
pytest.raises(Exception, "request.getfuncargvalue('arg2')")
|
||||
"""))
|
||||
sub2.join("conftest.py").write(py.code.Source("""
|
||||
sub2.join("conftest.py").write(_pytest._code.Source("""
|
||||
import pytest
|
||||
def pytest_funcarg__arg2(request):
|
||||
pytest.raises(Exception, "request.getfuncargvalue('arg1')")
|
||||
@@ -156,7 +160,7 @@ class TestFillFixtures:
|
||||
return 'spam'
|
||||
""")
|
||||
pkg = testdir.mkpydir("pkg")
|
||||
pkg.join("conftest.py").write(py.code.Source("""
|
||||
pkg.join("conftest.py").write(_pytest._code.Source("""
|
||||
import pytest
|
||||
|
||||
@pytest.fixture
|
||||
@@ -164,7 +168,7 @@ class TestFillFixtures:
|
||||
return spam * 2
|
||||
"""))
|
||||
testfile = pkg.join("test_spam.py")
|
||||
testfile.write(py.code.Source("""
|
||||
testfile.write(_pytest._code.Source("""
|
||||
def test_spam(spam):
|
||||
assert spam == "spamspam"
|
||||
"""))
|
||||
@@ -258,7 +262,7 @@ class TestFillFixtures:
|
||||
return request.param
|
||||
""")
|
||||
subdir = testdir.mkpydir('subdir')
|
||||
subdir.join("conftest.py").write(py.code.Source("""
|
||||
subdir.join("conftest.py").write(_pytest._code.Source("""
|
||||
import pytest
|
||||
|
||||
@pytest.fixture
|
||||
@@ -266,7 +270,7 @@ class TestFillFixtures:
|
||||
return 'spam'
|
||||
"""))
|
||||
testfile = subdir.join("test_spam.py")
|
||||
testfile.write(py.code.Source("""
|
||||
testfile.write(_pytest._code.Source("""
|
||||
def test_spam(spam):
|
||||
assert spam == "spam"
|
||||
"""))
|
||||
@@ -312,7 +316,7 @@ class TestFillFixtures:
|
||||
return 'spam'
|
||||
""")
|
||||
subdir = testdir.mkpydir('subdir')
|
||||
subdir.join("conftest.py").write(py.code.Source("""
|
||||
subdir.join("conftest.py").write(_pytest._code.Source("""
|
||||
import pytest
|
||||
|
||||
@pytest.fixture(params=[1, 2, 3])
|
||||
@@ -320,7 +324,7 @@ class TestFillFixtures:
|
||||
return request.param
|
||||
"""))
|
||||
testfile = subdir.join("test_spam.py")
|
||||
testfile.write(py.code.Source("""
|
||||
testfile.write(_pytest._code.Source("""
|
||||
params = {'spam': 1}
|
||||
|
||||
def test_spam(spam):
|
||||
@@ -609,7 +613,7 @@ class TestRequestBasic:
|
||||
def test_fixtures_sub_subdir_normalize_sep(self, testdir):
|
||||
# this tests that normalization of nodeids takes place
|
||||
b = testdir.mkdir("tests").mkdir("unit")
|
||||
b.join("conftest.py").write(py.code.Source("""
|
||||
b.join("conftest.py").write(_pytest._code.Source("""
|
||||
def pytest_funcarg__arg1():
|
||||
pass
|
||||
"""))
|
||||
@@ -1349,7 +1353,7 @@ class TestAutouseDiscovery:
|
||||
class TestAutouseManagement:
|
||||
def test_autouse_conftest_mid_directory(self, testdir):
|
||||
pkgdir = testdir.mkpydir("xyz123")
|
||||
pkgdir.join("conftest.py").write(py.code.Source("""
|
||||
pkgdir.join("conftest.py").write(_pytest._code.Source("""
|
||||
import pytest
|
||||
@pytest.fixture(autouse=True)
|
||||
def app():
|
||||
@@ -1357,7 +1361,7 @@ class TestAutouseManagement:
|
||||
sys._myapp = "hello"
|
||||
"""))
|
||||
t = pkgdir.ensure("tests", "test_app.py")
|
||||
t.write(py.code.Source("""
|
||||
t.write(_pytest._code.Source("""
|
||||
import sys
|
||||
def test_app():
|
||||
assert sys._myapp == "hello"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user