Compare commits
331 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9927e457b | ||
|
|
a71fdd26b3 | ||
|
|
2c7d00579b | ||
|
|
de35b077a2 | ||
|
|
086d4e4ced | ||
|
|
431ce79d94 | ||
|
|
db6f347db6 | ||
|
|
4b709037ab | ||
|
|
d946299b0a | ||
|
|
694c6fd0e7 | ||
|
|
8b1e53f6d3 | ||
|
|
a930f44e60 | ||
|
|
ac19212b2d | ||
|
|
03c314e3be | ||
|
|
fad7bd4393 | ||
|
|
b80e875525 | ||
|
|
1fc466e8ac | ||
|
|
209a0cd5b2 | ||
|
|
9ddfd62848 | ||
|
|
48838727ae | ||
|
|
39503932a4 | ||
|
|
da7133d201 | ||
|
|
684d7f8668 | ||
|
|
1327eb7cef | ||
|
|
030c337c68 | ||
|
|
3ab9b48782 | ||
|
|
2bdd034242 | ||
|
|
b5a83a6af1 | ||
|
|
d565df90ad | ||
|
|
88dc5f8204 | ||
|
|
fb6282caaa | ||
|
|
f7f569f730 | ||
|
|
63a924b922 | ||
|
|
f5897498f3 | ||
|
|
c478027805 | ||
|
|
109e2f215f | ||
|
|
41df742faf | ||
|
|
86f70f5201 | ||
|
|
94ee37cdb3 | ||
|
|
8360c1e687 | ||
|
|
ca5d02df06 | ||
|
|
c3fcf4d928 | ||
|
|
517d498285 | ||
|
|
9686a4129c | ||
|
|
b7d31952eb | ||
|
|
b879074a64 | ||
|
|
4800aeaef7 | ||
|
|
ca7c1f5d8e | ||
|
|
f5a6a84314 | ||
|
|
2888988eb6 | ||
|
|
e27dbdd071 | ||
|
|
6c54ee03de | ||
|
|
0cd7effe35 | ||
|
|
5851a99b82 | ||
|
|
863a206727 | ||
|
|
722b35b0a6 | ||
|
|
51dd63d3e1 | ||
|
|
57f997b0b4 | ||
|
|
654212d93b | ||
|
|
88aa8f5435 | ||
|
|
0755d0605f | ||
|
|
4b88d6d2d7 | ||
|
|
407283ef81 | ||
|
|
3fddf99661 | ||
|
|
21f72b7163 | ||
|
|
719e89fc1a | ||
|
|
afa88a479b | ||
|
|
7d86827b5e | ||
|
|
b2ebb80878 | ||
|
|
7fc0d45a4c | ||
|
|
3b85a56db2 | ||
|
|
743711cd1f | ||
|
|
cfe1d4f7c9 | ||
|
|
2cdb54225c | ||
|
|
8f24e10571 | ||
|
|
3ac36f6572 | ||
|
|
e96da76c3b | ||
|
|
ca6f3d24c3 | ||
|
|
e24b56af6c | ||
|
|
007a77c2ba | ||
|
|
18fa7d866d | ||
|
|
9ccd52d538 | ||
|
|
cbbbfcd101 | ||
|
|
72a48d69cd | ||
|
|
7e4b21e9a7 | ||
|
|
b90d82c17f | ||
|
|
b60a8c12d5 | ||
|
|
a24c7b42f1 | ||
|
|
0350a1929d | ||
|
|
99783b6fba | ||
|
|
0e3abdc1af | ||
|
|
905c648b99 | ||
|
|
b81c257360 | ||
|
|
64e9956770 | ||
|
|
2385553790 | ||
|
|
4c6a11b8e1 | ||
|
|
1111d5b26b | ||
|
|
44d5524be2 | ||
|
|
1023390f53 | ||
|
|
02199c218d | ||
|
|
46901fb813 | ||
|
|
ef2ddb6f16 | ||
|
|
e3a2e1bbf8 | ||
|
|
8a0a880294 | ||
|
|
6817a56270 | ||
|
|
87860600fb | ||
|
|
377f63085a | ||
|
|
2a7c79dbf5 | ||
|
|
7a8134660f | ||
|
|
863a255c9d | ||
|
|
9b51105afa | ||
|
|
4b56b075c1 | ||
|
|
788303523e | ||
|
|
4b87810fc2 | ||
|
|
ad72e7f29d | ||
|
|
15ec5a898c | ||
|
|
9d9dd381bc | ||
|
|
997df928b7 | ||
|
|
6d145ac93a | ||
|
|
1280add047 | ||
|
|
c53556b88d | ||
|
|
60a53c75a6 | ||
|
|
ca8281f229 | ||
|
|
af7db5195b | ||
|
|
c2138e9733 | ||
|
|
31c1c69901 | ||
|
|
b7054495a7 | ||
|
|
fc03c35bbd | ||
|
|
8320232f38 | ||
|
|
52838a7834 | ||
|
|
87eae6213e | ||
|
|
5d8b950b7f | ||
|
|
7b54476d71 | ||
|
|
13ddce2381 | ||
|
|
be30eb22a6 | ||
|
|
589138ea71 | ||
|
|
f7a9beaefb | ||
|
|
214793f697 | ||
|
|
c36186ce65 | ||
|
|
e6a063ee47 | ||
|
|
723e414814 | ||
|
|
a00b516f9a | ||
|
|
31421cb6d7 | ||
|
|
a6f2af23fb | ||
|
|
ffa1bf726d | ||
|
|
199f0314b9 | ||
|
|
87df85f12d | ||
|
|
ae327ef435 | ||
|
|
1736242bd7 | ||
|
|
1a319056fc | ||
|
|
ea7a997afc | ||
|
|
2c7613c15c | ||
|
|
ab637e028b | ||
|
|
1934f515ef | ||
|
|
d9f0a28da2 | ||
|
|
ca88c02507 | ||
|
|
ea4a3adfd6 | ||
|
|
c4c966683c | ||
|
|
469830fffa | ||
|
|
f9720a38fe | ||
|
|
28b2859718 | ||
|
|
5e77eb23eb | ||
|
|
3bcd3317ad | ||
|
|
4af052b098 | ||
|
|
ac3d8800fd | ||
|
|
2951c3ace9 | ||
|
|
17e1106584 | ||
|
|
7e2d4daa68 | ||
|
|
0cfc4a49ea | ||
|
|
da1996b5f5 | ||
|
|
345b8391c4 | ||
|
|
b1595d3f61 | ||
|
|
c294a417bd | ||
|
|
bc5a5a63f2 | ||
|
|
655afba17d | ||
|
|
212f4b4d64 | ||
|
|
60906f7a46 | ||
|
|
72afbbbd71 | ||
|
|
583c736f0c | ||
|
|
8a0a18e9b3 | ||
|
|
bbc61c85ac | ||
|
|
02511d1564 | ||
|
|
f78408df77 | ||
|
|
f2175146a9 | ||
|
|
d8bc40271a | ||
|
|
fe27f3cc7d | ||
|
|
3aa0e4a526 | ||
|
|
ee65ca10f4 | ||
|
|
afbeb056f0 | ||
|
|
242b67de17 | ||
|
|
ed1095565b | ||
|
|
5373a63008 | ||
|
|
e6e86fa462 | ||
|
|
5a1ce3c45c | ||
|
|
67279418ff | ||
|
|
1f1c24fe15 | ||
|
|
7803bca335 | ||
|
|
c610c903f6 | ||
|
|
36e7cc1b9c | ||
|
|
d69c9da656 | ||
|
|
a113c4c6d3 | ||
|
|
9e3cd03721 | ||
|
|
0e5f2847f1 | ||
|
|
963b944e79 | ||
|
|
55cd3d8bf3 | ||
|
|
150ad0172f | ||
|
|
9d8645b45d | ||
|
|
bbd265184d | ||
|
|
3279cfa28b | ||
|
|
5fb4a100c9 | ||
|
|
71b4908233 | ||
|
|
77d2f6adde | ||
|
|
51688270ac | ||
|
|
19f3e06ab0 | ||
|
|
d2dc797779 | ||
|
|
56aa9962fc | ||
|
|
8e41ef5776 | ||
|
|
331bd84ef4 | ||
|
|
4ac3445056 | ||
|
|
8c7ae7f7a5 | ||
|
|
05c4ecf892 | ||
|
|
c5f9958783 | ||
|
|
7a90515d49 | ||
|
|
3ab94544b9 | ||
|
|
3c317dc35e | ||
|
|
b2cb93e06d | ||
|
|
c93fbb0e57 | ||
|
|
cf7cae0780 | ||
|
|
55c349a9eb | ||
|
|
73446e98be | ||
|
|
0bc98eb9d2 | ||
|
|
bfe9779b37 | ||
|
|
bb6f3ebd31 | ||
|
|
ee69b43c7a | ||
|
|
63a6936d82 | ||
|
|
1cbd2db621 | ||
|
|
94aa76fec0 | ||
|
|
265a4de06e | ||
|
|
712898cfe1 | ||
|
|
f31dc7a8b7 | ||
|
|
9c9679945e | ||
|
|
ba79c1926c | ||
|
|
76fb51a4ba | ||
|
|
93da606763 | ||
|
|
5e479c94ce | ||
|
|
1884be0121 | ||
|
|
8f8466ee40 | ||
|
|
dfcb0e322c | ||
|
|
da3b42ce46 | ||
|
|
fa9bd8443f | ||
|
|
5a3547dd7e | ||
|
|
c4b3a09886 | ||
|
|
f747d363b0 | ||
|
|
65c69a34ac | ||
|
|
ba0100e057 | ||
|
|
37c47155e0 | ||
|
|
5ba2a7f628 | ||
|
|
0cf79b29cd | ||
|
|
6d1662e4b7 | ||
|
|
850fd2b7f7 | ||
|
|
48e6aa9dc7 | ||
|
|
0dd05023b8 | ||
|
|
aeba66ac6a | ||
|
|
d23f9fab46 | ||
|
|
69ef750091 | ||
|
|
ca8b3c2307 | ||
|
|
857c99d354 | ||
|
|
3785f1aae3 | ||
|
|
d0e18ac63f | ||
|
|
296f752cca | ||
|
|
456731ed0f | ||
|
|
c8653b4c02 | ||
|
|
e7a86caac2 | ||
|
|
162c3689c6 | ||
|
|
b94c3084a6 | ||
|
|
570ad36eaf | ||
|
|
9d107523a1 | ||
|
|
06ab38a2fc | ||
|
|
e007f2dc54 | ||
|
|
25547e3afb | ||
|
|
64e6c71bf6 | ||
|
|
80f590288b | ||
|
|
570688f701 | ||
|
|
c5f587d6db | ||
|
|
ee713ad036 | ||
|
|
51b40dd22c | ||
|
|
65edf87ea6 | ||
|
|
4d4b551079 | ||
|
|
e13fedc256 | ||
|
|
97f9bc2e46 | ||
|
|
d0bf65e6c8 | ||
|
|
8d25e52e1e | ||
|
|
6fefab0e3a | ||
|
|
1e94d900f2 | ||
|
|
5f99511ab7 | ||
|
|
22dd5e29e2 | ||
|
|
725e63db66 | ||
|
|
3d79e7060e | ||
|
|
1d7c71884e | ||
|
|
ffb5b8efa1 | ||
|
|
68786a6434 | ||
|
|
b97de57ebe | ||
|
|
03445913e0 | ||
|
|
8580058ffb | ||
|
|
1c9ef2443f | ||
|
|
cac1a48fc7 | ||
|
|
b5955c5979 | ||
|
|
765b053984 | ||
|
|
a9adfa9114 | ||
|
|
7f403950ad | ||
|
|
f263f54889 | ||
|
|
d66ff7e63e | ||
|
|
f3e03fc298 | ||
|
|
2ef350aede | ||
|
|
b940ed11a0 | ||
|
|
e15da7cbef | ||
|
|
5b64b0130d | ||
|
|
af89a9667f | ||
|
|
c64c567b75 | ||
|
|
d31f4dcba8 | ||
|
|
d9ce7f143e | ||
|
|
4ac465acfb | ||
|
|
a4909a0ae4 | ||
|
|
c790490387 | ||
|
|
664b01ca42 | ||
|
|
ff0c75aa34 | ||
|
|
476d210d09 | ||
|
|
eedc4242ef | ||
|
|
370f5dd5cb | ||
|
|
79f45928a4 | ||
|
|
dbff4ae034 |
33
.gitignore
vendored
Normal file
33
.gitignore
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
# Automatically generated by `hgimportsvn`
|
||||
.svn
|
||||
.hgsvn
|
||||
|
||||
# Ignore local virtualenvs
|
||||
lib/
|
||||
bin/
|
||||
include/
|
||||
.Python/
|
||||
|
||||
# These lines are suggested according to the svn:ignore property
|
||||
# Feel free to enable them by uncommenting them
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.swp
|
||||
*.html
|
||||
*.class
|
||||
*.orig
|
||||
*~
|
||||
|
||||
doc/*/_build
|
||||
build/
|
||||
dist/
|
||||
*.egg-info
|
||||
issue/
|
||||
env/
|
||||
3rdparty/
|
||||
.tox
|
||||
.cache
|
||||
.coverage
|
||||
.ropeproject
|
||||
.idea
|
||||
|
||||
10
.hgignore
10
.hgignore
@@ -1,9 +1,15 @@
|
||||
|
||||
# Automatically generated by `hgimportsvn`
|
||||
syntax:glob
|
||||
.svn
|
||||
.hgsvn
|
||||
|
||||
# Ignore local virtualenvs
|
||||
syntax:glob
|
||||
lib/
|
||||
bin/
|
||||
include/
|
||||
.Python/
|
||||
|
||||
# These lines are suggested according to the svn:ignore property
|
||||
# Feel free to enable them by uncommenting them
|
||||
syntax:glob
|
||||
@@ -25,3 +31,5 @@ env/
|
||||
.tox
|
||||
.cache
|
||||
.coverage
|
||||
.ropeproject
|
||||
*.sublime-*
|
||||
|
||||
8
.hgtags
8
.hgtags
@@ -52,3 +52,11 @@ ad9fe504a371ad8eb613052d58f229aa66f53527 2.2.4
|
||||
c27a60097767c16a54ae56d9669a77925b213b9b 2.3.0
|
||||
acf0e1477fb19a1d35a4e40242b77fa6af32eb17 2.3.1
|
||||
8738b828dec53937765db71951ef955cca4c51f6 2.3.2
|
||||
7fe44182c434f8ac89149a3c340479872a5d5ccb 2.3.3
|
||||
ef299e57f24218dbdd949498d7e660723636bcc3 2.3.4
|
||||
fc3a793e87ec907000a47ea0d3a372a2fe218c0a 2.3.5
|
||||
b93ac0cdae02effaa3c136a681cc45bba757fe46 1.4.14
|
||||
b93ac0cdae02effaa3c136a681cc45bba757fe46 1.4.14
|
||||
0000000000000000000000000000000000000000 1.4.14
|
||||
0000000000000000000000000000000000000000 1.4.14
|
||||
0000000000000000000000000000000000000000 1.4.14
|
||||
|
||||
10
.travis.yml
Normal file
10
.travis.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
language: python
|
||||
# command to install dependencies
|
||||
install: "pip install -U detox"
|
||||
# # command to run tests
|
||||
script: detox --recreate
|
||||
notifications:
|
||||
irc:
|
||||
- "chat.freenode.net#pytest-dev"
|
||||
email:
|
||||
- pytest-commit@python.org
|
||||
12
AUTHORS
12
AUTHORS
@@ -6,9 +6,15 @@ Contributors include::
|
||||
Ronny Pfannschmidt
|
||||
Benjamin Peterson
|
||||
Floris Bruynooghe
|
||||
Jason R. Coombs
|
||||
Wouter van Ackooy
|
||||
Samuele Pedroni
|
||||
Anatoly Bubenkoff
|
||||
Brianna Laugher
|
||||
Carl Friedrich Bolz
|
||||
Armin Rigo
|
||||
Maho
|
||||
Jaap Broekhuizen
|
||||
Maciek Fijalkowski
|
||||
Guido Wesdorp
|
||||
Brian Dorsey
|
||||
@@ -23,3 +29,9 @@ Grig Gheorghiu
|
||||
Bob Ippolito
|
||||
Christian Tismer
|
||||
Daniel Nuri
|
||||
Graham Horler
|
||||
Andreas Zeidler
|
||||
Brian Okken
|
||||
Katarzyna Jachim
|
||||
Christian Theunert
|
||||
Anthon van der Neut
|
||||
|
||||
256
CHANGELOG
256
CHANGELOG
@@ -1,3 +1,257 @@
|
||||
Changes between 2.3.5 and 2.4
|
||||
-----------------------------------
|
||||
|
||||
known incompatibilities:
|
||||
|
||||
- if calling --genscript from python2.7 or above, you only get a
|
||||
standalone script which works on python2.7 or above. Use Python2.6
|
||||
to also get a python2.5 compatible version.
|
||||
|
||||
- all xunit-style teardown methods (nose-style, pytest-style,
|
||||
unittest-style) will not be called if the corresponding setup method failed,
|
||||
see issue322 below.
|
||||
|
||||
- the pytest_plugin_unregister hook wasn't ever properly called
|
||||
and there is no known implementation of the hook - so it got removed.
|
||||
|
||||
- pytest.fixture-decorated functions cannot be generators (i.e. use
|
||||
yield) anymore. This change might be reversed in 2.4.1 if it causes
|
||||
unforeseen real-life issues. However, you can always write and return
|
||||
an inner function/generator and change the fixture consumer to iterate
|
||||
over the returned generator. This change was done in lieu of the new
|
||||
``pytest.yield_fixture`` decorator, see below.
|
||||
|
||||
new features:
|
||||
|
||||
- experimentally introduce a new ``pytest.yield_fixture`` decorator
|
||||
which accepts exactly the same parameters as pytest.fixture but
|
||||
mandates a ``yield`` statement instead of a ``return statement`` from
|
||||
fixture functions. This allows direct integration with "with-style"
|
||||
context managers in fixture functions and generally avoids registering
|
||||
of finalization callbacks in favour of treating the "after-yield" as
|
||||
teardown code. Thanks Andreas Pelme, Vladimir Keleshev, Floris
|
||||
Bruynooghe, Ronny Pfannschmidt and many others for discussions.
|
||||
|
||||
- allow boolean expression directly with skipif/xfail
|
||||
if a "reason" is also specified. Rework skipping documentation
|
||||
to recommend "condition as booleans" because it prevents surprises
|
||||
when importing markers between modules. Specifying conditions
|
||||
as strings will remain fully supported.
|
||||
|
||||
- reporting: color the last line red or green depending if
|
||||
failures/errors occured or everything passed. thanks Christian
|
||||
Theunert.
|
||||
|
||||
- make "import pdb ; pdb.set_trace()" work natively wrt capturing (no
|
||||
"-s" needed anymore), making ``pytest.set_trace()`` a mere shortcut.
|
||||
|
||||
- fix issue181: --pdb now also works on collect errors (and
|
||||
on internal errors) . This was implemented by a slight internal
|
||||
refactoring and the introduction of a new hook
|
||||
``pytest_exception_interact`` hook (see next item).
|
||||
|
||||
- fix issue341: introduce new experimental hook for IDEs/terminals to
|
||||
intercept debugging: ``pytest_exception_interact(node, call, report)``.
|
||||
|
||||
- new monkeypatch.setattr() variant to provide a shorter
|
||||
invocation for patching out classes/functions from modules:
|
||||
|
||||
monkeypatch.setattr("requests.get", myfunc)
|
||||
|
||||
will replace the "get" function of the "requests" module with ``myfunc``.
|
||||
|
||||
- fix issue322: tearDownClass is not run if setUpClass failed. Thanks
|
||||
Mathieu Agopian for the initial fix. Also make all of pytest/nose
|
||||
finalizer mimick the same generic behaviour: if a setupX exists and
|
||||
fails, don't run teardownX. This internally introduces a new method
|
||||
"node.addfinalizer()" helper which can only be called during the setup
|
||||
phase of a node.
|
||||
|
||||
- simplify pytest.mark.parametrize() signature: allow to pass a
|
||||
CSV-separated string to specify argnames. For example:
|
||||
``pytest.mark.parametrize("input,expected", [(1,2), (2,3)])``
|
||||
works as well as the previous:
|
||||
``pytest.mark.parametrize(("input", "expected"), ...)``.
|
||||
|
||||
- add support for setUpModule/tearDownModule detection, thanks Brian Okken.
|
||||
|
||||
- integrate tab-completion on options through use of "argcomplete".
|
||||
Thanks Anthon van der Neut for the PR.
|
||||
|
||||
- change option names to be hyphen-separated long options but keep the
|
||||
old spelling backward compatible. py.test -h will only show the
|
||||
hyphenated version, for example "--collect-only" but "--collectonly"
|
||||
will remain valid as well (for backward-compat reasons). Many thanks to
|
||||
Anthon van der Neut for the implementation and to Hynek Schlawack for
|
||||
pushing us.
|
||||
|
||||
- fix issue 308 - allow to mark/xfail/skip individual parameter sets
|
||||
when parametrizing. Thanks Brianna Laugher.
|
||||
|
||||
- call new experimental pytest_load_initial_conftests hook to allow
|
||||
3rd party plugins to do something before a conftest is loaded.
|
||||
|
||||
Bug fixes:
|
||||
|
||||
- fix issue358 - capturing options are now parsed more properly
|
||||
by using a new parser.parse_known_args method.
|
||||
|
||||
- pytest now uses argparse instead of optparse (thanks Anthon) which
|
||||
means that "argparse" is added as a dependency if installing into python2.6
|
||||
environments or below.
|
||||
|
||||
- fix issue333: fix a case of bad unittest/pytest hook interaction.
|
||||
|
||||
- PR27: correctly handle nose.SkipTest during collection. Thanks
|
||||
Antonio Cuni, Ronny Pfannschmidt.
|
||||
|
||||
- fix issue355: junitxml puts name="pytest" attribute to testsuite tag.
|
||||
|
||||
- fix issue336: autouse fixture in plugins should work again.
|
||||
|
||||
- fix issue279: improve object comparisons on assertion failure
|
||||
for standard datatypes and recognise collections.abc. Thanks to
|
||||
Brianna Laugher and Mathieu Agopian.
|
||||
|
||||
- fix issue317: assertion rewriter support for the is_package method
|
||||
|
||||
- fix issue335: document py.code.ExceptionInfo() object returned
|
||||
from pytest.raises(), thanks Mathieu Agopian.
|
||||
|
||||
- remove implicit distribute_setup support from setup.py.
|
||||
|
||||
- fix issue305: ignore any problems when writing pyc files.
|
||||
|
||||
- SO-17664702: call fixture finalizers even if the fixture function
|
||||
partially failed (finalizers would not always be called before)
|
||||
|
||||
- fix issue320 - fix class scope for fixtures when mixed with
|
||||
module-level functions. Thanks Anatloy Bubenkoff.
|
||||
|
||||
- you can specify "-q" or "-qq" to get different levels of "quieter"
|
||||
reporting (thanks Katarzyna Jachim)
|
||||
|
||||
- fix issue300 - Fix order of conftest loading when starting py.test
|
||||
in a subdirectory.
|
||||
|
||||
- fix issue323 - sorting of many module-scoped arg parametrizations
|
||||
|
||||
- make sessionfinish hooks execute with the same cwd-context as at
|
||||
session start (helps fix plugin behaviour which write output files
|
||||
with relative path such as pytest-cov)
|
||||
|
||||
- fix issue316 - properly reference collection hooks in docs
|
||||
|
||||
- fix issue 306 - cleanup of -k/-m options to only match markers/test
|
||||
names/keywords respectively. Thanks Wouter van Ackooy.
|
||||
|
||||
- improved doctest counting for doctests in python modules --
|
||||
files without any doctest items will not show up anymore
|
||||
and doctest examples are counted as separate test items.
|
||||
thanks Danilo Bellini.
|
||||
|
||||
- fix issue245 by depending on the released py-1.4.14
|
||||
which fixes py.io.dupfile to work with files with no
|
||||
mode. Thanks Jason R. Coombs.
|
||||
|
||||
- fix junitxml generation when test output contains control characters,
|
||||
addressing issue267, thanks Jaap Broekhuizen
|
||||
|
||||
- fix issue338: honor --tb style for setup/teardown errors as well. Thanks Maho.
|
||||
|
||||
- fix issue307 - use yaml.safe_load in example, thanks Mark Eichin.
|
||||
|
||||
- better parametrize error messages, thanks Brianna Laugher
|
||||
|
||||
- pytest_terminal_summary(terminalreporter) hooks can now use
|
||||
".section(title)" and ".line(msg)" methods to print extra
|
||||
information at the end of a test run.
|
||||
|
||||
Changes between 2.3.4 and 2.3.5
|
||||
-----------------------------------
|
||||
|
||||
- fix issue169: respect --tb=style with setup/teardown errors as well.
|
||||
|
||||
- never consider a fixture function for test function collection
|
||||
|
||||
- allow re-running of test items / helps to fix pytest-reruntests plugin
|
||||
and also help to keep less fixture/resource references alive
|
||||
|
||||
- put captured stdout/stderr into junitxml output even for passing tests
|
||||
(thanks Adam Goucher)
|
||||
|
||||
- Issue 265 - integrate nose setup/teardown with setupstate
|
||||
so it doesnt try to teardown if it did not setup
|
||||
|
||||
- issue 271 - dont write junitxml on slave nodes
|
||||
|
||||
- Issue 274 - dont try to show full doctest example
|
||||
when doctest does not know the example location
|
||||
|
||||
- issue 280 - disable assertion rewriting on buggy CPython 2.6.0
|
||||
|
||||
- inject "getfixture()" helper to retrieve fixtures from doctests,
|
||||
thanks Andreas Zeidler
|
||||
|
||||
- issue 259 - when assertion rewriting, be consistent with the default
|
||||
source encoding of ASCII on Python 2
|
||||
|
||||
- issue 251 - report a skip instead of ignoring classes with init
|
||||
|
||||
- issue250 unicode/str mixes in parametrization names and values now works
|
||||
|
||||
- issue257, assertion-triggered compilation of source ending in a
|
||||
comment line doesn't blow up in python2.5 (fixed through py>=1.4.13.dev6)
|
||||
|
||||
- fix --genscript option to generate standalone scripts that also
|
||||
work with python3.3 (importer ordering)
|
||||
|
||||
- issue171 - in assertion rewriting, show the repr of some
|
||||
global variables
|
||||
|
||||
- fix option help for "-k"
|
||||
|
||||
- move long description of distribution into README.rst
|
||||
|
||||
- improve docstring for metafunc.parametrize()
|
||||
|
||||
- fix bug where using capsys with pytest.set_trace() in a test
|
||||
function would break when looking at capsys.readouterr()
|
||||
|
||||
- allow to specify prefixes starting with "_" when
|
||||
customizing python_functions test discovery. (thanks Graham Horler)
|
||||
|
||||
- improve PYTEST_DEBUG tracing output by puting
|
||||
extra data on a new lines with additional indent
|
||||
|
||||
- ensure OutcomeExceptions like skip/fail have initialized exception attributes
|
||||
|
||||
- issue 260 - don't use nose special setup on plain unittest cases
|
||||
|
||||
- fix issue134 - print the collect errors that prevent running specified test items
|
||||
|
||||
- fix issue266 - accept unicode in MarkEvaluator expressions
|
||||
|
||||
Changes between 2.3.3 and 2.3.4
|
||||
-----------------------------------
|
||||
|
||||
- yielded test functions will now have autouse-fixtures active but
|
||||
cannot accept fixtures as funcargs - it's anyway recommended to
|
||||
rather use the post-2.0 parametrize features instead of yield, see:
|
||||
http://pytest.org/latest/example/parametrize.html
|
||||
- fix autouse-issue where autouse-fixtures would not be discovered
|
||||
if defined in a a/conftest.py file and tests in a/tests/test_some.py
|
||||
- fix issue226 - LIFO ordering for fixture teardowns
|
||||
- fix issue224 - invocations with >256 char arguments now work
|
||||
- fix issue91 - add/discuss package/directory level setups in example
|
||||
- allow to dynamically define markers via
|
||||
item.keywords[...]=assignment integrating with "-m" option
|
||||
- make "-k" accept an expressions the same as with "-m" so that one
|
||||
can write: -k "name1 or name2" etc. This is a slight incompatibility
|
||||
if you used special syntax like "TestClass.test_method" which you now
|
||||
need to write as -k "TestClass and test_method" to match a certain
|
||||
method in a certain test class.
|
||||
|
||||
Changes between 2.3.2 and 2.3.3
|
||||
-----------------------------------
|
||||
|
||||
@@ -81,7 +335,7 @@ Changes between 2.2.4 and 2.3.0
|
||||
|
||||
- fix issue202 - better automatic names for parametrized test functions
|
||||
- fix issue139 - introduce @pytest.fixture which allows direct scoping
|
||||
and parametrization of funcarg factories.
|
||||
and parametrization of funcarg factories.
|
||||
- fix issue198 - conftest fixtures were not found on windows32 in some
|
||||
circumstances with nested directory structures due to path manipulation issues
|
||||
- fix issue193 skip test functions with were parametrized with empty
|
||||
|
||||
137
ISSUES.txt
137
ISSUES.txt
@@ -1,10 +1,22 @@
|
||||
|
||||
improve / add to dependency/test resource injection
|
||||
|
||||
recorder = monkeypatch.function(".......")
|
||||
-------------------------------------------------------------
|
||||
tags: wish feature docs
|
||||
tags: nice feature
|
||||
|
||||
write up better examples showing the connection between
|
||||
the two.
|
||||
Like monkeypatch.replace but sets a mock-like call recorder:
|
||||
|
||||
recorder = monkeypatch.function("os.path.abspath")
|
||||
recorder.set_return("/hello")
|
||||
os.path.abspath("hello")
|
||||
call, = recorder.calls
|
||||
assert call.args.path == "hello"
|
||||
assert call.returned == "/hello"
|
||||
...
|
||||
|
||||
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
|
||||
-------------------------------------------------------------
|
||||
@@ -94,20 +106,6 @@ appropriately to avoid this issue. Moreover/Alternatively, we could
|
||||
record which implementations of a hook succeeded and only call their
|
||||
teardown.
|
||||
|
||||
consider and document __init__ file usage in test directories
|
||||
---------------------------------------------------------------
|
||||
tags: bug core
|
||||
|
||||
Currently, a test module is imported with its fully qualified
|
||||
package path, determined by checking __init__ files upwards.
|
||||
This has the side effect that a source package at the root
|
||||
of the test dir could be imported as well. This is somewhat
|
||||
convenient but complicates the picture for running tests against
|
||||
different versions of a package. Also, implicit sys.path
|
||||
manipulations are problematic per-se. Maybe factorting out
|
||||
a pytest_addsyspath hook which can be disabled from the command line
|
||||
makes sense. In any case documentation/recommendations for
|
||||
certain scenarios makes sense.
|
||||
|
||||
relax requirement to have tests/testing contain an __init__
|
||||
----------------------------------------------------------------
|
||||
@@ -220,32 +218,10 @@ to further reduce py.test globality. Also consider
|
||||
having py.test.config and ensuretemp coming from
|
||||
a plugin rather than being there from the start.
|
||||
|
||||
consider allowing funcargs for setup methods
|
||||
--------------------------------------------------------------
|
||||
tags: experimental-wish
|
||||
|
||||
Users have expressed the wish to have funcargs available to setup
|
||||
functions. Experiment with allowing funcargs there - it might
|
||||
also help to make the py.test.ensuretemp and config deprecation.
|
||||
For filling funcargs for setup methods, we could call funcarg
|
||||
factories with a request object that not have a cls/function
|
||||
attributes. However, how to handle parametrized test functions
|
||||
and funcargs?
|
||||
|
||||
maybe introduce a setup method like:
|
||||
|
||||
setup_invocation(self, request)
|
||||
|
||||
which has full access to the test invocation through "request"
|
||||
through which you can get funcargvalues, use cached_setup etc.
|
||||
Therefore, the access to funcargs would be indirect but it
|
||||
could be consistently implemented. setup_invocation() would
|
||||
be a "glue" function for bringing together the xUnit and funcargs
|
||||
world.
|
||||
|
||||
consider pytest_addsyspath hook
|
||||
-----------------------------------------
|
||||
tags:
|
||||
tags: wish
|
||||
|
||||
py.test could call a new pytest_addsyspath() in order to systematically
|
||||
allow manipulation of sys.path and to inhibit it via --no-addsyspath
|
||||
@@ -255,13 +231,6 @@ Alternatively it could also be done via the config object
|
||||
and pytest_configure.
|
||||
|
||||
|
||||
show plugin information in test header
|
||||
----------------------------------------------------------------
|
||||
tags: feature
|
||||
|
||||
Now that external plugins are becoming more numerous
|
||||
it would be useful to have external plugins along with
|
||||
their versions displayed as a header line.
|
||||
|
||||
deprecate global py.test.config usage
|
||||
----------------------------------------------------------------
|
||||
@@ -330,3 +299,75 @@ in one content string::
|
||||
This could be run with at least three different ways to invoke pytest:
|
||||
through the shell, through "python -m pytest" and inlined. As inlined
|
||||
would be the fastest it could be run first (or "--fast" mode).
|
||||
|
||||
|
||||
Create isolate plugin
|
||||
---------------------
|
||||
tags: feature
|
||||
|
||||
The idea is that you can e.g. import modules in a test and afterwards
|
||||
sys.modules, sys.meta_path etc would be reverted. It can go further
|
||||
then just importing however, e.g. current working direcroty, file
|
||||
descriptors, ...
|
||||
|
||||
This would probably be done by marking::
|
||||
|
||||
@pytest.mark.isolate(importing=True, cwd=True, fds=False)
|
||||
def test_foo():
|
||||
...
|
||||
|
||||
With the possibility of doing this globally in an ini-file.
|
||||
|
||||
|
||||
fnmatch for test names
|
||||
----------------------
|
||||
tags: feature-wish
|
||||
|
||||
various testsuites use suffixes instead of prefixes for test classes
|
||||
also it lends itself to bdd style test names::
|
||||
|
||||
class UserBehaviour:
|
||||
def anonymous_should_not_have_inbox(user):
|
||||
...
|
||||
def registred_should_have_inbox(user):
|
||||
..
|
||||
|
||||
using the following in pytest.ini::
|
||||
|
||||
[pytest]
|
||||
python_classes = Test *Behaviour *Test
|
||||
python_functions = test *_should_*
|
||||
|
||||
|
||||
mechanism for running named parts of tests with different reporting behaviour
|
||||
------------------------------------------------------------------------------
|
||||
tags: feature-wish-incomplete
|
||||
|
||||
a few use-cases come to mind:
|
||||
|
||||
* fail assertions and record that without stopping a complete test
|
||||
|
||||
* this is in particular hepfull if a small bit of a test is known to fail/xfail::
|
||||
|
||||
def test_fun():
|
||||
with pytest.section('fdcheck, marks=pytest.mark.xfail_if(...)):
|
||||
breaks_on_windows()
|
||||
|
||||
* divide functional/acceptance tests into sections
|
||||
* provide a different mechanism for generators, maybe something like::
|
||||
|
||||
def pytest_runtest_call(item)
|
||||
if not generator:
|
||||
...
|
||||
prepare_check = GeneratorCheckprepare()
|
||||
|
||||
gen = item.obj(**fixtures)
|
||||
for check in gen
|
||||
id, call = prepare_check(check)
|
||||
# bubble should only prevent exception propagation after a failure
|
||||
# the whole test should still fail
|
||||
# there might be need for a loer level api and taking custom markers into account
|
||||
with pytest.section(id, bubble=False):
|
||||
call()
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
include CHANGELOG
|
||||
include README.txt
|
||||
include README.rst
|
||||
include setup.py
|
||||
include distribute_setup.py
|
||||
include tox.ini
|
||||
include LICENSE
|
||||
graft doc
|
||||
|
||||
45
README.rst
Normal file
45
README.rst
Normal file
@@ -0,0 +1,45 @@
|
||||
The ``py.test`` testing tool makes it easy to write small tests, yet
|
||||
scales to support complex functional testing. It provides
|
||||
|
||||
- `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 ``py.test`` 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 to Python2.4 all the way up to Python3.3,
|
||||
PyPy-1.9 and Jython-2.5.1.
|
||||
|
||||
- many `external plugins <http://pytest.org/latest/plugins.html#installing-external-plugins-searching>`_.
|
||||
|
||||
.. image:: https://secure.travis-ci.org/hpk42/pytest.png
|
||||
:target: http://travis-ci.org/hpk42/pytest
|
||||
|
||||
A simple example for a test::
|
||||
|
||||
# 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:
|
||||
|
||||
http://bitbucket.org/hpk42/pytest/issues/
|
||||
|
||||
and checkout repos at:
|
||||
|
||||
http://github.com/hpk42/pytest/ (mirror)
|
||||
http://bitbucket.org/hpk42/pytest/
|
||||
|
||||
|
||||
Copyright Holger Krekel and others, 2004-2013
|
||||
Licensed under the MIT license.
|
||||
@@ -1,4 +0,0 @@
|
||||
py.test is a simple and popular testing tool for Python.
|
||||
|
||||
See http://pytest.org for more documentation.
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
#
|
||||
__version__ = '2.3.3'
|
||||
__version__ = '2.4.0'
|
||||
|
||||
100
_pytest/_argcomplete.py
Normal file
100
_pytest/_argcomplete.py
Normal file
@@ -0,0 +1,100 @@
|
||||
|
||||
"""allow bash-completion for argparse with argcomplete if installed
|
||||
needs argcomplete>=0.5.6 for python 3.2/3.3 (older versions fail
|
||||
to find the magic string, so _ARGCOMPLETE env. var is never set, and
|
||||
this does not need special code.
|
||||
|
||||
argcomplete does not support python 2.5 (although the changes for that
|
||||
are minor).
|
||||
|
||||
Function try_argcomplete(parser) should be called directly before
|
||||
the call to ArgumentParser.parse_args().
|
||||
|
||||
The filescompleter is what you normally would use on the positional
|
||||
arguments specification, in order to get "dirname/" after "dirn<TAB>"
|
||||
instead of the default "dirname ":
|
||||
|
||||
optparser.add_argument(Config._file_or_dir, nargs='*'
|
||||
).completer=filescompleter
|
||||
|
||||
Other, application specific, completers should go in the file
|
||||
doing the add_argument calls as they need to be specified as .completer
|
||||
attributes as well. (If argcomplete is not installed, the function the
|
||||
attribute points to will not be used).
|
||||
|
||||
SPEEDUP
|
||||
=======
|
||||
The generic argcomplete script for bash-completion
|
||||
(/etc/bash_completion.d/python-argcomplete.sh )
|
||||
uses a python program to determine startup script generated by pip.
|
||||
You can speed up completion somewhat by changing this script to include
|
||||
# PYTHON_ARGCOMPLETE_OK
|
||||
so the the python-argcomplete-check-easy-install-script does not
|
||||
need to be called to find the entry point of the code and see if that is
|
||||
marked with PYTHON_ARGCOMPLETE_OK
|
||||
|
||||
INSTALL/DEBUGGING
|
||||
=================
|
||||
To include this support in another application that has setup.py generated
|
||||
scripts:
|
||||
- add the line:
|
||||
# PYTHON_ARGCOMPLETE_OK
|
||||
near the top of the main python entry point
|
||||
- include in the file calling parse_args():
|
||||
from _argcomplete import try_argcomplete, filescompleter
|
||||
, call try_argcomplete just before parse_args(), and optionally add
|
||||
filescompleter to the positional arguments' add_argument()
|
||||
If things do not work right away:
|
||||
- switch on argcomplete debugging with (also helpful when doing custom
|
||||
completers):
|
||||
export _ARC_DEBUG=1
|
||||
- run:
|
||||
python-argcomplete-check-easy-install-script $(which appname)
|
||||
echo $?
|
||||
will echo 0 if the magic line has been found, 1 if not
|
||||
- sometimes it helps to find early on errors using:
|
||||
_ARGCOMPLETE=1 _ARC_DEBUG=1 appname
|
||||
which should throw a KeyError: 'COMPLINE' (which is properly set by the
|
||||
global argcomplete script).
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
from glob import glob
|
||||
|
||||
class FastFilesCompleter:
|
||||
'Fast file completer class'
|
||||
def __init__(self, directories=True):
|
||||
self.directories = directories
|
||||
|
||||
def __call__(self, prefix, **kwargs):
|
||||
"""only called on non option completions"""
|
||||
if os.path.sep in prefix[1:]: #
|
||||
prefix_dir = len(os.path.dirname(prefix) + os.path.sep)
|
||||
else:
|
||||
prefix_dir = 0
|
||||
completion = []
|
||||
if '*' not in prefix and '?' not in prefix:
|
||||
prefix += '*'
|
||||
for x in sorted(glob(prefix)):
|
||||
if os.path.isdir(x):
|
||||
x += '/'
|
||||
# append stripping the prefix (like bash, not like compgen)
|
||||
completion.append(x[prefix_dir:])
|
||||
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:
|
||||
sys.exit(-1)
|
||||
filescompleter = FastFilesCompleter()
|
||||
|
||||
def try_argcomplete(parser):
|
||||
argcomplete.autocomplete(parser)
|
||||
else:
|
||||
def try_argcomplete(parser): pass
|
||||
filescompleter = None
|
||||
@@ -19,8 +19,8 @@ def pytest_addoption(parser):
|
||||
to provide assert expression information. """)
|
||||
group.addoption('--no-assert', action="store_true", default=False,
|
||||
dest="noassert", help="DEPRECATED equivalent to --assert=plain")
|
||||
group.addoption('--nomagic', action="store_true", default=False,
|
||||
dest="nomagic", help="DEPRECATED equivalent to --assert=plain")
|
||||
group.addoption('--nomagic', '--no-magic', action="store_true",
|
||||
default=False, help="DEPRECATED equivalent to --assert=plain")
|
||||
|
||||
class AssertionState:
|
||||
"""State for the assertion plugin."""
|
||||
@@ -39,7 +39,10 @@ def pytest_configure(config):
|
||||
except ImportError:
|
||||
mode = "reinterp"
|
||||
else:
|
||||
if sys.platform.startswith('java'):
|
||||
# Both Jython and CPython 2.6.0 have AST bugs that make the
|
||||
# assertion rewriting hook malfunction.
|
||||
if (sys.platform.startswith('java') or
|
||||
sys.version_info[:3] == (2, 6, 0)):
|
||||
mode = "reinterp"
|
||||
if mode != "plain":
|
||||
_load_modules(mode)
|
||||
@@ -78,7 +81,7 @@ def pytest_runtest_setup(item):
|
||||
if new_expl:
|
||||
# Don't include pageloads of data unless we are very verbose (-vv)
|
||||
if len(''.join(new_expl[1:])) > 80*8 and item.config.option.verbose < 2:
|
||||
new_expl[1:] = ['Detailed information too verbose, truncated']
|
||||
new_expl[1:] = ['Detailed information truncated, use "-vv" to see']
|
||||
res = '\n~'.join(new_expl)
|
||||
if item.config.getvalue("assertmode") == "rewrite":
|
||||
# The result will be fed back a python % formatting
|
||||
|
||||
@@ -11,7 +11,7 @@ from _pytest.assertion import util
|
||||
from _pytest.assertion.reinterpret import BuiltinAssertionError
|
||||
|
||||
|
||||
if sys.platform.startswith("java") and sys.version_info < (2, 5, 2):
|
||||
if sys.platform.startswith("java"):
|
||||
# See http://bugs.jython.org/issue1497
|
||||
_exprs = ("BoolOp", "BinOp", "UnaryOp", "Lambda", "IfExp", "Dict",
|
||||
"ListComp", "GeneratorExp", "Yield", "Compare", "Call",
|
||||
|
||||
@@ -6,6 +6,7 @@ import itertools
|
||||
import imp
|
||||
import marshal
|
||||
import os
|
||||
import re
|
||||
import struct
|
||||
import sys
|
||||
import types
|
||||
@@ -14,12 +15,6 @@ import py
|
||||
from _pytest.assertion import util
|
||||
|
||||
|
||||
# Windows gives ENOENT in places *nix gives ENOTDIR.
|
||||
if sys.platform.startswith("win"):
|
||||
PATH_COMPONENT_NOT_DIR = errno.ENOENT
|
||||
else:
|
||||
PATH_COMPONENT_NOT_DIR = errno.ENOTDIR
|
||||
|
||||
# py.test caches rewritten pycs in __pycache__.
|
||||
if hasattr(imp, "get_tag"):
|
||||
PYTEST_TAG = imp.get_tag() + "-PYTEST"
|
||||
@@ -38,6 +33,7 @@ PYC_EXT = ".py" + (__debug__ and "c" or "o")
|
||||
PYC_TAIL = "." + PYTEST_TAG + PYC_EXT
|
||||
|
||||
REWRITE_NEWLINES = sys.version_info[:2] != (2, 7) and sys.version_info < (3, 2)
|
||||
ASCII_IS_DEFAULT_ENCODING = sys.version_info[0] < 3
|
||||
|
||||
class AssertionRewritingHook(object):
|
||||
"""PEP302 Import hook which rewrites asserts."""
|
||||
@@ -117,7 +113,7 @@ class AssertionRewritingHook(object):
|
||||
# common case) or it's blocked by a non-dir node. In the
|
||||
# latter case, we'll ignore it in _write_pyc.
|
||||
pass
|
||||
elif e == PATH_COMPONENT_NOT_DIR:
|
||||
elif e in [errno.ENOENT, errno.ENOTDIR]:
|
||||
# One of the path components was not a directory, likely
|
||||
# because we're in a zip file.
|
||||
write = False
|
||||
@@ -154,13 +150,26 @@ class AssertionRewritingHook(object):
|
||||
mod.__file__ = co.co_filename
|
||||
# Normally, this attribute is 3.2+.
|
||||
mod.__cached__ = pyc
|
||||
mod.__loader__ = self
|
||||
py.builtin.exec_(co, mod.__dict__)
|
||||
except:
|
||||
del sys.modules[name]
|
||||
raise
|
||||
return sys.modules[name]
|
||||
|
||||
def _write_pyc(co, source_path, pyc):
|
||||
|
||||
|
||||
def is_package(self, name):
|
||||
try:
|
||||
fd, fn, desc = imp.find_module(name)
|
||||
except ImportError:
|
||||
return False
|
||||
if fd is not None:
|
||||
fd.close()
|
||||
tp = desc[2]
|
||||
return tp == imp.PKG_DIRECTORY
|
||||
|
||||
def _write_pyc(state, co, source_path, pyc):
|
||||
# Technically, we don't have to have the same pyc format as
|
||||
# (C)Python, since these "pycs" should never be seen by builtin
|
||||
# import. However, there's little reason deviate, and I hope
|
||||
@@ -171,11 +180,11 @@ def _write_pyc(co, source_path, pyc):
|
||||
fp = open(pyc, "wb")
|
||||
except IOError:
|
||||
err = sys.exc_info()[1].errno
|
||||
if err == PATH_COMPONENT_NOT_DIR:
|
||||
# This happens when we get a EEXIST in find_module creating the
|
||||
# __pycache__ directory and __pycache__ is by some non-dir node.
|
||||
return False
|
||||
raise
|
||||
state.trace("error writing pyc file at %s: errno=%s" %(pyc, err))
|
||||
# we ignore any failure to write the cache file
|
||||
# there are many reasons, permission-denied, __pycache__ being a
|
||||
# file etc.
|
||||
return False
|
||||
try:
|
||||
fp.write(imp.get_magic())
|
||||
fp.write(struct.pack("<l", mtime))
|
||||
@@ -187,12 +196,43 @@ def _write_pyc(co, source_path, pyc):
|
||||
RN = "\r\n".encode("utf-8")
|
||||
N = "\n".encode("utf-8")
|
||||
|
||||
cookie_re = re.compile("coding[:=]\s*[-\w.]+")
|
||||
BOM_UTF8 = '\xef\xbb\xbf'
|
||||
|
||||
def _rewrite_test(state, fn):
|
||||
"""Try to read and rewrite *fn* and return the code object."""
|
||||
try:
|
||||
source = fn.read("rb")
|
||||
except EnvironmentError:
|
||||
return None
|
||||
if ASCII_IS_DEFAULT_ENCODING:
|
||||
# ASCII is the default encoding in Python 2. Without a coding
|
||||
# declaration, Python 2 will complain about any bytes in the file
|
||||
# outside the ASCII range. Sadly, this behavior does not extend to
|
||||
# compile() or ast.parse(), which prefer to interpret the bytes as
|
||||
# latin-1. (At least they properly handle explicit coding cookies.) To
|
||||
# preserve this error behavior, we could force ast.parse() to use ASCII
|
||||
# as the encoding by inserting a coding cookie. Unfortunately, that
|
||||
# messes up line numbers. Thus, we have to check ourselves if anything
|
||||
# is outside the ASCII range in the case no encoding is explicitly
|
||||
# declared. For more context, see issue #269. Yay for Python 3 which
|
||||
# gets this right.
|
||||
end1 = source.find("\n")
|
||||
end2 = source.find("\n", end1 + 1)
|
||||
if (not source.startswith(BOM_UTF8) and
|
||||
(not cookie_re.match(source[0:end1]) or
|
||||
not cookie_re.match(source[end1:end2]))):
|
||||
if hasattr(state, "_indecode"):
|
||||
return None # encodings imported us again, we don't rewrite
|
||||
state._indecode = True
|
||||
try:
|
||||
try:
|
||||
source.decode("ascii")
|
||||
except UnicodeDecodeError:
|
||||
# Let it fail in real import.
|
||||
return None
|
||||
finally:
|
||||
del state._indecode
|
||||
# On Python versions which are not 2.7 and less than or equal to 3.1, the
|
||||
# parser expects *nix newlines.
|
||||
if REWRITE_NEWLINES:
|
||||
@@ -218,12 +258,12 @@ def _make_rewritten_pyc(state, fn, pyc, co):
|
||||
if sys.platform.startswith("win"):
|
||||
# Windows grants exclusive access to open files and doesn't have atomic
|
||||
# rename, so just write into the final file.
|
||||
_write_pyc(co, fn, pyc)
|
||||
_write_pyc(state, co, fn, pyc)
|
||||
else:
|
||||
# When not on windows, assume rename is atomic. Dump the code object
|
||||
# into a file specific to this process and atomically replace it.
|
||||
proc_pyc = pyc + "." + str(os.getpid())
|
||||
if _write_pyc(co, fn, proc_pyc):
|
||||
if _write_pyc(state, co, fn, proc_pyc):
|
||||
os.rename(proc_pyc, pyc)
|
||||
|
||||
def _read_pyc(source, pyc):
|
||||
@@ -262,6 +302,9 @@ def rewrite_asserts(mod):
|
||||
_saferepr = py.io.saferepr
|
||||
from _pytest.assertion.util import format_explanation as _format_explanation
|
||||
|
||||
def _should_repr_global_name(obj):
|
||||
return not hasattr(obj, "__name__") and not py.builtin.callable(obj)
|
||||
|
||||
def _format_boolop(explanations, is_or):
|
||||
return "(" + (is_or and " or " or " and ").join(explanations) + ")"
|
||||
|
||||
@@ -473,11 +516,12 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||
return self.statements
|
||||
|
||||
def visit_Name(self, name):
|
||||
# Check if the name is local or not.
|
||||
# Display the repr of the name if it's a local variable or
|
||||
# _should_repr_global_name() thinks it's acceptable.
|
||||
locs = ast.Call(self.builtin("locals"), [], [], None, None)
|
||||
globs = ast.Call(self.builtin("globals"), [], [], None, None)
|
||||
ops = [ast.In(), ast.IsNot()]
|
||||
test = ast.Compare(ast.Str(name.id), ops, [locs, globs])
|
||||
inlocs = ast.Compare(ast.Str(name.id), [ast.In()], [locs])
|
||||
dorepr = self.helper("should_repr_global_name", name)
|
||||
test = ast.BoolOp(ast.Or(), [inlocs, dorepr])
|
||||
expr = ast.IfExp(test, self.display(name), ast.Str(name.id))
|
||||
return name, self.explanation_param(expr)
|
||||
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
"""Utilities for assertion debugging"""
|
||||
|
||||
import py
|
||||
try:
|
||||
from collections.abc import Sequence
|
||||
except ImportError:
|
||||
try:
|
||||
from collections import Sequence
|
||||
except ImportError:
|
||||
Sequence = list
|
||||
|
||||
|
||||
BuiltinAssertionError = py.builtin.builtins.AssertionError
|
||||
|
||||
@@ -10,6 +18,7 @@ BuiltinAssertionError = py.builtin.builtins.AssertionError
|
||||
# DebugInterpreter.
|
||||
_reprcompare = None
|
||||
|
||||
|
||||
def format_explanation(explanation):
|
||||
"""This formats an explanation
|
||||
|
||||
@@ -83,82 +92,83 @@ except NameError:
|
||||
basestring = str
|
||||
|
||||
|
||||
def assertrepr_compare(op, left, right):
|
||||
"""return specialised explanations for some operators/operands"""
|
||||
width = 80 - 15 - len(op) - 2 # 15 chars indentation, 1 space around op
|
||||
def assertrepr_compare(config, op, left, right):
|
||||
"""Return specialised explanations for some operators/operands"""
|
||||
width = 80 - 15 - len(op) - 2 # 15 chars indentation, 1 space around op
|
||||
left_repr = py.io.saferepr(left, maxsize=int(width/2))
|
||||
right_repr = py.io.saferepr(right, maxsize=width-len(left_repr))
|
||||
summary = '%s %s %s' % (left_repr, op, right_repr)
|
||||
|
||||
issequence = lambda x: isinstance(x, (list, tuple))
|
||||
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)
|
||||
isset = lambda x: isinstance(x, (set, frozenset))
|
||||
|
||||
verbose = config.getoption('verbose')
|
||||
explanation = None
|
||||
try:
|
||||
if op == '==':
|
||||
if istext(left) and istext(right):
|
||||
explanation = _diff_text(left, right)
|
||||
explanation = _diff_text(left, right, verbose)
|
||||
elif issequence(left) and issequence(right):
|
||||
explanation = _compare_eq_sequence(left, right)
|
||||
explanation = _compare_eq_sequence(left, right, verbose)
|
||||
elif isset(left) and isset(right):
|
||||
explanation = _compare_eq_set(left, right)
|
||||
explanation = _compare_eq_set(left, right, verbose)
|
||||
elif isdict(left) and isdict(right):
|
||||
explanation = _diff_text(py.std.pprint.pformat(left),
|
||||
py.std.pprint.pformat(right))
|
||||
explanation = _compare_eq_dict(left, right, verbose)
|
||||
elif op == 'not in':
|
||||
if istext(left) and istext(right):
|
||||
explanation = _notin_text(left, right)
|
||||
explanation = _notin_text(left, right, verbose)
|
||||
except py.builtin._sysex:
|
||||
raise
|
||||
except:
|
||||
excinfo = py.code.ExceptionInfo()
|
||||
explanation = ['(pytest_assertion plugin: representation of '
|
||||
'details failed. Probably an object has a faulty __repr__.)',
|
||||
str(excinfo)]
|
||||
explanation = [
|
||||
'(pytest_assertion plugin: representation of details failed. '
|
||||
'Probably an object has a faulty __repr__.)', str(excinfo)]
|
||||
|
||||
if not explanation:
|
||||
return None
|
||||
|
||||
|
||||
return [summary] + explanation
|
||||
|
||||
|
||||
def _diff_text(left, right):
|
||||
def _diff_text(left, right, verbose=False):
|
||||
"""Return the explanation for the diff between text
|
||||
|
||||
This will skip leading and trailing characters which are
|
||||
identical to keep the diff minimal.
|
||||
Unless --verbose is used this will skip leading and trailing
|
||||
characters which are identical to keep the diff minimal.
|
||||
"""
|
||||
explanation = []
|
||||
i = 0 # just in case left or right has zero length
|
||||
for i in range(min(len(left), len(right))):
|
||||
if left[i] != right[i]:
|
||||
break
|
||||
if i > 42:
|
||||
i -= 10 # Provide some context
|
||||
explanation = ['Skipping %s identical '
|
||||
'leading characters in diff' % i]
|
||||
left = left[i:]
|
||||
right = right[i:]
|
||||
if len(left) == len(right):
|
||||
for i in range(len(left)):
|
||||
if left[-i] != right[-i]:
|
||||
if not verbose:
|
||||
i = 0 # just in case left or right has zero length
|
||||
for i in range(min(len(left), len(right))):
|
||||
if left[i] != right[i]:
|
||||
break
|
||||
if i > 42:
|
||||
i -= 10 # Provide some context
|
||||
explanation += ['Skipping %s identical '
|
||||
'trailing characters in diff' % i]
|
||||
left = left[:-i]
|
||||
right = right[:-i]
|
||||
i -= 10 # Provide some context
|
||||
explanation = ['Skipping %s identical leading '
|
||||
'characters in diff, use -v to show' % i]
|
||||
left = left[i:]
|
||||
right = right[i:]
|
||||
if len(left) == len(right):
|
||||
for i in range(len(left)):
|
||||
if left[-i] != right[-i]:
|
||||
break
|
||||
if i > 42:
|
||||
i -= 10 # Provide some context
|
||||
explanation += ['Skipping %s identical trailing '
|
||||
'characters in diff, use -v to show' % i]
|
||||
left = left[:-i]
|
||||
right = right[:-i]
|
||||
explanation += [line.strip('\n')
|
||||
for line in py.std.difflib.ndiff(left.splitlines(),
|
||||
right.splitlines())]
|
||||
return explanation
|
||||
|
||||
|
||||
def _compare_eq_sequence(left, right):
|
||||
def _compare_eq_sequence(left, right, verbose=False):
|
||||
explanation = []
|
||||
for i in range(min(len(left), len(right))):
|
||||
if left[i] != right[i]:
|
||||
@@ -166,16 +176,18 @@ def _compare_eq_sequence(left, right):
|
||||
(i, left[i], right[i])]
|
||||
break
|
||||
if len(left) > len(right):
|
||||
explanation += ['Left contains more items, '
|
||||
'first extra item: %s' % py.io.saferepr(left[len(right)],)]
|
||||
explanation += [
|
||||
'Left contains more items, first extra item: %s' %
|
||||
py.io.saferepr(left[len(right)],)]
|
||||
elif len(left) < len(right):
|
||||
explanation += ['Right contains more items, '
|
||||
'first extra item: %s' % py.io.saferepr(right[len(left)],)]
|
||||
return explanation # + _diff_text(py.std.pprint.pformat(left),
|
||||
# py.std.pprint.pformat(right))
|
||||
explanation += [
|
||||
'Right contains more items, first extra item: %s' %
|
||||
py.io.saferepr(right[len(left)],)]
|
||||
return explanation # + _diff_text(py.std.pprint.pformat(left),
|
||||
# py.std.pprint.pformat(right))
|
||||
|
||||
|
||||
def _compare_eq_set(left, right):
|
||||
def _compare_eq_set(left, right, verbose=False):
|
||||
explanation = []
|
||||
diff_left = left - right
|
||||
diff_right = right - left
|
||||
@@ -190,12 +202,41 @@ def _compare_eq_set(left, right):
|
||||
return explanation
|
||||
|
||||
|
||||
def _notin_text(term, text):
|
||||
def _compare_eq_dict(left, right, verbose=False):
|
||||
explanation = []
|
||||
common = set(left).intersection(set(right))
|
||||
same = dict((k, left[k]) for k in common if left[k] == right[k])
|
||||
if same and not verbose:
|
||||
explanation += ['Omitting %s identical items, use -v to show' %
|
||||
len(same)]
|
||||
elif same:
|
||||
explanation += ['Common items:']
|
||||
explanation += py.std.pprint.pformat(same).splitlines()
|
||||
diff = set(k for k in common if left[k] != right[k])
|
||||
if diff:
|
||||
explanation += ['Differing items:']
|
||||
for k in diff:
|
||||
explanation += [py.io.saferepr({k: left[k]}) + ' != ' +
|
||||
py.io.saferepr({k: right[k]})]
|
||||
extra_left = set(left) - set(right)
|
||||
if extra_left:
|
||||
explanation.append('Left contains more items:')
|
||||
explanation.extend(py.std.pprint.pformat(
|
||||
dict((k, left[k]) for k in extra_left)).splitlines())
|
||||
extra_right = set(right) - set(left)
|
||||
if extra_right:
|
||||
explanation.append('Right contains more items:')
|
||||
explanation.extend(py.std.pprint.pformat(
|
||||
dict((k, right[k]) for k in extra_right)).splitlines())
|
||||
return explanation
|
||||
|
||||
|
||||
def _notin_text(term, text, verbose=False):
|
||||
index = text.find(term)
|
||||
head = text[:index]
|
||||
tail = text[index+len(term):]
|
||||
correct_text = head + tail
|
||||
diff = _diff_text(correct_text, text)
|
||||
diff = _diff_text(correct_text, text, verbose)
|
||||
newdiff = ['%s is contained here:' % py.io.saferepr(term, maxsize=42)]
|
||||
for line in diff:
|
||||
if line.startswith('Skipping'):
|
||||
|
||||
@@ -1,27 +1,51 @@
|
||||
""" per-test stdout/stderr capturing mechanisms, ``capsys`` and ``capfd`` function arguments. """
|
||||
|
||||
import pytest, py
|
||||
import sys
|
||||
import os
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("general")
|
||||
group._addoption('--capture', action="store", default=None,
|
||||
metavar="method", type="choice", choices=['fd', 'sys', 'no'],
|
||||
metavar="method", choices=['fd', 'sys', 'no'],
|
||||
help="per-test capturing method: one of fd (default)|sys|no.")
|
||||
group._addoption('-s', action="store_const", const="no", dest="capture",
|
||||
help="shortcut for --capture=no.")
|
||||
|
||||
@pytest.mark.tryfirst
|
||||
def pytest_cmdline_parse(pluginmanager, args):
|
||||
# we want to perform capturing already for plugin/conftest loading
|
||||
if '-s' in args or "--capture=no" in args:
|
||||
method = "no"
|
||||
elif hasattr(os, 'dup') and '--capture=sys' not in args:
|
||||
def pytest_load_initial_conftests(early_config, parser, args, __multicall__):
|
||||
ns = parser.parse_known_args(args)
|
||||
method = ns.capture
|
||||
if not method:
|
||||
method = "fd"
|
||||
else:
|
||||
if method == "fd" and not hasattr(os, "dup"):
|
||||
method = "sys"
|
||||
capman = CaptureManager(method)
|
||||
pluginmanager.register(capman, "capturemanager")
|
||||
early_config.pluginmanager.register(capman, "capturemanager")
|
||||
# make sure that capturemanager is properly reset at final shutdown
|
||||
def teardown():
|
||||
try:
|
||||
capman.reset_capturings()
|
||||
except ValueError:
|
||||
pass
|
||||
early_config.pluginmanager.add_shutdown(teardown)
|
||||
# make sure logging does not raise exceptions if it is imported
|
||||
def silence_logging_at_shutdown():
|
||||
if "logging" in sys.modules:
|
||||
sys.modules["logging"].raiseExceptions = False
|
||||
early_config.pluginmanager.add_shutdown(silence_logging_at_shutdown)
|
||||
|
||||
# finally trigger conftest loading but while capturing (issue93)
|
||||
capman.resumecapture()
|
||||
try:
|
||||
try:
|
||||
return __multicall__.execute()
|
||||
finally:
|
||||
out, err = capman.suspendcapture()
|
||||
except:
|
||||
sys.stdout.write(out)
|
||||
sys.stderr.write(err)
|
||||
raise
|
||||
|
||||
def addouterr(rep, outerr):
|
||||
for secname, content in zip(["out", "err"], outerr):
|
||||
@@ -173,8 +197,7 @@ class CaptureManager:
|
||||
if funcarg_outerr is not None:
|
||||
outerr = (outerr[0] + funcarg_outerr[0],
|
||||
outerr[1] + funcarg_outerr[1])
|
||||
if not rep.passed:
|
||||
addouterr(rep, outerr)
|
||||
addouterr(rep, outerr)
|
||||
if not rep.passed or rep.when == "teardown":
|
||||
outerr = ('', '')
|
||||
item.outerr = outerr
|
||||
@@ -211,12 +234,15 @@ class CaptureFixture:
|
||||
|
||||
def _finalize(self):
|
||||
if hasattr(self, 'capture'):
|
||||
outerr = self.capture.reset()
|
||||
outerr = self._outerr = self.capture.reset()
|
||||
del self.capture
|
||||
return outerr
|
||||
|
||||
def readouterr(self):
|
||||
return self.capture.readouterr()
|
||||
try:
|
||||
return self.capture.readouterr()
|
||||
except AttributeError:
|
||||
return self._outerr
|
||||
|
||||
def close(self):
|
||||
self._finalize()
|
||||
|
||||
@@ -2,21 +2,87 @@
|
||||
|
||||
import py
|
||||
import sys, os
|
||||
from _pytest import hookspec # the extension point definitions
|
||||
from _pytest.core import PluginManager
|
||||
import pytest
|
||||
|
||||
def pytest_cmdline_parse(pluginmanager, args):
|
||||
config = Config(pluginmanager)
|
||||
config.parse(args)
|
||||
return config
|
||||
# pytest startup
|
||||
|
||||
def main(args=None, plugins=None):
|
||||
""" return exit code, after performing an in-process test run.
|
||||
|
||||
:arg args: list of command line arguments.
|
||||
|
||||
:arg plugins: list of plugin objects to be auto-registered during
|
||||
initialization.
|
||||
"""
|
||||
config = _prepareconfig(args, plugins)
|
||||
exitstatus = config.hook.pytest_cmdline_main(config=config)
|
||||
return exitstatus
|
||||
|
||||
class cmdline: # compatibility namespace
|
||||
main = staticmethod(main)
|
||||
|
||||
class UsageError(Exception):
|
||||
""" error in py.test usage or invocation"""
|
||||
|
||||
_preinit = []
|
||||
|
||||
default_plugins = (
|
||||
"mark main terminal runner python pdb unittest capture skipping "
|
||||
"tmpdir monkeypatch recwarn pastebin helpconfig nose assertion genscript "
|
||||
"junitxml resultlog doctest").split()
|
||||
|
||||
def _preloadplugins():
|
||||
assert not _preinit
|
||||
_preinit.append(get_plugin_manager())
|
||||
|
||||
def get_plugin_manager():
|
||||
if _preinit:
|
||||
return _preinit.pop(0)
|
||||
# subsequent calls to main will create a fresh instance
|
||||
pluginmanager = PytestPluginManager()
|
||||
pluginmanager.config = config = Config(pluginmanager) # XXX attr needed?
|
||||
for spec in default_plugins:
|
||||
pluginmanager.import_plugin(spec)
|
||||
return pluginmanager
|
||||
|
||||
def _prepareconfig(args=None, plugins=None):
|
||||
if args is None:
|
||||
args = sys.argv[1:]
|
||||
elif isinstance(args, py.path.local):
|
||||
args = [str(args)]
|
||||
elif not isinstance(args, (tuple, list)):
|
||||
if not isinstance(args, str):
|
||||
raise ValueError("not a string or argument list: %r" % (args,))
|
||||
args = py.std.shlex.split(args)
|
||||
pluginmanager = get_plugin_manager()
|
||||
if plugins:
|
||||
for plugin in plugins:
|
||||
pluginmanager.register(plugin)
|
||||
return pluginmanager.hook.pytest_cmdline_parse(
|
||||
pluginmanager=pluginmanager, args=args)
|
||||
|
||||
class PytestPluginManager(PluginManager):
|
||||
def __init__(self, hookspecs=[hookspec]):
|
||||
super(PytestPluginManager, self).__init__(hookspecs=hookspecs)
|
||||
self.register(self)
|
||||
if os.environ.get('PYTEST_DEBUG'):
|
||||
err = sys.stderr
|
||||
encoding = getattr(err, 'encoding', 'utf8')
|
||||
try:
|
||||
err = py.io.dupfile(err, encoding=encoding)
|
||||
except Exception:
|
||||
pass
|
||||
self.trace.root.setwriter(err.write)
|
||||
|
||||
def pytest_configure(self, config):
|
||||
config.addinivalue_line("markers",
|
||||
"tryfirst: mark a hook implementation function such that the "
|
||||
"plugin machinery will try to call it first/as early as possible.")
|
||||
config.addinivalue_line("markers",
|
||||
"trylast: mark a hook implementation function such that the "
|
||||
"plugin machinery will try to call it last/as late as possible.")
|
||||
|
||||
def pytest_unconfigure(config):
|
||||
while 1:
|
||||
try:
|
||||
fin = config._cleanup.pop()
|
||||
except IndexError:
|
||||
break
|
||||
fin()
|
||||
|
||||
class Parser:
|
||||
""" Parser for command line arguments and ini-file values. """
|
||||
@@ -75,21 +141,38 @@ class Parser:
|
||||
self._anonymous.addoption(*opts, **attrs)
|
||||
|
||||
def parse(self, args):
|
||||
self.optparser = optparser = MyOptionParser(self)
|
||||
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])
|
||||
|
||||
def _getparser(self):
|
||||
from _pytest._argcomplete import filescompleter
|
||||
optparser = MyOptionParser(self)
|
||||
groups = self._groups + [self._anonymous]
|
||||
for group in groups:
|
||||
if group.options:
|
||||
desc = group.description or group.name
|
||||
optgroup = py.std.optparse.OptionGroup(optparser, desc)
|
||||
optgroup.add_options(group.options)
|
||||
optparser.add_option_group(optgroup)
|
||||
return self.optparser.parse_args([str(x) for x in args])
|
||||
arggroup = optparser.add_argument_group(desc)
|
||||
for option in group.options:
|
||||
n = option.names()
|
||||
a = option.attrs()
|
||||
arggroup.add_argument(*n, **a)
|
||||
# bash like autocompletion for dirs (appending '/')
|
||||
optparser.add_argument(FILE_OR_DIR, nargs='*'
|
||||
).completer=filescompleter
|
||||
return optparser
|
||||
|
||||
def parse_setoption(self, args, option):
|
||||
parsedoption, args = self.parse(args)
|
||||
parsedoption = self.parse(args)
|
||||
for name, value in parsedoption.__dict__.items():
|
||||
setattr(option, name, value)
|
||||
return args
|
||||
return getattr(parsedoption, FILE_OR_DIR)
|
||||
|
||||
def parse_known_args(self, args):
|
||||
optparser = self._getparser()
|
||||
args = [str(x) for x in args]
|
||||
return optparser.parse_known_args(args)[0]
|
||||
|
||||
def addini(self, name, help, type=None, default=None):
|
||||
""" register an ini-file option.
|
||||
@@ -106,6 +189,158 @@ class Parser:
|
||||
self._ininames.append(name)
|
||||
|
||||
|
||||
class ArgumentError(Exception):
|
||||
"""
|
||||
Raised if an Argument instance is created with invalid or
|
||||
inconsistent arguments.
|
||||
"""
|
||||
|
||||
def __init__(self, msg, option):
|
||||
self.msg = msg
|
||||
self.option_id = str(option)
|
||||
|
||||
def __str__(self):
|
||||
if self.option_id:
|
||||
return "option %s: %s" % (self.option_id, self.msg)
|
||||
else:
|
||||
return self.msg
|
||||
|
||||
|
||||
class Argument:
|
||||
"""class that mimics the necessary behaviour of py.std.optparse.Option """
|
||||
_typ_map = {
|
||||
'int': int,
|
||||
'string': str,
|
||||
}
|
||||
# enable after some grace period for plugin writers
|
||||
TYPE_WARN = False
|
||||
|
||||
def __init__(self, *names, **attrs):
|
||||
"""store parms in private vars for use in add_argument"""
|
||||
self._attrs = attrs
|
||||
self._short_opts = []
|
||||
self._long_opts = []
|
||||
self.dest = attrs.get('dest')
|
||||
if self.TYPE_WARN:
|
||||
try:
|
||||
help = attrs['help']
|
||||
if '%default' in help:
|
||||
py.std.warnings.warn(
|
||||
'py.test now uses argparse. "%default" should be'
|
||||
' changed to "%(default)s" ',
|
||||
FutureWarning,
|
||||
stacklevel=3)
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
typ = attrs['type']
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
# this might raise a keyerror as well, don't want to catch that
|
||||
if isinstance(typ, str):
|
||||
if typ == 'choice':
|
||||
if self.TYPE_WARN:
|
||||
py.std.warnings.warn(
|
||||
'type argument to addoption() is a string %r.'
|
||||
' For parsearg this is optional and when supplied '
|
||||
' should be a type.'
|
||||
' (options: %s)' % (typ, names),
|
||||
FutureWarning,
|
||||
stacklevel=3)
|
||||
# argparse expects a type here take it from
|
||||
# the type of the first element
|
||||
attrs['type'] = type(attrs['choices'][0])
|
||||
else:
|
||||
if self.TYPE_WARN:
|
||||
py.std.warnings.warn(
|
||||
'type argument to addoption() is a string %r.'
|
||||
' For parsearg this should be a type.'
|
||||
' (options: %s)' % (typ, names),
|
||||
FutureWarning,
|
||||
stacklevel=3)
|
||||
attrs['type'] = Argument._typ_map[typ]
|
||||
# used in test_parseopt -> test_parse_defaultgetter
|
||||
self.type = attrs['type']
|
||||
else:
|
||||
self.type = typ
|
||||
try:
|
||||
# attribute existence is tested in Config._processopt
|
||||
self.default = attrs['default']
|
||||
except KeyError:
|
||||
pass
|
||||
self._set_opt_strings(names)
|
||||
if not self.dest:
|
||||
if self._long_opts:
|
||||
self.dest = self._long_opts[0][2:].replace('-', '_')
|
||||
else:
|
||||
try:
|
||||
self.dest = self._short_opts[0][1:]
|
||||
except IndexError:
|
||||
raise ArgumentError(
|
||||
'need a long or short option', self)
|
||||
|
||||
def names(self):
|
||||
return self._short_opts + self._long_opts
|
||||
|
||||
def attrs(self):
|
||||
# update any attributes set by processopt
|
||||
attrs = 'default dest help'.split()
|
||||
if self.dest:
|
||||
attrs.append(self.dest)
|
||||
for attr in attrs:
|
||||
try:
|
||||
self._attrs[attr] = getattr(self, attr)
|
||||
except AttributeError:
|
||||
pass
|
||||
if self._attrs.get('help'):
|
||||
a = self._attrs['help']
|
||||
a = a.replace('%default', '%(default)s')
|
||||
#a = a.replace('%prog', '%(prog)s')
|
||||
self._attrs['help'] = a
|
||||
return self._attrs
|
||||
|
||||
def _set_opt_strings(self, opts):
|
||||
"""directly from optparse
|
||||
|
||||
might not be necessary as this is passed to argparse later on"""
|
||||
for opt in opts:
|
||||
if len(opt) < 2:
|
||||
raise ArgumentError(
|
||||
"invalid option string %r: "
|
||||
"must be at least two characters long" % opt, self)
|
||||
elif len(opt) == 2:
|
||||
if not (opt[0] == "-" and opt[1] != "-"):
|
||||
raise ArgumentError(
|
||||
"invalid short option string %r: "
|
||||
"must be of the form -x, (x any non-dash char)" % opt,
|
||||
self)
|
||||
self._short_opts.append(opt)
|
||||
else:
|
||||
if not (opt[0:2] == "--" and opt[2] != "-"):
|
||||
raise ArgumentError(
|
||||
"invalid long option string %r: "
|
||||
"must start with --, followed by non-dash" % opt,
|
||||
self)
|
||||
self._long_opts.append(opt)
|
||||
|
||||
def __repr__(self):
|
||||
retval = 'Argument('
|
||||
if self._short_opts:
|
||||
retval += '_short_opts: ' + repr(self._short_opts) + ', '
|
||||
if self._long_opts:
|
||||
retval += '_long_opts: ' + repr(self._long_opts) + ', '
|
||||
retval += 'dest: ' + repr(self.dest) + ', '
|
||||
if hasattr(self, 'type'):
|
||||
retval += 'type: ' + repr(self.type) + ', '
|
||||
if hasattr(self, 'default'):
|
||||
retval += 'default: ' + repr(self.default) + ', '
|
||||
if retval[-2:] == ', ': # always long enough to test ("Argument(" )
|
||||
retval = retval[:-2]
|
||||
retval += ')'
|
||||
return retval
|
||||
|
||||
|
||||
class OptionGroup:
|
||||
def __init__(self, name, description="", parser=None):
|
||||
self.name = name
|
||||
@@ -114,12 +349,18 @@ class OptionGroup:
|
||||
self.parser = parser
|
||||
|
||||
def addoption(self, *optnames, **attrs):
|
||||
""" add an option to this group. """
|
||||
option = py.std.optparse.Option(*optnames, **attrs)
|
||||
""" add an option to this group.
|
||||
|
||||
if a shortened version of a long option is specified it will
|
||||
be suppressed in the help. addoption('--twowords', '--two-words')
|
||||
results in help showing '--two-words' only, but --twowords gets
|
||||
accepted **and** the automatic destination is in args.twowords
|
||||
"""
|
||||
option = Argument(*optnames, **attrs)
|
||||
self._addoption_instance(option, shortupper=False)
|
||||
|
||||
def _addoption(self, *optnames, **attrs):
|
||||
option = py.std.optparse.Option(*optnames, **attrs)
|
||||
option = Argument(*optnames, **attrs)
|
||||
self._addoption_instance(option, shortupper=True)
|
||||
|
||||
def _addoption_instance(self, option, shortupper=False):
|
||||
@@ -132,11 +373,12 @@ class OptionGroup:
|
||||
self.options.append(option)
|
||||
|
||||
|
||||
class MyOptionParser(py.std.optparse.OptionParser):
|
||||
class MyOptionParser(py.std.argparse.ArgumentParser):
|
||||
def __init__(self, parser):
|
||||
self._parser = parser
|
||||
py.std.optparse.OptionParser.__init__(self, usage=parser._usage,
|
||||
add_help_option=False)
|
||||
py.std.argparse.ArgumentParser.__init__(self, usage=parser._usage,
|
||||
add_help=False, formatter_class=DropShorterLongHelpFormatter)
|
||||
|
||||
def format_epilog(self, formatter):
|
||||
hints = self._parser.hints
|
||||
if hints:
|
||||
@@ -145,6 +387,66 @@ class MyOptionParser(py.std.optparse.OptionParser):
|
||||
return s
|
||||
return ""
|
||||
|
||||
def parse_args(self, args=None, namespace=None):
|
||||
"""allow splitting of positional arguments"""
|
||||
args, argv = self.parse_known_args(args, namespace)
|
||||
if argv:
|
||||
for arg in argv:
|
||||
if arg and arg[0] == '-':
|
||||
msg = py.std.argparse._('unrecognized arguments: %s')
|
||||
self.error(msg % ' '.join(argv))
|
||||
getattr(args, FILE_OR_DIR).extend(argv)
|
||||
return args
|
||||
|
||||
class DropShorterLongHelpFormatter(py.std.argparse.HelpFormatter):
|
||||
"""shorten help for long options that differ only in extra hyphens
|
||||
|
||||
- collapse **long** options that are the same except for extra hyphens
|
||||
- special action attribute map_long_option allows surpressing additional
|
||||
long options
|
||||
- shortcut if there are only two options and one of them is a short one
|
||||
- cache result on action object as this is called at least 2 times
|
||||
"""
|
||||
def _format_action_invocation(self, action):
|
||||
orgstr = py.std.argparse.HelpFormatter._format_action_invocation(self, action)
|
||||
if orgstr and orgstr[0] != '-': # only optional arguments
|
||||
return orgstr
|
||||
res = getattr(action, '_formatted_action_invocation', None)
|
||||
if res:
|
||||
return res
|
||||
options = orgstr.split(', ')
|
||||
if len(options) == 2 and (len(options[0]) == 2 or len(options[1]) == 2):
|
||||
# a shortcut for '-h, --help' or '--abc', '-a'
|
||||
action._formatted_action_invocation = orgstr
|
||||
return orgstr
|
||||
return_list = []
|
||||
option_map = getattr(action, 'map_long_option', {})
|
||||
if option_map is None:
|
||||
option_map = {}
|
||||
short_long = {}
|
||||
for option in options:
|
||||
if len(option) == 2 or option[2] == ' ':
|
||||
continue
|
||||
if not option.startswith('--'):
|
||||
raise ArgumentError('long optional argument without "--": [%s]'
|
||||
% (option), self)
|
||||
xxoption = option[2:]
|
||||
if xxoption.split()[0] not in option_map:
|
||||
shortened = xxoption.replace('-', '')
|
||||
if shortened not in short_long or \
|
||||
len(short_long[shortened]) < len(xxoption):
|
||||
short_long[shortened] = xxoption
|
||||
# now short_long has been filled out to the longest with dashes
|
||||
# **and** we keep the right option ordering from add_argument
|
||||
for option in options: #
|
||||
if len(option) == 2 or option[2] == ' ':
|
||||
return_list.append(option)
|
||||
if option[2:] == short_long.get(option.replace('-', '')):
|
||||
return_list.append(option)
|
||||
action._formatted_action_invocation = ', '.join(return_list)
|
||||
return action._formatted_action_invocation
|
||||
|
||||
|
||||
class Conftest(object):
|
||||
""" the single place for accessing values and interacting
|
||||
towards conftest modules from py.test objects.
|
||||
@@ -181,7 +483,7 @@ class Conftest(object):
|
||||
if hasattr(arg, 'startswith') and arg.startswith("--"):
|
||||
continue
|
||||
anchor = current.join(arg, abs=1)
|
||||
if anchor.check(): # we found some file object
|
||||
if exists(anchor): # we found some file object
|
||||
self._try_load_conftest(anchor)
|
||||
foundanchor = True
|
||||
if not foundanchor:
|
||||
@@ -196,27 +498,20 @@ class Conftest(object):
|
||||
self.getconftestmodules(x)
|
||||
|
||||
def getconftestmodules(self, path):
|
||||
""" return a list of imported conftest modules for the given path. """
|
||||
try:
|
||||
clist = self._path2confmods[path]
|
||||
except KeyError:
|
||||
if path is None:
|
||||
raise ValueError("missing default confest.")
|
||||
dp = path.dirpath()
|
||||
raise ValueError("missing default conftest.")
|
||||
clist = []
|
||||
if dp != path:
|
||||
cutdir = self._confcutdir
|
||||
if cutdir and path != cutdir and not path.relto(cutdir):
|
||||
pass
|
||||
else:
|
||||
conftestpath = path.join("conftest.py")
|
||||
if conftestpath.check(file=1):
|
||||
clist.append(self.importconftest(conftestpath))
|
||||
clist[:0] = self.getconftestmodules(dp)
|
||||
for parent in path.parts():
|
||||
if self._confcutdir and self._confcutdir.relto(parent):
|
||||
continue
|
||||
conftestpath = parent.join("conftest.py")
|
||||
if conftestpath.check(file=1):
|
||||
clist.append(self.importconftest(conftestpath))
|
||||
self._path2confmods[path] = clist
|
||||
# be defensive: avoid changes from caller side to
|
||||
# affect us by always returning a copy of the actual list
|
||||
return clist[:]
|
||||
return clist
|
||||
|
||||
def rget(self, name, path=None):
|
||||
mod, value = self.rget_with_confmod(name, path)
|
||||
@@ -268,32 +563,88 @@ class CmdOptions(object):
|
||||
def __repr__(self):
|
||||
return "<CmdOptions %r>" %(self.__dict__,)
|
||||
|
||||
FILE_OR_DIR = 'file_or_dir'
|
||||
class Config(object):
|
||||
""" access to configuration values, pluginmanager and plugin hooks. """
|
||||
def __init__(self, pluginmanager=None):
|
||||
|
||||
def __init__(self, pluginmanager):
|
||||
#: access to command line option as attributes.
|
||||
#: (deprecated), use :py:func:`getoption() <_pytest.config.Config.getoption>` instead
|
||||
self.option = CmdOptions()
|
||||
_a = FILE_OR_DIR
|
||||
self._parser = Parser(
|
||||
usage="usage: %prog [options] [file_or_dir] [file_or_dir] [...]",
|
||||
usage="%%(prog)s [options] [%s] [%s] [...]" % (_a, _a),
|
||||
processopt=self._processopt,
|
||||
)
|
||||
#: a pluginmanager instance
|
||||
self.pluginmanager = pluginmanager or PluginManager(load=True)
|
||||
self.pluginmanager = pluginmanager
|
||||
self.trace = self.pluginmanager.trace.root.get("config")
|
||||
self._conftest = Conftest(onimport=self._onimportconftest)
|
||||
self.hook = self.pluginmanager.hook
|
||||
self._inicache = {}
|
||||
self._opt2dest = {}
|
||||
self._cleanup = []
|
||||
self.pluginmanager.register(self, "pytestconfig")
|
||||
self.pluginmanager.set_register_callback(self._register_plugin)
|
||||
self._configured = False
|
||||
|
||||
def _register_plugin(self, plugin, name):
|
||||
call_plugin = self.pluginmanager.call_plugin
|
||||
call_plugin(plugin, "pytest_addhooks",
|
||||
{'pluginmanager': self.pluginmanager})
|
||||
self.hook.pytest_plugin_registered(plugin=plugin,
|
||||
manager=self.pluginmanager)
|
||||
dic = call_plugin(plugin, "pytest_namespace", {}) or {}
|
||||
if dic:
|
||||
import pytest
|
||||
setns(pytest, dic)
|
||||
call_plugin(plugin, "pytest_addoption", {'parser': self._parser})
|
||||
if self._configured:
|
||||
call_plugin(plugin, "pytest_configure", {'config': self})
|
||||
|
||||
def do_configure(self):
|
||||
assert not self._configured
|
||||
self._configured = True
|
||||
self.hook.pytest_configure(config=self)
|
||||
|
||||
def do_unconfigure(self):
|
||||
assert self._configured
|
||||
self._configured = False
|
||||
self.hook.pytest_unconfigure(config=self)
|
||||
self.pluginmanager.ensure_shutdown()
|
||||
|
||||
def pytest_cmdline_parse(self, pluginmanager, args):
|
||||
assert self == pluginmanager.config, (self, pluginmanager.config)
|
||||
self.parse(args)
|
||||
return self
|
||||
|
||||
def pytest_unconfigure(config):
|
||||
while config._cleanup:
|
||||
fin = config._cleanup.pop()
|
||||
fin()
|
||||
|
||||
def notify_exception(self, excinfo, option=None):
|
||||
if option and option.fulltrace:
|
||||
style = "long"
|
||||
else:
|
||||
style = "native"
|
||||
excrepr = excinfo.getrepr(funcargs=True,
|
||||
showlocals=getattr(option, 'showlocals', False),
|
||||
style=style,
|
||||
)
|
||||
res = self.hook.pytest_internalerror(excrepr=excrepr,
|
||||
excinfo=excinfo)
|
||||
if not py.builtin.any(res):
|
||||
for line in str(excrepr).split("\n"):
|
||||
sys.stderr.write("INTERNALERROR> %s\n" %line)
|
||||
sys.stderr.flush()
|
||||
|
||||
|
||||
@classmethod
|
||||
def fromdictargs(cls, option_dict, args):
|
||||
""" constructor useable for subprocesses. """
|
||||
config = cls()
|
||||
# XXX slightly crude way to initialize capturing
|
||||
import _pytest.capture
|
||||
_pytest.capture.pytest_cmdline_parse(config.pluginmanager, args)
|
||||
pluginmanager = get_plugin_manager()
|
||||
config = pluginmanager.config
|
||||
config._preparse(args, addopts=False)
|
||||
config.option.__dict__.update(option_dict)
|
||||
for x in config.option.plugins:
|
||||
@@ -319,21 +670,9 @@ class Config(object):
|
||||
plugins += self._conftest.getconftestmodules(fspath)
|
||||
return plugins
|
||||
|
||||
def _setinitialconftest(self, args):
|
||||
# capture output during conftest init (#issue93)
|
||||
# XXX introduce load_conftest hook to avoid needing to know
|
||||
# about capturing plugin here
|
||||
capman = self.pluginmanager.getplugin("capturemanager")
|
||||
capman.resumecapture()
|
||||
try:
|
||||
try:
|
||||
self._conftest.setinitial(args)
|
||||
finally:
|
||||
out, err = capman.suspendcapture() # logging might have got it
|
||||
except:
|
||||
sys.stdout.write(out)
|
||||
sys.stderr.write(err)
|
||||
raise
|
||||
def pytest_load_initial_conftests(self, parser, args):
|
||||
self._conftest.setinitial(args)
|
||||
pytest_load_initial_conftests.trylast = True
|
||||
|
||||
def _initini(self, args):
|
||||
self.inicfg = getcfg(args, ["pytest.ini", "tox.ini", "setup.cfg"])
|
||||
@@ -348,12 +687,11 @@ class Config(object):
|
||||
self.pluginmanager.consider_preparse(args)
|
||||
self.pluginmanager.consider_setuptools_entrypoints()
|
||||
self.pluginmanager.consider_env()
|
||||
self._setinitialconftest(args)
|
||||
self.pluginmanager.do_addoption(self._parser)
|
||||
if addopts:
|
||||
self.hook.pytest_cmdline_preparse(config=self, args=args)
|
||||
self.hook.pytest_load_initial_conftests(early_config=self,
|
||||
args=args, parser=self._parser)
|
||||
|
||||
def _checkversion(self):
|
||||
import pytest
|
||||
minver = self.inicfg.get('minversion', None)
|
||||
if minver:
|
||||
ver = minver.split(".")
|
||||
@@ -371,6 +709,8 @@ class Config(object):
|
||||
"can only parse cmdline args at most once per Config object")
|
||||
self._origargs = args
|
||||
self._preparse(args)
|
||||
# XXX deprecated hook:
|
||||
self.hook.pytest_cmdline_preparse(config=self, args=args)
|
||||
self._parser.hints.extend(self.pluginmanager._hints)
|
||||
args = self._parser.parse_setoption(args, self.option)
|
||||
if not args:
|
||||
@@ -479,6 +819,11 @@ class Config(object):
|
||||
except KeyError:
|
||||
py.test.skip("no %r value found" %(name,))
|
||||
|
||||
def exists(path, ignore=EnvironmentError):
|
||||
try:
|
||||
return path.check()
|
||||
except ignore:
|
||||
return False
|
||||
|
||||
def getcfg(args, inibasenames):
|
||||
args = [x for x in args if not str(x).startswith("-")]
|
||||
@@ -489,9 +834,29 @@ def getcfg(args, inibasenames):
|
||||
for base in arg.parts(reverse=True):
|
||||
for inibasename in inibasenames:
|
||||
p = base.join(inibasename)
|
||||
if p.check():
|
||||
if exists(p):
|
||||
iniconfig = py.iniconfig.IniConfig(p)
|
||||
if 'pytest' in iniconfig.sections:
|
||||
return iniconfig['pytest']
|
||||
return {}
|
||||
|
||||
|
||||
def setns(obj, dic):
|
||||
import pytest
|
||||
for name, value in dic.items():
|
||||
if isinstance(value, dict):
|
||||
mod = getattr(obj, name, None)
|
||||
if mod is None:
|
||||
modname = "pytest.%s" % name
|
||||
mod = py.std.types.ModuleType(modname)
|
||||
sys.modules[modname] = mod
|
||||
mod.__all__ = []
|
||||
setattr(obj, name, mod)
|
||||
obj.__all__.append(name)
|
||||
setns(mod, value)
|
||||
else:
|
||||
setattr(obj, name, value)
|
||||
obj.__all__.append(name)
|
||||
#if obj != pytest:
|
||||
# pytest.__all__.append(name)
|
||||
setattr(pytest, name, value)
|
||||
|
||||
202
_pytest/core.py
202
_pytest/core.py
@@ -1,20 +1,13 @@
|
||||
"""
|
||||
pytest PluginManager, basic initialization and tracing.
|
||||
(c) Holger Krekel 2004-2010
|
||||
"""
|
||||
import sys, os
|
||||
import inspect
|
||||
import py
|
||||
from _pytest import hookspec # the extension point definitions
|
||||
|
||||
assert py.__version__.split(".")[:2] >= ['1', '4'], ("installation problem: "
|
||||
"%s is too old, remove or upgrade 'py'" % (py.__version__))
|
||||
|
||||
default_plugins = (
|
||||
"config mark main terminal runner python pdb unittest capture skipping "
|
||||
"tmpdir monkeypatch recwarn pastebin helpconfig nose assertion genscript "
|
||||
"junitxml resultlog doctest").split()
|
||||
|
||||
class TagTracer:
|
||||
def __init__(self):
|
||||
self._tag2proc = {}
|
||||
@@ -24,12 +17,28 @@ class TagTracer:
|
||||
def get(self, name):
|
||||
return TagTracerSub(self, (name,))
|
||||
|
||||
def format_message(self, tags, args):
|
||||
if isinstance(args[-1], dict):
|
||||
extra = args[-1]
|
||||
args = args[:-1]
|
||||
else:
|
||||
extra = {}
|
||||
|
||||
content = " ".join(map(str, args))
|
||||
indent = " " * self.indent
|
||||
|
||||
lines = [
|
||||
"%s%s [%s]\n" %(indent, content, ":".join(tags))
|
||||
]
|
||||
|
||||
for name, value in extra.items():
|
||||
lines.append("%s %s: %s\n" % (indent, name, value))
|
||||
return lines
|
||||
|
||||
def processmessage(self, tags, args):
|
||||
if self.writer is not None:
|
||||
if args:
|
||||
indent = " " * self.indent
|
||||
content = " ".join(map(str, args))
|
||||
self.writer("%s%s [%s]\n" %(indent, content, ":".join(tags)))
|
||||
if self.writer is not None and args:
|
||||
lines = self.format_message(tags, args)
|
||||
self.writer(''.join(lines))
|
||||
try:
|
||||
self._tag2proc[tags](tags, args)
|
||||
except KeyError:
|
||||
@@ -57,37 +66,32 @@ class TagTracerSub:
|
||||
return self.__class__(self.root, self.tags + (name,))
|
||||
|
||||
class PluginManager(object):
|
||||
def __init__(self, load=False):
|
||||
def __init__(self, hookspecs=None):
|
||||
self._name2plugin = {}
|
||||
self._listattrcache = {}
|
||||
self._plugins = []
|
||||
self._hints = []
|
||||
self.trace = TagTracer().get("pluginmanage")
|
||||
self._plugin_distinfo = []
|
||||
if os.environ.get('PYTEST_DEBUG'):
|
||||
err = sys.stderr
|
||||
encoding = getattr(err, 'encoding', 'utf8')
|
||||
try:
|
||||
err = py.io.dupfile(err, encoding=encoding)
|
||||
except Exception:
|
||||
pass
|
||||
self.trace.root.setwriter(err.write)
|
||||
self.hook = HookRelay([hookspec], pm=self)
|
||||
self.register(self)
|
||||
if load:
|
||||
for spec in default_plugins:
|
||||
self.import_plugin(spec)
|
||||
self._shutdown = []
|
||||
self.hook = HookRelay(hookspecs or [], pm=self)
|
||||
|
||||
def set_register_callback(self, callback):
|
||||
assert not hasattr(self, "_registercallback")
|
||||
self._registercallback = callback
|
||||
|
||||
def register(self, plugin, name=None, prepend=False):
|
||||
if self._name2plugin.get(name, None) == -1:
|
||||
return
|
||||
name = name or getattr(plugin, '__name__', str(id(plugin)))
|
||||
if self.isregistered(plugin, name):
|
||||
raise ValueError("Plugin already registered: %s=%s" %(name, plugin))
|
||||
raise ValueError("Plugin already registered: %s=%s\n%s" %(
|
||||
name, plugin, self._name2plugin))
|
||||
#self.trace("registering", name, plugin)
|
||||
self._name2plugin[name] = plugin
|
||||
self.call_plugin(plugin, "pytest_addhooks", {'pluginmanager': self})
|
||||
self.hook.pytest_plugin_registered(manager=self, plugin=plugin)
|
||||
reg = getattr(self, "_registercallback", None)
|
||||
if reg is not None:
|
||||
reg(plugin, name)
|
||||
if not prepend:
|
||||
self._plugins.append(plugin)
|
||||
else:
|
||||
@@ -98,11 +102,21 @@ class PluginManager(object):
|
||||
if plugin is None:
|
||||
plugin = self.getplugin(name=name)
|
||||
self._plugins.remove(plugin)
|
||||
self.hook.pytest_plugin_unregistered(plugin=plugin)
|
||||
for name, value in list(self._name2plugin.items()):
|
||||
if value == plugin:
|
||||
del self._name2plugin[name]
|
||||
|
||||
def add_shutdown(self, func):
|
||||
self._shutdown.append(func)
|
||||
|
||||
def ensure_shutdown(self):
|
||||
while self._shutdown:
|
||||
func = self._shutdown.pop()
|
||||
func()
|
||||
self._plugins = []
|
||||
self._name2plugin.clear()
|
||||
self._listattrcache.clear()
|
||||
|
||||
def isregistered(self, plugin, name=None):
|
||||
if self.getplugin(name) is not None:
|
||||
return True
|
||||
@@ -110,8 +124,8 @@ class PluginManager(object):
|
||||
if plugin == val:
|
||||
return True
|
||||
|
||||
def addhooks(self, spec):
|
||||
self.hook._addhooks(spec, prefix="pytest_")
|
||||
def addhooks(self, spec, prefix="pytest_"):
|
||||
self.hook._addhooks(spec, prefix=prefix)
|
||||
|
||||
def getplugins(self):
|
||||
return list(self._plugins)
|
||||
@@ -193,7 +207,6 @@ class PluginManager(object):
|
||||
if self.getplugin(modname) is not None:
|
||||
return
|
||||
try:
|
||||
#self.trace("importing", modname)
|
||||
mod = importplugin(modname)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
@@ -212,82 +225,6 @@ class PluginManager(object):
|
||||
self.register(mod, modname)
|
||||
self.consider_module(mod)
|
||||
|
||||
def pytest_configure(self, config):
|
||||
config.addinivalue_line("markers",
|
||||
"tryfirst: mark a hook implementation function such that the "
|
||||
"plugin machinery will try to call it first/as early as possible.")
|
||||
config.addinivalue_line("markers",
|
||||
"trylast: mark a hook implementation function such that the "
|
||||
"plugin machinery will try to call it last/as late as possible.")
|
||||
|
||||
def pytest_plugin_registered(self, plugin):
|
||||
import pytest
|
||||
dic = self.call_plugin(plugin, "pytest_namespace", {}) or {}
|
||||
if dic:
|
||||
self._setns(pytest, dic)
|
||||
if hasattr(self, '_config'):
|
||||
self.call_plugin(plugin, "pytest_addoption",
|
||||
{'parser': self._config._parser})
|
||||
self.call_plugin(plugin, "pytest_configure",
|
||||
{'config': self._config})
|
||||
|
||||
def _setns(self, obj, dic):
|
||||
import pytest
|
||||
for name, value in dic.items():
|
||||
if isinstance(value, dict):
|
||||
mod = getattr(obj, name, None)
|
||||
if mod is None:
|
||||
modname = "pytest.%s" % name
|
||||
mod = py.std.types.ModuleType(modname)
|
||||
sys.modules[modname] = mod
|
||||
mod.__all__ = []
|
||||
setattr(obj, name, mod)
|
||||
obj.__all__.append(name)
|
||||
self._setns(mod, value)
|
||||
else:
|
||||
setattr(obj, name, value)
|
||||
obj.__all__.append(name)
|
||||
#if obj != pytest:
|
||||
# pytest.__all__.append(name)
|
||||
setattr(pytest, name, value)
|
||||
|
||||
def pytest_terminal_summary(self, terminalreporter):
|
||||
tw = terminalreporter._tw
|
||||
if terminalreporter.config.option.traceconfig:
|
||||
for hint in self._hints:
|
||||
tw.line("hint: %s" % hint)
|
||||
|
||||
def do_addoption(self, parser):
|
||||
mname = "pytest_addoption"
|
||||
methods = reversed(self.listattr(mname))
|
||||
MultiCall(methods, {'parser': parser}).execute()
|
||||
|
||||
def do_configure(self, config):
|
||||
assert not hasattr(self, '_config')
|
||||
self._config = config
|
||||
config.hook.pytest_configure(config=self._config)
|
||||
|
||||
def do_unconfigure(self, config):
|
||||
config = self._config
|
||||
del self._config
|
||||
config.hook.pytest_unconfigure(config=config)
|
||||
config.pluginmanager.unregister(self)
|
||||
|
||||
def notify_exception(self, excinfo, option=None):
|
||||
if option and option.fulltrace:
|
||||
style = "long"
|
||||
else:
|
||||
style = "native"
|
||||
excrepr = excinfo.getrepr(funcargs=True,
|
||||
showlocals=getattr(option, 'showlocals', False),
|
||||
style=style,
|
||||
)
|
||||
res = self.hook.pytest_internalerror(excrepr=excrepr)
|
||||
if not py.builtin.any(res):
|
||||
for line in str(excrepr).split("\n"):
|
||||
sys.stderr.write("INTERNALERROR> %s\n" %line)
|
||||
sys.stderr.flush()
|
||||
|
||||
def listattr(self, attrname, plugins=None):
|
||||
if plugins is None:
|
||||
plugins = self._plugins
|
||||
@@ -322,6 +259,7 @@ def importplugin(importspec):
|
||||
name = importspec
|
||||
try:
|
||||
mod = "_pytest." + name
|
||||
#print >>sys.stderr, "tryimport", mod
|
||||
__import__(mod)
|
||||
return sys.modules[mod]
|
||||
except ImportError:
|
||||
@@ -329,7 +267,11 @@ def importplugin(importspec):
|
||||
#if str(e).find(name) == -1:
|
||||
# raise
|
||||
pass #
|
||||
__import__(importspec)
|
||||
try:
|
||||
#print >>sys.stderr, "tryimport", importspec
|
||||
__import__(importspec)
|
||||
except ImportError:
|
||||
raise ImportError(importspec)
|
||||
return sys.modules[importspec]
|
||||
|
||||
class MultiCall:
|
||||
@@ -437,43 +379,3 @@ class HookCaller:
|
||||
self.trace.root.indent -= 1
|
||||
return res
|
||||
|
||||
_preinit = []
|
||||
|
||||
def _preloadplugins():
|
||||
_preinit.append(PluginManager(load=True))
|
||||
|
||||
def _prepareconfig(args=None, plugins=None):
|
||||
if args is None:
|
||||
args = sys.argv[1:]
|
||||
elif isinstance(args, py.path.local):
|
||||
args = [str(args)]
|
||||
elif not isinstance(args, (tuple, list)):
|
||||
if not isinstance(args, str):
|
||||
raise ValueError("not a string or argument list: %r" % (args,))
|
||||
args = py.std.shlex.split(args)
|
||||
if _preinit:
|
||||
_pluginmanager = _preinit.pop(0)
|
||||
else: # subsequent calls to main will create a fresh instance
|
||||
_pluginmanager = PluginManager(load=True)
|
||||
hook = _pluginmanager.hook
|
||||
if plugins:
|
||||
for plugin in plugins:
|
||||
_pluginmanager.register(plugin)
|
||||
return hook.pytest_cmdline_parse(
|
||||
pluginmanager=_pluginmanager, args=args)
|
||||
|
||||
def main(args=None, plugins=None):
|
||||
""" return exit code, after performing an in-process test run.
|
||||
|
||||
:arg args: list of command line arguments.
|
||||
|
||||
:arg plugins: list of plugin objects to be auto-registered during
|
||||
initialization.
|
||||
"""
|
||||
config = _prepareconfig(args, plugins)
|
||||
exitstatus = config.hook.pytest_cmdline_main(config=config)
|
||||
return exitstatus
|
||||
|
||||
class UsageError(Exception):
|
||||
""" error in py.test usage or invocation"""
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
""" discover and run doctests in modules and test files."""
|
||||
|
||||
import pytest, py
|
||||
from _pytest.python import FixtureRequest, FuncFixtureInfo
|
||||
from py._code.code import TerminalRepr, ReprFileLocation
|
||||
|
||||
def pytest_addoption(parser):
|
||||
@@ -33,6 +34,14 @@ class ReprFailDoctest(TerminalRepr):
|
||||
self.reprlocation.toterminal(tw)
|
||||
|
||||
class DoctestItem(pytest.Item):
|
||||
def __init__(self, name, parent, runner=None, dtest=None):
|
||||
super(DoctestItem, self).__init__(name, parent)
|
||||
self.runner = runner
|
||||
self.dtest = dtest
|
||||
|
||||
def runtest(self):
|
||||
self.runner.run(self.dtest)
|
||||
|
||||
def repr_failure(self, excinfo):
|
||||
doctest = py.std.doctest
|
||||
if excinfo.errisinstance((doctest.DocTestFailure,
|
||||
@@ -41,17 +50,27 @@ class DoctestItem(pytest.Item):
|
||||
example = doctestfailure.example
|
||||
test = doctestfailure.test
|
||||
filename = test.filename
|
||||
lineno = test.lineno + example.lineno + 1
|
||||
if test.lineno is None:
|
||||
lineno = None
|
||||
else:
|
||||
lineno = test.lineno + example.lineno + 1
|
||||
message = excinfo.type.__name__
|
||||
reprlocation = ReprFileLocation(filename, lineno, message)
|
||||
checker = py.std.doctest.OutputChecker()
|
||||
REPORT_UDIFF = py.std.doctest.REPORT_UDIFF
|
||||
filelines = py.path.local(filename).readlines(cr=0)
|
||||
i = max(test.lineno, max(0, lineno - 10)) # XXX?
|
||||
lines = []
|
||||
for line in filelines[i:lineno]:
|
||||
lines.append("%03d %s" % (i+1, line))
|
||||
i += 1
|
||||
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
|
||||
else:
|
||||
lines.append('EXAMPLE LOCATION UNKNOWN, not showing all tests of that example')
|
||||
indent = '>>>'
|
||||
for line in example.source.splitlines():
|
||||
lines.append('??? %s %s' % (indent, line))
|
||||
indent = '...'
|
||||
if excinfo.errisinstance(doctest.DocTestFailure):
|
||||
lines += checker.output_difference(example,
|
||||
doctestfailure.got, REPORT_UDIFF).split("\n")
|
||||
@@ -65,23 +84,37 @@ class DoctestItem(pytest.Item):
|
||||
return super(DoctestItem, self).repr_failure(excinfo)
|
||||
|
||||
def reportinfo(self):
|
||||
return self.fspath, None, "[doctest]"
|
||||
return self.fspath, None, "[doctest] %s" % self.name
|
||||
|
||||
class DoctestTextfile(DoctestItem, pytest.File):
|
||||
def runtest(self):
|
||||
doctest = py.std.doctest
|
||||
# satisfy `FixtureRequest` constructor...
|
||||
self.funcargs = {}
|
||||
self._fixtureinfo = FuncFixtureInfo((), [], {})
|
||||
fixture_request = FixtureRequest(self)
|
||||
failed, tot = doctest.testfile(
|
||||
str(self.fspath), module_relative=False,
|
||||
optionflags=doctest.ELLIPSIS,
|
||||
extraglobs=dict(getfixture=fixture_request.getfuncargvalue),
|
||||
raise_on_error=True, verbose=0)
|
||||
|
||||
class DoctestModule(DoctestItem, pytest.File):
|
||||
def runtest(self):
|
||||
class DoctestModule(pytest.File):
|
||||
def collect(self):
|
||||
doctest = py.std.doctest
|
||||
if self.fspath.basename == "conftest.py":
|
||||
module = self.config._conftest.importconftest(self.fspath)
|
||||
else:
|
||||
module = self.fspath.pyimport()
|
||||
failed, tot = doctest.testmod(
|
||||
module, raise_on_error=True, verbose=0,
|
||||
optionflags=doctest.ELLIPSIS)
|
||||
# satisfy `FixtureRequest` constructor...
|
||||
self.funcargs = {}
|
||||
self._fixtureinfo = FuncFixtureInfo((), [], {})
|
||||
fixture_request = FixtureRequest(self)
|
||||
doctest_globals = dict(getfixture=fixture_request.getfuncargvalue)
|
||||
# uses internal doctest module parsing mechanism
|
||||
finder = doctest.DocTestFinder()
|
||||
runner = doctest.DebugRunner(verbose=0, optionflags=doctest.ELLIPSIS)
|
||||
for test in finder.find(module, module.__name__,
|
||||
extraglobs=doctest_globals):
|
||||
if test.examples: # skip empty doctests
|
||||
yield DoctestItem(test.name, self, runner, test)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
""" generate a single-file self-contained version of py.test """
|
||||
import py
|
||||
import sys
|
||||
|
||||
def find_toplevel(name):
|
||||
for syspath in py.std.sys.path:
|
||||
@@ -59,11 +60,21 @@ def pytest_addoption(parser):
|
||||
def pytest_cmdline_main(config):
|
||||
genscript = config.getvalue("genscript")
|
||||
if genscript:
|
||||
tw = py.io.TerminalWriter()
|
||||
deps = ['py', '_pytest', 'pytest']
|
||||
if sys.version_info < (2,7):
|
||||
deps.append("argparse")
|
||||
tw.line("generated script will run on python2.5-python3.3++")
|
||||
else:
|
||||
tw.line("WARNING: generated script will not run on python2.6 "
|
||||
"or below due to 'argparse' dependency. Use python2.6 "
|
||||
"to generate a python2.5/6 compatible script", red=True)
|
||||
script = generate_script(
|
||||
'import py; raise SystemExit(py.test.cmdline.main())',
|
||||
['py', '_pytest', 'pytest'],
|
||||
deps,
|
||||
)
|
||||
|
||||
genscript = py.path.local(genscript)
|
||||
genscript.write(script)
|
||||
tw.line("generated pytest standalone script: %s" % genscript,
|
||||
bold=True)
|
||||
return 0
|
||||
|
||||
@@ -13,8 +13,8 @@ def pytest_addoption(parser):
|
||||
group._addoption('-p', action="append", dest="plugins", default = [],
|
||||
metavar="name",
|
||||
help="early-load given plugin (multi-allowed).")
|
||||
group.addoption('--traceconfig',
|
||||
action="store_true", dest="traceconfig", default=False,
|
||||
group.addoption('--traceconfig', '--trace-config',
|
||||
action="store_true", default=False,
|
||||
help="trace considerations of conftest.py files."),
|
||||
group.addoption('--debug',
|
||||
action="store_true", dest="debug", default=False,
|
||||
@@ -54,14 +54,15 @@ def pytest_cmdline_main(config):
|
||||
sys.stderr.write(line + "\n")
|
||||
return 0
|
||||
elif config.option.help:
|
||||
config.pluginmanager.do_configure(config)
|
||||
config.do_configure()
|
||||
showhelp(config)
|
||||
config.pluginmanager.do_unconfigure(config)
|
||||
config.do_unconfigure()
|
||||
return 0
|
||||
|
||||
def showhelp(config):
|
||||
tw = py.io.TerminalWriter()
|
||||
tw.write(config._parser.optparser.format_help())
|
||||
tw.write(config._parser.optparser.format_epilog(None))
|
||||
tw.line()
|
||||
tw.line()
|
||||
#tw.sep( "=", "config file settings")
|
||||
|
||||
@@ -20,10 +20,10 @@ def pytest_cmdline_parse(pluginmanager, args):
|
||||
pytest_cmdline_parse.firstresult = True
|
||||
|
||||
def pytest_cmdline_preparse(config, args):
|
||||
"""modify command line arguments before option parsing. """
|
||||
"""(deprecated) modify command line arguments before option parsing. """
|
||||
|
||||
def pytest_addoption(parser):
|
||||
"""register optparse-style options and ini-style config values.
|
||||
"""register argparse-style options and ini-style config values.
|
||||
|
||||
This function must be implemented in a :ref:`plugin <pluginorder>` and is
|
||||
called once at the beginning of a test run.
|
||||
@@ -52,6 +52,10 @@ def pytest_cmdline_main(config):
|
||||
implementation will invoke the configure hooks and runtest_mainloop. """
|
||||
pytest_cmdline_main.firstresult = True
|
||||
|
||||
def pytest_load_initial_conftests(args, early_config, parser):
|
||||
""" implements loading initial conftests.
|
||||
"""
|
||||
|
||||
def pytest_configure(config):
|
||||
""" called after command line options have been parsed
|
||||
and all plugins and initial conftest files been loaded.
|
||||
@@ -221,7 +225,7 @@ def pytest_report_teststatus(report):
|
||||
pytest_report_teststatus.firstresult = True
|
||||
|
||||
def pytest_terminal_summary(terminalreporter):
|
||||
""" add additional section in terminal summary reporting. """
|
||||
""" add additional section in terminal summary reporting. """
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# doctest hooks
|
||||
@@ -236,13 +240,20 @@ pytest_doctest_prepare_content.firstresult = True
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
def pytest_plugin_registered(plugin, manager):
|
||||
""" a new py lib plugin got registered. """
|
||||
""" a new pytest plugin got registered. """
|
||||
|
||||
def pytest_plugin_unregistered(plugin):
|
||||
""" a py lib plugin got unregistered. """
|
||||
|
||||
def pytest_internalerror(excrepr):
|
||||
def pytest_internalerror(excrepr, excinfo):
|
||||
""" called for internal errors. """
|
||||
|
||||
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
|
||||
interactively handled.
|
||||
|
||||
This hook is only called if an exception was raised
|
||||
that is not an internal exception like "skip.Exception".
|
||||
"""
|
||||
|
||||
|
||||
@@ -36,7 +36,8 @@ class Junit(py.xml.Namespace):
|
||||
# | [#x10000-#x10FFFF]
|
||||
_legal_chars = (0x09, 0x0A, 0x0d)
|
||||
_legal_ranges = (
|
||||
(0x20, 0xD7FF),
|
||||
(0x20, 0x7E),
|
||||
(0x80, 0xD7FF),
|
||||
(0xE000, 0xFFFD),
|
||||
(0x10000, 0x10FFFF),
|
||||
)
|
||||
@@ -61,16 +62,17 @@ def bin_xml_escape(arg):
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("terminal reporting")
|
||||
group.addoption('--junitxml', action="store", dest="xmlpath",
|
||||
metavar="path", default=None,
|
||||
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', action="store", dest="junitprefix",
|
||||
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
|
||||
if 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)
|
||||
|
||||
@@ -102,15 +104,24 @@ class LogXML(object):
|
||||
classnames.insert(0, self.prefix)
|
||||
self.tests.append(Junit.testcase(
|
||||
classname=".".join(classnames),
|
||||
name=names[-1],
|
||||
name=bin_xml_escape(names[-1]),
|
||||
time=getattr(report, 'duration', 0)
|
||||
))
|
||||
|
||||
def _write_captured_output(self, report):
|
||||
sec = dict(report.sections)
|
||||
for name in ('out', 'err'):
|
||||
content = sec.get("Captured std%s" % name)
|
||||
if content:
|
||||
tag = getattr(Junit, 'system-'+name)
|
||||
self.append(tag(bin_xml_escape(content)))
|
||||
|
||||
def append(self, obj):
|
||||
self.tests[-1].append(obj)
|
||||
|
||||
def append_pass(self, report):
|
||||
self.passed += 1
|
||||
self._write_captured_output(report)
|
||||
|
||||
def append_failure(self, report):
|
||||
#msg = str(report.longrepr.reprtraceback.extraline)
|
||||
@@ -119,16 +130,11 @@ class LogXML(object):
|
||||
Junit.skipped(message="xfail-marked test passes unexpectedly"))
|
||||
self.skipped += 1
|
||||
else:
|
||||
sec = dict(report.sections)
|
||||
fail = Junit.failure(message="test failure")
|
||||
fail.append(str(report.longrepr))
|
||||
self.append(fail)
|
||||
for name in ('out', 'err'):
|
||||
content = sec.get("Captured std%s" % name)
|
||||
if content:
|
||||
tag = getattr(Junit, 'system-'+name)
|
||||
self.append(tag(bin_xml_escape(content)))
|
||||
self.failed += 1
|
||||
self._write_captured_output(report)
|
||||
|
||||
def append_collect_failure(self, report):
|
||||
#msg = str(report.longrepr.reprtraceback.extraline)
|
||||
@@ -161,6 +167,7 @@ class LogXML(object):
|
||||
message=skipreason
|
||||
))
|
||||
self.skipped += 1
|
||||
self._write_captured_output(report)
|
||||
|
||||
def pytest_runtest_logreport(self, report):
|
||||
if report.passed:
|
||||
@@ -210,7 +217,7 @@ class LogXML(object):
|
||||
logfile.write('<?xml version="1.0" encoding="utf-8"?>')
|
||||
logfile.write(Junit.testsuite(
|
||||
self.tests,
|
||||
name="",
|
||||
name="pytest",
|
||||
errors=self.errors,
|
||||
failures=self.failed,
|
||||
skips=self.skipped,
|
||||
|
||||
@@ -10,6 +10,7 @@ except ImportError:
|
||||
from UserDict import DictMixin as MappingMixin
|
||||
|
||||
from _pytest.mark import MarkInfo
|
||||
from _pytest.runner import collect_one_node, Skipped
|
||||
|
||||
tracebackcutdir = py.path.local(_pytest.__file__).dirpath()
|
||||
|
||||
@@ -35,19 +36,20 @@ def pytest_addoption(parser):
|
||||
dest="exitfirst",
|
||||
help="exit instantly on first error or failed test."),
|
||||
group._addoption('--maxfail', metavar="num",
|
||||
action="store", type="int", dest="maxfail", default=0,
|
||||
action="store", type=int, dest="maxfail", default=0,
|
||||
help="exit after first num failures or errors.")
|
||||
group._addoption('--strict', action="store_true",
|
||||
help="run pytest in strict mode, warnings become errors.")
|
||||
|
||||
group = parser.getgroup("collect", "collection")
|
||||
group.addoption('--collectonly',
|
||||
action="store_true", dest="collectonly",
|
||||
group.addoption('--collectonly', '--collect-only', action="store_true",
|
||||
help="only collect tests, don't execute them."),
|
||||
group.addoption('--pyargs', action="store_true",
|
||||
help="try to interpret all arguments as python packages.")
|
||||
group.addoption("--ignore", action="append", metavar="path",
|
||||
help="ignore path during collection (multi-allowed).")
|
||||
# when changing this to --conf-cut-dir, config.py Conftest.setinitial
|
||||
# needs upgrading as well
|
||||
group.addoption('--confcutdir', dest="confcutdir", default=None,
|
||||
metavar="dir",
|
||||
help="only load conftest.py's relative to specified dir.")
|
||||
@@ -74,7 +76,7 @@ def wrap_session(config, doit):
|
||||
initstate = 0
|
||||
try:
|
||||
try:
|
||||
config.pluginmanager.do_configure(config)
|
||||
config.do_configure()
|
||||
initstate = 1
|
||||
config.hook.pytest_sessionstart(session=session)
|
||||
initstate = 2
|
||||
@@ -89,18 +91,22 @@ def wrap_session(config, doit):
|
||||
session.exitstatus = EXIT_INTERRUPTED
|
||||
except:
|
||||
excinfo = py.code.ExceptionInfo()
|
||||
config.pluginmanager.notify_exception(excinfo, config.option)
|
||||
config.notify_exception(excinfo, config.option)
|
||||
session.exitstatus = EXIT_INTERNALERROR
|
||||
if excinfo.errisinstance(SystemExit):
|
||||
sys.stderr.write("mainloop: caught Spurious SystemExit!\n")
|
||||
else:
|
||||
if session._testsfailed:
|
||||
session.exitstatus = EXIT_TESTSFAILED
|
||||
finally:
|
||||
session.startdir.chdir()
|
||||
if initstate >= 2:
|
||||
config.hook.pytest_sessionfinish(session=session,
|
||||
exitstatus=session.exitstatus or (session._testsfailed and 1))
|
||||
if not session.exitstatus and session._testsfailed:
|
||||
session.exitstatus = EXIT_TESTSFAILED
|
||||
config.hook.pytest_sessionfinish(
|
||||
session=session,
|
||||
exitstatus=session.exitstatus)
|
||||
if initstate >= 1:
|
||||
config.pluginmanager.do_unconfigure(config)
|
||||
config.do_unconfigure()
|
||||
config.pluginmanager.ensure_shutdown()
|
||||
return session.exitstatus
|
||||
|
||||
def pytest_cmdline_main(config):
|
||||
@@ -214,6 +220,9 @@ class Node(object):
|
||||
#: keywords/markers collected from all scopes
|
||||
self.keywords = NodeKeywords(self)
|
||||
|
||||
#: allow adding of extra keywords to use for matching
|
||||
self.extra_keyword_matches = set()
|
||||
|
||||
#self.extrainit()
|
||||
|
||||
@property
|
||||
@@ -305,12 +314,28 @@ class Node(object):
|
||||
chain.reverse()
|
||||
return chain
|
||||
|
||||
def listextrakeywords(self):
|
||||
""" Return a set of all extra keywords in self and any parents."""
|
||||
extra_keywords = set()
|
||||
item = self
|
||||
for item in self.listchain():
|
||||
extra_keywords.update(item.extra_keyword_matches)
|
||||
return extra_keywords
|
||||
|
||||
def listnames(self):
|
||||
return [x.name for x in self.listchain()]
|
||||
|
||||
def getplugins(self):
|
||||
return self.config._getmatchingplugins(self.fspath)
|
||||
|
||||
def addfinalizer(self, fin):
|
||||
""" register a function to be called when this node is finalized.
|
||||
|
||||
This method can only be called when this node is active
|
||||
in a setup chain, for example during self.setup().
|
||||
"""
|
||||
self.session._setupstate.addfinalizer(fin, self)
|
||||
|
||||
def getparent(self, cls):
|
||||
current = self
|
||||
while current and not isinstance(current, cls):
|
||||
@@ -345,6 +370,11 @@ class Collector(Node):
|
||||
""" Collector instances create children through collect()
|
||||
and thus iteratively build a tree.
|
||||
"""
|
||||
|
||||
# the set of exceptions to interpret as "Skip the whole module" during
|
||||
# collection
|
||||
skip_exceptions = (Skipped,)
|
||||
|
||||
class CollectError(Exception):
|
||||
""" an error during collection, contains a custom message. """
|
||||
|
||||
@@ -439,6 +469,7 @@ class Session(FSCollector):
|
||||
self.shouldstop = False
|
||||
self.trace = config.trace.root.get("collection")
|
||||
self._norecursepatterns = config.getini("norecursedirs")
|
||||
self.startdir = py.path.local()
|
||||
|
||||
def pytest_collectstart(self):
|
||||
if self.shouldstop:
|
||||
@@ -482,8 +513,7 @@ class Session(FSCollector):
|
||||
parts = self._parsearg(arg)
|
||||
self._initialparts.append(parts)
|
||||
self._initialpaths.add(parts[0])
|
||||
self.ihook.pytest_collectstart(collector=self)
|
||||
rep = self.ihook.pytest_make_collect_report(collector=self)
|
||||
rep = collect_one_node(self)
|
||||
self.ihook.pytest_collectreport(report=rep)
|
||||
self.trace.root.indent -= 1
|
||||
if self._notfound:
|
||||
@@ -612,8 +642,7 @@ class Session(FSCollector):
|
||||
resultnodes.append(node)
|
||||
continue
|
||||
assert isinstance(node, pytest.Collector)
|
||||
node.ihook.pytest_collectstart(collector=node)
|
||||
rep = node.ihook.pytest_make_collect_report(collector=node)
|
||||
rep = collect_one_node(node)
|
||||
if rep.passed:
|
||||
has_matched = False
|
||||
for x in rep.result:
|
||||
@@ -634,8 +663,7 @@ class Session(FSCollector):
|
||||
yield node
|
||||
else:
|
||||
assert isinstance(node, pytest.Collector)
|
||||
node.ihook.pytest_collectstart(collector=node)
|
||||
rep = node.ihook.pytest_make_collect_report(collector=node)
|
||||
rep = collect_one_node(node)
|
||||
if rep.passed:
|
||||
for subnode in rep.result:
|
||||
for x in self.genitems(subnode):
|
||||
|
||||
158
_pytest/mark.py
158
_pytest/mark.py
@@ -1,43 +1,56 @@
|
||||
""" generic mechanism for marking and selecting python functions. """
|
||||
import pytest, py
|
||||
import py
|
||||
|
||||
|
||||
def pytest_namespace():
|
||||
return {'mark': MarkGenerator()}
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("general")
|
||||
group._addoption('-k',
|
||||
action="store", dest="keyword", default='', metavar="KEYWORDEXPR",
|
||||
help="only run tests which match given keyword expression. "
|
||||
"An expression consists of space-separated terms. "
|
||||
"Each term must match. Precede a term with '-' to negate. "
|
||||
"Terminate expression with ':' to make the first match match "
|
||||
"all subsequent tests (usually file-order). ")
|
||||
group._addoption(
|
||||
'-k',
|
||||
action="store", dest="keyword", default='', metavar="EXPRESSION",
|
||||
help="only run tests which match the given substring expression. "
|
||||
"An expression is a python evaluatable expression "
|
||||
"where all names are substring-matched against test names "
|
||||
"and their parent classes. Example: -k 'test_method or test "
|
||||
"other' matches all test functions and classes whose name "
|
||||
"contains 'test_method' or 'test_other'. "
|
||||
"Additionally keywords are matched to classes and functions "
|
||||
"containing extra names in their 'extra_keyword_matches' set, "
|
||||
"as well as functions which have names assigned directly to them."
|
||||
)
|
||||
|
||||
group._addoption("-m",
|
||||
group._addoption(
|
||||
"-m",
|
||||
action="store", dest="markexpr", default="", metavar="MARKEXPR",
|
||||
help="only run tests matching given mark expression. "
|
||||
"example: -m 'mark1 and not mark2'."
|
||||
)
|
||||
)
|
||||
|
||||
group.addoption("--markers", action="store_true", help=
|
||||
"show markers (builtin, plugin and per-project ones).")
|
||||
group.addoption(
|
||||
"--markers", action="store_true",
|
||||
help="show markers (builtin, plugin and per-project ones)."
|
||||
)
|
||||
|
||||
parser.addini("markers", "markers for test functions", 'linelist')
|
||||
|
||||
|
||||
def pytest_cmdline_main(config):
|
||||
if config.option.markers:
|
||||
config.pluginmanager.do_configure(config)
|
||||
config.do_configure()
|
||||
tw = py.io.TerminalWriter()
|
||||
for line in config.getini("markers"):
|
||||
name, rest = line.split(":", 1)
|
||||
tw.write("@pytest.mark.%s:" % name, bold=True)
|
||||
tw.write("@pytest.mark.%s:" % name, bold=True)
|
||||
tw.line(rest)
|
||||
tw.line()
|
||||
config.pluginmanager.do_unconfigure(config)
|
||||
config.do_unconfigure()
|
||||
return 0
|
||||
pytest_cmdline_main.tryfirst = True
|
||||
|
||||
|
||||
def pytest_collection_modifyitems(items, config):
|
||||
keywordexpr = config.option.keyword
|
||||
matchexpr = config.option.markexpr
|
||||
@@ -51,7 +64,7 @@ def pytest_collection_modifyitems(items, config):
|
||||
remaining = []
|
||||
deselected = []
|
||||
for colitem in items:
|
||||
if keywordexpr and skipbykeyword(colitem, keywordexpr):
|
||||
if keywordexpr and not matchkeyword(colitem, keywordexpr):
|
||||
deselected.append(colitem)
|
||||
else:
|
||||
if selectuntil:
|
||||
@@ -66,42 +79,77 @@ def pytest_collection_modifyitems(items, config):
|
||||
config.hook.pytest_deselected(items=deselected)
|
||||
items[:] = remaining
|
||||
|
||||
class BoolDict:
|
||||
def __init__(self, mydict):
|
||||
self._mydict = mydict
|
||||
def __getitem__(self, name):
|
||||
return name in self._mydict
|
||||
|
||||
def matchmark(colitem, matchexpr):
|
||||
return eval(matchexpr, {}, BoolDict(colitem.obj.__dict__))
|
||||
class MarkMapping:
|
||||
"""Provides a local mapping for markers.
|
||||
Only the marker names from the given :class:`NodeKeywords` will be mapped,
|
||||
so the names are taken only from :class:`MarkInfo` or
|
||||
:class:`MarkDecorator` items.
|
||||
"""
|
||||
def __init__(self, keywords):
|
||||
mymarks = set()
|
||||
for key, value in keywords.items():
|
||||
if isinstance(value, MarkInfo) or isinstance(value, MarkDecorator):
|
||||
mymarks.add(key)
|
||||
self._mymarks = mymarks
|
||||
|
||||
def __getitem__(self, markname):
|
||||
return markname in self._mymarks
|
||||
|
||||
|
||||
class KeywordMapping:
|
||||
"""Provides a local mapping for keywords.
|
||||
Given a list of names, map any substring of one of these names to True.
|
||||
"""
|
||||
def __init__(self, names):
|
||||
self._names = names
|
||||
|
||||
def __getitem__(self, subname):
|
||||
for name in self._names:
|
||||
if subname in name:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def matchmark(colitem, markexpr):
|
||||
"""Tries to match on any marker names, attached to the given colitem."""
|
||||
return eval(markexpr, {}, MarkMapping(colitem.keywords))
|
||||
|
||||
|
||||
def matchkeyword(colitem, keywordexpr):
|
||||
"""Tries to match given keyword expression to given collector item.
|
||||
|
||||
Will match on the name of colitem, including the names of its parents.
|
||||
Only matches names of items which are either a :class:`Class` or a
|
||||
:class:`Function`.
|
||||
Additionally, matches on names in the 'extra_keyword_matches' set of
|
||||
any item, as well as names directly assigned to test functions.
|
||||
"""
|
||||
keywordexpr = keywordexpr.replace("-", "not ")
|
||||
mapped_names = set()
|
||||
|
||||
# Add the names of the current item and any parent items
|
||||
import pytest
|
||||
for item in colitem.listchain():
|
||||
if not isinstance(item, pytest.Instance):
|
||||
mapped_names.add(item.name)
|
||||
|
||||
# Add the names added as extra keywords to current or parent items
|
||||
for name in colitem.listextrakeywords():
|
||||
mapped_names.add(name)
|
||||
|
||||
# Add the names attached to the current function through direct assignment
|
||||
for name in colitem.function.__dict__:
|
||||
mapped_names.add(name)
|
||||
|
||||
return eval(keywordexpr, {}, KeywordMapping(mapped_names))
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
import pytest
|
||||
if config.option.strict:
|
||||
pytest.mark._config = config
|
||||
|
||||
def skipbykeyword(colitem, keywordexpr):
|
||||
""" return True if they given keyword expression means to
|
||||
skip this collector/item.
|
||||
"""
|
||||
if not keywordexpr:
|
||||
return
|
||||
|
||||
itemkeywords = colitem.keywords
|
||||
for key in filter(None, keywordexpr.split()):
|
||||
eor = key[:1] == '-'
|
||||
if eor:
|
||||
key = key[1:]
|
||||
if not (eor ^ matchonekeyword(key, itemkeywords)):
|
||||
return True
|
||||
|
||||
def matchonekeyword(key, itemkeywords):
|
||||
for elem in key.split("."):
|
||||
for kw in itemkeywords:
|
||||
if elem in kw:
|
||||
break
|
||||
else:
|
||||
return False
|
||||
return True
|
||||
|
||||
class MarkGenerator:
|
||||
""" Factory for :class:`MarkDecorator` objects - exposed as
|
||||
@@ -136,6 +184,7 @@ class MarkGenerator:
|
||||
if name not in self._markers:
|
||||
raise AttributeError("%r not a registered marker" % (name,))
|
||||
|
||||
|
||||
class MarkDecorator:
|
||||
""" A decorator for test functions and test classes. When applied
|
||||
it will create :class:`MarkInfo` objects which may be
|
||||
@@ -159,7 +208,7 @@ class MarkDecorator:
|
||||
def __repr__(self):
|
||||
d = self.__dict__.copy()
|
||||
name = d.pop('markname')
|
||||
return "<MarkDecorator %r %r>" %(name, d)
|
||||
return "<MarkDecorator %r %r>" % (name, d)
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
""" if passed a single callable argument: decorate it with mark info.
|
||||
@@ -172,15 +221,17 @@ class MarkDecorator:
|
||||
if hasattr(func, 'pytestmark'):
|
||||
l = func.pytestmark
|
||||
if not isinstance(l, list):
|
||||
func.pytestmark = [l, self]
|
||||
func.pytestmark = [l, self]
|
||||
else:
|
||||
l.append(self)
|
||||
l.append(self)
|
||||
else:
|
||||
func.pytestmark = [self]
|
||||
func.pytestmark = [self]
|
||||
else:
|
||||
holder = getattr(func, self.markname, None)
|
||||
if holder is None:
|
||||
holder = MarkInfo(self.markname, self.args, self.kwargs)
|
||||
holder = MarkInfo(
|
||||
self.markname, self.args, self.kwargs
|
||||
)
|
||||
setattr(func, self.markname, holder)
|
||||
else:
|
||||
holder.add(self.args, self.kwargs)
|
||||
@@ -190,6 +241,7 @@ class MarkDecorator:
|
||||
args = self.args + args
|
||||
return self.__class__(self.markname, args=args, kwargs=kw)
|
||||
|
||||
|
||||
class MarkInfo:
|
||||
""" Marking object created by :class:`MarkDecorator` instances. """
|
||||
def __init__(self, name, args, kwargs):
|
||||
@@ -203,7 +255,8 @@ class MarkInfo:
|
||||
|
||||
def __repr__(self):
|
||||
return "<MarkInfo %r args=%r kwargs=%r>" % (
|
||||
self.name, self.args, self.kwargs)
|
||||
self.name, self.args, self.kwargs
|
||||
)
|
||||
|
||||
def add(self, args, kwargs):
|
||||
""" add a MarkInfo with the given args and kwargs. """
|
||||
@@ -215,4 +268,3 @@ class MarkInfo:
|
||||
""" yield MarkInfo objects each relating to a marking-call. """
|
||||
for args, kwargs in self._arglist:
|
||||
yield MarkInfo(self.name, args, kwargs)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
""" monkeypatching and mocking functionality. """
|
||||
|
||||
import os, sys, inspect
|
||||
import pytest
|
||||
|
||||
def pytest_funcarg__monkeypatch(request):
|
||||
"""The returned ``monkeypatch`` funcarg provides these
|
||||
@@ -24,6 +25,39 @@ def pytest_funcarg__monkeypatch(request):
|
||||
request.addfinalizer(mpatch.undo)
|
||||
return mpatch
|
||||
|
||||
|
||||
|
||||
def derive_importpath(import_path):
|
||||
if not isinstance(import_path, str) 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]
|
||||
getattr(obj, attr)
|
||||
except AttributeError:
|
||||
__tracebackhide__ = True
|
||||
pytest.fail("object %r has no attribute %r" % (obj, attr))
|
||||
return attr, obj
|
||||
|
||||
|
||||
|
||||
notset = object()
|
||||
|
||||
class monkeypatch:
|
||||
@@ -33,28 +67,66 @@ class monkeypatch:
|
||||
self._setitem = []
|
||||
self._cwd = None
|
||||
|
||||
def setattr(self, obj, name, value, raising=True):
|
||||
""" set attribute ``name`` on ``obj`` to ``value``, by default
|
||||
raise AttributeEror if the attribute did not exist. """
|
||||
oldval = getattr(obj, name, notset)
|
||||
def setattr(self, target, name, value=notset, raising=True):
|
||||
""" set attribute value on target, memorizing the old value.
|
||||
By default raise AttributeError if the attribute did not exist.
|
||||
|
||||
For convenience you can specify a string as ``target`` which
|
||||
will be interpreted as a dotted import path, with the last part
|
||||
being the attribute name. Example:
|
||||
``monkeypatch.setattr("os.getcwd", lambda x: "/")``
|
||||
would set the ``getcwd`` function of the ``os`` module.
|
||||
|
||||
The ``raising`` value determines if the setattr should fail
|
||||
if the attribute is not already present (defaults to True
|
||||
which means it will raise).
|
||||
"""
|
||||
__tracebackhide__ = True
|
||||
|
||||
if value is notset:
|
||||
if not isinstance(target, str):
|
||||
raise TypeError("use setattr(target, name, value) or "
|
||||
"setattr(target, value) with target being a dotted "
|
||||
"import string")
|
||||
value = name
|
||||
name, target = derive_importpath(target)
|
||||
|
||||
oldval = getattr(target, name, notset)
|
||||
if raising and oldval is notset:
|
||||
raise AttributeError("%r has no attribute %r" %(obj, name))
|
||||
raise AttributeError("%r has no attribute %r" %(target, name))
|
||||
|
||||
# avoid class descriptors like staticmethod/classmethod
|
||||
if inspect.isclass(obj):
|
||||
oldval = obj.__dict__.get(name, notset)
|
||||
self._setattr.insert(0, (obj, name, oldval))
|
||||
setattr(obj, name, value)
|
||||
if inspect.isclass(target):
|
||||
oldval = target.__dict__.get(name, notset)
|
||||
self._setattr.insert(0, (target, name, oldval))
|
||||
setattr(target, name, value)
|
||||
|
||||
def delattr(self, obj, name, raising=True):
|
||||
""" delete attribute ``name`` from ``obj``, by default raise
|
||||
AttributeError it the attribute did not previously exist. """
|
||||
if not hasattr(obj, name):
|
||||
def delattr(self, target, name=notset, raising=True):
|
||||
""" delete attribute ``name`` from ``target``, by default raise
|
||||
AttributeError it the attribute did not previously exist.
|
||||
|
||||
If no ``name`` is specified and ``target`` is a string
|
||||
it will be interpreted as a dotted import path with the
|
||||
last part being the attribute name.
|
||||
|
||||
If raising is set to false, the attribute is allowed to not
|
||||
pre-exist.
|
||||
"""
|
||||
__tracebackhide__ = True
|
||||
if name is notset:
|
||||
if not isinstance(target, str):
|
||||
raise TypeError("use delattr(target, name) or "
|
||||
"delattr(target) with target being a dotted "
|
||||
"import string")
|
||||
name, target = derive_importpath(target)
|
||||
|
||||
if not hasattr(target, name):
|
||||
if raising:
|
||||
raise AttributeError(name)
|
||||
else:
|
||||
self._setattr.insert(0, (obj, name, getattr(obj, name, notset)))
|
||||
delattr(obj, name)
|
||||
self._setattr.insert(0, (target, name,
|
||||
getattr(target, name, notset)))
|
||||
delattr(target, name)
|
||||
|
||||
def setitem(self, dic, name, value):
|
||||
""" set dictionary entry ``name`` to value. """
|
||||
|
||||
@@ -3,19 +3,21 @@
|
||||
import pytest, py
|
||||
import inspect
|
||||
import sys
|
||||
from _pytest import unittest
|
||||
|
||||
def pytest_runtest_makereport(__multicall__, item, call):
|
||||
SkipTest = getattr(sys.modules.get('nose', None), 'SkipTest', None)
|
||||
if SkipTest:
|
||||
if call.excinfo and call.excinfo.errisinstance(SkipTest):
|
||||
# let's substitute the excinfo with a py.test.skip one
|
||||
call2 = call.__class__(lambda: py.test.skip(str(call.excinfo.value)), call.when)
|
||||
call2 = call.__class__(lambda:
|
||||
pytest.skip(str(call.excinfo.value)), call.when)
|
||||
call.excinfo = call2.excinfo
|
||||
|
||||
|
||||
@pytest.mark.trylast
|
||||
def pytest_runtest_setup(item):
|
||||
if isinstance(item, (pytest.Function)):
|
||||
if is_potential_nosetest(item):
|
||||
if isinstance(item.parent, pytest.Generator):
|
||||
gen = item.parent
|
||||
if not hasattr(gen, '_nosegensetup'):
|
||||
@@ -26,9 +28,11 @@ def pytest_runtest_setup(item):
|
||||
if not call_optional(item.obj, 'setup'):
|
||||
# call module level setup if there is no object level one
|
||||
call_optional(item.parent.obj, 'setup')
|
||||
#XXX this implies we only call teardown when setup worked
|
||||
item.session._setupstate.addfinalizer((lambda: teardown_nose(item)), item)
|
||||
|
||||
def pytest_runtest_teardown(item):
|
||||
if isinstance(item, pytest.Function):
|
||||
def teardown_nose(item):
|
||||
if is_potential_nosetest(item):
|
||||
if not call_optional(item.obj, 'teardown'):
|
||||
call_optional(item.parent.obj, 'teardown')
|
||||
#if hasattr(item.parent, '_nosegensetup'):
|
||||
@@ -36,12 +40,27 @@ def pytest_runtest_teardown(item):
|
||||
# del item.parent._nosegensetup
|
||||
|
||||
def pytest_make_collect_report(collector):
|
||||
SkipTest = getattr(sys.modules.get('unittest', None), 'SkipTest', None)
|
||||
if SkipTest is not None:
|
||||
collector.skip_exceptions += (SkipTest,)
|
||||
SkipTest = getattr(sys.modules.get('nose', None), 'SkipTest', None)
|
||||
if SkipTest is not None:
|
||||
collector.skip_exceptions += (SkipTest,)
|
||||
if isinstance(collector, pytest.Generator):
|
||||
call_optional(collector.obj, 'setup')
|
||||
|
||||
|
||||
def is_potential_nosetest(item):
|
||||
# extra check needed since we do not do nose style setup/teardown
|
||||
# on direct unittest style classes
|
||||
return isinstance(item, pytest.Function) and \
|
||||
not isinstance(item, unittest.TestCaseFunction)
|
||||
|
||||
|
||||
def call_optional(obj, name):
|
||||
method = getattr(obj, name, None)
|
||||
if method is not None and not hasattr(method, "_pytestfixturefunction") and py.builtin.callable(method):
|
||||
isfixture = hasattr(method, "_pytestfixturefunction")
|
||||
if method is not None and not isfixture and py.builtin.callable(method):
|
||||
# If there's any problems allow the exception to raise rather than
|
||||
# silently ignoring them
|
||||
method()
|
||||
|
||||
@@ -10,7 +10,7 @@ def pytest_addoption(parser):
|
||||
group = parser.getgroup("terminal reporting")
|
||||
group._addoption('--pastebin', metavar="mode",
|
||||
action='store', dest="pastebin", default=None,
|
||||
type="choice", choices=['failed', 'all'],
|
||||
choices=['failed', 'all'],
|
||||
help="send failed|all info to bpaste.net pastebin service.")
|
||||
|
||||
def pytest_configure(__multicall__, config):
|
||||
|
||||
@@ -16,6 +16,12 @@ def pytest_configure(config):
|
||||
if config.getvalue("usepdb"):
|
||||
config.pluginmanager.register(PdbInvoke(), 'pdbinvoke')
|
||||
|
||||
old_trace = py.std.pdb.set_trace
|
||||
def fin():
|
||||
py.std.pdb.set_trace = old_trace
|
||||
py.std.pdb.set_trace = pytest.set_trace
|
||||
config._cleanup.append(fin)
|
||||
|
||||
class pytestPDB:
|
||||
""" Pseudo PDB that defers to the real pdb. """
|
||||
item = None
|
||||
@@ -52,33 +58,47 @@ def pytest_runtest_makereport():
|
||||
pytestPDB.item = None
|
||||
|
||||
class PdbInvoke:
|
||||
@pytest.mark.tryfirst
|
||||
def pytest_runtest_makereport(self, item, call, __multicall__):
|
||||
rep = __multicall__.execute()
|
||||
if not call.excinfo or \
|
||||
call.excinfo.errisinstance(pytest.skip.Exception) or \
|
||||
call.excinfo.errisinstance(py.std.bdb.BdbQuit):
|
||||
return rep
|
||||
if hasattr(rep, "wasxfail"):
|
||||
return rep
|
||||
# we assume that the above execute() suspended capturing
|
||||
# XXX we re-use the TerminalReporter's terminalwriter
|
||||
# because this seems to avoid some encoding related troubles
|
||||
# for not completely clear reasons.
|
||||
tw = item.config.pluginmanager.getplugin("terminalreporter")._tw
|
||||
tw.line()
|
||||
tw.sep(">", "traceback")
|
||||
rep.toterminal(tw)
|
||||
tw.sep(">", "entering PDB")
|
||||
# A doctest.UnexpectedException is not useful for post_mortem.
|
||||
# Use the underlying exception instead:
|
||||
if isinstance(call.excinfo.value, py.std.doctest.UnexpectedException):
|
||||
tb = call.excinfo.value.exc_info[2]
|
||||
else:
|
||||
tb = call.excinfo._excinfo[2]
|
||||
def pytest_exception_interact(self, node, call, report):
|
||||
return _enter_pdb(node, call.excinfo, report)
|
||||
|
||||
def pytest_internalerror(self, excrepr, excinfo):
|
||||
for line in str(excrepr).split("\n"):
|
||||
sys.stderr.write("INTERNALERROR> %s\n" %line)
|
||||
sys.stderr.flush()
|
||||
tb = _postmortem_traceback(excinfo)
|
||||
post_mortem(tb)
|
||||
rep._pdbshown = True
|
||||
return rep
|
||||
|
||||
|
||||
def _enter_pdb(node, excinfo, rep):
|
||||
# XXX we re-use the TerminalReporter's terminalwriter
|
||||
# because this seems to avoid some encoding related troubles
|
||||
# for not completely clear reasons.
|
||||
tw = node.config.pluginmanager.getplugin("terminalreporter")._tw
|
||||
tw.line()
|
||||
tw.sep(">", "traceback")
|
||||
rep.toterminal(tw)
|
||||
tw.sep(">", "entering PDB")
|
||||
tb = _postmortem_traceback(excinfo)
|
||||
post_mortem(tb)
|
||||
rep._pdbshown = True
|
||||
return rep
|
||||
|
||||
|
||||
def _postmortem_traceback(excinfo):
|
||||
# A doctest.UnexpectedException is not useful for post_mortem.
|
||||
# Use the underlying exception instead:
|
||||
if isinstance(excinfo.value, py.std.doctest.UnexpectedException):
|
||||
return excinfo.value.exc_info[2]
|
||||
else:
|
||||
return excinfo._excinfo[2]
|
||||
|
||||
|
||||
def _find_last_non_hidden_frame(stack):
|
||||
i = max(0, len(stack) - 1)
|
||||
while i and stack[i][0].f_locals.get("__tracebackhide__", False):
|
||||
i -= 1
|
||||
return i
|
||||
|
||||
|
||||
def post_mortem(t):
|
||||
pdb = py.std.pdb
|
||||
@@ -86,9 +106,7 @@ def post_mortem(t):
|
||||
def get_stack(self, f, t):
|
||||
stack, i = pdb.Pdb.get_stack(self, f, t)
|
||||
if f is None:
|
||||
i = max(0, len(stack) - 1)
|
||||
while i and stack[i][0].f_locals.get("__tracebackhide__", False):
|
||||
i-=1
|
||||
i = _find_last_non_hidden_frame(stack)
|
||||
return stack, i
|
||||
p = Pdb()
|
||||
p.reset()
|
||||
|
||||
@@ -11,6 +11,12 @@ from _pytest.main import Session, EXIT_OK
|
||||
from py.builtin import print_
|
||||
from _pytest.core import HookRelay
|
||||
|
||||
|
||||
def get_public_names(l):
|
||||
"""Only return names from iterator l without a leading underscore."""
|
||||
return [x for x in l if x[0] != "_"]
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("pylib")
|
||||
group.addoption('--no-tools-on-path',
|
||||
@@ -77,7 +83,8 @@ class HookRecorder:
|
||||
|
||||
def finish_recording(self):
|
||||
for recorder in self._recorders.values():
|
||||
self._pluginmanager.unregister(recorder)
|
||||
if self._pluginmanager.isregistered(recorder):
|
||||
self._pluginmanager.unregister(recorder)
|
||||
self._recorders.clear()
|
||||
|
||||
def _makecallparser(self, method):
|
||||
@@ -195,10 +202,7 @@ class TmpTestdir:
|
||||
except py.error.EEXIST:
|
||||
continue
|
||||
break
|
||||
# we need to create another subdir
|
||||
# because Directory.collect() currently loads
|
||||
# conftest.py from sibling directories
|
||||
self.tmpdir = tmpdir.mkdir(name)
|
||||
self.tmpdir = tmpdir
|
||||
self.plugins = []
|
||||
self._syspathremove = []
|
||||
self.chdir() # always chdir
|
||||
@@ -358,7 +362,7 @@ class TmpTestdir:
|
||||
if not plugins:
|
||||
plugins = []
|
||||
plugins.append(Collect())
|
||||
ret = self.pytestmain(list(args), plugins=plugins)
|
||||
ret = pytest.main(list(args), plugins=plugins)
|
||||
reprec = rec[0]
|
||||
reprec.ret = ret
|
||||
assert len(rec) == 1
|
||||
@@ -371,23 +375,24 @@ class TmpTestdir:
|
||||
break
|
||||
else:
|
||||
args.append("--basetemp=%s" % self.tmpdir.dirpath('basetemp'))
|
||||
import _pytest.core
|
||||
config = _pytest.core._prepareconfig(args, self.plugins)
|
||||
# the in-process pytest invocation needs to avoid leaking FDs
|
||||
# so we register a "reset_capturings" callmon the capturing manager
|
||||
# and make sure it gets called
|
||||
config._cleanup.append(
|
||||
config.pluginmanager.getplugin("capturemanager").reset_capturings)
|
||||
import _pytest.config
|
||||
self.request.addfinalizer(
|
||||
lambda: _pytest.config.pytest_unconfigure(config))
|
||||
config = _pytest.config._prepareconfig(args, self.plugins)
|
||||
# we don't know what the test will do with this half-setup config
|
||||
# object and thus we make sure it gets unconfigured properly in any
|
||||
# case (otherwise capturing could still be active, for example)
|
||||
def ensure_unconfigure():
|
||||
if hasattr(config.pluginmanager, "_config"):
|
||||
config.pluginmanager.do_unconfigure(config)
|
||||
config.pluginmanager.ensure_shutdown()
|
||||
|
||||
self.request.addfinalizer(ensure_unconfigure)
|
||||
return config
|
||||
|
||||
def parseconfigure(self, *args):
|
||||
config = self.parseconfig(*args)
|
||||
config.pluginmanager.do_configure(config)
|
||||
config.do_configure()
|
||||
self.request.addfinalizer(lambda:
|
||||
config.pluginmanager.do_unconfigure(config))
|
||||
config.do_unconfigure())
|
||||
return config
|
||||
|
||||
def getitem(self, source, funcname="test_func"):
|
||||
@@ -422,18 +427,8 @@ class TmpTestdir:
|
||||
str(os.getcwd()), env.get('PYTHONPATH', '')]))
|
||||
kw['env'] = env
|
||||
#print "env", env
|
||||
return py.std.subprocess.Popen(cmdargs, stdout=stdout, stderr=stderr, **kw)
|
||||
|
||||
def pytestmain(self, *args, **kwargs):
|
||||
class ResetCapturing:
|
||||
@pytest.mark.trylast
|
||||
def pytest_unconfigure(self, config):
|
||||
capman = config.pluginmanager.getplugin("capturemanager")
|
||||
capman.reset_capturings()
|
||||
plugins = kwargs.setdefault("plugins", [])
|
||||
rc = ResetCapturing()
|
||||
plugins.append(rc)
|
||||
return pytest.main(*args, **kwargs)
|
||||
return py.std.subprocess.Popen(cmdargs,
|
||||
stdout=stdout, stderr=stderr, **kw)
|
||||
|
||||
def run(self, *cmdargs):
|
||||
return self._run(*cmdargs)
|
||||
@@ -600,9 +595,10 @@ class ReportRecorder(object):
|
||||
passed = []
|
||||
skipped = []
|
||||
failed = []
|
||||
for rep in self.getreports("pytest_runtest_logreport"):
|
||||
for rep in self.getreports(
|
||||
"pytest_collectreport pytest_runtest_logreport"):
|
||||
if rep.passed:
|
||||
if rep.when == "call":
|
||||
if getattr(rep, "when", None) == "call":
|
||||
passed.append(rep)
|
||||
elif rep.skipped:
|
||||
skipped.append(rep)
|
||||
|
||||
@@ -4,12 +4,15 @@ import inspect
|
||||
import sys
|
||||
import pytest
|
||||
from _pytest.main import getfslineno
|
||||
from _pytest.mark import MarkDecorator, MarkInfo
|
||||
from _pytest.monkeypatch import monkeypatch
|
||||
from py._code.code import TerminalRepr
|
||||
|
||||
import _pytest
|
||||
cutdir = py.path.local(_pytest.__file__).dirpath()
|
||||
|
||||
NoneType = type(None)
|
||||
|
||||
callable = py.builtin.callable
|
||||
|
||||
def getimfunc(func):
|
||||
@@ -23,14 +26,16 @@ def getimfunc(func):
|
||||
|
||||
|
||||
class FixtureFunctionMarker:
|
||||
def __init__(self, scope, params, autouse=False):
|
||||
def __init__(self, scope, params, autouse=False, yieldctx=False):
|
||||
self.scope = scope
|
||||
self.params = params
|
||||
self.autouse = autouse
|
||||
self.yieldctx = yieldctx
|
||||
|
||||
def __call__(self, function):
|
||||
if inspect.isclass(function):
|
||||
raise ValueError("class fixtures not supported (may be in the future)")
|
||||
raise ValueError(
|
||||
"class fixtures not supported (may be in the future)")
|
||||
function._pytestfixturefunction = self
|
||||
return function
|
||||
|
||||
@@ -56,12 +61,30 @@ def fixture(scope="function", params=None, autouse=False):
|
||||
:arg autouse: if True, the fixture func is activated for all tests that
|
||||
can see it. If False (the default) then an explicit
|
||||
reference is needed to activate the fixture.
|
||||
|
||||
"""
|
||||
if callable(scope) and params is None and autouse == False:
|
||||
# direct decoration
|
||||
return FixtureFunctionMarker("function", params, autouse)(scope)
|
||||
return FixtureFunctionMarker(
|
||||
"function", params, autouse)(scope)
|
||||
else:
|
||||
return FixtureFunctionMarker(scope, params, autouse=autouse)
|
||||
return FixtureFunctionMarker(scope, params, autouse)
|
||||
|
||||
def yield_fixture(scope="function", params=None, autouse=False):
|
||||
""" (return a) decorator to mark a yield-fixture factory function
|
||||
(EXPERIMENTAL).
|
||||
|
||||
This takes the same arguments as :py:func:`pytest.fixture` but
|
||||
expects a fixture function to use a ``yield`` instead of a ``return``
|
||||
statement to provide a fixture. See
|
||||
http://pytest.org/en/latest/yieldfixture.html for more info.
|
||||
"""
|
||||
if callable(scope) and params is None and autouse == False:
|
||||
# direct decoration
|
||||
return FixtureFunctionMarker(
|
||||
"function", params, autouse, yieldctx=True)(scope)
|
||||
else:
|
||||
return FixtureFunctionMarker(scope, params, autouse, yieldctx=True)
|
||||
|
||||
defaultfuncargprefixmarker = fixture()
|
||||
|
||||
@@ -126,6 +149,7 @@ def pytest_namespace():
|
||||
raises.Exception = pytest.fail.Exception
|
||||
return {
|
||||
'fixture': fixture,
|
||||
'yield_fixture': yield_fixture,
|
||||
'raises' : raises,
|
||||
'collect': {
|
||||
'Module': Module, 'Class': Class, 'Instance': Instance,
|
||||
@@ -175,10 +199,10 @@ def pytest_pycollect_makeitem(__multicall__, collector, name, obj):
|
||||
#if hasattr(collector.obj, 'unittest'):
|
||||
# return # we assume it's a mixin class for a TestCase derived one
|
||||
if collector.classnamefilter(name):
|
||||
if not hasinit(obj):
|
||||
Class = collector._getcustomclass("Class")
|
||||
return Class(name, parent=collector)
|
||||
elif collector.funcnamefilter(name) and hasattr(obj, '__call__'):
|
||||
Class = collector._getcustomclass("Class")
|
||||
return Class(name, parent=collector)
|
||||
elif collector.funcnamefilter(name) and hasattr(obj, '__call__') and \
|
||||
getfixturemarker(obj) is None:
|
||||
if is_generator(obj):
|
||||
return Generator(name, parent=collector)
|
||||
else:
|
||||
@@ -277,13 +301,12 @@ class PyCollector(PyobjMixin, pytest.Collector):
|
||||
if name in seen:
|
||||
continue
|
||||
seen[name] = True
|
||||
if name[0] != "_":
|
||||
res = self.makeitem(name, obj)
|
||||
if res is None:
|
||||
continue
|
||||
if not isinstance(res, list):
|
||||
res = [res]
|
||||
l.extend(res)
|
||||
res = self.makeitem(name, obj)
|
||||
if res is None:
|
||||
continue
|
||||
if not isinstance(res, list):
|
||||
res = [res]
|
||||
l.extend(res)
|
||||
l.sort(key=lambda item: item.reportinfo()[:2])
|
||||
return l
|
||||
|
||||
@@ -371,7 +394,9 @@ class Module(pytest.File, PyCollector):
|
||||
return mod
|
||||
|
||||
def setup(self):
|
||||
setup_module = xunitsetup(self.obj, "setup_module")
|
||||
setup_module = xunitsetup(self.obj, "setUpModule")
|
||||
if setup_module is None:
|
||||
setup_module = xunitsetup(self.obj, "setup_module")
|
||||
if setup_module is not None:
|
||||
#XXX: nose compat hack, move to nose plugin
|
||||
# if it takes a positional arg, its probably a pytest style one
|
||||
@@ -380,21 +405,28 @@ class Module(pytest.File, PyCollector):
|
||||
setup_module(self.obj)
|
||||
else:
|
||||
setup_module()
|
||||
|
||||
def teardown(self):
|
||||
teardown_module = xunitsetup(self.obj, 'teardown_module')
|
||||
if teardown_module is not None:
|
||||
fin = getattr(self.obj, 'tearDownModule', None)
|
||||
if fin is None:
|
||||
fin = getattr(self.obj, 'teardown_module', None)
|
||||
if fin is not None:
|
||||
#XXX: nose compat hack, move to nose plugin
|
||||
# if it takes a positional arg, its probably a py.test style one
|
||||
# so we pass the current module object
|
||||
if inspect.getargspec(teardown_module)[0]:
|
||||
teardown_module(self.obj)
|
||||
if inspect.getargspec(fin)[0]:
|
||||
finalizer = lambda: fin(self.obj)
|
||||
else:
|
||||
teardown_module()
|
||||
finalizer = fin
|
||||
self.addfinalizer(finalizer)
|
||||
|
||||
|
||||
class Class(PyCollector):
|
||||
""" Collector for test methods. """
|
||||
def collect(self):
|
||||
if hasinit(self.obj):
|
||||
pytest.skip("class %s.%s with __init__ won't get collected" % (
|
||||
self.obj.__module__,
|
||||
self.obj.__name__,
|
||||
))
|
||||
return [self._getcustomclass("Instance")(name="()", parent=self)]
|
||||
|
||||
def setup(self):
|
||||
@@ -404,12 +436,11 @@ class Class(PyCollector):
|
||||
setup_class = getattr(setup_class, '__func__', setup_class)
|
||||
setup_class(self.obj)
|
||||
|
||||
def teardown(self):
|
||||
teardown_class = xunitsetup(self.obj, 'teardown_class')
|
||||
if teardown_class is not None:
|
||||
teardown_class = getattr(teardown_class, 'im_func', teardown_class)
|
||||
teardown_class = getattr(teardown_class, '__func__', teardown_class)
|
||||
teardown_class(self.obj)
|
||||
fin_class = getattr(self.obj, 'teardown_class', None)
|
||||
if fin_class is not None:
|
||||
fin_class = getattr(fin_class, 'im_func', fin_class)
|
||||
fin_class = getattr(fin_class, '__func__', fin_class)
|
||||
self.addfinalizer(lambda: fin_class(self.obj))
|
||||
|
||||
class Instance(PyCollector):
|
||||
def _getobj(self):
|
||||
@@ -438,23 +469,17 @@ class FunctionMixin(PyobjMixin):
|
||||
else:
|
||||
obj = self.parent.obj
|
||||
if inspect.ismethod(self.obj):
|
||||
name = 'setup_method'
|
||||
setup_name = 'setup_method'
|
||||
teardown_name = 'teardown_method'
|
||||
else:
|
||||
name = 'setup_function'
|
||||
setup_func_or_method = xunitsetup(obj, name)
|
||||
setup_name = 'setup_function'
|
||||
teardown_name = 'teardown_function'
|
||||
setup_func_or_method = xunitsetup(obj, setup_name)
|
||||
if setup_func_or_method is not None:
|
||||
setup_func_or_method(self.obj)
|
||||
|
||||
def teardown(self):
|
||||
""" perform teardown for this test function. """
|
||||
if inspect.ismethod(self.obj):
|
||||
name = 'teardown_method'
|
||||
else:
|
||||
name = 'teardown_function'
|
||||
obj = self.parent.obj
|
||||
teardown_func_or_meth = xunitsetup(obj, name)
|
||||
if teardown_func_or_meth is not None:
|
||||
teardown_func_or_meth(self.obj)
|
||||
fin = getattr(obj, teardown_name, None)
|
||||
if fin is not None:
|
||||
self.addfinalizer(lambda: fin(self.obj))
|
||||
|
||||
def _prunetraceback(self, excinfo):
|
||||
if hasattr(self, '_obj') and not self.config.option.fulltrace:
|
||||
@@ -528,7 +553,7 @@ def hasinit(obj):
|
||||
|
||||
def fillfixtures(function):
|
||||
""" fill missing funcargs for a test function. """
|
||||
if getattr(function, "_args", None) is None: # not a yielded function
|
||||
if 1 or getattr(function, "_args", None) is None: # not a yielded function
|
||||
try:
|
||||
request = function._request
|
||||
except AttributeError:
|
||||
@@ -561,11 +586,13 @@ class CallSpec2(object):
|
||||
self._globalid_args = set()
|
||||
self._globalparam = _notexists
|
||||
self._arg2scopenum = {} # used for sorting parametrized resources
|
||||
self.keywords = {}
|
||||
|
||||
def copy(self, metafunc):
|
||||
cs = CallSpec2(self.metafunc)
|
||||
cs.funcargs.update(self.funcargs)
|
||||
cs.params.update(self.params)
|
||||
cs.keywords.update(self.keywords)
|
||||
cs._arg2scopenum.update(self._arg2scopenum)
|
||||
cs._idlist = list(self._idlist)
|
||||
cs._globalid = self._globalid
|
||||
@@ -589,7 +616,7 @@ class CallSpec2(object):
|
||||
def id(self):
|
||||
return "-".join(map(str, filter(None, self._idlist)))
|
||||
|
||||
def setmulti(self, valtype, argnames, valset, id, scopenum=0):
|
||||
def setmulti(self, valtype, argnames, valset, id, keywords, scopenum=0):
|
||||
for arg,val in zip(argnames, valset):
|
||||
self._checkargnotcontained(arg)
|
||||
getattr(self, valtype)[arg] = val
|
||||
@@ -601,6 +628,7 @@ class CallSpec2(object):
|
||||
if val is _notexists:
|
||||
self._emptyparamspecified = True
|
||||
self._idlist.append(id)
|
||||
self.keywords.update(keywords)
|
||||
|
||||
def setall(self, funcargs, id, param):
|
||||
for x in funcargs:
|
||||
@@ -641,10 +669,15 @@ class Metafunc(FuncargnamesCompatAttr):
|
||||
during the collection phase. If you need to setup expensive resources
|
||||
see about setting indirect=True to do it rather at test setup time.
|
||||
|
||||
:arg argnames: an argument name or a list of argument names
|
||||
:arg argnames: a comma-separated string denoting one or more argument
|
||||
names, or a list/tuple of argument strings.
|
||||
|
||||
:arg argvalues: a list of values for the argname or a list of tuples of
|
||||
values for the list of argument names.
|
||||
:arg argvalues: The list of argvalues determines how often a
|
||||
test is invoked with different argument values. If only one
|
||||
argname was specified argvalues is a list of simple values. If N
|
||||
argnames were specified, argvalues must be a list of N-tuples,
|
||||
where each tuple-element specifies a value for its respective
|
||||
argname.
|
||||
|
||||
:arg indirect: if True each argvalue corresponding to an argname will
|
||||
be passed as request.param to its respective argname fixture
|
||||
@@ -660,8 +693,24 @@ class Metafunc(FuncargnamesCompatAttr):
|
||||
It will also override any fixture-function defined scope, allowing
|
||||
to set a dynamic scope using test context or configuration.
|
||||
"""
|
||||
|
||||
# individual parametrized argument sets can be wrapped in a
|
||||
# marker in which case we unwrap the values and apply the mark
|
||||
# at Function init
|
||||
newkeywords = {}
|
||||
unwrapped_argvalues = []
|
||||
for i, argval in enumerate(argvalues):
|
||||
if isinstance(argval, MarkDecorator):
|
||||
newmark = MarkDecorator(argval.markname,
|
||||
argval.args[:-1], argval.kwargs)
|
||||
newkeywords[i] = {newmark.markname: newmark}
|
||||
argval = argval.args[-1]
|
||||
unwrapped_argvalues.append(argval)
|
||||
argvalues = unwrapped_argvalues
|
||||
|
||||
if not isinstance(argnames, (tuple, list)):
|
||||
argnames = (argnames,)
|
||||
argnames = [x.strip() for x in argnames.split(",") if x.strip()]
|
||||
if len(argnames) == 1:
|
||||
argvalues = [(val,) for val in argvalues]
|
||||
if not argvalues:
|
||||
argvalues = [(_notexists,) * len(argnames)]
|
||||
@@ -676,6 +725,9 @@ class Metafunc(FuncargnamesCompatAttr):
|
||||
raise ValueError("%r uses no fixture %r" %(
|
||||
self.function, arg))
|
||||
valtype = indirect and "params" or "funcargs"
|
||||
if ids and len(ids) != len(argvalues):
|
||||
raise ValueError('%d tests specified with %d ids' %(
|
||||
len(argvalues), len(ids)))
|
||||
if not ids:
|
||||
ids = idmaker(argnames, argvalues)
|
||||
newcalls = []
|
||||
@@ -684,7 +736,7 @@ class Metafunc(FuncargnamesCompatAttr):
|
||||
assert len(valset) == len(argnames)
|
||||
newcallspec = callspec.copy(self)
|
||||
newcallspec.setmulti(valtype, argnames, valset, ids[i],
|
||||
scopenum)
|
||||
newkeywords.get(i, {}), scopenum)
|
||||
newcalls.append(newcallspec)
|
||||
self._calls = newcalls
|
||||
|
||||
@@ -729,8 +781,8 @@ def idmaker(argnames, argvalues):
|
||||
for valindex, valset in enumerate(argvalues):
|
||||
this_id = []
|
||||
for nameindex, val in enumerate(valset):
|
||||
if not isinstance(val, (float, int, str)):
|
||||
this_id.append(argnames[nameindex]+str(valindex))
|
||||
if not isinstance(val, (float, int, str, bool, NoneType)):
|
||||
this_id.append(str(argnames[nameindex])+str(valindex))
|
||||
else:
|
||||
this_id.append(str(val))
|
||||
idlist.append("-".join(this_id))
|
||||
@@ -806,6 +858,8 @@ def raises(ExpectedException, *args, **kwargs):
|
||||
""" assert that a code block/function call raises @ExpectedException
|
||||
and raise a failure exception otherwise.
|
||||
|
||||
This helper produces a ``py.code.ExceptionInfo()`` object.
|
||||
|
||||
If using Python 2.5 or above, you may use this function as a
|
||||
context manager::
|
||||
|
||||
@@ -901,28 +955,38 @@ class Function(FunctionMixin, pytest.Item, FuncargnamesCompatAttr):
|
||||
|
||||
for name, val in (py.builtin._getfuncdict(self.obj) or {}).items():
|
||||
self.keywords[name] = val
|
||||
if callspec:
|
||||
for name, val in callspec.keywords.items():
|
||||
self.keywords[name] = val
|
||||
if keywords:
|
||||
for name, val in keywords.items():
|
||||
self.keywords[name] = val
|
||||
|
||||
fm = self.session._fixturemanager
|
||||
self._fixtureinfo = fi = fm.getfixtureinfo(self.parent,
|
||||
self.obj, self.cls)
|
||||
isyield = self._isyieldedfunction()
|
||||
self._fixtureinfo = fi = fm.getfixtureinfo(self.parent, self.obj,
|
||||
self.cls,
|
||||
funcargs=not isyield)
|
||||
self.fixturenames = fi.names_closure
|
||||
if callspec is not None:
|
||||
self.callspec = callspec
|
||||
self._initrequest()
|
||||
|
||||
def _initrequest(self):
|
||||
if self._isyieldedfunction():
|
||||
assert not callspec, (
|
||||
assert not hasattr(self, "callspec"), (
|
||||
"yielded functions (deprecated) cannot have funcargs")
|
||||
self.funcargs = {}
|
||||
else:
|
||||
if callspec is not None:
|
||||
self.callspec = callspec
|
||||
self.funcargs = callspec.funcargs or {}
|
||||
if hasattr(self, "callspec"):
|
||||
callspec = self.callspec
|
||||
self.funcargs = callspec.funcargs.copy()
|
||||
self._genid = callspec.id
|
||||
if hasattr(callspec, "param"):
|
||||
self.param = callspec.param
|
||||
else:
|
||||
self.funcargs = {}
|
||||
self._request = req = FixtureRequest(self)
|
||||
#req._discoverfactories()
|
||||
self._request = FixtureRequest(self)
|
||||
|
||||
@property
|
||||
def function(self):
|
||||
@@ -1197,8 +1261,10 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||
self._fixturestack.pop()
|
||||
|
||||
def _getfuncargvalue(self, fixturedef):
|
||||
if fixturedef.active:
|
||||
return fixturedef.cached_result
|
||||
try:
|
||||
return fixturedef.cached_result # set by fixturedef.execute()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
# prepare request fixturename and param attributes before
|
||||
# calling into fixture function
|
||||
@@ -1224,6 +1290,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||
if paramscopenum != scopenum_subfunction:
|
||||
scope = scopes[paramscopenum]
|
||||
|
||||
# check if a higher-level scoped fixture accesses a lower level one
|
||||
if scope is not None:
|
||||
__tracebackhide__ = True
|
||||
if scopemismatch(self.scope, scope):
|
||||
@@ -1236,15 +1303,24 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||
__tracebackhide__ = False
|
||||
mp.setattr(self, "scope", scope)
|
||||
|
||||
# prepare finalization according to scope
|
||||
# (XXX analyse exact finalizing mechanics / cleanup)
|
||||
self.session._setupstate.addfinalizer(fixturedef.finish, self.node)
|
||||
self._fixturemanager.addargfinalizer(fixturedef.finish, argname)
|
||||
for subargname in fixturedef.argnames: # XXX all deps?
|
||||
self._fixturemanager.addargfinalizer(fixturedef.finish, subargname)
|
||||
# route request.addfinalizer to fixturedef
|
||||
mp.setattr(self, "addfinalizer", fixturedef.addfinalizer)
|
||||
# finally perform the fixture call
|
||||
val = fixturedef.execute(request=self)
|
||||
|
||||
try:
|
||||
# perform the fixture call
|
||||
val = fixturedef.execute(request=self)
|
||||
finally:
|
||||
# if the fixture function failed it might still have
|
||||
# registered finalizers so we can register
|
||||
|
||||
# prepare finalization according to scope
|
||||
# (XXX analyse exact finalizing mechanics / cleanup)
|
||||
self.session._setupstate.addfinalizer(fixturedef.finish,
|
||||
self.node)
|
||||
self._fixturemanager.addargfinalizer(fixturedef.finish, argname)
|
||||
for subargname in fixturedef.argnames: # XXX all deps?
|
||||
self._fixturemanager.addargfinalizer(fixturedef.finish,
|
||||
subargname)
|
||||
mp.undo()
|
||||
return val
|
||||
|
||||
@@ -1268,7 +1344,8 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||
x = self._pyfuncitem.getparent(pytest.Class)
|
||||
if x is not None:
|
||||
return x
|
||||
scope = "module"
|
||||
# fallback to function
|
||||
return self._pyfuncitem
|
||||
if scope == "module":
|
||||
return self._pyfuncitem.getparent(pytest.Module)
|
||||
raise ValueError("unknown finalization scope %r" %(scope,))
|
||||
@@ -1392,13 +1469,13 @@ class FixtureManager:
|
||||
|
||||
self._nodename2fixtureinfo = {}
|
||||
|
||||
def getfixtureinfo(self, node, func, cls):
|
||||
def getfixtureinfo(self, node, func, cls, funcargs=True):
|
||||
key = (node, func.__name__)
|
||||
try:
|
||||
return self._nodename2fixtureinfo[key]
|
||||
except KeyError:
|
||||
pass
|
||||
if not hasattr(node, "nofuncargs"):
|
||||
if funcargs and not hasattr(node, "nofuncargs"):
|
||||
if cls is not None:
|
||||
startindex = 1
|
||||
else:
|
||||
@@ -1423,8 +1500,7 @@ class FixtureManager:
|
||||
def pytest_plugin_registered(self, plugin):
|
||||
if plugin in self._seenplugins:
|
||||
return
|
||||
#print "plugin_registered", plugin
|
||||
nodeid = ""
|
||||
nodeid = None
|
||||
try:
|
||||
p = py.path.local(plugin.__file__)
|
||||
except AttributeError:
|
||||
@@ -1449,7 +1525,7 @@ class FixtureManager:
|
||||
for baseid, basenames in self._nodeid_and_autousenames:
|
||||
if nodeid.startswith(baseid):
|
||||
if baseid:
|
||||
i = len(baseid) + 1
|
||||
i = len(baseid)
|
||||
nextchar = nodeid[i:i+1]
|
||||
if nextchar and nextchar not in ":/":
|
||||
continue
|
||||
@@ -1503,6 +1579,8 @@ class FixtureManager:
|
||||
items[:] = parametrize_sorted(items, set(), {}, 0)
|
||||
|
||||
def pytest_runtest_teardown(self, item, nextitem):
|
||||
# XXX teardown needs to be normalized for parametrized and
|
||||
# no-parametrized functions
|
||||
try:
|
||||
cs1 = item.callspec
|
||||
except AttributeError:
|
||||
@@ -1524,13 +1602,13 @@ class FixtureManager:
|
||||
keylist.sort()
|
||||
for (scopenum, name, param) in keylist:
|
||||
item.session._setupstate._callfinalizers((name, param))
|
||||
l = self._arg2finish.get(name)
|
||||
l = self._arg2finish.pop(name, None)
|
||||
if l is not None:
|
||||
for fin in reversed(l):
|
||||
fin()
|
||||
|
||||
def parsefactories(self, node_or_obj, nodeid=None, unittest=False):
|
||||
if nodeid is not None:
|
||||
def parsefactories(self, node_or_obj, nodeid=_dummy, unittest=False):
|
||||
if nodeid is not _dummy:
|
||||
holderobj = node_or_obj
|
||||
else:
|
||||
holderobj = node_or_obj.obj
|
||||
@@ -1545,15 +1623,7 @@ class FixtureManager:
|
||||
continue
|
||||
# fixture functions have a pytest_funcarg__ prefix (pre-2.3 style)
|
||||
# or are "@pytest.fixture" marked
|
||||
try:
|
||||
marker = obj._pytestfixturefunction
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except Exception:
|
||||
# some objects raise errors like request (from flask import request)
|
||||
# we don't expect them to be fixture functions
|
||||
marker = None
|
||||
|
||||
marker = getfixturemarker(obj)
|
||||
if marker is None:
|
||||
if not name.startswith(self._argprefix):
|
||||
continue
|
||||
@@ -1567,13 +1637,22 @@ class FixtureManager:
|
||||
assert not name.startswith(self._argprefix)
|
||||
fixturedef = FixtureDef(self, nodeid, name, obj,
|
||||
marker.scope, marker.params,
|
||||
yieldctx=marker.yieldctx,
|
||||
unittest=unittest)
|
||||
faclist = self._arg2fixturedefs.setdefault(name, [])
|
||||
faclist.append(fixturedef)
|
||||
if not fixturedef.has_location:
|
||||
# All fixturedefs with no location are at the front
|
||||
# so this inserts the current fixturedef after the
|
||||
# existing fixturedefs from external plugins but
|
||||
# before the fixturedefs provided in conftests.
|
||||
i = len([f for f in faclist if not f.has_location])
|
||||
else:
|
||||
i = len(faclist) # append
|
||||
faclist.insert(i, fixturedef)
|
||||
if marker.autouse:
|
||||
autousenames.append(name)
|
||||
if autousenames:
|
||||
self._nodeid_and_autousenames.append((nodeid, autousenames))
|
||||
self._nodeid_and_autousenames.append((nodeid or '', autousenames))
|
||||
|
||||
def getfixturedefs(self, argname, nodeid):
|
||||
try:
|
||||
@@ -1599,12 +1678,48 @@ class FixtureManager:
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def fail_fixturefunc(fixturefunc, msg):
|
||||
fs, lineno = getfslineno(fixturefunc)
|
||||
location = "%s:%s" % (fs, lineno+1)
|
||||
source = py.code.Source(fixturefunc)
|
||||
pytest.fail(msg + ":\n\n" + str(source.indent()) + "\n" + location,
|
||||
pytrace=False)
|
||||
|
||||
def call_fixture_func(fixturefunc, request, kwargs, yieldctx):
|
||||
if yieldctx:
|
||||
if not is_generator(fixturefunc):
|
||||
fail_fixturefunc(fixturefunc,
|
||||
msg="yield_fixture requires yield statement in function")
|
||||
iter = fixturefunc(**kwargs)
|
||||
next = getattr(iter, "__next__", None)
|
||||
if next is None:
|
||||
next = getattr(iter, "next")
|
||||
res = next()
|
||||
def teardown():
|
||||
try:
|
||||
next()
|
||||
except StopIteration:
|
||||
pass
|
||||
else:
|
||||
fail_fixturefunc(fixturefunc,
|
||||
"yield_fixture function has more than one 'yield'")
|
||||
request.addfinalizer(teardown)
|
||||
else:
|
||||
if is_generator(fixturefunc):
|
||||
fail_fixturefunc(fixturefunc,
|
||||
msg="pytest.fixture functions cannot use ``yield``. "
|
||||
"Instead write and return an inner function/generator "
|
||||
"and let the consumer call and iterate over it.")
|
||||
res = fixturefunc(**kwargs)
|
||||
return res
|
||||
|
||||
class FixtureDef:
|
||||
""" A container for a factory definition. """
|
||||
def __init__(self, fixturenanager, baseid, argname, func, scope, params,
|
||||
unittest=False):
|
||||
self._fixturemanager = fixturenanager
|
||||
self.baseid = baseid
|
||||
def __init__(self, fixturemanager, baseid, argname, func, scope, params,
|
||||
yieldctx, unittest=False):
|
||||
self._fixturemanager = fixturemanager
|
||||
self.baseid = baseid or ''
|
||||
self.has_location = baseid is not None
|
||||
self.func = func
|
||||
self.argname = argname
|
||||
self.scope = scope
|
||||
@@ -1612,8 +1727,8 @@ class FixtureDef:
|
||||
self.params = params
|
||||
startindex = unittest and 1 or None
|
||||
self.argnames = getfuncargnames(func, startindex=startindex)
|
||||
self.yieldctx = yieldctx
|
||||
self.unittest = unittest
|
||||
self.active = False
|
||||
self._finalizer = []
|
||||
|
||||
def addfinalizer(self, finalizer):
|
||||
@@ -1625,9 +1740,11 @@ class FixtureDef:
|
||||
func()
|
||||
# check neccesity of next commented call
|
||||
self._fixturemanager.removefinalizer(self.finish)
|
||||
self.active = False
|
||||
#print "finished", self
|
||||
#del self.cached_result
|
||||
try:
|
||||
del self.cached_result
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def execute(self, request):
|
||||
kwargs = {}
|
||||
@@ -1648,13 +1765,15 @@ class FixtureDef:
|
||||
fixturefunc = fixturefunc.__get__(request.instance)
|
||||
except AttributeError:
|
||||
pass
|
||||
result = fixturefunc(**kwargs)
|
||||
self.active = True
|
||||
result = call_fixture_func(fixturefunc, request, kwargs,
|
||||
self.yieldctx)
|
||||
assert not hasattr(self, "cached_result")
|
||||
self.cached_result = result
|
||||
return result
|
||||
|
||||
def __repr__(self):
|
||||
return "<FixtureDef name=%r scope=%r>" % (self.argname, self.scope)
|
||||
return ("<FixtureDef name=%r scope=%r baseid=%r module=%r>" %
|
||||
(self.argname, self.scope, self.baseid, self.func.__module__))
|
||||
|
||||
def getfuncargnames(function, startindex=None):
|
||||
# XXX merge with main.py's varnames
|
||||
@@ -1680,29 +1799,39 @@ def getfuncargnames(function, startindex=None):
|
||||
def parametrize_sorted(items, ignore, cache, scopenum):
|
||||
if scopenum >= 3:
|
||||
return items
|
||||
newitems = []
|
||||
olditems = []
|
||||
|
||||
# we pick the first item which has a arg/param combo in the
|
||||
# requested scope and sort other items with the same combo
|
||||
# into "newitems" which then is a list of all items using this
|
||||
# arg/param.
|
||||
|
||||
similar_items = []
|
||||
other_items = []
|
||||
slicing_argparam = None
|
||||
slicing_index = 0
|
||||
for item in items:
|
||||
argparamlist = getfuncargparams(item, ignore, scopenum, cache)
|
||||
if slicing_argparam is None and argparamlist:
|
||||
slicing_argparam = argparamlist[0]
|
||||
slicing_index = len(olditems)
|
||||
slicing_index = len(other_items)
|
||||
if slicing_argparam in argparamlist:
|
||||
newitems.append(item)
|
||||
similar_items.append(item)
|
||||
else:
|
||||
olditems.append(item)
|
||||
if newitems:
|
||||
other_items.append(item)
|
||||
|
||||
if (len(similar_items) + slicing_index) > 1:
|
||||
newignore = ignore.copy()
|
||||
newignore.add(slicing_argparam)
|
||||
newitems = parametrize_sorted(newitems + olditems[slicing_index:],
|
||||
newignore, cache, scopenum)
|
||||
old1 = parametrize_sorted(olditems[:slicing_index], newignore,
|
||||
cache, scopenum+1)
|
||||
return old1 + newitems
|
||||
part2 = parametrize_sorted(
|
||||
similar_items + other_items[slicing_index:],
|
||||
newignore, cache, scopenum)
|
||||
part1 = parametrize_sorted(
|
||||
other_items[:slicing_index], newignore,
|
||||
cache, scopenum+1)
|
||||
return part1 + part2
|
||||
else:
|
||||
olditems = parametrize_sorted(olditems, ignore, cache, scopenum+1)
|
||||
return olditems + newitems
|
||||
other_items = parametrize_sorted(other_items, ignore, cache, scopenum+1)
|
||||
return other_items + similar_items
|
||||
|
||||
def getfuncargparams(item, ignore, scopenum, cache):
|
||||
""" return list of (arg,param) tuple, sorted by broader scope first. """
|
||||
@@ -1749,6 +1878,18 @@ def getfuncargparams(item, ignore, scopenum, cache):
|
||||
|
||||
def xunitsetup(obj, name):
|
||||
meth = getattr(obj, name, None)
|
||||
if meth is not None:
|
||||
if not hasattr(meth, "_pytestfixturefunction"):
|
||||
return meth
|
||||
if getfixturemarker(meth) is None:
|
||||
return meth
|
||||
|
||||
def getfixturemarker(obj):
|
||||
""" return fixturemarker or None if it doesn't exist or raised
|
||||
exceptions."""
|
||||
try:
|
||||
return getattr(obj, "_pytestfixturefunction", None)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except Exception:
|
||||
# some objects raise errors like request (from flask import request)
|
||||
# we don't expect them to be fixture functions
|
||||
return None
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import py
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("terminal reporting", "resultlog plugin options")
|
||||
group.addoption('--resultlog', action="store", dest="resultlog",
|
||||
group.addoption('--resultlog', '--result-log', action="store",
|
||||
metavar="path", default=None,
|
||||
help="path for machine-readable result log.")
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ def pytest_namespace():
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("terminal reporting", "reporting", after="general")
|
||||
group.addoption('--durations',
|
||||
action="store", type="int", default=None, metavar="N",
|
||||
action="store", type=int, default=None, metavar="N",
|
||||
help="show N slowest setup/test durations (N=0 for all)."),
|
||||
|
||||
def pytest_terminal_summary(terminalreporter):
|
||||
@@ -63,12 +63,20 @@ def pytest_runtest_protocol(item, nextitem):
|
||||
return True
|
||||
|
||||
def runtestprotocol(item, log=True, nextitem=None):
|
||||
hasrequest = hasattr(item, "_request")
|
||||
if hasrequest and not item._request:
|
||||
item._initrequest()
|
||||
rep = call_and_report(item, "setup", log)
|
||||
reports = [rep]
|
||||
if rep.passed:
|
||||
reports.append(call_and_report(item, "call", log))
|
||||
reports.append(call_and_report(item, "teardown", log,
|
||||
nextitem=nextitem))
|
||||
# after all teardown hooks have been called
|
||||
# want funcargs and request info to go away
|
||||
if hasrequest:
|
||||
item._request = False
|
||||
item.funcargs = None
|
||||
return reports
|
||||
|
||||
def pytest_runtest_setup(item):
|
||||
@@ -100,8 +108,16 @@ def call_and_report(item, when, log=True, **kwds):
|
||||
report = hook.pytest_runtest_makereport(item=item, call=call)
|
||||
if log:
|
||||
hook.pytest_runtest_logreport(report=report)
|
||||
if check_interactive_exception(call, report):
|
||||
hook.pytest_exception_interact(node=item, call=call, report=report)
|
||||
return report
|
||||
|
||||
def check_interactive_exception(call, report):
|
||||
return call.excinfo and not (
|
||||
hasattr(report, "wasxfail") or
|
||||
call.excinfo.errisinstance(skip.Exception) or
|
||||
call.excinfo.errisinstance(py.std.bdb.BdbQuit))
|
||||
|
||||
def call_runtest_hook(item, when, **kwds):
|
||||
hookname = "pytest_runtest_" + when
|
||||
ihook = getattr(item.ihook, hookname)
|
||||
@@ -190,7 +206,8 @@ def pytest_runtest_makereport(item, call):
|
||||
if call.when == "call":
|
||||
longrepr = item.repr_failure(excinfo)
|
||||
else: # exception in setup or teardown
|
||||
longrepr = item._repr_failure_py(excinfo)
|
||||
longrepr = item._repr_failure_py(excinfo,
|
||||
style=item.config.option.tbstyle)
|
||||
return TestReport(item.nodeid, item.location,
|
||||
keywords, outcome, longrepr, when,
|
||||
duration=duration)
|
||||
@@ -249,7 +266,7 @@ def pytest_make_collect_report(collector):
|
||||
if not call.excinfo:
|
||||
outcome = "passed"
|
||||
else:
|
||||
if call.excinfo.errisinstance(py.test.skip.Exception):
|
||||
if call.excinfo.errisinstance(collector.skip_exceptions):
|
||||
outcome = "skipped"
|
||||
r = collector._repr_failure_py(call.excinfo, "line").reprcrash
|
||||
longrepr = (str(r.path), r.lineno, r.message)
|
||||
@@ -259,8 +276,11 @@ def pytest_make_collect_report(collector):
|
||||
if not hasattr(errorinfo, "toterminal"):
|
||||
errorinfo = CollectErrorRepr(errorinfo)
|
||||
longrepr = errorinfo
|
||||
return CollectReport(collector.nodeid, outcome, longrepr,
|
||||
rep = CollectReport(collector.nodeid, outcome, longrepr,
|
||||
getattr(call, 'result', None))
|
||||
rep.call = call # see collect_one_node
|
||||
return rep
|
||||
|
||||
|
||||
class CollectReport(BaseReport):
|
||||
def __init__(self, nodeid, outcome, longrepr, result, sections=(), **extra):
|
||||
@@ -355,6 +375,16 @@ class SetupState(object):
|
||||
col._prepare_exc = sys.exc_info()
|
||||
raise
|
||||
|
||||
def collect_one_node(collector):
|
||||
ihook = collector.ihook
|
||||
ihook.pytest_collectstart(collector=collector)
|
||||
rep = ihook.pytest_make_collect_report(collector=collector)
|
||||
call = rep.__dict__.pop("call", None)
|
||||
if call and check_interactive_exception(call, rep):
|
||||
ihook.pytest_exception_interact(node=collector, call=call, report=rep)
|
||||
return rep
|
||||
|
||||
|
||||
# =============================================================
|
||||
# Test OutcomeExceptions and helpers for creating them.
|
||||
|
||||
@@ -364,6 +394,7 @@ class OutcomeException(Exception):
|
||||
contain info about test and collection outcomes.
|
||||
"""
|
||||
def __init__(self, msg=None, pytrace=True):
|
||||
Exception.__init__(self, msg)
|
||||
self.msg = msg
|
||||
self.pytrace = pytrace
|
||||
|
||||
|
||||
@@ -86,10 +86,14 @@ class MarkEvaluator:
|
||||
self.result = False
|
||||
for expr in self.holder.args:
|
||||
self.expr = expr
|
||||
if isinstance(expr, str):
|
||||
if isinstance(expr, py.builtin._basestring):
|
||||
result = cached_eval(self.item.config, expr, d)
|
||||
else:
|
||||
pytest.fail("expression is not a string")
|
||||
if self.get("reason") is None:
|
||||
# XXX better be checked at collection time
|
||||
pytest.fail("you need to specify reason=STRING "
|
||||
"when using booleans as conditions.")
|
||||
result = bool(expr)
|
||||
if result:
|
||||
self.result = True
|
||||
self.expr = expr
|
||||
|
||||
@@ -13,6 +13,10 @@ class DictImporter(object):
|
||||
self.sources = sources
|
||||
|
||||
def find_module(self, fullname, path=None):
|
||||
if fullname == "argparse" and sys.version_info >= (2,7):
|
||||
# we were generated with <python2.7 (which pulls in argparse)
|
||||
# but we are running now on a stdlib which has it, so use that.
|
||||
return None
|
||||
if fullname in self.sources:
|
||||
return self
|
||||
if fullname + '.__init__' in self.sources:
|
||||
@@ -57,7 +61,7 @@ if __name__ == "__main__":
|
||||
sources = pickle.loads(zlib.decompress(base64.decodestring(sources)))
|
||||
|
||||
importer = DictImporter(sources)
|
||||
sys.meta_path.append(importer)
|
||||
sys.meta_path.insert(0, importer)
|
||||
|
||||
entry = "@ENTRY@"
|
||||
do_exec(entry, locals())
|
||||
|
||||
@@ -25,18 +25,19 @@ def pytest_addoption(parser):
|
||||
help="(deprecated, use -r)")
|
||||
group._addoption('--tb', metavar="style",
|
||||
action="store", dest="tbstyle", default='long',
|
||||
type="choice", choices=['long', 'short', 'no', 'line', 'native'],
|
||||
choices=['long', 'short', 'no', 'line', 'native'],
|
||||
help="traceback print mode (long/short/line/native/no).")
|
||||
group._addoption('--fulltrace',
|
||||
action="store_true", dest="fulltrace", default=False,
|
||||
group._addoption('--fulltrace', '--full-trace',
|
||||
action="store_true", default=False,
|
||||
help="don't cut any tracebacks (default is to cut).")
|
||||
|
||||
def pytest_configure(config):
|
||||
config.option.verbose -= config.option.quiet
|
||||
# we try hard to make printing resilient against
|
||||
# later changes on FD level.
|
||||
# later changes on FD level. (unless capturing is turned off)
|
||||
stdout = py.std.sys.stdout
|
||||
if hasattr(os, 'dup') and hasattr(stdout, 'fileno'):
|
||||
capture = config.option.capture != "no"
|
||||
if capture and hasattr(os, 'dup') and hasattr(stdout, 'fileno'):
|
||||
try:
|
||||
newstdout = py.io.dupfile(stdout, buffering=1,
|
||||
encoding=stdout.encoding)
|
||||
@@ -44,7 +45,7 @@ def pytest_configure(config):
|
||||
pass
|
||||
else:
|
||||
config._cleanup.append(lambda: newstdout.close())
|
||||
assert stdout.encoding == newstdout.encoding
|
||||
assert stdout.encoding == newstdout.encoding
|
||||
stdout = newstdout
|
||||
|
||||
reporter = TerminalReporter(config, stdout)
|
||||
@@ -99,13 +100,13 @@ class TerminalReporter:
|
||||
self.startdir = self.curdir = py.path.local()
|
||||
if file is None:
|
||||
file = py.std.sys.stdout
|
||||
self._tw = py.io.TerminalWriter(file)
|
||||
self._tw = self.writer = py.io.TerminalWriter(file)
|
||||
self.currentfspath = None
|
||||
self.reportchars = getreportopt(config)
|
||||
self.hasmarkup = self._tw.hasmarkup
|
||||
|
||||
def hasopt(self, char):
|
||||
char = {'xfailed': 'x', 'skipped': 's'}.get(char,char)
|
||||
char = {'xfailed': 'x', 'skipped': 's'}.get(char, char)
|
||||
return char in self.reportchars
|
||||
|
||||
def write_fspath_result(self, fspath, res):
|
||||
@@ -147,6 +148,12 @@ class TerminalReporter:
|
||||
self.ensure_newline()
|
||||
self._tw.sep(sep, title, **markup)
|
||||
|
||||
def section(self, title, sep="=", **kw):
|
||||
self._tw.sep(sep, title, **kw)
|
||||
|
||||
def line(self, msg, **kw):
|
||||
self._tw.line(msg, **kw)
|
||||
|
||||
def pytest_internalerror(self, excrepr):
|
||||
for line in str(excrepr).split("\n"):
|
||||
self.write_line("INTERNALERROR> " + line)
|
||||
@@ -154,7 +161,7 @@ class TerminalReporter:
|
||||
|
||||
def pytest_plugin_registered(self, plugin):
|
||||
if self.config.option.traceconfig:
|
||||
msg = "PLUGIN registered: %s" %(plugin,)
|
||||
msg = "PLUGIN registered: %s" % (plugin,)
|
||||
# XXX this event may happen during setup/teardown time
|
||||
# which unfortunately captures our output here
|
||||
# which garbles our output if we use self.write_line
|
||||
@@ -178,6 +185,7 @@ class TerminalReporter:
|
||||
res = self.config.hook.pytest_report_teststatus(report=rep)
|
||||
cat, letter, word = res
|
||||
self.stats.setdefault(cat, []).append(rep)
|
||||
self._tests_ran = True
|
||||
if not letter and not word:
|
||||
# probably passed setup/teardown
|
||||
return
|
||||
@@ -209,7 +217,7 @@ class TerminalReporter:
|
||||
self.currentfspath = -2
|
||||
|
||||
def pytest_collection(self):
|
||||
if not self.hasmarkup and self.config.option.verbose >=1:
|
||||
if not self.hasmarkup and self.config.option.verbose >= 1:
|
||||
self.write("collecting ... ", bold=True)
|
||||
|
||||
def pytest_collectreport(self, report):
|
||||
@@ -325,15 +333,16 @@ class TerminalReporter:
|
||||
stack.append(col)
|
||||
#if col.name == "()":
|
||||
# continue
|
||||
indent = (len(stack)-1) * " "
|
||||
self._tw.line("%s%s" %(indent, col))
|
||||
indent = (len(stack) - 1) * " "
|
||||
self._tw.line("%s%s" % (indent, col))
|
||||
|
||||
def pytest_sessionfinish(self, exitstatus, __multicall__):
|
||||
__multicall__.execute()
|
||||
self._tw.line("")
|
||||
if exitstatus in (0, 1, 2):
|
||||
if exitstatus in (0, 1, 2, 4):
|
||||
self.summary_errors()
|
||||
self.summary_failures()
|
||||
self.summary_hints()
|
||||
self.config.hook.pytest_terminal_summary(terminalreporter=self)
|
||||
if exitstatus == 2:
|
||||
self._report_keyboardinterrupt()
|
||||
@@ -399,6 +408,11 @@ class TerminalReporter:
|
||||
l.append(x)
|
||||
return l
|
||||
|
||||
def summary_hints(self):
|
||||
if self.config.option.traceconfig:
|
||||
for hint in self.config.pluginmanager._hints:
|
||||
self._tw.line("hint: %s" % hint)
|
||||
|
||||
def summary_failures(self):
|
||||
if self.config.option.tbstyle != "no":
|
||||
reports = self.getreports('failed')
|
||||
@@ -452,14 +466,20 @@ class TerminalReporter:
|
||||
if key: # setup/teardown reports have an empty key, ignore them
|
||||
val = self.stats.get(key, None)
|
||||
if val:
|
||||
parts.append("%d %s" %(len(val), key))
|
||||
parts.append("%d %s" % (len(val), key))
|
||||
line = ", ".join(parts)
|
||||
# XXX coloring
|
||||
msg = "%s in %.2f seconds" %(line, session_duration)
|
||||
msg = "%s in %.2f seconds" % (line, session_duration)
|
||||
|
||||
markup = {'bold': True}
|
||||
if 'failed' in self.stats or 'error' in self.stats:
|
||||
markup = {'red': True, 'bold': True}
|
||||
else:
|
||||
markup = {'green': True, 'bold': True}
|
||||
|
||||
if self.verbosity >= 0:
|
||||
self.write_sep("=", msg, bold=True)
|
||||
#else:
|
||||
# self.write_line(msg, bold=True)
|
||||
self.write_sep("=", msg, **markup)
|
||||
if self.verbosity == -1:
|
||||
self.write_line(msg, **markup)
|
||||
|
||||
def summary_deselected(self):
|
||||
if 'deselected' in self.stats:
|
||||
@@ -471,7 +491,7 @@ class TerminalReporter:
|
||||
if m:
|
||||
l.append("-m %r" % m)
|
||||
if l:
|
||||
self.write_sep("=", "%d tests deselected by %r" %(
|
||||
self.write_sep("=", "%d tests deselected by %r" % (
|
||||
len(self.stats['deselected']), " ".join(l)), bold=True)
|
||||
|
||||
def repr_pythonversion(v=None):
|
||||
|
||||
@@ -54,15 +54,15 @@ def pytest_configure(config):
|
||||
mp.setattr(config, '_tmpdirhandler', t, raising=False)
|
||||
mp.setattr(pytest, 'ensuretemp', t.ensuretemp, raising=False)
|
||||
|
||||
def pytest_funcarg__tmpdir(request):
|
||||
@pytest.fixture
|
||||
def tmpdir(request):
|
||||
"""return a temporary directory path object
|
||||
which is unique to each test function invocation,
|
||||
created as a sub directory of the base temporary
|
||||
directory. The returned object is a `py.path.local`_
|
||||
path object.
|
||||
"""
|
||||
name = request._pyfuncitem.name
|
||||
name = request.node.name
|
||||
name = py.std.re.sub("[\W]", "_", name)
|
||||
x = request.config._tmpdirhandler.mktemp(name, numbered=True)
|
||||
return x
|
||||
|
||||
|
||||
@@ -5,23 +5,40 @@ import sys, pdb
|
||||
# for transfering markers
|
||||
from _pytest.python import transfer_markers
|
||||
|
||||
def pytest_pycollect_makeitem(collector, name, obj):
|
||||
|
||||
def is_unittest(obj):
|
||||
"""Is obj a subclass of unittest.TestCase?"""
|
||||
unittest = sys.modules.get('unittest')
|
||||
if unittest is None:
|
||||
return # nobody can have derived unittest.TestCase
|
||||
return # nobody can have derived unittest.TestCase
|
||||
try:
|
||||
isunit = issubclass(obj, unittest.TestCase)
|
||||
return issubclass(obj, unittest.TestCase)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
if isunit:
|
||||
return UnitTestCase(name, parent=collector)
|
||||
except:
|
||||
return False
|
||||
|
||||
|
||||
def pytest_pycollect_makeitem(collector, name, obj):
|
||||
if is_unittest(obj):
|
||||
return UnitTestCase(name, parent=collector)
|
||||
|
||||
|
||||
class UnitTestCase(pytest.Class):
|
||||
nofuncargs = True # marker for fixturemanger.getfixtureinfo()
|
||||
# to declare that our children do not support funcargs
|
||||
#
|
||||
def setup(self):
|
||||
cls = self.obj
|
||||
if getattr(cls, '__unittest_skip__', False):
|
||||
return # skipped
|
||||
setup = getattr(cls, 'setUpClass', None)
|
||||
if setup is not None:
|
||||
setup()
|
||||
teardown = getattr(cls, 'tearDownClass', None)
|
||||
if teardown is not None:
|
||||
self.addfinalizer(teardown)
|
||||
super(UnitTestCase, self).setup()
|
||||
|
||||
def collect(self):
|
||||
self.session._fixturemanager.parsefactories(self, unittest=True)
|
||||
@@ -45,21 +62,7 @@ class UnitTestCase(pytest.Class):
|
||||
if ut is None or runtest != ut.TestCase.runTest:
|
||||
yield TestCaseFunction('runTest', parent=self)
|
||||
|
||||
def setup(self):
|
||||
if getattr(self.obj, '__unittest_skip__', False):
|
||||
return
|
||||
meth = getattr(self.obj, 'setUpClass', None)
|
||||
if meth is not None:
|
||||
meth()
|
||||
super(UnitTestCase, self).setup()
|
||||
|
||||
def teardown(self):
|
||||
if getattr(self.obj, '__unittest_skip__', False):
|
||||
return
|
||||
meth = getattr(self.obj, 'tearDownClass', None)
|
||||
if meth is not None:
|
||||
meth()
|
||||
super(UnitTestCase, self).teardown()
|
||||
|
||||
class TestCaseFunction(pytest.Function):
|
||||
_excinfo = None
|
||||
@@ -147,7 +150,10 @@ def pytest_runtest_makereport(item, call):
|
||||
if isinstance(item, TestCaseFunction):
|
||||
if item._excinfo:
|
||||
call.excinfo = item._excinfo.pop(0)
|
||||
del call.result
|
||||
try:
|
||||
del call.result
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
# twisted trial support
|
||||
def pytest_runtest_protocol(item, __multicall__):
|
||||
|
||||
19
bench/bench_argcomplete.py
Normal file
19
bench/bench_argcomplete.py
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
|
||||
# 10000 iterations, just for relative comparison
|
||||
# 2.7.5 3.3.2
|
||||
# FilesCompleter 75.1109 69.2116
|
||||
# FastFilesCompleter 0.7383 1.0760
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
import timeit
|
||||
from argcomplete.completers import FilesCompleter
|
||||
from _pytest._argcomplete import FastFilesCompleter
|
||||
count = 1000 # only a few seconds
|
||||
setup = 'from __main__ import FastFilesCompleter\nfc = FastFilesCompleter()'
|
||||
run = 'fc("/d")'
|
||||
sys.stdout.write('%s\n' % (timeit.timeit(run,
|
||||
setup=setup.replace('Fast', ''), number=count)))
|
||||
sys.stdout.write('%s\n' % (timeit.timeit(run, setup=setup, number=count)))
|
||||
@@ -1,497 +0,0 @@
|
||||
#!python
|
||||
"""Bootstrap distribute installation
|
||||
|
||||
If you want to use setuptools in your package's setup.py, just include this
|
||||
file in the same directory with it, and add this to the top of your setup.py::
|
||||
|
||||
from distribute_setup import use_setuptools
|
||||
use_setuptools()
|
||||
|
||||
If you want to require a specific version of setuptools, set a download
|
||||
mirror, or use an alternate download directory, you can do so by supplying
|
||||
the appropriate options to ``use_setuptools()``.
|
||||
|
||||
This file can also be run as a script to install or upgrade setuptools.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import fnmatch
|
||||
import tempfile
|
||||
import tarfile
|
||||
from distutils import log
|
||||
|
||||
try:
|
||||
from site import USER_SITE
|
||||
except ImportError:
|
||||
USER_SITE = None
|
||||
|
||||
try:
|
||||
import subprocess
|
||||
|
||||
def _python_cmd(*args):
|
||||
args = (sys.executable,) + args
|
||||
return subprocess.call(args) == 0
|
||||
|
||||
except ImportError:
|
||||
# will be used for python 2.3
|
||||
def _python_cmd(*args):
|
||||
args = (sys.executable,) + args
|
||||
# quoting arguments if windows
|
||||
if sys.platform == 'win32':
|
||||
def quote(arg):
|
||||
if ' ' in arg:
|
||||
return '"%s"' % arg
|
||||
return arg
|
||||
args = [quote(arg) for arg in args]
|
||||
return os.spawnl(os.P_WAIT, sys.executable, *args) == 0
|
||||
|
||||
DEFAULT_VERSION = "0.6.27"
|
||||
DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/"
|
||||
SETUPTOOLS_FAKED_VERSION = "0.6c11"
|
||||
|
||||
SETUPTOOLS_PKG_INFO = """\
|
||||
Metadata-Version: 1.0
|
||||
Name: setuptools
|
||||
Version: %s
|
||||
Summary: xxxx
|
||||
Home-page: xxx
|
||||
Author: xxx
|
||||
Author-email: xxx
|
||||
License: xxx
|
||||
Description: xxx
|
||||
""" % SETUPTOOLS_FAKED_VERSION
|
||||
|
||||
|
||||
def _install(tarball, install_args=()):
|
||||
# extracting the tarball
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
log.warn('Extracting in %s', tmpdir)
|
||||
old_wd = os.getcwd()
|
||||
try:
|
||||
os.chdir(tmpdir)
|
||||
tar = tarfile.open(tarball)
|
||||
_extractall(tar)
|
||||
tar.close()
|
||||
|
||||
# going in the directory
|
||||
subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
|
||||
os.chdir(subdir)
|
||||
log.warn('Now working in %s', subdir)
|
||||
|
||||
# installing
|
||||
log.warn('Installing Distribute')
|
||||
if not _python_cmd('setup.py', 'install', *install_args):
|
||||
log.warn('Something went wrong during the installation.')
|
||||
log.warn('See the error message above.')
|
||||
finally:
|
||||
os.chdir(old_wd)
|
||||
|
||||
|
||||
def _build_egg(egg, tarball, to_dir):
|
||||
# extracting the tarball
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
log.warn('Extracting in %s', tmpdir)
|
||||
old_wd = os.getcwd()
|
||||
try:
|
||||
os.chdir(tmpdir)
|
||||
tar = tarfile.open(tarball)
|
||||
_extractall(tar)
|
||||
tar.close()
|
||||
|
||||
# going in the directory
|
||||
subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
|
||||
os.chdir(subdir)
|
||||
log.warn('Now working in %s', subdir)
|
||||
|
||||
# building an egg
|
||||
log.warn('Building a Distribute egg in %s', to_dir)
|
||||
_python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir)
|
||||
|
||||
finally:
|
||||
os.chdir(old_wd)
|
||||
# returning the result
|
||||
log.warn(egg)
|
||||
if not os.path.exists(egg):
|
||||
raise IOError('Could not build the egg.')
|
||||
|
||||
|
||||
def _do_download(version, download_base, to_dir, download_delay):
|
||||
egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg'
|
||||
% (version, sys.version_info[0], sys.version_info[1]))
|
||||
if not os.path.exists(egg):
|
||||
tarball = download_setuptools(version, download_base,
|
||||
to_dir, download_delay)
|
||||
_build_egg(egg, tarball, to_dir)
|
||||
sys.path.insert(0, egg)
|
||||
import setuptools
|
||||
setuptools.bootstrap_install_from = egg
|
||||
|
||||
|
||||
def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
|
||||
to_dir=os.curdir, download_delay=15, no_fake=True):
|
||||
# making sure we use the absolute path
|
||||
to_dir = os.path.abspath(to_dir)
|
||||
was_imported = 'pkg_resources' in sys.modules or \
|
||||
'setuptools' in sys.modules
|
||||
try:
|
||||
try:
|
||||
import pkg_resources
|
||||
if not hasattr(pkg_resources, '_distribute'):
|
||||
if not no_fake:
|
||||
_fake_setuptools()
|
||||
raise ImportError
|
||||
except ImportError:
|
||||
return _do_download(version, download_base, to_dir, download_delay)
|
||||
try:
|
||||
pkg_resources.require("distribute>="+version)
|
||||
return
|
||||
except pkg_resources.VersionConflict:
|
||||
e = sys.exc_info()[1]
|
||||
if was_imported:
|
||||
sys.stderr.write(
|
||||
"The required version of distribute (>=%s) is not available,\n"
|
||||
"and can't be installed while this script is running. Please\n"
|
||||
"install a more recent version first, using\n"
|
||||
"'easy_install -U distribute'."
|
||||
"\n\n(Currently using %r)\n" % (version, e.args[0]))
|
||||
sys.exit(2)
|
||||
else:
|
||||
del pkg_resources, sys.modules['pkg_resources'] # reload ok
|
||||
return _do_download(version, download_base, to_dir,
|
||||
download_delay)
|
||||
except pkg_resources.DistributionNotFound:
|
||||
return _do_download(version, download_base, to_dir,
|
||||
download_delay)
|
||||
finally:
|
||||
if not no_fake:
|
||||
_create_fake_setuptools_pkg_info(to_dir)
|
||||
|
||||
def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
|
||||
to_dir=os.curdir, delay=15):
|
||||
"""Download distribute from a specified location and return its filename
|
||||
|
||||
`version` should be a valid distribute version number that is available
|
||||
as an egg for download under the `download_base` URL (which should end
|
||||
with a '/'). `to_dir` is the directory where the egg will be downloaded.
|
||||
`delay` is the number of seconds to pause before an actual download
|
||||
attempt.
|
||||
"""
|
||||
# making sure we use the absolute path
|
||||
to_dir = os.path.abspath(to_dir)
|
||||
try:
|
||||
from urllib.request import urlopen
|
||||
except ImportError:
|
||||
from urllib2 import urlopen
|
||||
tgz_name = "distribute-%s.tar.gz" % version
|
||||
url = download_base + tgz_name
|
||||
saveto = os.path.join(to_dir, tgz_name)
|
||||
src = dst = None
|
||||
if not os.path.exists(saveto): # Avoid repeated downloads
|
||||
try:
|
||||
log.warn("Downloading %s", url)
|
||||
src = urlopen(url)
|
||||
# Read/write all in one block, so we don't create a corrupt file
|
||||
# if the download is interrupted.
|
||||
data = src.read()
|
||||
dst = open(saveto, "wb")
|
||||
dst.write(data)
|
||||
finally:
|
||||
if src:
|
||||
src.close()
|
||||
if dst:
|
||||
dst.close()
|
||||
return os.path.realpath(saveto)
|
||||
|
||||
def _no_sandbox(function):
|
||||
def __no_sandbox(*args, **kw):
|
||||
try:
|
||||
from setuptools.sandbox import DirectorySandbox
|
||||
if not hasattr(DirectorySandbox, '_old'):
|
||||
def violation(*args):
|
||||
pass
|
||||
DirectorySandbox._old = DirectorySandbox._violation
|
||||
DirectorySandbox._violation = violation
|
||||
patched = True
|
||||
else:
|
||||
patched = False
|
||||
except ImportError:
|
||||
patched = False
|
||||
|
||||
try:
|
||||
return function(*args, **kw)
|
||||
finally:
|
||||
if patched:
|
||||
DirectorySandbox._violation = DirectorySandbox._old
|
||||
del DirectorySandbox._old
|
||||
|
||||
return __no_sandbox
|
||||
|
||||
def _patch_file(path, content):
|
||||
"""Will backup the file then patch it"""
|
||||
existing_content = open(path).read()
|
||||
if existing_content == content:
|
||||
# already patched
|
||||
log.warn('Already patched.')
|
||||
return False
|
||||
log.warn('Patching...')
|
||||
_rename_path(path)
|
||||
f = open(path, 'w')
|
||||
try:
|
||||
f.write(content)
|
||||
finally:
|
||||
f.close()
|
||||
return True
|
||||
|
||||
_patch_file = _no_sandbox(_patch_file)
|
||||
|
||||
def _same_content(path, content):
|
||||
return open(path).read() == content
|
||||
|
||||
def _rename_path(path):
|
||||
new_name = path + '.OLD.%s' % time.time()
|
||||
log.warn('Renaming %s into %s', path, new_name)
|
||||
os.rename(path, new_name)
|
||||
return new_name
|
||||
|
||||
def _remove_flat_installation(placeholder):
|
||||
if not os.path.isdir(placeholder):
|
||||
log.warn('Unkown installation at %s', placeholder)
|
||||
return False
|
||||
found = False
|
||||
for file in os.listdir(placeholder):
|
||||
if fnmatch.fnmatch(file, 'setuptools*.egg-info'):
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
log.warn('Could not locate setuptools*.egg-info')
|
||||
return
|
||||
|
||||
log.warn('Removing elements out of the way...')
|
||||
pkg_info = os.path.join(placeholder, file)
|
||||
if os.path.isdir(pkg_info):
|
||||
patched = _patch_egg_dir(pkg_info)
|
||||
else:
|
||||
patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO)
|
||||
|
||||
if not patched:
|
||||
log.warn('%s already patched.', pkg_info)
|
||||
return False
|
||||
# now let's move the files out of the way
|
||||
for element in ('setuptools', 'pkg_resources.py', 'site.py'):
|
||||
element = os.path.join(placeholder, element)
|
||||
if os.path.exists(element):
|
||||
_rename_path(element)
|
||||
else:
|
||||
log.warn('Could not find the %s element of the '
|
||||
'Setuptools distribution', element)
|
||||
return True
|
||||
|
||||
_remove_flat_installation = _no_sandbox(_remove_flat_installation)
|
||||
|
||||
def _after_install(dist):
|
||||
log.warn('After install bootstrap.')
|
||||
placeholder = dist.get_command_obj('install').install_purelib
|
||||
_create_fake_setuptools_pkg_info(placeholder)
|
||||
|
||||
def _create_fake_setuptools_pkg_info(placeholder):
|
||||
if not placeholder or not os.path.exists(placeholder):
|
||||
log.warn('Could not find the install location')
|
||||
return
|
||||
pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1])
|
||||
setuptools_file = 'setuptools-%s-py%s.egg-info' % \
|
||||
(SETUPTOOLS_FAKED_VERSION, pyver)
|
||||
pkg_info = os.path.join(placeholder, setuptools_file)
|
||||
if os.path.exists(pkg_info):
|
||||
log.warn('%s already exists', pkg_info)
|
||||
return
|
||||
|
||||
if not os.access(pkg_info, os.W_OK):
|
||||
log.warn("Don't have permissions to write %s, skipping", pkg_info)
|
||||
|
||||
log.warn('Creating %s', pkg_info)
|
||||
f = open(pkg_info, 'w')
|
||||
try:
|
||||
f.write(SETUPTOOLS_PKG_INFO)
|
||||
finally:
|
||||
f.close()
|
||||
|
||||
pth_file = os.path.join(placeholder, 'setuptools.pth')
|
||||
log.warn('Creating %s', pth_file)
|
||||
f = open(pth_file, 'w')
|
||||
try:
|
||||
f.write(os.path.join(os.curdir, setuptools_file))
|
||||
finally:
|
||||
f.close()
|
||||
|
||||
_create_fake_setuptools_pkg_info = _no_sandbox(_create_fake_setuptools_pkg_info)
|
||||
|
||||
def _patch_egg_dir(path):
|
||||
# let's check if it's already patched
|
||||
pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
|
||||
if os.path.exists(pkg_info):
|
||||
if _same_content(pkg_info, SETUPTOOLS_PKG_INFO):
|
||||
log.warn('%s already patched.', pkg_info)
|
||||
return False
|
||||
_rename_path(path)
|
||||
os.mkdir(path)
|
||||
os.mkdir(os.path.join(path, 'EGG-INFO'))
|
||||
pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
|
||||
f = open(pkg_info, 'w')
|
||||
try:
|
||||
f.write(SETUPTOOLS_PKG_INFO)
|
||||
finally:
|
||||
f.close()
|
||||
return True
|
||||
|
||||
_patch_egg_dir = _no_sandbox(_patch_egg_dir)
|
||||
|
||||
def _before_install():
|
||||
log.warn('Before install bootstrap.')
|
||||
_fake_setuptools()
|
||||
|
||||
|
||||
def _under_prefix(location):
|
||||
if 'install' not in sys.argv:
|
||||
return True
|
||||
args = sys.argv[sys.argv.index('install')+1:]
|
||||
for index, arg in enumerate(args):
|
||||
for option in ('--root', '--prefix'):
|
||||
if arg.startswith('%s=' % option):
|
||||
top_dir = arg.split('root=')[-1]
|
||||
return location.startswith(top_dir)
|
||||
elif arg == option:
|
||||
if len(args) > index:
|
||||
top_dir = args[index+1]
|
||||
return location.startswith(top_dir)
|
||||
if arg == '--user' and USER_SITE is not None:
|
||||
return location.startswith(USER_SITE)
|
||||
return True
|
||||
|
||||
|
||||
def _fake_setuptools():
|
||||
log.warn('Scanning installed packages')
|
||||
try:
|
||||
import pkg_resources
|
||||
except ImportError:
|
||||
# we're cool
|
||||
log.warn('Setuptools or Distribute does not seem to be installed.')
|
||||
return
|
||||
ws = pkg_resources.working_set
|
||||
try:
|
||||
setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools',
|
||||
replacement=False))
|
||||
except TypeError:
|
||||
# old distribute API
|
||||
setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools'))
|
||||
|
||||
if setuptools_dist is None:
|
||||
log.warn('No setuptools distribution found')
|
||||
return
|
||||
# detecting if it was already faked
|
||||
setuptools_location = setuptools_dist.location
|
||||
log.warn('Setuptools installation detected at %s', setuptools_location)
|
||||
|
||||
# if --root or --preix was provided, and if
|
||||
# setuptools is not located in them, we don't patch it
|
||||
if not _under_prefix(setuptools_location):
|
||||
log.warn('Not patching, --root or --prefix is installing Distribute'
|
||||
' in another location')
|
||||
return
|
||||
|
||||
# let's see if its an egg
|
||||
if not setuptools_location.endswith('.egg'):
|
||||
log.warn('Non-egg installation')
|
||||
res = _remove_flat_installation(setuptools_location)
|
||||
if not res:
|
||||
return
|
||||
else:
|
||||
log.warn('Egg installation')
|
||||
pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO')
|
||||
if (os.path.exists(pkg_info) and
|
||||
_same_content(pkg_info, SETUPTOOLS_PKG_INFO)):
|
||||
log.warn('Already patched.')
|
||||
return
|
||||
log.warn('Patching...')
|
||||
# let's create a fake egg replacing setuptools one
|
||||
res = _patch_egg_dir(setuptools_location)
|
||||
if not res:
|
||||
return
|
||||
log.warn('Patched done.')
|
||||
_relaunch()
|
||||
|
||||
|
||||
def _relaunch():
|
||||
log.warn('Relaunching...')
|
||||
# we have to relaunch the process
|
||||
# pip marker to avoid a relaunch bug
|
||||
if sys.argv[:3] == ['-c', 'install', '--single-version-externally-managed']:
|
||||
sys.argv[0] = 'setup.py'
|
||||
args = [sys.executable] + sys.argv
|
||||
sys.exit(subprocess.call(args))
|
||||
|
||||
|
||||
def _extractall(self, path=".", members=None):
|
||||
"""Extract all members from the archive to the current working
|
||||
directory and set owner, modification time and permissions on
|
||||
directories afterwards. `path' specifies a different directory
|
||||
to extract to. `members' is optional and must be a subset of the
|
||||
list returned by getmembers().
|
||||
"""
|
||||
import copy
|
||||
import operator
|
||||
from tarfile import ExtractError
|
||||
directories = []
|
||||
|
||||
if members is None:
|
||||
members = self
|
||||
|
||||
for tarinfo in members:
|
||||
if tarinfo.isdir():
|
||||
# Extract directories with a safe mode.
|
||||
directories.append(tarinfo)
|
||||
tarinfo = copy.copy(tarinfo)
|
||||
tarinfo.mode = 448 # decimal for oct 0700
|
||||
self.extract(tarinfo, path)
|
||||
|
||||
# Reverse sort directories.
|
||||
if sys.version_info < (2, 4):
|
||||
def sorter(dir1, dir2):
|
||||
return cmp(dir1.name, dir2.name)
|
||||
directories.sort(sorter)
|
||||
directories.reverse()
|
||||
else:
|
||||
directories.sort(key=operator.attrgetter('name'), reverse=True)
|
||||
|
||||
# Set correct owner, mtime and filemode on directories.
|
||||
for tarinfo in directories:
|
||||
dirpath = os.path.join(path, tarinfo.name)
|
||||
try:
|
||||
self.chown(tarinfo, dirpath)
|
||||
self.utime(tarinfo, dirpath)
|
||||
self.chmod(tarinfo, dirpath)
|
||||
except ExtractError:
|
||||
e = sys.exc_info()[1]
|
||||
if self.errorlevel > 1:
|
||||
raise
|
||||
else:
|
||||
self._dbg(1, "tarfile: %s" % e)
|
||||
|
||||
def _build_install_args(argv):
|
||||
install_args = []
|
||||
user_install = '--user' in argv
|
||||
if user_install and sys.version_info < (2,6):
|
||||
log.warn("--user requires Python 2.6 or later")
|
||||
raise SystemExit(1)
|
||||
if user_install:
|
||||
install_args.append('--user')
|
||||
return install_args
|
||||
|
||||
def main(argv, version=DEFAULT_VERSION):
|
||||
"""Install or upgrade setuptools and EasyInstall"""
|
||||
tarball = download_setuptools()
|
||||
_install(tarball, _build_install_args(argv))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv[1:])
|
||||
@@ -12,7 +12,7 @@ PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
SITETARGET=latest
|
||||
SITETARGET=dev
|
||||
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
|
||||
|
||||
@@ -31,6 +31,8 @@ help:
|
||||
@echo " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " texinfo to make Texinfo files"
|
||||
@echo " info to make Texinfo files and run them through makeinfo"
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
@echo " text to make text files"
|
||||
@echo " man to make manual pages"
|
||||
@@ -142,3 +144,18 @@ doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
||||
|
||||
texinfo:
|
||||
mkdir -p $(BUILDDIR)/texinfo
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo
|
||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||
"(use \`make info' here to do that automatically)."
|
||||
|
||||
info:
|
||||
mkdir -p $(BUILDDIR)/texinfo
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo "Running Texinfo files through makeinfo..."
|
||||
make -C $(BUILDDIR)/texinfo info
|
||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
{% block relbaritems %}
|
||||
{{ super() }}
|
||||
<g:plusone></g:plusone>
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block footer %}
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
<a href="https://bitbucket.org/hpk42/pytest/issues?status=new&status=open">issues[bb]</a>
|
||||
</td><td>
|
||||
<a href="{{ pathto('contact') }}">contact</a>
|
||||
</td></tr><tr><td>
|
||||
<a href="{{ pathto('talks') }}">Talks/Posts</a>
|
||||
</td></tr></table>
|
||||
</div>
|
||||
{% extends "basic/localtoc.html" %}
|
||||
|
||||
@@ -5,6 +5,8 @@ Release announcements
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
release-2.3.5
|
||||
release-2.3.4
|
||||
release-2.3.3
|
||||
release-2.3.2
|
||||
release-2.3.1
|
||||
|
||||
62
doc/en/announce/release-2.3.3.txt
Normal file
62
doc/en/announce/release-2.3.3.txt
Normal file
@@ -0,0 +1,62 @@
|
||||
pytest-2.3.3: integration fixes, py24 suport, ``*/**`` shown in traceback
|
||||
===========================================================================
|
||||
|
||||
pytest-2.3.3 is a another stabilization release of the py.test tool
|
||||
which offers uebersimple assertions, scalable fixture mechanisms
|
||||
and deep customization for testing with Python. Particularly,
|
||||
this release provides:
|
||||
|
||||
- integration fixes and improvements related to flask, numpy, nose,
|
||||
unittest, mock
|
||||
|
||||
- makes pytest work on py24 again (yes, people sometimes still need to use it)
|
||||
|
||||
- show ``*,**`` args in pytest tracebacks
|
||||
|
||||
Thanks to Manuel Jacob, Thomas Waldmann, Ronny Pfannschmidt, Pavel Repin
|
||||
and Andreas Taumoefolau for providing patches and all for the issues.
|
||||
|
||||
See
|
||||
|
||||
http://pytest.org/
|
||||
|
||||
for general information. To install or upgrade pytest:
|
||||
|
||||
pip install -U pytest # or
|
||||
easy_install -U pytest
|
||||
|
||||
best,
|
||||
holger krekel
|
||||
|
||||
Changes between 2.3.2 and 2.3.3
|
||||
-----------------------------------
|
||||
|
||||
- fix issue214 - parse modules that contain special objects like e. g.
|
||||
flask's request object which blows up on getattr access if no request
|
||||
is active. thanks Thomas Waldmann.
|
||||
|
||||
- fix issue213 - allow to parametrize with values like numpy arrays that
|
||||
do not support an __eq__ operator
|
||||
|
||||
- fix issue215 - split test_python.org into multiple files
|
||||
|
||||
- fix issue148 - @unittest.skip on classes is now recognized and avoids
|
||||
calling setUpClass/tearDownClass, thanks Pavel Repin
|
||||
|
||||
- fix issue209 - reintroduce python2.4 support by depending on newer
|
||||
pylib which re-introduced statement-finding for pre-AST interpreters
|
||||
|
||||
- nose support: only call setup if its a callable, thanks Andrew
|
||||
Taumoefolau
|
||||
|
||||
- fix issue219 - add py2.4-3.3 classifiers to TROVE list
|
||||
|
||||
- in tracebacks *,** arg values are now shown next to normal arguments
|
||||
(thanks Manuel Jacob)
|
||||
|
||||
- fix issue217 - support mock.patch with pytest's fixtures - note that
|
||||
you need either mock-1.0.1 or the python3.3 builtin unittest.mock.
|
||||
|
||||
- fix issue127 - improve documentation for pytest_addoption() and
|
||||
add a ``config.getoption(name)`` helper function for consistency.
|
||||
|
||||
39
doc/en/announce/release-2.3.4.txt
Normal file
39
doc/en/announce/release-2.3.4.txt
Normal file
@@ -0,0 +1,39 @@
|
||||
pytest-2.3.4: stabilization, more flexible selection via "-k expr"
|
||||
===========================================================================
|
||||
|
||||
pytest-2.3.4 is a small stabilization release of the py.test tool
|
||||
which offers uebersimple assertions, scalable fixture mechanisms
|
||||
and deep customization for testing with Python. This release
|
||||
comes with the following fixes and features:
|
||||
|
||||
- make "-k" option accept an expressions the same as with "-m" so that one
|
||||
can write: -k "name1 or name2" etc. This is a slight usage incompatibility
|
||||
if you used special syntax like "TestClass.test_method" which you now
|
||||
need to write as -k "TestClass and test_method" to match a certain
|
||||
method in a certain test class.
|
||||
- allow to dynamically define markers via
|
||||
item.keywords[...]=assignment integrating with "-m" option
|
||||
- yielded test functions will now have autouse-fixtures active but
|
||||
cannot accept fixtures as funcargs - it's anyway recommended to
|
||||
rather use the post-2.0 parametrize features instead of yield, see:
|
||||
http://pytest.org/latest/example/parametrize.html
|
||||
- fix autouse-issue where autouse-fixtures would not be discovered
|
||||
if defined in a a/conftest.py file and tests in a/tests/test_some.py
|
||||
- fix issue226 - LIFO ordering for fixture teardowns
|
||||
- fix issue224 - invocations with >256 char arguments now work
|
||||
- fix issue91 - add/discuss package/directory level setups in example
|
||||
- fixes related to autouse discovery and calling
|
||||
|
||||
Thanks in particular to Thomas Waldmann for spotting and reporting issues.
|
||||
|
||||
See
|
||||
|
||||
http://pytest.org/
|
||||
|
||||
for general information. To install or upgrade pytest:
|
||||
|
||||
pip install -U pytest # or
|
||||
easy_install -U pytest
|
||||
|
||||
best,
|
||||
holger krekel
|
||||
97
doc/en/announce/release-2.3.5.txt
Normal file
97
doc/en/announce/release-2.3.5.txt
Normal file
@@ -0,0 +1,97 @@
|
||||
pytest-2.3.5: bug fixes and little improvements
|
||||
===========================================================================
|
||||
|
||||
pytest-2.3.5 is a maintenance release with many bug fixes and little
|
||||
improvements. See the changelog below for details. No backward
|
||||
compatibility issues are foreseen and all plugins which worked with the
|
||||
prior version are expected to work unmodified. Speaking of which, a
|
||||
few interesting new plugins saw the light last month:
|
||||
|
||||
- pytest-instafail: show failure information while tests are running
|
||||
- pytest-qt: testing of GUI applications written with QT/Pyside
|
||||
- pytest-xprocess: managing external processes across test runs
|
||||
- pytest-random: randomize test ordering
|
||||
|
||||
And several others like pytest-django saw maintenance releases.
|
||||
For a more complete list, check out
|
||||
https://pypi.python.org/pypi?%3Aaction=search&term=pytest&submit=search.
|
||||
|
||||
For general information see:
|
||||
|
||||
http://pytest.org/
|
||||
|
||||
To install or upgrade pytest:
|
||||
|
||||
pip install -U pytest # or
|
||||
easy_install -U pytest
|
||||
|
||||
Particular thanks to Floris, Ronny, Benjamin and the many bug reporters
|
||||
and fix providers.
|
||||
|
||||
may the fixtures be with you,
|
||||
holger krekel
|
||||
|
||||
|
||||
Changes between 2.3.4 and 2.3.5
|
||||
-----------------------------------
|
||||
|
||||
- never consider a fixture function for test function collection
|
||||
|
||||
- allow re-running of test items / helps to fix pytest-reruntests plugin
|
||||
and also help to keep less fixture/resource references alive
|
||||
|
||||
- put captured stdout/stderr into junitxml output even for passing tests
|
||||
(thanks Adam Goucher)
|
||||
|
||||
- Issue 265 - integrate nose setup/teardown with setupstate
|
||||
so it doesnt try to teardown if it did not setup
|
||||
|
||||
- issue 271 - dont write junitxml on slave nodes
|
||||
|
||||
- Issue 274 - dont try to show full doctest example
|
||||
when doctest does not know the example location
|
||||
|
||||
- issue 280 - disable assertion rewriting on buggy CPython 2.6.0
|
||||
|
||||
- inject "getfixture()" helper to retrieve fixtures from doctests,
|
||||
thanks Andreas Zeidler
|
||||
|
||||
- issue 259 - when assertion rewriting, be consistent with the default
|
||||
source encoding of ASCII on Python 2
|
||||
|
||||
- issue 251 - report a skip instead of ignoring classes with init
|
||||
|
||||
- issue250 unicode/str mixes in parametrization names and values now works
|
||||
|
||||
- issue257, assertion-triggered compilation of source ending in a
|
||||
comment line doesn't blow up in python2.5 (fixed through py>=1.4.13.dev6)
|
||||
|
||||
- fix --genscript option to generate standalone scripts that also
|
||||
work with python3.3 (importer ordering)
|
||||
|
||||
- issue171 - in assertion rewriting, show the repr of some
|
||||
global variables
|
||||
|
||||
- fix option help for "-k"
|
||||
|
||||
- move long description of distribution into README.rst
|
||||
|
||||
- improve docstring for metafunc.parametrize()
|
||||
|
||||
- fix bug where using capsys with pytest.set_trace() in a test
|
||||
function would break when looking at capsys.readouterr()
|
||||
|
||||
- allow to specify prefixes starting with "_" when
|
||||
customizing python_functions test discovery. (thanks Graham Horler)
|
||||
|
||||
- improve PYTEST_DEBUG tracing output by puting
|
||||
extra data on a new lines with additional indent
|
||||
|
||||
- ensure OutcomeExceptions like skip/fail have initialized exception attributes
|
||||
|
||||
- issue 260 - don't use nose special setup on plain unittest cases
|
||||
|
||||
- fix issue134 - print the collect errors that prevent running specified test items
|
||||
|
||||
- fix issue266 - accept unicode in MarkEvaluator expressions
|
||||
|
||||
@@ -11,6 +11,7 @@ py.test reference documentation
|
||||
customize.txt
|
||||
assert.txt
|
||||
fixture.txt
|
||||
yieldfixture.txt
|
||||
parametrize.txt
|
||||
xunit_setup.txt
|
||||
capture.txt
|
||||
|
||||
@@ -26,7 +26,7 @@ you will see the return value of the function call::
|
||||
|
||||
$ py.test test_assert1.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 1 items
|
||||
|
||||
test_assert1.py F
|
||||
@@ -78,6 +78,12 @@ and if you need to have access to the actual exception info you may use::
|
||||
|
||||
# do checks related to excinfo.type, excinfo.value, excinfo.traceback
|
||||
|
||||
``excinfo`` is a `py.code.ExceptionInfo`_ instance, which is a wrapper around
|
||||
the actual exception raised.
|
||||
|
||||
.. _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::
|
||||
|
||||
@@ -110,7 +116,7 @@ if you run this module::
|
||||
|
||||
$ py.test test_assert2.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 1 items
|
||||
|
||||
test_assert2.py F
|
||||
|
||||
28
doc/en/bash-completion.txt
Normal file
28
doc/en/bash-completion.txt
Normal file
@@ -0,0 +1,28 @@
|
||||
|
||||
.. _bash_completion:
|
||||
|
||||
Setting up bash completion
|
||||
==========================
|
||||
|
||||
When using bash as your shell, ``py.test`` can use argcomplete
|
||||
(https://argcomplete.readthedocs.org/) for auto-completion.
|
||||
For this ``argcomplete`` needs to be installed **and** enabled.
|
||||
|
||||
Install argcomplete using::
|
||||
|
||||
sudo pip install 'argcomplete>=0.5.7'
|
||||
|
||||
For global activation of all argcomplete enabled python applications run::
|
||||
|
||||
sudo activate-global-python-argcomplete
|
||||
|
||||
For permanent (but not global) ``py.test`` activation, use::
|
||||
|
||||
register-python-argcomplete py.test >> ~/.bashrc
|
||||
|
||||
For one-time activation of argcomplete for ``py.test`` only, use::
|
||||
|
||||
eval "$(register-python-argcomplete py.test)"
|
||||
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ of the failing function and hide the other one::
|
||||
|
||||
$ py.test
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 2 items
|
||||
|
||||
test_module.py .F
|
||||
@@ -78,7 +78,7 @@ of the failing function and hide the other one::
|
||||
|
||||
test_module.py:9: AssertionError
|
||||
----------------------------- Captured stdout ------------------------------
|
||||
setting up <function test_func2 at 0x2d63d70>
|
||||
setting up <function test_func2 at 0x2d79f50>
|
||||
==================== 1 failed, 1 passed in 0.01 seconds ====================
|
||||
|
||||
Accessing captured output from a test function
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
# The short X.Y version.
|
||||
version = release = "2.3.3"
|
||||
version = release = "2.4.0.dev"
|
||||
|
||||
import sys, os
|
||||
|
||||
@@ -273,6 +273,19 @@ epub_copyright = u'2012, holger krekel et alii'
|
||||
#epub_tocdup = True
|
||||
|
||||
|
||||
# -- Options for texinfo output ------------------------------------------------
|
||||
|
||||
texinfo_documents = [
|
||||
(master_doc, 'pytest', 'pytest Documentation',
|
||||
('Holger Krekel@*Benjamin Peterson@*Ronny Pfannschmidt@*'
|
||||
'Floris Bruynooghe@*others'),
|
||||
'pytest',
|
||||
'simple powerful testing with Pytho',
|
||||
'Programming',
|
||||
1),
|
||||
]
|
||||
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {'python': ('http://docs.python.org/', None),
|
||||
# 'lib': ("http://docs.python.org/2.7library/", None),
|
||||
|
||||
@@ -5,27 +5,28 @@
|
||||
Contact channels
|
||||
===================================
|
||||
|
||||
- `new issue tracker`_ to report bugs or suggest features (for version
|
||||
2.0 and above). You may also peek at the `old issue tracker`_ but please
|
||||
don't submit bugs there anymore.
|
||||
- `pytest issue tracker`_ to report bugs or suggest features (for version
|
||||
2.0 and above).
|
||||
|
||||
- `pytest on stackoverflow.com <http://stackoverflow.com/search?q=pytest>`_
|
||||
to post questions with the tag ``pytest``. New Questions will usually
|
||||
be seen by pytest users or developers.
|
||||
be seen by pytest users or developers and answered quickly.
|
||||
|
||||
- `Testing In Python`_: a mailing list for Python testing tools and discussion.
|
||||
|
||||
- `py-dev developers list`_ pytest specific announcements and discussions.
|
||||
- `pytest-dev at python.org (mailing list)`_ pytest specific announcements and discussions.
|
||||
|
||||
- `pytest-commit at python.org (mailing list)`_: for commits and new issues
|
||||
|
||||
- #pylib on irc.freenode.net IRC channel for random questions.
|
||||
|
||||
- private mail to Holger.Krekel at gmail com if you want to communicate sensitive issues
|
||||
|
||||
- `commit mailing list`_
|
||||
|
||||
- `merlinux.eu`_ offers on-site teaching and consulting services.
|
||||
- `merlinux.eu`_ offers pytest and tox-related professional teaching and
|
||||
consulting.
|
||||
|
||||
.. _`new issue tracker`: http://bitbucket.org/hpk42/pytest/issues/
|
||||
.. _`pytest issue tracker`: http://bitbucket.org/hpk42/pytest/issues/
|
||||
.. _`old issue tracker`: http://bitbucket.org/hpk42/py-trunk/issues/
|
||||
|
||||
.. _`merlinux.eu`: http://merlinux.eu
|
||||
@@ -41,7 +42,7 @@ Contact channels
|
||||
.. _FOAF: http://en.wikipedia.org/wiki/FOAF
|
||||
.. _`py-dev`:
|
||||
.. _`development mailing list`:
|
||||
.. _`py-dev developers list`: http://codespeak.net/mailman/listinfo/py-dev
|
||||
.. _`pytest-dev at python.org (mailing list)`: http://mail.python.org/mailman/listinfo/pytest-dev
|
||||
.. _`py-svn`:
|
||||
.. _`commit mailing list`: http://codespeak.net/mailman/listinfo/py-svn
|
||||
.. _`pytest-commit at python.org (mailing list)`: http://mail.python.org/mailman/listinfo/pytest-commit
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ Builtin configuration file options
|
||||
[seq] matches any character in seq
|
||||
[!seq] matches any char not in seq
|
||||
|
||||
Default patterns are ``.* _* CVS {args}``. Setting a ``norecursedir``
|
||||
Default patterns are ``.* _darcs CVS {args}``. Setting a ``norecursedir``
|
||||
replaces the default. Here is an example of how to avoid
|
||||
certain directories::
|
||||
|
||||
|
||||
@@ -44,9 +44,15 @@ then you can just invoke ``py.test`` without command line options::
|
||||
|
||||
$ py.test
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 1 items
|
||||
|
||||
mymodule.py .
|
||||
|
||||
========================= 1 passed in 0.02 seconds =========================
|
||||
|
||||
It is possible to use fixtures using the ``getfixture`` helper::
|
||||
|
||||
# content of example.rst
|
||||
>>> tmp = getfixture('tmpdir')
|
||||
>>> ...
|
||||
|
||||
@@ -66,7 +66,7 @@ class TestSpecialisedExplanations(object):
|
||||
assert a == b
|
||||
|
||||
def test_eq_dict(self):
|
||||
assert {'a': 0, 'b': 1} == {'a': 0, 'b': 2}
|
||||
assert {'a': 0, 'b': 1, 'c': 0} == {'a': 0, 'b': 2, 'd': 0}
|
||||
|
||||
def test_eq_set(self):
|
||||
assert set([0, 10, 11, 12]) == set([0, 20, 21])
|
||||
|
||||
@@ -29,5 +29,6 @@ The following examples aim at various use cases you might encounter.
|
||||
simple.txt
|
||||
parametrize.txt
|
||||
markers.txt
|
||||
special.txt
|
||||
pythoncollection.txt
|
||||
nonpython.txt
|
||||
|
||||
@@ -19,6 +19,8 @@ You can "mark" a test function with custom metadata like this::
|
||||
pass # perform some webtest test for your app
|
||||
def test_something_quick():
|
||||
pass
|
||||
def test_another():
|
||||
pass
|
||||
|
||||
.. versionadded:: 2.2
|
||||
|
||||
@@ -26,25 +28,72 @@ You can then restrict a test run to only run tests marked with ``webtest``::
|
||||
|
||||
$ py.test -v -m webtest
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3 -- /home/hpk/p/pytest/.tox/regen/bin/python
|
||||
collecting ... collected 2 items
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5 -- /home/hpk/p/pytest/.tox/regen/bin/python
|
||||
collecting ... collected 3 items
|
||||
|
||||
test_server.py:3: test_send_http PASSED
|
||||
|
||||
=================== 1 tests deselected by "-m 'webtest'" ===================
|
||||
================== 1 passed, 1 deselected in 0.02 seconds ==================
|
||||
=================== 2 tests deselected by "-m 'webtest'" ===================
|
||||
================== 1 passed, 2 deselected in 0.01 seconds ==================
|
||||
|
||||
Or the inverse, running all tests except the webtest ones::
|
||||
|
||||
$ py.test -v -m "not webtest"
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3 -- /home/hpk/p/pytest/.tox/regen/bin/python
|
||||
collecting ... collected 2 items
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5 -- /home/hpk/p/pytest/.tox/regen/bin/python
|
||||
collecting ... collected 3 items
|
||||
|
||||
test_server.py:6: test_something_quick PASSED
|
||||
test_server.py:8: test_another PASSED
|
||||
|
||||
================= 1 tests deselected by "-m 'not webtest'" =================
|
||||
================== 1 passed, 1 deselected in 0.01 seconds ==================
|
||||
================== 2 passed, 1 deselected in 0.01 seconds ==================
|
||||
|
||||
Using ``-k expr`` to select tests based on their name
|
||||
-------------------------------------------------------
|
||||
|
||||
.. versionadded: 2.0/2.3.4
|
||||
|
||||
You can use the ``-k`` command line option to specify an expression
|
||||
which implements a substring match on the test names instead of the
|
||||
exact match on markers that ``-m`` provides. This makes it easy to
|
||||
select tests based on their names::
|
||||
|
||||
$ py.test -v -k http # running with the above defined example module
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5 -- /home/hpk/p/pytest/.tox/regen/bin/python
|
||||
collecting ... collected 3 items
|
||||
|
||||
test_server.py:3: test_send_http PASSED
|
||||
|
||||
====================== 2 tests deselected by '-khttp' ======================
|
||||
================== 1 passed, 2 deselected in 0.01 seconds ==================
|
||||
|
||||
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 linux2 -- Python 2.7.3 -- pytest-2.3.5 -- /home/hpk/p/pytest/.tox/regen/bin/python
|
||||
collecting ... collected 3 items
|
||||
|
||||
test_server.py:6: test_something_quick PASSED
|
||||
test_server.py:8: test_another PASSED
|
||||
|
||||
================= 1 tests deselected by '-knot send_http' ==================
|
||||
================== 2 passed, 1 deselected in 0.01 seconds ==================
|
||||
|
||||
Or to select "http" and "quick" tests::
|
||||
|
||||
$ py.test -k "http or quick" -v
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5 -- /home/hpk/p/pytest/.tox/regen/bin/python
|
||||
collecting ... collected 3 items
|
||||
|
||||
test_server.py:3: test_send_http PASSED
|
||||
test_server.py:6: test_something_quick PASSED
|
||||
|
||||
================= 1 tests deselected by '-khttp or quick' ==================
|
||||
================== 2 passed, 1 deselected in 0.01 seconds ==================
|
||||
|
||||
Registering markers
|
||||
-------------------------------------
|
||||
@@ -136,47 +185,30 @@ You can also set a module level marker::
|
||||
in which case it will be applied to all functions and
|
||||
methods defined in the module.
|
||||
|
||||
.. _`marking individual tests when using parametrize`:
|
||||
|
||||
Using ``-k TEXT`` to select tests
|
||||
----------------------------------------------------
|
||||
Marking individual tests when using parametrize
|
||||
-----------------------------------------------
|
||||
|
||||
You can use the ``-k`` command line option to only run tests with names matching
|
||||
the given argument::
|
||||
When using parametrize, applying a mark will make it apply
|
||||
to each individual test. However it is also possible to
|
||||
apply a marker to an individual test instance::
|
||||
|
||||
$ py.test -k send_http # running with the above defined examples
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
collected 4 items
|
||||
|
||||
test_server.py .
|
||||
|
||||
=================== 3 tests deselected by '-ksend_http' ====================
|
||||
================== 1 passed, 3 deselected in 0.01 seconds ==================
|
||||
import pytest
|
||||
|
||||
And you can also run all tests except the ones that match the keyword::
|
||||
@pytest.mark.foo
|
||||
@pytest.mark.parametrize(("n", "expected"), [
|
||||
(1, 2),
|
||||
pytest.mark.bar((1, 3)),
|
||||
(2, 3),
|
||||
])
|
||||
def test_increment(n, expected):
|
||||
assert n + 1 == expected
|
||||
|
||||
$ py.test -k-send_http
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
collected 4 items
|
||||
|
||||
test_mark_classlevel.py ..
|
||||
test_server.py .
|
||||
|
||||
=================== 1 tests deselected by '-k-send_http' ===================
|
||||
================== 3 passed, 1 deselected in 0.01 seconds ==================
|
||||
In this example the mark "foo" will apply to each of the three
|
||||
tests, whereas the "bar" mark is only applied to the second test.
|
||||
Skip and xfail marks can also be applied in this way, see :ref:`skip/xfail with parametrize`.
|
||||
|
||||
Or to only select the class::
|
||||
|
||||
$ py.test -kTestClass
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
collected 4 items
|
||||
|
||||
test_mark_classlevel.py ..
|
||||
|
||||
=================== 2 tests deselected by '-kTestClass' ====================
|
||||
================== 2 passed, 2 deselected in 0.01 seconds ==================
|
||||
|
||||
.. _`adding a custom marker from a plugin`:
|
||||
|
||||
@@ -223,7 +255,7 @@ the test needs::
|
||||
|
||||
$ py.test -E stage2
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 1 items
|
||||
|
||||
test_someenv.py s
|
||||
@@ -234,7 +266,7 @@ and here is one that specifies exactly the environment needed::
|
||||
|
||||
$ py.test -E stage1
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 1 items
|
||||
|
||||
test_someenv.py .
|
||||
@@ -351,12 +383,12 @@ 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 linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 4 items
|
||||
|
||||
test_plat.py s.s.
|
||||
========================= short test summary info ==========================
|
||||
SKIP [2] /tmp/doc-exec-57/conftest.py:12: cannot run on platform linux2
|
||||
SKIP [2] /tmp/doc-exec-273/conftest.py:12: cannot run on platform linux2
|
||||
|
||||
=================== 2 passed, 2 skipped in 0.01 seconds ====================
|
||||
|
||||
@@ -364,7 +396,7 @@ Note that if you specify a platform via the marker-command line option like this
|
||||
|
||||
$ py.test -m linux2
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 4 items
|
||||
|
||||
test_plat.py .
|
||||
@@ -373,3 +405,86 @@ Note that if you specify a platform via the marker-command line option like this
|
||||
================== 1 passed, 3 deselected in 0.01 seconds ==================
|
||||
|
||||
then the unmarked-tests will not be run. It is thus a way to restrict the run to the specific tests.
|
||||
|
||||
Automatically adding markers based on test names
|
||||
--------------------------------------------------------
|
||||
|
||||
.. regendoc:wipe
|
||||
|
||||
If you a test suite where test function names indicate a certain
|
||||
type of test, you can implement a hook that automatically defines
|
||||
markers so that you can use the ``-m`` option with it. Let's look
|
||||
at this test module::
|
||||
|
||||
# content of test_module.py
|
||||
|
||||
def test_interface_simple():
|
||||
assert 0
|
||||
|
||||
def test_interface_complex():
|
||||
assert 0
|
||||
|
||||
def test_event_simple():
|
||||
assert 0
|
||||
|
||||
def test_something_else():
|
||||
assert 0
|
||||
|
||||
We want to dynamically define two markers and can do it in a
|
||||
``conftest.py`` plugin::
|
||||
|
||||
# content of conftest.py
|
||||
|
||||
import pytest
|
||||
def pytest_collection_modifyitems(items):
|
||||
for item in items:
|
||||
if "interface" in item.nodeid:
|
||||
item.keywords["interface"] = pytest.mark.interface
|
||||
elif "event" in item.nodeid:
|
||||
item.keywords["event"] = pytest.mark.event
|
||||
|
||||
We can now use the ``-m option`` to select one set::
|
||||
|
||||
$ py.test -m interface --tb=short
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 4 items
|
||||
|
||||
test_module.py FF
|
||||
|
||||
================================= FAILURES =================================
|
||||
__________________________ test_interface_simple ___________________________
|
||||
test_module.py:3: in test_interface_simple
|
||||
> assert 0
|
||||
E assert 0
|
||||
__________________________ test_interface_complex __________________________
|
||||
test_module.py:6: in test_interface_complex
|
||||
> assert 0
|
||||
E assert 0
|
||||
================== 2 tests deselected by "-m 'interface'" ==================
|
||||
================== 2 failed, 2 deselected in 0.01 seconds ==================
|
||||
|
||||
or to select both "event" and "interface" tests::
|
||||
|
||||
$ py.test -m "interface or event" --tb=short
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 4 items
|
||||
|
||||
test_module.py FFF
|
||||
|
||||
================================= FAILURES =================================
|
||||
__________________________ test_interface_simple ___________________________
|
||||
test_module.py:3: in test_interface_simple
|
||||
> assert 0
|
||||
E assert 0
|
||||
__________________________ test_interface_complex __________________________
|
||||
test_module.py:6: in test_interface_complex
|
||||
> assert 0
|
||||
E assert 0
|
||||
____________________________ test_event_simple _____________________________
|
||||
test_module.py:9: in test_event_simple
|
||||
> assert 0
|
||||
E assert 0
|
||||
============= 1 tests deselected by "-m 'interface or event'" ==============
|
||||
================== 3 failed, 1 deselected in 0.02 seconds ==================
|
||||
|
||||
@@ -27,7 +27,7 @@ now execute the test specification::
|
||||
|
||||
nonpython $ py.test test_simple.yml
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 2 items
|
||||
|
||||
test_simple.yml .F
|
||||
@@ -37,7 +37,7 @@ now execute the test specification::
|
||||
usecase execution failed
|
||||
spec failed: 'some': 'other'
|
||||
no further details known at this point.
|
||||
==================== 1 failed, 1 passed in 0.04 seconds ====================
|
||||
==================== 1 failed, 1 passed in 0.05 seconds ====================
|
||||
|
||||
You get one dot for the passing ``sub1: sub1`` check and one failure.
|
||||
Obviously in the above ``conftest.py`` you'll want to implement a more
|
||||
@@ -56,7 +56,7 @@ consulted when reporting in ``verbose`` mode::
|
||||
|
||||
nonpython $ py.test -v
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3 -- /home/hpk/p/pytest/.tox/regen/bin/python
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5 -- /home/hpk/p/pytest/.tox/regen/bin/python
|
||||
collecting ... collected 2 items
|
||||
|
||||
test_simple.yml:1: usecase: ok PASSED
|
||||
@@ -67,17 +67,17 @@ consulted when reporting in ``verbose`` mode::
|
||||
usecase execution failed
|
||||
spec failed: 'some': 'other'
|
||||
no further details known at this point.
|
||||
==================== 1 failed, 1 passed in 0.04 seconds ====================
|
||||
==================== 1 failed, 1 passed in 0.05 seconds ====================
|
||||
|
||||
While developing your custom test collection and execution it's also
|
||||
interesting to just look at the collection tree::
|
||||
|
||||
nonpython $ py.test --collectonly
|
||||
nonpython $ py.test --collect-only
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 2 items
|
||||
<YamlFile 'test_simple.yml'>
|
||||
<YamlItem 'ok'>
|
||||
<YamlItem 'hello'>
|
||||
|
||||
============================= in 0.04 seconds =============================
|
||||
============================= in 0.05 seconds =============================
|
||||
|
||||
@@ -9,7 +9,7 @@ def pytest_collect_file(parent, path):
|
||||
class YamlFile(pytest.File):
|
||||
def collect(self):
|
||||
import yaml # we need a yaml parser, e.g. PyYAML
|
||||
raw = yaml.load(self.fspath.open())
|
||||
raw = yaml.safe_load(self.fspath.open())
|
||||
for name, spec in raw.items():
|
||||
yield YamlItem(name, self, spec)
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ this is a fully self-contained example which you can run with::
|
||||
|
||||
$ py.test test_scenarios.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 4 items
|
||||
|
||||
test_scenarios.py ....
|
||||
@@ -114,9 +114,9 @@ this is a fully self-contained example which you can run with::
|
||||
If you just collect tests you'll also nicely see 'advanced' and 'basic' as variants for the test function::
|
||||
|
||||
|
||||
$ py.test --collectonly test_scenarios.py
|
||||
$ py.test --collect-only test_scenarios.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 4 items
|
||||
<Module 'test_scenarios.py'>
|
||||
<Class 'TestSampleWithScenarios'>
|
||||
@@ -178,9 +178,9 @@ creates a database object for the actual test invocations::
|
||||
|
||||
Let's first see how it looks like at collection time::
|
||||
|
||||
$ py.test test_backends.py --collectonly
|
||||
$ py.test test_backends.py --collect-only
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 2 items
|
||||
<Module 'test_backends.py'>
|
||||
<Function 'test_db_initialized[d1]'>
|
||||
@@ -195,7 +195,7 @@ And then when we run the test::
|
||||
================================= FAILURES =================================
|
||||
_________________________ test_db_initialized[d2] __________________________
|
||||
|
||||
db = <conftest.DB2 instance at 0x1d8aef0>
|
||||
db = <conftest.DB2 instance at 0x2038f80>
|
||||
|
||||
def test_db_initialized(db):
|
||||
# a dummy test
|
||||
@@ -250,7 +250,7 @@ argument sets to use for each test function. Let's run it::
|
||||
================================= FAILURES =================================
|
||||
________________________ TestClass.test_equals[1-2] ________________________
|
||||
|
||||
self = <test_parametrize.TestClass instance at 0x1628cb0>, a = 1, b = 2
|
||||
self = <test_parametrize.TestClass instance at 0x1338f80>, a = 1, b = 2
|
||||
|
||||
def test_equals(self, a, b):
|
||||
> assert a == b
|
||||
@@ -278,3 +278,73 @@ Running it results in some skips if we don't have all the python interpreters in
|
||||
............sss............sss............sss............ssssssssssssssssss
|
||||
========================= short test summary info ==========================
|
||||
SKIP [27] /home/hpk/p/pytest/doc/en/example/multipython.py:21: 'python2.8' not found
|
||||
|
||||
Indirect parametrization of optional implementations/imports
|
||||
--------------------------------------------------------------------
|
||||
|
||||
If you want to compare the outcomes of several implementations of a given
|
||||
API, you can write test functions that receive the already imported implementations
|
||||
and get skipped in case the implementation is not importable/available. Let's
|
||||
say we have a "base" implementation and the other (possibly optimized ones)
|
||||
need to provide similar results::
|
||||
|
||||
# content of conftest.py
|
||||
|
||||
import pytest
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def basemod(request):
|
||||
return pytest.importorskip("base")
|
||||
|
||||
@pytest.fixture(scope="session", params=["opt1", "opt2"])
|
||||
def optmod(request):
|
||||
return pytest.importorskip(request.param)
|
||||
|
||||
And then a base implementation of a simple function::
|
||||
|
||||
# content of base.py
|
||||
def func1():
|
||||
return 1
|
||||
|
||||
And an optimized version::
|
||||
|
||||
# content of opt1.py
|
||||
def func1():
|
||||
return 1.0001
|
||||
|
||||
And finally a little test module::
|
||||
|
||||
# content of test_module.py
|
||||
|
||||
def test_func1(basemod, optmod):
|
||||
assert round(basemod.func1(), 3) == round(optmod.func1(), 3)
|
||||
|
||||
|
||||
If you run this with reporting for skips enabled::
|
||||
|
||||
$ py.test -rs test_module.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 2 items
|
||||
|
||||
test_module.py .s
|
||||
========================= short test summary info ==========================
|
||||
SKIP [1] /tmp/doc-exec-275/conftest.py:10: could not import 'opt2'
|
||||
|
||||
=================== 1 passed, 1 skipped in 0.01 seconds ====================
|
||||
|
||||
You'll see that we don't have a ``opt2`` module and thus the second test run
|
||||
of our ``test_func1`` was skipped. A few notes:
|
||||
|
||||
- the fixture functions in the ``conftest.py`` file are "session-scoped" because we
|
||||
don't need to import more than once
|
||||
|
||||
- if you have multiple test functions and a skipped import, you will see
|
||||
the ``[1]`` count increasing in the report
|
||||
|
||||
- you can put :ref:`@pytest.mark.parametrize <@pytest.mark.parametrize>` style
|
||||
parametrization on the test functions to parametrize input/output
|
||||
values as well.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
# run this with $ py.test --collectonly test_collectonly.py
|
||||
# run this with $ py.test --collect-only test_collectonly.py
|
||||
#
|
||||
def test_function():
|
||||
pass
|
||||
|
||||
@@ -41,9 +41,9 @@ in functions and classes. For example, if we have::
|
||||
|
||||
then the test collection looks like this::
|
||||
|
||||
$ py.test --collectonly
|
||||
$ py.test --collect-only
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 2 items
|
||||
<Module 'check_myapp.py'>
|
||||
<Class 'CheckMyApp'>
|
||||
@@ -80,9 +80,9 @@ Finding out what is collected
|
||||
|
||||
You can always peek at the collection tree without running tests like this::
|
||||
|
||||
. $ py.test --collectonly pythoncollection.py
|
||||
. $ py.test --collect-only pythoncollection.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 3 items
|
||||
<Module 'pythoncollection.py'>
|
||||
<Function 'test_function'>
|
||||
@@ -133,9 +133,9 @@ and a setup.py dummy file like this::
|
||||
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 --collectonly
|
||||
$ py.test --collect-only
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 1 items
|
||||
<Module 'pkg/module_py2.py'>
|
||||
<Function 'test_only_on_python2'>
|
||||
|
||||
@@ -13,7 +13,7 @@ get on the terminal - we are working on that):
|
||||
|
||||
assertion $ py.test failure_demo.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 39 items
|
||||
|
||||
failure_demo.py FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
|
||||
@@ -30,7 +30,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:15: AssertionError
|
||||
_________________________ TestFailing.test_simple __________________________
|
||||
|
||||
self = <failure_demo.TestFailing object at 0x1136710>
|
||||
self = <failure_demo.TestFailing object at 0x1445e10>
|
||||
|
||||
def test_simple(self):
|
||||
def f():
|
||||
@@ -40,13 +40,13 @@ get on the terminal - we are working on that):
|
||||
|
||||
> assert f() == g()
|
||||
E assert 42 == 43
|
||||
E + where 42 = <function f at 0x1146410>()
|
||||
E + and 43 = <function g at 0x1146488>()
|
||||
E + where 42 = <function f at 0x137c6e0>()
|
||||
E + and 43 = <function g at 0x137c758>()
|
||||
|
||||
failure_demo.py:28: AssertionError
|
||||
____________________ TestFailing.test_simple_multiline _____________________
|
||||
|
||||
self = <failure_demo.TestFailing object at 0x11329d0>
|
||||
self = <failure_demo.TestFailing object at 0x135a1d0>
|
||||
|
||||
def test_simple_multiline(self):
|
||||
otherfunc_multi(
|
||||
@@ -66,19 +66,19 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:11: AssertionError
|
||||
___________________________ TestFailing.test_not ___________________________
|
||||
|
||||
self = <failure_demo.TestFailing object at 0x10d09d0>
|
||||
self = <failure_demo.TestFailing object at 0x1458ed0>
|
||||
|
||||
def test_not(self):
|
||||
def f():
|
||||
return 42
|
||||
> assert not f()
|
||||
E assert not 42
|
||||
E + where 42 = <function f at 0x1146848>()
|
||||
E + where 42 = <function f at 0x137caa0>()
|
||||
|
||||
failure_demo.py:38: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_eq_text _________________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x10ca210>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x14451d0>
|
||||
|
||||
def test_eq_text(self):
|
||||
> assert 'spam' == 'eggs'
|
||||
@@ -89,7 +89,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:42: AssertionError
|
||||
_____________ TestSpecialisedExplanations.test_eq_similar_text _____________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x11368d0>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x1458c90>
|
||||
|
||||
def test_eq_similar_text(self):
|
||||
> assert 'foo 1 bar' == 'foo 2 bar'
|
||||
@@ -102,7 +102,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:45: AssertionError
|
||||
____________ TestSpecialisedExplanations.test_eq_multiline_text ____________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x11340d0>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x1434390>
|
||||
|
||||
def test_eq_multiline_text(self):
|
||||
> assert 'foo\nspam\nbar' == 'foo\neggs\nbar'
|
||||
@@ -115,15 +115,15 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:48: AssertionError
|
||||
______________ TestSpecialisedExplanations.test_eq_long_text _______________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x10cfd90>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x1459f50>
|
||||
|
||||
def test_eq_long_text(self):
|
||||
a = '1'*100 + 'a' + '2'*100
|
||||
b = '1'*100 + 'b' + '2'*100
|
||||
> assert a == b
|
||||
E assert '111111111111...2222222222222' == '1111111111111...2222222222222'
|
||||
E Skipping 90 identical leading characters in diff
|
||||
E Skipping 91 identical trailing characters in diff
|
||||
E Skipping 90 identical leading characters in diff, use -v to show
|
||||
E Skipping 91 identical trailing characters in diff, use -v to show
|
||||
E - 1111111111a222222222
|
||||
E ? ^
|
||||
E + 1111111111b222222222
|
||||
@@ -132,15 +132,15 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:53: AssertionError
|
||||
_________ TestSpecialisedExplanations.test_eq_long_text_multiline __________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x10d0b10>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x135a790>
|
||||
|
||||
def test_eq_long_text_multiline(self):
|
||||
a = '1\n'*100 + 'a' + '2\n'*100
|
||||
b = '1\n'*100 + 'b' + '2\n'*100
|
||||
> assert a == b
|
||||
E assert '1\n1\n1\n1\n...n2\n2\n2\n2\n' == '1\n1\n1\n1\n1...n2\n2\n2\n2\n'
|
||||
E Skipping 190 identical leading characters in diff
|
||||
E Skipping 191 identical trailing characters in diff
|
||||
E Skipping 190 identical leading characters in diff, use -v to show
|
||||
E Skipping 191 identical trailing characters in diff, use -v to show
|
||||
E 1
|
||||
E 1
|
||||
E 1
|
||||
@@ -156,7 +156,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:58: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_eq_list _________________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x1142dd0>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x138dfd0>
|
||||
|
||||
def test_eq_list(self):
|
||||
> assert [0, 1, 2] == [0, 1, 3]
|
||||
@@ -166,7 +166,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:61: AssertionError
|
||||
______________ TestSpecialisedExplanations.test_eq_list_long _______________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x1136850>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x135a990>
|
||||
|
||||
def test_eq_list_long(self):
|
||||
a = [0]*100 + [1] + [3]*100
|
||||
@@ -178,20 +178,23 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:66: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_eq_dict _________________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x1134e10>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x1459310>
|
||||
|
||||
def test_eq_dict(self):
|
||||
> assert {'a': 0, 'b': 1} == {'a': 0, 'b': 2}
|
||||
E assert {'a': 0, 'b': 1} == {'a': 0, 'b': 2}
|
||||
E - {'a': 0, 'b': 1}
|
||||
E ? ^
|
||||
E + {'a': 0, 'b': 2}
|
||||
E ? ^
|
||||
> assert {'a': 0, 'b': 1, 'c': 0} == {'a': 0, 'b': 2, 'd': 0}
|
||||
E assert {'a': 0, 'b': 1, 'c': 0} == {'a': 0, 'b': 2, 'd': 0}
|
||||
E Hiding 1 identical items, use -v to show
|
||||
E Differing items:
|
||||
E {'b': 1} != {'b': 2}
|
||||
E Left contains more items:
|
||||
E {'c': 0}
|
||||
E Right contains more items:
|
||||
E {'d': 0}
|
||||
|
||||
failure_demo.py:69: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_eq_set __________________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x1169c90>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x1434310>
|
||||
|
||||
def test_eq_set(self):
|
||||
> assert set([0, 10, 11, 12]) == set([0, 20, 21])
|
||||
@@ -207,7 +210,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:72: AssertionError
|
||||
_____________ TestSpecialisedExplanations.test_eq_longer_list ______________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x1142c50>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x138ded0>
|
||||
|
||||
def test_eq_longer_list(self):
|
||||
> assert [1,2] == [1,2,3]
|
||||
@@ -217,7 +220,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:75: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_in_list _________________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x10d0d90>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x1459e10>
|
||||
|
||||
def test_in_list(self):
|
||||
> assert 1 in [0, 2, 3, 4, 5]
|
||||
@@ -226,7 +229,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:78: AssertionError
|
||||
__________ TestSpecialisedExplanations.test_not_in_text_multiline __________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x10e0110>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x1434950>
|
||||
|
||||
def test_not_in_text_multiline(self):
|
||||
text = 'some multiline\ntext\nwhich\nincludes foo\nand a\ntail'
|
||||
@@ -244,7 +247,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:82: AssertionError
|
||||
___________ TestSpecialisedExplanations.test_not_in_text_single ____________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x10ca7d0>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x138dbd0>
|
||||
|
||||
def test_not_in_text_single(self):
|
||||
text = 'single foo line'
|
||||
@@ -257,7 +260,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:86: AssertionError
|
||||
_________ TestSpecialisedExplanations.test_not_in_text_single_long _________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x1142750>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x14593d0>
|
||||
|
||||
def test_not_in_text_single_long(self):
|
||||
text = 'head ' * 50 + 'foo ' + 'tail ' * 20
|
||||
@@ -270,7 +273,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:90: AssertionError
|
||||
______ TestSpecialisedExplanations.test_not_in_text_single_long_term _______
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x1134410>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x1459650>
|
||||
|
||||
def test_not_in_text_single_long_term(self):
|
||||
text = 'head ' * 50 + 'f'*70 + 'tail ' * 20
|
||||
@@ -289,7 +292,7 @@ get on the terminal - we are working on that):
|
||||
i = Foo()
|
||||
> assert i.b == 2
|
||||
E assert 1 == 2
|
||||
E + where 1 = <failure_demo.Foo object at 0x10e07d0>.b
|
||||
E + where 1 = <failure_demo.Foo object at 0x1434850>.b
|
||||
|
||||
failure_demo.py:101: AssertionError
|
||||
_________________________ test_attribute_instance __________________________
|
||||
@@ -299,8 +302,8 @@ get on the terminal - we are working on that):
|
||||
b = 1
|
||||
> assert Foo().b == 2
|
||||
E assert 1 == 2
|
||||
E + where 1 = <failure_demo.Foo object at 0x1132390>.b
|
||||
E + where <failure_demo.Foo object at 0x1132390> = <class 'failure_demo.Foo'>()
|
||||
E + where 1 = <failure_demo.Foo object at 0x1459dd0>.b
|
||||
E + where <failure_demo.Foo object at 0x1459dd0> = <class 'failure_demo.Foo'>()
|
||||
|
||||
failure_demo.py:107: AssertionError
|
||||
__________________________ test_attribute_failure __________________________
|
||||
@@ -316,7 +319,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:116:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
self = <failure_demo.Foo object at 0x1136fd0>
|
||||
self = <failure_demo.Foo object at 0x1434150>
|
||||
|
||||
def _get_b(self):
|
||||
> raise Exception('Failed to get attrib')
|
||||
@@ -332,15 +335,15 @@ get on the terminal - we are working on that):
|
||||
b = 2
|
||||
> assert Foo().b == Bar().b
|
||||
E assert 1 == 2
|
||||
E + where 1 = <failure_demo.Foo object at 0x1134c50>.b
|
||||
E + where <failure_demo.Foo object at 0x1134c50> = <class 'failure_demo.Foo'>()
|
||||
E + and 2 = <failure_demo.Bar object at 0x1134790>.b
|
||||
E + where <failure_demo.Bar object at 0x1134790> = <class 'failure_demo.Bar'>()
|
||||
E + where 1 = <failure_demo.Foo object at 0x14590d0>.b
|
||||
E + where <failure_demo.Foo object at 0x14590d0> = <class 'failure_demo.Foo'>()
|
||||
E + and 2 = <failure_demo.Bar object at 0x1459b10>.b
|
||||
E + where <failure_demo.Bar object at 0x1459b10> = <class 'failure_demo.Bar'>()
|
||||
|
||||
failure_demo.py:124: AssertionError
|
||||
__________________________ TestRaises.test_raises __________________________
|
||||
|
||||
self = <failure_demo.TestRaises instance at 0x10dc098>
|
||||
self = <failure_demo.TestRaises instance at 0x13a0d88>
|
||||
|
||||
def test_raises(self):
|
||||
s = 'qwe'
|
||||
@@ -352,10 +355,10 @@ get on the terminal - we are working on that):
|
||||
> int(s)
|
||||
E ValueError: invalid literal for int() with base 10: 'qwe'
|
||||
|
||||
<0-codegen /home/hpk/p/pytest/.tox/regen/lib/python2.7/site-packages/_pytest/python.py:851>:1: ValueError
|
||||
<0-codegen /home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/python.py:858>:1: ValueError
|
||||
______________________ TestRaises.test_raises_doesnt _______________________
|
||||
|
||||
self = <failure_demo.TestRaises instance at 0x10d8320>
|
||||
self = <failure_demo.TestRaises instance at 0x145fcf8>
|
||||
|
||||
def test_raises_doesnt(self):
|
||||
> raises(IOError, "int('3')")
|
||||
@@ -364,7 +367,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:136: Failed
|
||||
__________________________ TestRaises.test_raise ___________________________
|
||||
|
||||
self = <failure_demo.TestRaises instance at 0x10c0680>
|
||||
self = <failure_demo.TestRaises instance at 0x13a9ea8>
|
||||
|
||||
def test_raise(self):
|
||||
> raise ValueError("demo error")
|
||||
@@ -373,7 +376,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:139: ValueError
|
||||
________________________ TestRaises.test_tupleerror ________________________
|
||||
|
||||
self = <failure_demo.TestRaises instance at 0x11604d0>
|
||||
self = <failure_demo.TestRaises instance at 0x13843f8>
|
||||
|
||||
def test_tupleerror(self):
|
||||
> a,b = [1]
|
||||
@@ -382,7 +385,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:142: ValueError
|
||||
______ TestRaises.test_reinterpret_fails_with_print_for_the_fun_of_it ______
|
||||
|
||||
self = <failure_demo.TestRaises instance at 0x10e2290>
|
||||
self = <failure_demo.TestRaises instance at 0x14532d8>
|
||||
|
||||
def test_reinterpret_fails_with_print_for_the_fun_of_it(self):
|
||||
l = [1,2,3]
|
||||
@@ -395,7 +398,7 @@ get on the terminal - we are working on that):
|
||||
l is [1, 2, 3]
|
||||
________________________ TestRaises.test_some_error ________________________
|
||||
|
||||
self = <failure_demo.TestRaises instance at 0x10e2f80>
|
||||
self = <failure_demo.TestRaises instance at 0x139d290>
|
||||
|
||||
def test_some_error(self):
|
||||
> if namenotexi:
|
||||
@@ -423,7 +426,7 @@ get on the terminal - we are working on that):
|
||||
<2-codegen 'abc-123' /home/hpk/p/pytest/doc/en/example/assertion/failure_demo.py:162>:2: AssertionError
|
||||
____________________ TestMoreErrors.test_complex_error _____________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors instance at 0x10d1b90>
|
||||
self = <failure_demo.TestMoreErrors instance at 0x137d758>
|
||||
|
||||
def test_complex_error(self):
|
||||
def f():
|
||||
@@ -452,7 +455,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:5: AssertionError
|
||||
___________________ TestMoreErrors.test_z1_unpack_error ____________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors instance at 0x114f3b0>
|
||||
self = <failure_demo.TestMoreErrors instance at 0x13a5200>
|
||||
|
||||
def test_z1_unpack_error(self):
|
||||
l = []
|
||||
@@ -462,7 +465,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:179: ValueError
|
||||
____________________ TestMoreErrors.test_z2_type_error _____________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors instance at 0x11496c8>
|
||||
self = <failure_demo.TestMoreErrors instance at 0x1395290>
|
||||
|
||||
def test_z2_type_error(self):
|
||||
l = 3
|
||||
@@ -472,19 +475,19 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:183: TypeError
|
||||
______________________ TestMoreErrors.test_startswith ______________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors instance at 0x10cec20>
|
||||
self = <failure_demo.TestMoreErrors instance at 0x137f200>
|
||||
|
||||
def test_startswith(self):
|
||||
s = "123"
|
||||
g = "456"
|
||||
> assert s.startswith(g)
|
||||
E assert <built-in method startswith of str object at 0x113b918>('456')
|
||||
E + where <built-in method startswith of str object at 0x113b918> = '123'.startswith
|
||||
E assert <built-in method startswith of str object at 0x143f288>('456')
|
||||
E + where <built-in method startswith of str object at 0x143f288> = '123'.startswith
|
||||
|
||||
failure_demo.py:188: AssertionError
|
||||
__________________ TestMoreErrors.test_startswith_nested ___________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors instance at 0x10c87a0>
|
||||
self = <failure_demo.TestMoreErrors instance at 0x145fb00>
|
||||
|
||||
def test_startswith_nested(self):
|
||||
def f():
|
||||
@@ -492,15 +495,15 @@ get on the terminal - we are working on that):
|
||||
def g():
|
||||
return "456"
|
||||
> assert f().startswith(g())
|
||||
E assert <built-in method startswith of str object at 0x113b918>('456')
|
||||
E + where <built-in method startswith of str object at 0x113b918> = '123'.startswith
|
||||
E + where '123' = <function f at 0x10bea28>()
|
||||
E + and '456' = <function g at 0x10beaa0>()
|
||||
E assert <built-in method startswith of str object at 0x143f288>('456')
|
||||
E + where <built-in method startswith of str object at 0x143f288> = '123'.startswith
|
||||
E + where '123' = <function f at 0x13abaa0>()
|
||||
E + and '456' = <function g at 0x13ab578>()
|
||||
|
||||
failure_demo.py:195: AssertionError
|
||||
_____________________ TestMoreErrors.test_global_func ______________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors instance at 0x10c5488>
|
||||
self = <failure_demo.TestMoreErrors instance at 0x139cd40>
|
||||
|
||||
def test_global_func(self):
|
||||
> assert isinstance(globf(42), float)
|
||||
@@ -510,18 +513,18 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:198: AssertionError
|
||||
_______________________ TestMoreErrors.test_instance _______________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors instance at 0x113f710>
|
||||
self = <failure_demo.TestMoreErrors instance at 0x13593b0>
|
||||
|
||||
def test_instance(self):
|
||||
self.x = 6*7
|
||||
> assert self.x != 42
|
||||
E assert 42 != 42
|
||||
E + where 42 = <failure_demo.TestMoreErrors instance at 0x113f710>.x
|
||||
E + where 42 = <failure_demo.TestMoreErrors instance at 0x13593b0>.x
|
||||
|
||||
failure_demo.py:202: AssertionError
|
||||
_______________________ TestMoreErrors.test_compare ________________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors instance at 0x10bae18>
|
||||
self = <failure_demo.TestMoreErrors instance at 0x1465d40>
|
||||
|
||||
def test_compare(self):
|
||||
> assert globf(10) < 5
|
||||
@@ -531,7 +534,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:205: AssertionError
|
||||
_____________________ TestMoreErrors.test_try_finally ______________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors instance at 0x1160248>
|
||||
self = <failure_demo.TestMoreErrors instance at 0x1456ea8>
|
||||
|
||||
def test_try_finally(self):
|
||||
x = 1
|
||||
@@ -540,4 +543,4 @@ get on the terminal - we are working on that):
|
||||
E assert 1 == 0
|
||||
|
||||
failure_demo.py:210: AssertionError
|
||||
======================== 39 failed in 0.25 seconds =========================
|
||||
======================== 39 failed in 0.21 seconds =========================
|
||||
|
||||
@@ -106,7 +106,7 @@ directory with the above conftest.py::
|
||||
|
||||
$ py.test
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 0 items
|
||||
|
||||
============================= in 0.00 seconds =============================
|
||||
@@ -150,12 +150,12 @@ 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 linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 2 items
|
||||
|
||||
test_module.py .s
|
||||
========================= short test summary info ==========================
|
||||
SKIP [1] /tmp/doc-exec-62/conftest.py:9: need --runslow option to run
|
||||
SKIP [1] /tmp/doc-exec-278/conftest.py:9: need --runslow option to run
|
||||
|
||||
=================== 1 passed, 1 skipped in 0.01 seconds ====================
|
||||
|
||||
@@ -163,7 +163,7 @@ Or run it including the ``slow`` marked test::
|
||||
|
||||
$ py.test --runslow
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 2 items
|
||||
|
||||
test_module.py ..
|
||||
@@ -253,7 +253,7 @@ which will add the string to the test header accordingly::
|
||||
|
||||
$ py.test
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
project deps: mylib-1.1
|
||||
collected 0 items
|
||||
|
||||
@@ -276,7 +276,7 @@ which will add info only when run with "--v"::
|
||||
|
||||
$ py.test -v
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3 -- /home/hpk/p/pytest/.tox/regen/bin/python
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5 -- /home/hpk/p/pytest/.tox/regen/bin/python
|
||||
info1: did you know that ...
|
||||
did you?
|
||||
collecting ... collected 0 items
|
||||
@@ -287,7 +287,7 @@ and nothing when run plainly::
|
||||
|
||||
$ py.test
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 0 items
|
||||
|
||||
============================= in 0.00 seconds =============================
|
||||
@@ -319,7 +319,7 @@ Now we can profile which test functions execute the slowest::
|
||||
|
||||
$ py.test --durations=3
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 3 items
|
||||
|
||||
test_some_are_slow.py ...
|
||||
@@ -327,7 +327,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 call test_some_are_slow.py::test_funcfast
|
||||
0.00s setup test_some_are_slow.py::test_funcfast
|
||||
========================= 3 passed in 0.31 seconds =========================
|
||||
|
||||
incremental testing - test steps
|
||||
@@ -380,7 +380,7 @@ If we run this::
|
||||
|
||||
$ py.test -rx
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 4 items
|
||||
|
||||
test_step.py .Fx.
|
||||
@@ -388,7 +388,7 @@ If we run this::
|
||||
================================= FAILURES =================================
|
||||
____________________ TestUserHandling.test_modification ____________________
|
||||
|
||||
self = <test_step.TestUserHandling instance at 0x2677b90>
|
||||
self = <test_step.TestUserHandling instance at 0x282b8c0>
|
||||
|
||||
def test_modification(self):
|
||||
> assert 0
|
||||
@@ -398,8 +398,283 @@ If we run this::
|
||||
========================= 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.02 seconds ===============
|
||||
============== 1 failed, 2 passed, 1 xfailed in 0.01 seconds ===============
|
||||
|
||||
We'll see that ``test_deletion`` was not executed because ``test_modification``
|
||||
failed. It is reported as an "expected failure".
|
||||
|
||||
|
||||
Package/Directory-level fixtures (setups)
|
||||
-------------------------------------------------------
|
||||
|
||||
If you have nested test directories, you can have per-directory fixture scopes
|
||||
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
|
||||
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::
|
||||
|
||||
# content of a/conftest.py
|
||||
import pytest
|
||||
|
||||
class DB:
|
||||
pass
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def db():
|
||||
return DB()
|
||||
|
||||
and then a test module in that directory::
|
||||
|
||||
# content of a/test_db.py
|
||||
def test_a1(db):
|
||||
assert 0, db # to show value
|
||||
|
||||
another test module::
|
||||
|
||||
# content of a/test_db2.py
|
||||
def test_a2(db):
|
||||
assert 0, db # to show value
|
||||
|
||||
and then a module in a sister directory which will not see
|
||||
the ``db`` fixture::
|
||||
|
||||
# content of b/test_error.py
|
||||
def test_root(db): # no db here, will error out
|
||||
pass
|
||||
|
||||
We can run this::
|
||||
|
||||
$ py.test
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 7 items
|
||||
|
||||
test_step.py .Fx.
|
||||
a/test_db.py F
|
||||
a/test_db2.py F
|
||||
b/test_error.py E
|
||||
|
||||
================================== ERRORS ==================================
|
||||
_______________________ ERROR at setup of test_root ________________________
|
||||
file /tmp/doc-exec-278/b/test_error.py, line 1
|
||||
def test_root(db): # no db here, will error out
|
||||
fixture 'db' not found
|
||||
available fixtures: pytestconfig, recwarn, monkeypatch, capfd, capsys, tmpdir
|
||||
use 'py.test --fixtures [testpath]' for help on them.
|
||||
|
||||
/tmp/doc-exec-278/b/test_error.py:1
|
||||
================================= FAILURES =================================
|
||||
____________________ TestUserHandling.test_modification ____________________
|
||||
|
||||
self = <test_step.TestUserHandling instance at 0x26145f0>
|
||||
|
||||
def test_modification(self):
|
||||
> assert 0
|
||||
E assert 0
|
||||
|
||||
test_step.py:9: AssertionError
|
||||
_________________________________ test_a1 __________________________________
|
||||
|
||||
db = <conftest.DB instance at 0x26211b8>
|
||||
|
||||
def test_a1(db):
|
||||
> assert 0, db # to show value
|
||||
E AssertionError: <conftest.DB instance at 0x26211b8>
|
||||
|
||||
a/test_db.py:2: AssertionError
|
||||
_________________________________ test_a2 __________________________________
|
||||
|
||||
db = <conftest.DB instance at 0x26211b8>
|
||||
|
||||
def test_a2(db):
|
||||
> assert 0, db # to show value
|
||||
E AssertionError: <conftest.DB instance at 0x26211b8>
|
||||
|
||||
a/test_db2.py:2: AssertionError
|
||||
========== 3 failed, 2 passed, 1 xfailed, 1 error in 0.03 seconds ==========
|
||||
|
||||
The two test modules in the ``a`` directory see the same ``db`` fixture instance
|
||||
while the one test in the sister-directory ``b`` doesn't see it. We could of course
|
||||
also define a ``db`` fixture in that sister directory's ``conftest.py`` file.
|
||||
Note that each fixture is only instantiated if there is a test actually needing
|
||||
it (unless you use "autouse" fixture which are always executed ahead of the first test
|
||||
executing).
|
||||
|
||||
|
||||
post-process test reports / failures
|
||||
---------------------------------------
|
||||
|
||||
If you want to postprocess test reports and need access to the executing
|
||||
environment you can implement a hook that gets called when the test
|
||||
"report" object is about to be created. Here we write out all failing
|
||||
test calls and also access a fixture (if it was used by the test) in
|
||||
case you want to query/look at it during your post processing. In our
|
||||
case we just write some informations out to a ``failures`` file::
|
||||
|
||||
# content of conftest.py
|
||||
|
||||
import pytest
|
||||
import os.path
|
||||
|
||||
@pytest.mark.tryfirst
|
||||
def pytest_runtest_makereport(item, call, __multicall__):
|
||||
# execute all other hooks to obtain the report object
|
||||
rep = __multicall__.execute()
|
||||
|
||||
# we only look at actual failing test calls, not setup/teardown
|
||||
if rep.when == "call" and rep.failed:
|
||||
mode = "a" if os.path.exists("failures") else "w"
|
||||
with open("failures", mode) as f:
|
||||
# let's also access a fixture for the fun of it
|
||||
if "tmpdir" in item.funcargs:
|
||||
extra = " (%s)" % item.funcargs["tmpdir"]
|
||||
else:
|
||||
extra = ""
|
||||
|
||||
f.write(rep.nodeid + extra + "\n")
|
||||
return rep
|
||||
|
||||
if you then have failing tests::
|
||||
|
||||
# content of test_module.py
|
||||
def test_fail1(tmpdir):
|
||||
assert 0
|
||||
def test_fail2():
|
||||
assert 0
|
||||
|
||||
and run them::
|
||||
|
||||
$ py.test test_module.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 2 items
|
||||
|
||||
test_module.py FF
|
||||
|
||||
================================= FAILURES =================================
|
||||
________________________________ test_fail1 ________________________________
|
||||
|
||||
tmpdir = local('/tmp/pytest-326/test_fail10')
|
||||
|
||||
def test_fail1(tmpdir):
|
||||
> assert 0
|
||||
E assert 0
|
||||
|
||||
test_module.py:2: AssertionError
|
||||
________________________________ test_fail2 ________________________________
|
||||
|
||||
def test_fail2():
|
||||
> assert 0
|
||||
E assert 0
|
||||
|
||||
test_module.py:4: AssertionError
|
||||
========================= 2 failed in 0.02 seconds =========================
|
||||
|
||||
you will have a "failures" file which contains the failing test ids::
|
||||
|
||||
$ cat failures
|
||||
test_module.py::test_fail1 (/tmp/pytest-326/test_fail10)
|
||||
test_module.py::test_fail2
|
||||
|
||||
Making test result information available in fixtures
|
||||
-----------------------------------------------------------
|
||||
|
||||
.. regendoc:wipe
|
||||
|
||||
If you want to make test result reports available in fixture finalizers
|
||||
here is a little example implemented via a local plugin::
|
||||
|
||||
# content of conftest.py
|
||||
|
||||
import pytest
|
||||
|
||||
@pytest.mark.tryfirst
|
||||
def pytest_runtest_makereport(item, call, __multicall__):
|
||||
# execute all other hooks to obtain the report object
|
||||
rep = __multicall__.execute()
|
||||
|
||||
# set an report attribute for each phase of a call, which can
|
||||
# be "setup", "call", "teardown"
|
||||
|
||||
setattr(item, "rep_" + rep.when, rep)
|
||||
return rep
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def something(request):
|
||||
def fin():
|
||||
# request.node is an "item" because we use the default
|
||||
# "function" scope
|
||||
if request.node.rep_setup.failed:
|
||||
print "setting up a test failed!", request.node.nodeid
|
||||
elif request.node.rep_setup.passed:
|
||||
if request.node.rep_call.failed:
|
||||
print "executing test failed", request.node.nodeid
|
||||
request.addfinalizer(fin)
|
||||
|
||||
|
||||
if you then have failing tests::
|
||||
|
||||
# content of test_module.py
|
||||
|
||||
import pytest
|
||||
|
||||
@pytest.fixture
|
||||
def other():
|
||||
assert 0
|
||||
|
||||
def test_setup_fails(something, other):
|
||||
pass
|
||||
|
||||
def test_call_fails(something):
|
||||
assert 0
|
||||
|
||||
def test_fail2():
|
||||
assert 0
|
||||
|
||||
and run it::
|
||||
|
||||
$ py.test -s test_module.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 3 items
|
||||
|
||||
test_module.py EFF
|
||||
|
||||
================================== ERRORS ==================================
|
||||
____________________ ERROR at setup of test_setup_fails ____________________
|
||||
|
||||
@pytest.fixture
|
||||
def other():
|
||||
> assert 0
|
||||
E assert 0
|
||||
|
||||
test_module.py:6: AssertionError
|
||||
================================= FAILURES =================================
|
||||
_____________________________ test_call_fails ______________________________
|
||||
|
||||
something = None
|
||||
|
||||
def test_call_fails(something):
|
||||
> assert 0
|
||||
E assert 0
|
||||
|
||||
test_module.py:12: AssertionError
|
||||
________________________________ test_fail2 ________________________________
|
||||
|
||||
def test_fail2():
|
||||
> assert 0
|
||||
E assert 0
|
||||
|
||||
test_module.py:15: AssertionError
|
||||
==================== 2 failed, 1 error in 0.01 seconds =====================
|
||||
setting up a test failed! test_module.py::test_setup_fails
|
||||
executing test failed test_module.py::test_call_fails
|
||||
|
||||
You'll see that the fixture finalizers could use the precise reporting
|
||||
information.
|
||||
|
||||
|
||||
72
doc/en/example/special.txt
Normal file
72
doc/en/example/special.txt
Normal file
@@ -0,0 +1,72 @@
|
||||
|
||||
A sesssion-fixture which can look at all collected tests
|
||||
----------------------------------------------------------------
|
||||
|
||||
A session-scoped fixture effectively has access to all
|
||||
collected test items. Here is an example of a fixture
|
||||
function which walks all collected tests and looks
|
||||
if their test class defines a ``callme`` method and
|
||||
calls it::
|
||||
|
||||
# content of conftest.py
|
||||
|
||||
import pytest
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def callattr_ahead_of_alltests(request):
|
||||
print "callattr_ahead_of_alltests called"
|
||||
seen = set([None])
|
||||
session = request.node
|
||||
for item in session.items:
|
||||
cls = item.getparent(pytest.Class)
|
||||
if cls not in seen:
|
||||
if hasattr(cls.obj, "callme"):
|
||||
cls.obj.callme()
|
||||
seen.add(cls)
|
||||
|
||||
test classes may now define a ``callme`` method which
|
||||
will be called ahead of running any tests::
|
||||
|
||||
# content of test_module.py
|
||||
|
||||
class TestHello:
|
||||
@classmethod
|
||||
def callme(cls):
|
||||
print "callme called!"
|
||||
|
||||
def test_method1(self):
|
||||
print "test_method1 called"
|
||||
|
||||
def test_method2(self):
|
||||
print "test_method1 called"
|
||||
|
||||
class TestOther:
|
||||
@classmethod
|
||||
def callme(cls):
|
||||
print "callme other called"
|
||||
def test_other(self):
|
||||
print "test other"
|
||||
|
||||
# works with unittest as well ...
|
||||
import unittest
|
||||
|
||||
class SomeTest(unittest.TestCase):
|
||||
@classmethod
|
||||
def callme(self):
|
||||
print "SomeTest callme called"
|
||||
|
||||
def test_unit1(self):
|
||||
print "test_unit1 method called"
|
||||
|
||||
If you run this without output capturing::
|
||||
|
||||
$ py.test -q -s test_module.py
|
||||
....
|
||||
callattr_ahead_of_alltests called
|
||||
callme called!
|
||||
callme other called
|
||||
SomeTest callme called
|
||||
test_method1 called
|
||||
test_method1 called
|
||||
test other
|
||||
test_unit1 method called
|
||||
@@ -29,7 +29,7 @@ and does not handle Deferreds returned from a test in pytest style.
|
||||
If you are using trial's unittest.TestCase chances are that you can
|
||||
just run your tests even if you return Deferreds. In addition,
|
||||
there also is a dedicated `pytest-twisted
|
||||
<http://pypi.python.org/pypi/pytest-twisted`` plugin which allows to
|
||||
<http://pypi.python.org/pypi/pytest-twisted>`_ plugin which allows to
|
||||
return deferreds from pytest-style tests, allowing to use
|
||||
:ref:`fixtures` and other features.
|
||||
|
||||
@@ -171,16 +171,8 @@ Unfortunately, setuptools-0.6.11 does not ``if __name__=='__main__'``
|
||||
protect its generated command line script. This leads to infinite
|
||||
recursion when running a test that instantiates Processes.
|
||||
|
||||
A good solution is to `install Distribute`_ as a drop-in replacement
|
||||
for setuptools and then re-install ``pytest``. Otherwise you could
|
||||
fix the script that is created by setuptools by inserting an
|
||||
``if __name__ == '__main__'``. Or you can create a "pytest.py"
|
||||
script with this content and invoke that with the python version::
|
||||
|
||||
import pytest
|
||||
if __name__ == '__main__':
|
||||
pytest.main()
|
||||
|
||||
.. _`install distribute`: http://pypi.python.org/pypi/distribute#installation-instructions
|
||||
As of middle 2013, there shouldn't be a problem anymore when you
|
||||
use the standard setuptools (note that distribute has been merged
|
||||
back into setuptools which is now shipped directly with virtualenv).
|
||||
|
||||
.. include:: links.inc
|
||||
|
||||
@@ -7,14 +7,14 @@ pytest fixtures: explicit, modular, scalable
|
||||
|
||||
.. currentmodule:: _pytest.python
|
||||
|
||||
.. versionadded:: 2.0/2.3
|
||||
.. versionadded:: 2.0/2.3/2.4
|
||||
|
||||
.. _`xUnit`: http://en.wikipedia.org/wiki/XUnit
|
||||
.. _`general purpose of test fixtures`: http://en.wikipedia.org/wiki/Test_fixture#Software
|
||||
.. _`purpose of test fixtures`: http://en.wikipedia.org/wiki/Test_fixture#Software
|
||||
.. _`Dependency injection`: http://en.wikipedia.org/wiki/Dependency_injection#Definition
|
||||
|
||||
The `general purpose of test fixtures`_ is to provide a fixed baseline
|
||||
upon which tests can reliably and repeatedly execute. pytest-2.3 fixtures
|
||||
The `purpose of test fixtures`_ is to provide a fixed baseline
|
||||
upon which tests can reliably and repeatedly execute. pytest fixtures
|
||||
offer dramatic improvements over the classic xUnit style of setup/teardown
|
||||
functions:
|
||||
|
||||
@@ -22,8 +22,7 @@ functions:
|
||||
from test functions, modules, classes or whole projects.
|
||||
|
||||
* fixtures are implemented in a modular manner, as each fixture name
|
||||
triggers a *fixture function* which can itself easily use other
|
||||
fixtures.
|
||||
triggers a *fixture function* which can itself use other fixtures.
|
||||
|
||||
* fixture management scales from simple unit to complex
|
||||
functional testing, allowing to parametrize fixtures and tests according
|
||||
@@ -41,7 +40,7 @@ style <unittest.TestCase>` or :ref:`nose based <nosestyle>` projects.
|
||||
.. _`@pytest.fixture`:
|
||||
.. _`pytest.fixture`:
|
||||
|
||||
Fixtures as Function arguments (funcargs)
|
||||
Fixtures as Function arguments
|
||||
-----------------------------------------
|
||||
|
||||
Test functions can receive fixture objects by naming them as an input
|
||||
@@ -71,7 +70,8 @@ marked ``smtp`` fixture function. Running the test looks like this::
|
||||
|
||||
$ py.test test_smtpsimple.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.4.0.dev12
|
||||
plugins: xdist, pep8, cov, cache, capturelog, instafail
|
||||
collected 1 items
|
||||
|
||||
test_smtpsimple.py F
|
||||
@@ -79,7 +79,7 @@ marked ``smtp`` fixture function. Running the test looks like this::
|
||||
================================= FAILURES =================================
|
||||
________________________________ test_ehlo _________________________________
|
||||
|
||||
smtp = <smtplib.SMTP instance at 0x1992a70>
|
||||
smtp = <smtplib.SMTP instance at 0x22530e0>
|
||||
|
||||
def test_ehlo(smtp):
|
||||
response, msg = smtp.ehlo()
|
||||
@@ -89,7 +89,7 @@ marked ``smtp`` fixture function. Running the test looks like this::
|
||||
E assert 0
|
||||
|
||||
test_smtpsimple.py:12: AssertionError
|
||||
========================= 1 failed in 0.30 seconds =========================
|
||||
========================= 1 failed in 0.17 seconds =========================
|
||||
|
||||
In the failure traceback we see that the test function was called with a
|
||||
``smtp`` argument, the ``smtplib.SMTP()`` instance created by the fixture
|
||||
@@ -124,15 +124,15 @@ with a list of available function arguments.
|
||||
but is not anymore advertised as the primary means of declaring fixture
|
||||
functions.
|
||||
|
||||
Funcargs a prime example of dependency injection
|
||||
"Funcargs" a prime example of dependency injection
|
||||
---------------------------------------------------
|
||||
|
||||
When injecting fixtures to test functions, pytest-2.0 introduced the
|
||||
term "funcargs" or "funcarg mechanism" which continues to be present
|
||||
also in pytest-2.3 docs. It now refers to the specific case of injecting
|
||||
also in docs today. It now refers to the specific case of injecting
|
||||
fixture values as arguments to test functions. With pytest-2.3 there are
|
||||
more possibilities to use fixtures but "funcargs" probably will remain
|
||||
as the main way of dealing with fixtures.
|
||||
more possibilities to use fixtures but "funcargs" remain as the main way
|
||||
as they allow to directly state the dependencies of a test function.
|
||||
|
||||
As the following examples show in more detail, funcargs allow test
|
||||
functions to easily receive and work against specific pre-initialized
|
||||
@@ -143,7 +143,7 @@ functions take the role of the *injector* and test functions are the
|
||||
|
||||
.. _smtpshared:
|
||||
|
||||
Working with a module-shared fixture
|
||||
Sharing a fixture across tests in a module (or class/session)
|
||||
-----------------------------------------------------------------
|
||||
|
||||
.. regendoc:wipe
|
||||
@@ -154,10 +154,10 @@ can add a ``scope='module'`` parameter to the
|
||||
:py:func:`@pytest.fixture <_pytest.python.fixture>` invocation
|
||||
to cause the decorated ``smtp`` fixture function to only be invoked once
|
||||
per test module. Multiple test functions in a test module will thus
|
||||
each receive the same ``smtp`` fixture instance. The next example also
|
||||
extracts the fixture function into a separate ``conftest.py`` file so
|
||||
that all tests in test modules in the directory can access the fixture
|
||||
function::
|
||||
each receive the same ``smtp`` fixture instance. The next example puts
|
||||
the fixture function into a separate ``conftest.py`` file so
|
||||
that tests from multiple test modules in the directory can
|
||||
access the fixture function::
|
||||
|
||||
# content of conftest.py
|
||||
import pytest
|
||||
@@ -168,7 +168,7 @@ function::
|
||||
return smtplib.SMTP("merlinux.eu")
|
||||
|
||||
The name of the fixture again is ``smtp`` and you can access its result by
|
||||
listing the name ``smtp`` as an input parameter in any test or setup
|
||||
listing the name ``smtp`` as an input parameter in any test or fixture
|
||||
function (in or below the directory where ``conftest.py`` is located)::
|
||||
|
||||
# content of test_module.py
|
||||
@@ -189,7 +189,8 @@ inspect what is going on and can now run the tests::
|
||||
|
||||
$ py.test test_module.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.4.0.dev12
|
||||
plugins: xdist, pep8, cov, cache, capturelog, instafail
|
||||
collected 2 items
|
||||
|
||||
test_module.py FF
|
||||
@@ -197,7 +198,7 @@ inspect what is going on and can now run the tests::
|
||||
================================= FAILURES =================================
|
||||
________________________________ test_ehlo _________________________________
|
||||
|
||||
smtp = <smtplib.SMTP instance at 0x2b8a248>
|
||||
smtp = <smtplib.SMTP instance at 0x165fa28>
|
||||
|
||||
def test_ehlo(smtp):
|
||||
response = smtp.ehlo()
|
||||
@@ -209,7 +210,7 @@ inspect what is going on and can now run the tests::
|
||||
test_module.py:6: AssertionError
|
||||
________________________________ test_noop _________________________________
|
||||
|
||||
smtp = <smtplib.SMTP instance at 0x2b8a248>
|
||||
smtp = <smtplib.SMTP instance at 0x165fa28>
|
||||
|
||||
def test_noop(smtp):
|
||||
response = smtp.noop()
|
||||
@@ -218,7 +219,7 @@ inspect what is going on and can now run the tests::
|
||||
E assert 0
|
||||
|
||||
test_module.py:11: AssertionError
|
||||
========================= 2 failed in 0.48 seconds =========================
|
||||
========================= 2 failed in 0.18 seconds =========================
|
||||
|
||||
You see the two ``assert 0`` failing and more importantly you can also see
|
||||
that the same (module-scoped) ``smtp`` object was passed into the two
|
||||
@@ -233,24 +234,60 @@ instance, you can simply declare it::
|
||||
def smtp(...):
|
||||
# the returned fixture value will be shared for
|
||||
# all tests needing it
|
||||
|
||||
.. _`request-context`:
|
||||
|
||||
Fixtures can interact with the requesting test context
|
||||
.. _`finalization`:
|
||||
|
||||
fixture finalization / executing teardown code
|
||||
-------------------------------------------------------------
|
||||
|
||||
Fixture functions can themselves use other fixtures by naming
|
||||
them as an input argument just like test functions do, see
|
||||
:ref:`interdependent fixtures`. Moreover, pytest
|
||||
provides a builtin :py:class:`request <FixtureRequest>` object,
|
||||
which fixture functions can use to introspect the function, class or module
|
||||
for which they are invoked or to register finalizing (cleanup)
|
||||
functions which are called when the last test finished execution.
|
||||
pytest supports execution of fixture specific finalization code
|
||||
when the fixture goes out of scope. By accepting a ``request`` object
|
||||
into your fixture function you can call its ``request.addfinalizer`` one
|
||||
or multiple times::
|
||||
|
||||
# content of conftest.py
|
||||
|
||||
import smtplib
|
||||
import pytest
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def smtp(request):
|
||||
smtp = smtplib.SMTP("merlinux.eu")
|
||||
def fin():
|
||||
print ("teardown smtp")
|
||||
smtp.close()
|
||||
request.addfinalizer(fin)
|
||||
return smtp # provide the fixture value
|
||||
|
||||
The ``fin`` function will execute when the last test using
|
||||
the fixture in the module has finished execution.
|
||||
|
||||
Let's execute it::
|
||||
|
||||
$ py.test -s -q --tb=no
|
||||
FF
|
||||
2 failed in 0.20 seconds
|
||||
|
||||
We see that the ``smtp`` instance is finalized after the two
|
||||
tests finished execution. Note that if we decorated our fixture
|
||||
function with ``scope='function'`` then fixture setup and cleanup would
|
||||
occur around each single test. In either case the test
|
||||
module itself does not need to change or know about these details
|
||||
of fixture setup.
|
||||
|
||||
Note that pytest-2.4 introduced an experimental alternative
|
||||
:ref:`yield fixture mechanism <yieldfixture>` for easier context manager
|
||||
integration and more linear writing of teardown code.
|
||||
|
||||
.. _`request-context`:
|
||||
|
||||
Fixtures can introspect the requesting test context
|
||||
-------------------------------------------------------------
|
||||
|
||||
Fixture function can accept the :py:class:`request <FixtureRequest>` object
|
||||
to introspect the "requesting" test function, class or module context.
|
||||
Further extending the previous ``smtp`` fixture example, let's
|
||||
read an optional server URL from the module namespace and register
|
||||
a finalizer that closes the smtp connection after the last
|
||||
test in a module finished execution::
|
||||
read an optional server URL from the test module which uses our fixture::
|
||||
|
||||
# content of conftest.py
|
||||
import pytest
|
||||
@@ -260,26 +297,23 @@ test in a module finished execution::
|
||||
def smtp(request):
|
||||
server = getattr(request.module, "smtpserver", "merlinux.eu")
|
||||
smtp = smtplib.SMTP(server)
|
||||
|
||||
def fin():
|
||||
print ("finalizing %s" % smtp)
|
||||
print ("finalizing %s (%s)" % (smtp, server))
|
||||
smtp.close()
|
||||
request.addfinalizer(fin)
|
||||
return smtp
|
||||
|
||||
return smtp
|
||||
|
||||
The registered ``fin`` function will be called when the last test
|
||||
using it has executed::
|
||||
We use the ``request.module`` attribute to optionally obtain an
|
||||
``smtpserver`` attribute from the test module. If we just execute
|
||||
again, nothing much has changed::
|
||||
|
||||
$ py.test -s -q --tb=no
|
||||
FF
|
||||
finalizing <smtplib.SMTP instance at 0x1584908>
|
||||
|
||||
We see that the ``smtp`` instance is finalized after the two
|
||||
tests using it tests executed. If we had specified ``scope='function'``
|
||||
then fixture setup and cleanup would occur around each single test.
|
||||
Note that either case the test module itself does not need to change!
|
||||
2 failed in 0.18 seconds
|
||||
|
||||
Let's quickly create another test module that actually sets the
|
||||
server URL and has a test to verify the fixture picks it up::
|
||||
server URL in its module namespace::
|
||||
|
||||
# content of test_anothersmtp.py
|
||||
|
||||
@@ -298,7 +332,8 @@ Running it::
|
||||
> assert 0, smtp.helo()
|
||||
E AssertionError: (250, 'mail.python.org')
|
||||
|
||||
.. _`request`: :py:class:`_pytest.python.FixtureRequest`
|
||||
voila! The ``smtp`` fixture function picked up our mail server name
|
||||
from the module namespace.
|
||||
|
||||
.. _`fixture-parametrize`:
|
||||
|
||||
@@ -315,7 +350,7 @@ configured in multiple ways.
|
||||
Extending the previous example, we can flag the fixture to create two
|
||||
``smtp`` fixture instances which will cause all tests using the fixture
|
||||
to run twice. The fixture function gets access to each parameter
|
||||
through the special `request`_ object::
|
||||
through the special :py:class:`request <FixtureRequest>` object::
|
||||
|
||||
# content of conftest.py
|
||||
import pytest
|
||||
@@ -342,7 +377,7 @@ So let's just do another run::
|
||||
================================= FAILURES =================================
|
||||
__________________________ test_ehlo[merlinux.eu] __________________________
|
||||
|
||||
smtp = <smtplib.SMTP instance at 0x2368248>
|
||||
smtp = <smtplib.SMTP instance at 0x28d13b0>
|
||||
|
||||
def test_ehlo(smtp):
|
||||
response = smtp.ehlo()
|
||||
@@ -354,7 +389,7 @@ So let's just do another run::
|
||||
test_module.py:6: AssertionError
|
||||
__________________________ test_noop[merlinux.eu] __________________________
|
||||
|
||||
smtp = <smtplib.SMTP instance at 0x2368248>
|
||||
smtp = <smtplib.SMTP instance at 0x28d13b0>
|
||||
|
||||
def test_noop(smtp):
|
||||
response = smtp.noop()
|
||||
@@ -365,18 +400,18 @@ So let's just do another run::
|
||||
test_module.py:11: AssertionError
|
||||
________________________ test_ehlo[mail.python.org] ________________________
|
||||
|
||||
smtp = <smtplib.SMTP instance at 0x2377680>
|
||||
smtp = <smtplib.SMTP instance at 0x28d8440>
|
||||
|
||||
def test_ehlo(smtp):
|
||||
response = smtp.ehlo()
|
||||
assert response[0] == 250
|
||||
> assert "merlinux" in response[1]
|
||||
E assert 'merlinux' in 'mail.python.org\nSIZE 10240000\nETRN\nSTARTTLS\nENHANCEDSTATUSCODES\n8BITMIME\nDSN'
|
||||
E assert 'merlinux' in 'mail.python.org\nSIZE 25600000\nETRN\nSTARTTLS\nENHANCEDSTATUSCODES\n8BITMIME\nDSN'
|
||||
|
||||
test_module.py:5: AssertionError
|
||||
________________________ test_noop[mail.python.org] ________________________
|
||||
|
||||
smtp = <smtplib.SMTP instance at 0x2377680>
|
||||
smtp = <smtplib.SMTP instance at 0x28d8440>
|
||||
|
||||
def test_noop(smtp):
|
||||
response = smtp.noop()
|
||||
@@ -385,6 +420,7 @@ So let's just do another run::
|
||||
E assert 0
|
||||
|
||||
test_module.py:11: AssertionError
|
||||
4 failed in 6.47 seconds
|
||||
|
||||
We see that our two test functions each ran twice, against the different
|
||||
``smtp`` instances. Note also, that with the ``mail.python.org``
|
||||
@@ -424,13 +460,15 @@ Here we declare an ``app`` fixture which receives the previously defined
|
||||
|
||||
$ py.test -v test_appsetup.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3 -- /home/hpk/p/pytest/.tox/regen/bin/python
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.4.0.dev12 -- /home/hpk/venv/0/bin/python
|
||||
cachedir: /tmp/doc-exec-127/.cache
|
||||
plugins: xdist, pep8, cov, cache, capturelog, instafail
|
||||
collecting ... collected 2 items
|
||||
|
||||
test_appsetup.py:12: test_smtp_exists[merlinux.eu] PASSED
|
||||
test_appsetup.py:12: test_smtp_exists[mail.python.org] PASSED
|
||||
test_appsetup.py:12: test_smtp_exists[merlinux.eu] PASSED
|
||||
|
||||
========================= 2 passed in 6.79 seconds =========================
|
||||
========================= 2 passed in 6.07 seconds =========================
|
||||
|
||||
Due to the parametrization of ``smtp`` the test will run twice with two
|
||||
different ``App`` instances and respective smtp servers. There is no
|
||||
@@ -470,8 +508,7 @@ to show the setup/teardown flow::
|
||||
param = request.param
|
||||
print "create", param
|
||||
def fin():
|
||||
print "fin", param
|
||||
request.addfinalizer(fin)
|
||||
print ("fin %s" % param)
|
||||
return param
|
||||
|
||||
@pytest.fixture(scope="function", params=[1,2])
|
||||
@@ -489,38 +526,38 @@ 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 linux2 -- Python 2.7.3 -- pytest-2.3.3 -- /home/hpk/p/pytest/.tox/regen/bin/python
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.4.0.dev12 -- /home/hpk/venv/0/bin/python
|
||||
cachedir: /tmp/doc-exec-127/.cache
|
||||
plugins: xdist, pep8, cov, cache, capturelog, instafail
|
||||
collecting ... collected 8 items
|
||||
|
||||
test_module.py:16: test_0[1] PASSED
|
||||
test_module.py:16: test_0[2] PASSED
|
||||
test_module.py:18: test_1[mod1] PASSED
|
||||
test_module.py:20: test_2[1-mod1] PASSED
|
||||
test_module.py:20: test_2[2-mod1] PASSED
|
||||
test_module.py:18: test_1[mod2] PASSED
|
||||
test_module.py:20: test_2[1-mod2] PASSED
|
||||
test_module.py:20: test_2[2-mod2] PASSED
|
||||
|
||||
========================= 8 passed in 0.01 seconds =========================
|
||||
test0 1
|
||||
test0 2
|
||||
create mod1
|
||||
test_module.py:15: test_0[1] test0 1
|
||||
PASSED
|
||||
test_module.py:15: test_0[2] test0 2
|
||||
PASSED
|
||||
test_module.py:17: test_1[mod1] create mod1
|
||||
test1 mod1
|
||||
test2 1 mod1
|
||||
test2 2 mod1
|
||||
fin mod1
|
||||
create mod2
|
||||
PASSED
|
||||
test_module.py:19: test_2[1-mod1] test2 1 mod1
|
||||
PASSED
|
||||
test_module.py:19: test_2[2-mod1] test2 2 mod1
|
||||
PASSED
|
||||
test_module.py:17: test_1[mod2] create mod2
|
||||
test1 mod2
|
||||
test2 1 mod2
|
||||
test2 2 mod2
|
||||
fin mod2
|
||||
PASSED
|
||||
test_module.py:19: test_2[1-mod2] test2 1 mod2
|
||||
PASSED
|
||||
test_module.py:19: test_2[2-mod2] test2 2 mod2
|
||||
PASSED
|
||||
|
||||
========================= 8 passed in 0.02 seconds =========================
|
||||
|
||||
You can see that the parametrized module-scoped ``modarg`` resource caused
|
||||
an ordering of test execution that lead to the fewest possible "active" resources. The finalizer for the ``mod1`` parametrized resource was executed
|
||||
before the ``mod2`` resource was setup.
|
||||
|
||||
.. _`usefixtures`:
|
||||
|
||||
.. _`usefixtures`:
|
||||
|
||||
using fixtures from classes, modules or projects
|
||||
----------------------------------------------------------------------
|
||||
@@ -569,6 +606,7 @@ to verify our fixture is activated and the tests pass::
|
||||
|
||||
$ py.test -q
|
||||
..
|
||||
2 passed in 0.02 seconds
|
||||
|
||||
You can specify multiple fixtures like this::
|
||||
|
||||
@@ -639,6 +677,7 @@ If we run it, we get two passing tests::
|
||||
|
||||
$ py.test -q
|
||||
..
|
||||
2 passed in 0.02 seconds
|
||||
|
||||
Here is how autouse fixtures work in other scopes:
|
||||
|
||||
@@ -687,3 +726,4 @@ to a :ref:`conftest.py <conftest.py>` file or even separately installable
|
||||
fixtures functions starts at test classes, then test modules, then
|
||||
``conftest.py`` files and finally builtin and third party plugins.
|
||||
|
||||
|
||||
|
||||
@@ -157,7 +157,7 @@ several problems:
|
||||
that are never needed because it only co-ordinates the test run
|
||||
activities of the slave processes.
|
||||
|
||||
2. if you only perform a collection (with "--collectonly")
|
||||
2. if you only perform a collection (with "--collect-only")
|
||||
resource-setup will still be executed.
|
||||
|
||||
3. If a pytest_sessionstart is contained in some subdirectories
|
||||
@@ -182,7 +182,7 @@ funcargs/fixture discovery now happens at collection time
|
||||
|
||||
pytest-2.3 takes care to discover fixture/funcarg factories
|
||||
at collection time. This is more efficient especially for large test suites.
|
||||
Moreover, a call to "py.test --collectonly" should be able to in the future
|
||||
Moreover, a call to "py.test --collect-only" should be able to in the future
|
||||
show a lot of setup-information and thus presents a nice method to get an
|
||||
overview of fixture management in your project.
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ Installation options::
|
||||
To check your installation has installed the correct version::
|
||||
|
||||
$ py.test --version
|
||||
This is py.test version 2.3.3, imported from /home/hpk/p/pytest/.tox/regen/lib/python2.7/site-packages/pytest.pyc
|
||||
This is py.test version 2.3.5, imported from /home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/pytest.py
|
||||
|
||||
If you get an error checkout :ref:`installation issues`.
|
||||
|
||||
@@ -45,7 +45,7 @@ That's it. You can execute the test function now::
|
||||
|
||||
$ py.test
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 1 items
|
||||
|
||||
test_sample.py F
|
||||
@@ -122,7 +122,7 @@ run the module by passing its filename::
|
||||
================================= FAILURES =================================
|
||||
____________________________ TestClass.test_two ____________________________
|
||||
|
||||
self = <test_class.TestClass instance at 0x22a4d40>
|
||||
self = <test_class.TestClass instance at 0x315b488>
|
||||
|
||||
def test_two(self):
|
||||
x = "hello"
|
||||
@@ -157,7 +157,7 @@ before performing the test function call. Let's just run it::
|
||||
================================= FAILURES =================================
|
||||
_____________________________ test_needsfiles ______________________________
|
||||
|
||||
tmpdir = local('/tmp/pytest-594/test_needsfiles0')
|
||||
tmpdir = local('/tmp/pytest-322/test_needsfiles0')
|
||||
|
||||
def test_needsfiles(tmpdir):
|
||||
print tmpdir
|
||||
@@ -166,7 +166,7 @@ before performing the test function call. Let's just run it::
|
||||
|
||||
test_tmpdir.py:3: AssertionError
|
||||
----------------------------- Captured stdout ------------------------------
|
||||
/tmp/pytest-594/test_needsfiles0
|
||||
/tmp/pytest-322/test_needsfiles0
|
||||
|
||||
Before the test runs, a unique-per-test-invocation temporary directory
|
||||
was created. More info at :ref:`tmpdir handling`.
|
||||
@@ -198,11 +198,8 @@ easy_install or pip not found?
|
||||
|
||||
`Install pip`_ for a state of the art python package installer.
|
||||
|
||||
Or consult `distribute docs`_ to install the ``easy_install``
|
||||
tool on your machine.
|
||||
|
||||
You may also use the older `setuptools`_ project but it lacks bug fixes
|
||||
and does not work on Python3.
|
||||
Install `setuptools`_ to get ``easy_install`` which allows to install
|
||||
``.egg`` binary format packages in addition to source-based ones.
|
||||
|
||||
py.test not found on Windows despite installation?
|
||||
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
@@ -23,7 +23,7 @@ Use tox and Continuous Integration servers
|
||||
|
||||
If you frequently release code to the public you
|
||||
may want to look into `tox`_, the virtualenv test automation
|
||||
tool and its `pytest support <http://codespeak.net/tox/example/pytest.html>`_.
|
||||
tool and its `pytest support <http://testrun.org/tox/latest/example/pytest.html>`_.
|
||||
The basic idea is to generate a JUnitXML file through the ``--junitxml=PATH`` option and have a continuous integration server like Jenkins_ pick it up
|
||||
and generate reports.
|
||||
|
||||
@@ -46,8 +46,6 @@ You can tell people to download the script and then e.g. run it like this::
|
||||
python runtests.py
|
||||
|
||||
|
||||
.. _`Distribute for installation`: http://pypi.python.org/pypi/distribute#installation-instructions
|
||||
.. _`distribute installation`: http://pypi.python.org/pypi/distribute
|
||||
|
||||
|
||||
Integrating with distutils / ``python setup.py test``
|
||||
@@ -73,7 +71,7 @@ this to your ``setup.py`` file::
|
||||
pass
|
||||
def run(self):
|
||||
import sys,subprocess
|
||||
errno = subprocess.call([sys.executable, 'runtest.py'])
|
||||
errno = subprocess.call([sys.executable, 'runtests.py'])
|
||||
raise SystemExit(errno)
|
||||
setup(
|
||||
#...,
|
||||
@@ -85,19 +83,18 @@ If you now type::
|
||||
|
||||
python setup.py test
|
||||
|
||||
this will execute your tests using ``runtest.py``. As this is a
|
||||
this will execute your tests using ``runtests.py``. As this is a
|
||||
standalone version of ``py.test`` 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/distribute test commands
|
||||
Integration with setuptools test commands
|
||||
----------------------------------------------------
|
||||
|
||||
Distribute/Setuptools support test requirements,
|
||||
which means its really easy to extend its test command
|
||||
to support running a pytest from test requirements::
|
||||
Setuptools supports writing our own Test command for invoking
|
||||
pytest::
|
||||
|
||||
from setuptools.command.test import test as TestCommand
|
||||
import sys
|
||||
|
||||
BIN
doc/en/img/cramer2.png
Normal file
BIN
doc/en/img/cramer2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
BIN
doc/en/img/gaynor3.png
Normal file
BIN
doc/en/img/gaynor3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
BIN
doc/en/img/keleshev.png
Normal file
BIN
doc/en/img/keleshev.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
BIN
doc/en/img/theuni.png
Normal file
BIN
doc/en/img/theuni.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
@@ -1,6 +1,8 @@
|
||||
|
||||
.. _features:
|
||||
|
||||
.. note:: second training: `professional testing with Python <http://www.python-academy.com/courses/specialtopics/python_course_testing.html>`_ , 25-27th November 2013, Leipzig.
|
||||
|
||||
pytest: helps you write better programs
|
||||
=============================================
|
||||
|
||||
@@ -8,8 +10,11 @@ pytest: helps you write better programs
|
||||
|
||||
- runs on Posix/Windows, Python 2.4-3.3, PyPy and Jython-2.5.1
|
||||
- :ref:`comprehensive online <toc>` and `PDF documentation <pytest.pdf>`_
|
||||
- many :ref:`third party plugins <extplugins>` and
|
||||
:ref:`builtin helpers <pytest helpers>`
|
||||
- used in :ref:`many projects and organisations <projects>`, in test
|
||||
suites ranging from 10 to 10s of thousands of tests
|
||||
suites with up to twenty thousand tests
|
||||
- strict policy of remaining backward compatible across releases
|
||||
- comes with many :ref:`tested examples <examples>`
|
||||
|
||||
**provides easy no-boilerplate testing**
|
||||
@@ -23,13 +28,13 @@ pytest: helps you write better programs
|
||||
|
||||
**scales from simple unit to complex functional testing**
|
||||
|
||||
- (new in 2.3) :ref:`modular parametrizeable fixtures <fixture>`
|
||||
- :ref:`modular parametrizeable fixtures <fixture>` (new in 2.3,
|
||||
improved in 2.4)
|
||||
- :ref:`parametrized test functions <parametrized test functions>`
|
||||
- :ref:`mark`
|
||||
- :ref:`skipping`
|
||||
- :ref:`skipping` (improved in 2.4)
|
||||
- can :ref:`distribute tests to multiple CPUs <xdistcpu>` through :ref:`xdist plugin <xdist>`
|
||||
- can :ref:`continuously re-run failing tests <looponfailing>`
|
||||
- many :ref:`builtin helpers <pytest helpers>` and :ref:`plugins <plugins>`
|
||||
- flexible :ref:`Python test discovery`
|
||||
|
||||
**integrates many common testing methods**:
|
||||
@@ -47,8 +52,9 @@ pytest: helps you write better programs
|
||||
**extensive plugin and customization system**:
|
||||
|
||||
- all collection, reporting, running aspects are delegated to hook functions
|
||||
- customizations can be per-directory, per-project or per PyPI released plugins
|
||||
- it is easy to add command line options or do other kind of add-ons and customizations.
|
||||
- customizations can be per-directory, per-project or per PyPI released plugin
|
||||
- it is easy to add command line options or customize existing behaviour
|
||||
|
||||
|
||||
.. _`Javascript unit- and functional testing`: http://pypi.python.org/pypi/oejskit
|
||||
|
||||
|
||||
@@ -17,5 +17,5 @@
|
||||
.. _`virtualenv`: http://pypi.python.org/pypi/virtualenv
|
||||
.. _hudson: http://hudson-ci.org/
|
||||
.. _jenkins: http://jenkins-ci.org/
|
||||
.. _tox: http://codespeak.net/tox
|
||||
.. _tox: http://testrun.org/tox
|
||||
.. _pylib: http://pylib.org
|
||||
|
||||
@@ -37,11 +37,45 @@ Here our test function monkeypatches ``os.path.expanduser`` and
|
||||
then calls into an function that calls it. After the test function
|
||||
finishes the ``os.path.expanduser`` modification will be undone.
|
||||
|
||||
example: preventing "requests" from remote operations
|
||||
------------------------------------------------------
|
||||
|
||||
If you want to prevent the "requests" library from performing http
|
||||
requests in all your tests, you can do::
|
||||
|
||||
# content of conftest.py
|
||||
|
||||
import pytest
|
||||
@pytest.fixture(autouse=True)
|
||||
def no_requests(monkeypatch):
|
||||
monkeypatch.delattr("requests.session.Session.request")
|
||||
|
||||
This autouse fixture will be executed for each test function and it
|
||||
will delete the method ``request.session.Session.request``
|
||||
so that any attempts within tests to create http requests will fail.
|
||||
|
||||
example: setting an attribute on some class
|
||||
------------------------------------------------------
|
||||
|
||||
If you need to patch out ``os.getcwd()`` to return an artifical
|
||||
value::
|
||||
|
||||
def test_some_interaction(monkeypatch):
|
||||
monkeypatch.setattr("os.getcwd", lambda: "/")
|
||||
|
||||
which is equivalent to the long form::
|
||||
|
||||
def test_some_interaction(monkeypatch):
|
||||
import os
|
||||
monkeypatch.setattr(os, "getcwd", lambda: "/")
|
||||
|
||||
|
||||
|
||||
Method reference of the monkeypatch function argument
|
||||
-----------------------------------------------------
|
||||
|
||||
.. autoclass:: monkeypatch
|
||||
:members: setattr, delattr, setitem, delitem, setenv, delenv, syspath_prepend, chdir, undo
|
||||
:members: setattr, replace, delattr, setitem, delitem, setenv, delenv, syspath_prepend, chdir, undo
|
||||
|
||||
``monkeypatch.setattr/delattr/delitem/delenv()`` all
|
||||
by default raise an Exception if the target does not exist.
|
||||
|
||||
@@ -12,6 +12,7 @@ Usage
|
||||
|
||||
After :ref:`installation` type::
|
||||
|
||||
python setup.py develop # make sure tests can import our package
|
||||
py.test # instead of 'nosetests'
|
||||
|
||||
and you should be able to run your nose style tests and
|
||||
|
||||
@@ -30,7 +30,7 @@ pytest supports test parametrization in several well-integrated ways:
|
||||
|
||||
.. regendoc: wipe
|
||||
|
||||
.. versionadded:: 2.2
|
||||
.. versionadded:: 2.2, improved in 2.4
|
||||
|
||||
The builtin ``pytest.mark.parametrize`` decorator enables
|
||||
parametrization of arguments for a test function. Here is a typical example
|
||||
@@ -39,7 +39,7 @@ to an expected output::
|
||||
|
||||
# content of test_expectation.py
|
||||
import pytest
|
||||
@pytest.mark.parametrize(("input", "expected"), [
|
||||
@pytest.mark.parametrize("input,expected", [
|
||||
("3+5", 8),
|
||||
("2+4", 6),
|
||||
("6*9", 42),
|
||||
@@ -47,23 +47,24 @@ to an expected output::
|
||||
def test_eval(input, expected):
|
||||
assert eval(input) == expected
|
||||
|
||||
Here, the ``@parametrize`` decorator defines three different argument
|
||||
sets for the two ``(input, output)`` arguments of the ``test_eval`` function
|
||||
which will thus run three times::
|
||||
Here, the ``@parametrize`` decorator defines three different ``(input,output)``
|
||||
tuples so that that the ``test_eval`` function will run three times using
|
||||
them in turn::
|
||||
|
||||
$ py.test
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
============================= test session starts ==============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.4.0.dev3
|
||||
plugins: xdist, cache, cli, pep8, xprocess, cov, capturelog, bdd-splinter, rerunfailures, instafail, localserver
|
||||
collected 3 items
|
||||
|
||||
test_expectation.py ..F
|
||||
|
||||
================================= FAILURES =================================
|
||||
____________________________ test_eval[6*9-42] _____________________________
|
||||
=================================== FAILURES ===================================
|
||||
______________________________ test_eval[6*9-42] _______________________________
|
||||
|
||||
input = '6*9', expected = 42
|
||||
|
||||
@pytest.mark.parametrize(("input", "expected"), [
|
||||
@pytest.mark.parametrize("input,expected", [
|
||||
("3+5", 8),
|
||||
("2+4", 6),
|
||||
("6*9", 42),
|
||||
@@ -74,14 +75,49 @@ which will thus run three times::
|
||||
E + where 54 = eval('6*9')
|
||||
|
||||
test_expectation.py:8: AssertionError
|
||||
==================== 1 failed, 2 passed in 0.01 seconds ====================
|
||||
====================== 1 failed, 2 passed in 0.02 seconds ======================
|
||||
|
||||
As expected only one pair of input/output values fails the simple test function.
|
||||
And as usual with test function arguments, you can see the ``input`` and ``output`` values in the traceback.
|
||||
As designed in this example, only one pair of input/output values fails
|
||||
the simple test function. And as usual with test function arguments,
|
||||
you can see the ``input`` and ``output`` values in the traceback.
|
||||
|
||||
Note that there ways how you can mark a class or a module,
|
||||
see :ref:`mark`.
|
||||
Note that you could also use the parametrize marker on a class or a module
|
||||
(see :ref:`mark`) which would invoke several functions with the argument sets.
|
||||
|
||||
It is also possible to mark individual test instances within parametrize,
|
||||
for example with the builtin ``mark.xfail``::
|
||||
|
||||
# content of test_expectation.py
|
||||
import pytest
|
||||
@pytest.mark.parametrize("input,expected", [
|
||||
("3+5", 8),
|
||||
("2+4", 6),
|
||||
pytest.mark.xfail(("6*9", 42)),
|
||||
])
|
||||
def test_eval(input, expected):
|
||||
assert eval(input) == expected
|
||||
|
||||
Let's run this::
|
||||
|
||||
$ py.test
|
||||
============================= test session starts ==============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.4.0.dev3
|
||||
plugins: xdist, cache, cli, pep8, xprocess, cov, capturelog, bdd-splinter, rerunfailures, instafail, localserver
|
||||
collected 3 items
|
||||
|
||||
test_expectation.py ..x
|
||||
|
||||
===================== 2 passed, 1 xfailed in 0.02 seconds ======================
|
||||
|
||||
The one parameter set which caused a failure previously now
|
||||
shows up as an "xfailed (expected to fail)" test.
|
||||
|
||||
.. note::
|
||||
|
||||
In versions prior to 2.4 one needed to specify the argument
|
||||
names as a tuple. This remains valid but the simpler ``"name1,name2,..."``
|
||||
comma-separated-string syntax is now advertised fist because
|
||||
it's easier to write, produces less line noise.
|
||||
|
||||
.. _`pytest_generate_tests`:
|
||||
|
||||
@@ -128,15 +164,15 @@ Let's also run with a stringinput that will lead to a failing test::
|
||||
|
||||
$ py.test -q --stringinput="!" test_strings.py
|
||||
F
|
||||
================================= FAILURES =================================
|
||||
___________________________ test_valid_string[!] ___________________________
|
||||
=================================== FAILURES ===================================
|
||||
_____________________________ test_valid_string[!] _____________________________
|
||||
|
||||
stringinput = '!'
|
||||
|
||||
def test_valid_string(stringinput):
|
||||
> assert stringinput.isalpha()
|
||||
E assert <built-in method isalpha of str object at 0x2b1792721fa8>()
|
||||
E + where <built-in method isalpha of str object at 0x2b1792721fa8> = '!'.isalpha
|
||||
E assert <built-in method isalpha of str object at 0x7fd657390fd0>()
|
||||
E + where <built-in method isalpha of str object at 0x7fd657390fd0> = '!'.isalpha
|
||||
|
||||
test_strings.py:3: AssertionError
|
||||
|
||||
@@ -148,8 +184,8 @@ listlist::
|
||||
|
||||
$ py.test -q -rs test_strings.py
|
||||
s
|
||||
========================= short test summary info ==========================
|
||||
SKIP [1] /home/hpk/p/pytest/.tox/regen/lib/python2.7/site-packages/_pytest/python.py:960: got empty parameter set, function test_valid_string at /tmp/doc-exec-26/test_strings.py:1
|
||||
=========================== short test summary info ============================
|
||||
SKIP [1] /home/hpk/p/pytest/_pytest/python.py:999: got empty parameter set, function test_valid_string at /tmp/doc-exec-2/test_strings.py:1
|
||||
|
||||
For further examples, you might want to look at :ref:`more
|
||||
parametrization examples <paramexamples>`.
|
||||
|
||||
@@ -78,12 +78,22 @@ there is no need to activate it. Here is a initial list of known plugins:
|
||||
* `pytest-capturelog <http://pypi.python.org/pypi/pytest-capturelog>`_:
|
||||
to capture and assert about messages from the logging module
|
||||
|
||||
* `pytest-cov <http://pypi.python.org/pypi/pytest-cov>`_:
|
||||
coverage reporting, compatible with distributed testing
|
||||
|
||||
* `pytest-xdist <http://pypi.python.org/pypi/pytest-xdist>`_:
|
||||
to distribute tests to CPUs and remote hosts, to run in boxed
|
||||
mode which allows to survive segmentation faults, to run in
|
||||
looponfailing mode, automatically re-running failing tests
|
||||
on file changes, see also :ref:`xdist`
|
||||
|
||||
* `pytest-instafail <http://pypi.python.org/pypi/pytest-instafail>`_:
|
||||
to report failures while the test run is happening.
|
||||
|
||||
* `pytest-bdd <http://pypi.python.org/pypi/pytest-bdd>`_ and
|
||||
`pytest-konira <http://pypi.python.org/pypi/pytest-konira>`_
|
||||
to write tests using behaviour-driven testing.
|
||||
|
||||
* `pytest-timeout <http://pypi.python.org/pypi/pytest-timeout>`_:
|
||||
to timeout tests based on function marks or global definitions.
|
||||
|
||||
@@ -91,9 +101,6 @@ there is no need to activate it. Here is a initial list of known plugins:
|
||||
to interactively re-run failing tests and help other plugins to
|
||||
store test run information across invocations.
|
||||
|
||||
* `pytest-cov <http://pypi.python.org/pypi/pytest-cov>`_:
|
||||
coverage reporting, compatible with distributed testing
|
||||
|
||||
* `pytest-pep8 <http://pypi.python.org/pypi/pytest-pep8>`_:
|
||||
a ``--pep8`` option to enable PEP8 compliance checking.
|
||||
|
||||
@@ -332,21 +339,36 @@ you can use the following hook:
|
||||
.. autofunction:: pytest_pycollect_makeitem
|
||||
.. autofunction:: pytest_generate_tests
|
||||
|
||||
After collection is complete, you can modify the order of
|
||||
items, delete or otherwise amend the test items:
|
||||
|
||||
.. autofunction:: pytest_collection_modifyitems
|
||||
|
||||
Reporting hooks
|
||||
------------------------------
|
||||
|
||||
Session related reporting hooks:
|
||||
|
||||
.. autofunction: pytest_collectstart
|
||||
.. autofunction: pytest_itemcollected
|
||||
.. autofunction: pytest_collectreport
|
||||
.. autofunction: pytest_deselected
|
||||
.. autofunction:: pytest_collectstart
|
||||
.. autofunction:: pytest_itemcollected
|
||||
.. autofunction:: pytest_collectreport
|
||||
.. autofunction:: pytest_deselected
|
||||
|
||||
And here is the central hook for reporting about
|
||||
test execution:
|
||||
|
||||
.. autofunction: pytest_runtest_logreport
|
||||
.. autofunction:: pytest_runtest_logreport
|
||||
|
||||
|
||||
Debugging/Interaction hooks
|
||||
--------------------------------------
|
||||
|
||||
There are few hooks which can be used for special
|
||||
reporting or interaction with exceptions:
|
||||
|
||||
.. autofunction:: pytest_internalerror
|
||||
.. autofunction:: pytest_keyboard_interrupt
|
||||
.. autofunction:: pytest_exception_interact
|
||||
|
||||
Reference of objects involved in hooks
|
||||
===========================================================
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
.. _projects:
|
||||
|
||||
.. image:: img/gaynor3.png
|
||||
:width: 400px
|
||||
:align: right
|
||||
|
||||
.. image:: img/theuni.png
|
||||
:width: 400px
|
||||
:align: right
|
||||
|
||||
.. image:: img/cramer2.png
|
||||
:width: 400px
|
||||
:align: right
|
||||
|
||||
.. image:: img/keleshev.png
|
||||
:width: 400px
|
||||
:align: right
|
||||
|
||||
|
||||
Project examples
|
||||
==========================
|
||||
|
||||
@@ -8,7 +25,8 @@ Here are some examples of projects using py.test (please send notes via :ref:`co
|
||||
* `PyPy <http://pypy.org>`_, Python with a JIT compiler, running over
|
||||
`16000 tests <http://buildbot.pypy.org/summary?branch=%3Ctrunk%3E>`_
|
||||
* the `MoinMoin <http://moinmo.in>`_ Wiki Engine
|
||||
* `tox <http://codespeak.net/tox>`_, virtualenv/Hudson integration tool
|
||||
* `sentry <https://getsentry.com/welcome/>`_, realtime app-maintenance and exception tracking
|
||||
* `tox <http://testrun.org/tox>`_, virtualenv/Hudson integration tool
|
||||
* `PIDA <http://pida.co.uk>`_ framework for integrated development
|
||||
* `PyPM <http://code.activestate.com/pypm/>`_ ActiveState's package manager
|
||||
* `Fom <http://packages.python.org/Fom/>`_ a fluid object mapper for FluidDB
|
||||
@@ -18,7 +36,7 @@ Here are some examples of projects using py.test (please send notes via :ref:`co
|
||||
* `mwlib <http://pypi.python.org/pypi/mwlib>`_ mediawiki parser and utility library
|
||||
* `The Translate Toolkit <http://translate.sourceforge.net/wiki/toolkit/index>`_ for localization and conversion
|
||||
* `execnet <http://codespeak.net/execnet>`_ rapid multi-Python deployment
|
||||
* `pylib <http://pylib.org>`_ cross-platform path, IO, dynamic code library
|
||||
* `pylib <http://py.rtfd.org>`_ cross-platform path, IO, dynamic code library
|
||||
* `Pacha <http://pacha.cafepais.com/>`_ configuration management in five minutes
|
||||
* `bbfreeze <http://pypi.python.org/pypi/bbfreeze>`_ create standalone executables from Python scripts
|
||||
* `pdb++ <http://bitbucket.org/antocuni/pdb>`_ a fancier version of PDB
|
||||
@@ -46,8 +64,8 @@ These projects help integrate py.test into other Python frameworks:
|
||||
|
||||
* `pytest-django <http://pypi.python.org/pypi/pytest-django/>`_ for Django
|
||||
* `zope.pytest <http://packages.python.org/zope.pytest/>`_ for Zope and Grok
|
||||
* `pytest__gae <http://pypi.python.org/pypi/pytest_gae/0.2.1>`_ for Google App Engine
|
||||
* There is `some work <https://github.com/Pylons/Kotti/pull/101>`_ underway for Kotti, a CMS built in Pyramid/Pylons
|
||||
* `pytest_gae <http://pypi.python.org/pypi/pytest_gae/0.2.1>`_ for Google App Engine
|
||||
* There is `some work <https://github.com/Kotti/Kotti/blob/master/kotti/testing.py>`_ underway for Kotti, a CMS built in Pyramid/Pylons
|
||||
|
||||
|
||||
Some organisations using py.test
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
Asserting deprecation and other warnings
|
||||
=====================================================
|
||||
|
||||
.. _function_argument:
|
||||
|
||||
The recwarn function argument
|
||||
------------------------------------
|
||||
|
||||
@@ -24,6 +26,9 @@ The ``recwarn`` function argument provides these methods:
|
||||
* ``pop(category=None)``: return last warning matching the category.
|
||||
* ``clear()``: clear list of warnings
|
||||
|
||||
|
||||
.. _ensuring_function_triggers:
|
||||
|
||||
Ensuring a function triggers a deprecation warning
|
||||
-------------------------------------------------------
|
||||
|
||||
|
||||
@@ -9,86 +9,111 @@ If you have test functions that cannot be run on certain platforms
|
||||
or that you expect to fail you can mark them accordingly or you
|
||||
may call helper functions during execution of setup or test functions.
|
||||
|
||||
A *skip* means that you expect your test to pass unless a certain
|
||||
configuration or condition (e.g. wrong Python interpreter, missing
|
||||
dependency) prevents it to run. And *xfail* means that your test
|
||||
can run but you expect it to fail because there is an implementation problem.
|
||||
A *skip* means that you expect your test to pass unless the environment
|
||||
(e.g. wrong Python interpreter, missing dependency) prevents it to run.
|
||||
And *xfail* means that your test can run but you expect it to fail
|
||||
because there is an implementation problem.
|
||||
|
||||
py.test counts and lists *skip* and *xfail* tests separately. However,
|
||||
detailed information about skipped/xfailed tests is not shown by default
|
||||
to avoid cluttering the output. You can use the ``-r`` option to see
|
||||
details corresponding to the "short" letters shown in the test
|
||||
progress::
|
||||
py.test counts and lists *skip* and *xfail* tests separately. Detailed
|
||||
information about skipped/xfailed tests is not shown by default to avoid
|
||||
cluttering the output. You can use the ``-r`` option to see details
|
||||
corresponding to the "short" letters shown in the test progress::
|
||||
|
||||
py.test -rxs # show extra info on skips and xfails
|
||||
|
||||
(See :ref:`how to change command line options defaults`)
|
||||
|
||||
.. _skipif:
|
||||
.. _`condition booleans`:
|
||||
|
||||
Marking a test function to be skipped
|
||||
-------------------------------------------
|
||||
|
||||
.. versionadded:: 2.0, 2.4
|
||||
|
||||
Here is an example of marking a test function to be skipped
|
||||
when run on a Python3 interpreter::
|
||||
when run on a Python3.3 interpreter::
|
||||
|
||||
import sys
|
||||
@pytest.mark.skipif("sys.version_info >= (3,0)")
|
||||
@pytest.mark.skipif(sys.version_info >= (3,3),
|
||||
reason="requires python3.3")
|
||||
def test_function():
|
||||
...
|
||||
|
||||
During test function setup the skipif condition is
|
||||
evaluated by calling ``eval('sys.version_info >= (3,0)', namespace)``.
|
||||
(*New in version 2.0.2*) The namespace contains all the module globals of the test function so that
|
||||
you can for example check for versions of a module you are using::
|
||||
During test function setup the condition ("sys.version_info >= (3,3)") is
|
||||
checked. If it evaluates to True, the test function will be skipped
|
||||
with the specified reason. Note that pytest enforces specifying a reason
|
||||
in order to report meaningful "skip reasons" (e.g. when using ``-rs``).
|
||||
If the condition is a string, it will be evaluated as python expression.
|
||||
|
||||
You can share skipif markers between modules. Consider this test module::
|
||||
|
||||
# content of test_mymodule.py
|
||||
|
||||
import mymodule
|
||||
|
||||
@pytest.mark.skipif("mymodule.__version__ < '1.2'")
|
||||
def test_function():
|
||||
...
|
||||
|
||||
The test function will not be run ("skipped") if
|
||||
``mymodule`` is below the specified version. The reason
|
||||
for specifying the condition as a string is mainly that
|
||||
py.test can report a summary of skip conditions.
|
||||
For information on the construction of the ``namespace``
|
||||
see `evaluation of skipif/xfail conditions`_.
|
||||
|
||||
You can of course create a shortcut for your conditional skip
|
||||
decorator at module level like this::
|
||||
|
||||
win32only = pytest.mark.skipif("sys.platform != 'win32'")
|
||||
|
||||
@win32only
|
||||
minversion = pytest.mark.skipif(mymodule.__versioninfo__ >= (1,1),
|
||||
reason="at least mymodule-1.1 required")
|
||||
@minversion
|
||||
def test_function():
|
||||
...
|
||||
|
||||
Skip all test functions of a class
|
||||
--------------------------------------
|
||||
You can import it from another test module::
|
||||
|
||||
# test_myothermodule.py
|
||||
from test_mymodule import minversion
|
||||
|
||||
@minversion
|
||||
def test_anotherfunction():
|
||||
...
|
||||
|
||||
For larger test suites it's usually a good idea to have one file
|
||||
where you define the markers which you then consistently apply
|
||||
throughout your test suite.
|
||||
|
||||
Alternatively, the pre pytest-2.4 way to specify `condition strings <condition strings>`_ instead of booleans will remain fully supported in future
|
||||
versions of pytest. It couldn't be easily used for importing markers
|
||||
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`_. Here is an example
|
||||
for skipping all methods of a test class based on the platform::
|
||||
`whole class- or module level`_. If your code targets python2.6 or above you
|
||||
use the skipif decorator (and any other marker) on classes::
|
||||
|
||||
class TestPosixCalls:
|
||||
pytestmark = pytest.mark.skipif("sys.platform == 'win32'")
|
||||
|
||||
def test_function(self):
|
||||
"will not be setup or run under 'win32' platform"
|
||||
|
||||
The ``pytestmark`` special name tells py.test to apply it to each test
|
||||
function in the class. If your code targets python2.6 or above you can
|
||||
more naturally use the skipif decorator (and any other marker) on
|
||||
classes::
|
||||
|
||||
@pytest.mark.skipif("sys.platform == 'win32'")
|
||||
@pytest.mark.skipif(sys.platform == 'win32',
|
||||
reason="requires windows")
|
||||
class TestPosixCalls:
|
||||
|
||||
def test_function(self):
|
||||
"will not be setup or run under 'win32' platform"
|
||||
|
||||
Using multiple "skipif" decorators on a single function is generally fine - it means that if any of the conditions apply the function execution will be skipped.
|
||||
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
|
||||
py.test 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::
|
||||
|
||||
# test_module.py
|
||||
|
||||
pytestmark = pytest.mark.skipif(...)
|
||||
|
||||
If multiple "skipif" decorators are applied to a test function, it
|
||||
will be skipped if any of the skip conditions is true.
|
||||
|
||||
.. _`whole class- or module level`: mark.html#scoped-marking
|
||||
|
||||
@@ -118,7 +143,8 @@ as if it weren't marked at all.
|
||||
As with skipif_ you can also mark your expectation of a failure
|
||||
on a particular platform::
|
||||
|
||||
@pytest.mark.xfail("sys.version_info >= (3,0)")
|
||||
@pytest.mark.xfail(sys.version_info >= (3,3),
|
||||
reason="python3.3 api changes")
|
||||
def test_function():
|
||||
...
|
||||
|
||||
@@ -132,14 +158,14 @@ Running it with the report-on-xfail option gives this output::
|
||||
|
||||
example $ py.test -rx xfail_demo.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 6 items
|
||||
|
||||
|
||||
xfail_demo.py xxxxxx
|
||||
========================= short test summary info ==========================
|
||||
XFAIL xfail_demo.py::test_hello
|
||||
XFAIL xfail_demo.py::test_hello2
|
||||
reason: [NOTRUN]
|
||||
reason: [NOTRUN]
|
||||
XFAIL xfail_demo.py::test_hello3
|
||||
condition: hasattr(os, 'sep')
|
||||
XFAIL xfail_demo.py::test_hello4
|
||||
@@ -148,44 +174,44 @@ Running it with the report-on-xfail option gives this output::
|
||||
condition: pytest.__version__[0] != "17"
|
||||
XFAIL xfail_demo.py::test_hello6
|
||||
reason: reason
|
||||
|
||||
|
||||
======================== 6 xfailed in 0.05 seconds =========================
|
||||
|
||||
.. _`evaluation of skipif/xfail conditions`:
|
||||
.. _`skip/xfail with parametrize`:
|
||||
|
||||
Evaluation of skipif/xfail expressions
|
||||
----------------------------------------------------
|
||||
Skip/xfail with parametrize
|
||||
---------------------------
|
||||
|
||||
.. versionadded:: 2.0.2
|
||||
It is possible to apply markers like skip and xfail to individual
|
||||
test instances when using parametrize:
|
||||
|
||||
The evaluation of a condition string in ``pytest.mark.skipif(conditionstring)``
|
||||
or ``pytest.mark.xfail(conditionstring)`` takes place in a namespace
|
||||
dictionary which is constructed as follows:
|
||||
import pytest
|
||||
|
||||
* the namespace is initialized by putting the ``sys`` and ``os`` modules
|
||||
and the pytest ``config`` object into it.
|
||||
|
||||
* updated with the module globals of the test function for which the
|
||||
expression is applied.
|
||||
|
||||
The pytest ``config`` object allows you to skip based on a test configuration value
|
||||
which you might have added::
|
||||
|
||||
@pytest.mark.skipif("not config.getvalue('db')")
|
||||
def test_function(...):
|
||||
...
|
||||
@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
|
||||
------------------------------------------------------
|
||||
|
||||
If you cannot declare xfail-conditions at import time
|
||||
you can also imperatively produce an XFail-outcome from
|
||||
within test or setup code. Example::
|
||||
If you cannot declare xfail- of skipif conditions at import
|
||||
time you can also imperatively produce an according outcome
|
||||
imperatively, in test or setup code::
|
||||
|
||||
def test_function():
|
||||
if not valid_config():
|
||||
pytest.xfail("unsupported configuration")
|
||||
pytest.xfail("failing configuration (but should work)")
|
||||
# or
|
||||
pytest.skipif("unsupported configuration")
|
||||
|
||||
|
||||
Skipping on a missing import dependency
|
||||
@@ -202,16 +228,61 @@ version number of a library::
|
||||
|
||||
docutils = pytest.importorskip("docutils", minversion="0.3")
|
||||
|
||||
The version will be read from the specified module's ``__version__`` attribute.
|
||||
The version will be read from the specified
|
||||
module's ``__version__`` attribute.
|
||||
|
||||
Imperative skip from within a test or setup function
|
||||
------------------------------------------------------
|
||||
|
||||
If for some reason you cannot declare skip-conditions
|
||||
you can also imperatively produce a skip-outcome from
|
||||
within test or setup code. Example::
|
||||
.. _`string conditions`:
|
||||
|
||||
specifying conditions as strings versus booleans
|
||||
----------------------------------------------------------
|
||||
|
||||
Prior to pytest-2.4 the only way to specify skipif/xfail conditions was
|
||||
to use strings::
|
||||
|
||||
import sys
|
||||
@pytest.mark.skipif("sys.version_info >= (3,3)")
|
||||
def test_function():
|
||||
if not valid_config():
|
||||
pytest.skip("unsupported configuration")
|
||||
...
|
||||
|
||||
During test function setup the skipif condition is evaluated by calling
|
||||
``eval('sys.version_info >= (3,0)', namespace)``. The namespace contains
|
||||
all the module globals, and ``os`` and ``sys`` as a minimum.
|
||||
|
||||
Since pytest-2.4 `condition booleans`_ are considered preferable
|
||||
because markers can then be freely imported between test modules.
|
||||
With strings you need to import not only the marker but all variables
|
||||
everything used by the marker, which violates encapsulation.
|
||||
|
||||
The reason for specifying the condition as a string was that py.test can
|
||||
report a summary of skip conditions based purely on the condition string.
|
||||
With conditions as booleans you are required to specify a ``reason`` string.
|
||||
|
||||
Note that string conditions will remain fully supported and you are free
|
||||
to use them if you have no need for cross-importing markers.
|
||||
|
||||
The evaluation of a condition string in ``pytest.mark.skipif(conditionstring)``
|
||||
or ``pytest.mark.xfail(conditionstring)`` takes place in a namespace
|
||||
dictionary which is constructed as follows:
|
||||
|
||||
* the namespace is initialized by putting the ``sys`` and ``os`` modules
|
||||
and the pytest ``config`` object into it.
|
||||
|
||||
* updated with the module globals of the test function for which the
|
||||
expression is applied.
|
||||
|
||||
The pytest ``config`` object allows you to skip based on a test
|
||||
configuration value which you might have added::
|
||||
|
||||
@pytest.mark.skipif("not config.getvalue('db')")
|
||||
def test_function(...):
|
||||
...
|
||||
|
||||
The equivalent with "boolean conditions" is::
|
||||
|
||||
@pytest.mark.skipif(not pytest.config.getvalue("db"),
|
||||
reason="--db was not specified")
|
||||
def test_function(...):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@@ -12,6 +12,9 @@ Tutorial examples and blog postings
|
||||
|
||||
Basic usage and funcargs:
|
||||
|
||||
- `pytest introduction from Brian Okken (January 2013)
|
||||
<http://pythontesting.net/framework/pytest-introduction/>`_
|
||||
|
||||
- `pycon australia 2012 pytest talk from Brianna Laugher
|
||||
<http://2012.pycon-au.org/schedule/52/view_talk?day=sunday>`_ (`video <http://www.youtube.com/watch?v=DTNejE9EraI>`_, `slides <http://www.slideshare.net/pfctdayelise/funcargs-other-fun-with-pytest>`_, `code <https://gist.github.com/3386951>`_)
|
||||
- `pycon 2012 US talk video from Holger Krekel <http://www.youtube.com/watch?v=9LVqBQcFmyw>`_
|
||||
@@ -57,7 +60,7 @@ Plugin specific examples:
|
||||
.. _`generating parametrized tests with funcargs`: funcargs.html#test-generators
|
||||
.. _`test generators and cached setup`: http://bruynooghe.blogspot.com/2010/06/pytest-test-generators-and-cached-setup.html
|
||||
|
||||
Conference talks and tutorials
|
||||
Older conference talks and tutorials
|
||||
----------------------------------------
|
||||
|
||||
- `ep2009-rapidtesting.pdf`_ tutorial slides (July 2009):
|
||||
|
||||
@@ -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 linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 1 items
|
||||
|
||||
test_tmpdir.py F
|
||||
@@ -37,7 +37,7 @@ Running this would result in a passed test except for the last
|
||||
================================= FAILURES =================================
|
||||
_____________________________ test_create_file _____________________________
|
||||
|
||||
tmpdir = local('/tmp/pytest-595/test_create_file0')
|
||||
tmpdir = local('/tmp/pytest-323/test_create_file0')
|
||||
|
||||
def test_create_file(tmpdir):
|
||||
p = tmpdir.mkdir("sub").join("hello.txt")
|
||||
@@ -48,7 +48,7 @@ Running this would result in a passed test except for the last
|
||||
E assert 0
|
||||
|
||||
test_tmpdir.py:7: AssertionError
|
||||
========================= 1 failed in 0.03 seconds =========================
|
||||
========================= 1 failed in 0.02 seconds =========================
|
||||
|
||||
.. _`base temporary directory`:
|
||||
|
||||
@@ -68,6 +68,6 @@ When distributing tests on the local machine, ``py.test`` takes care to
|
||||
configure a basetemp directory for the sub processes such that all temporary
|
||||
data lands below a single per-test run basetemp directory.
|
||||
|
||||
.. _`py.path.local`: http://pylib.org/path.html
|
||||
.. _`py.path.local`: http://py.rtfd.org/en/latest/path.html
|
||||
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ the ``self.db`` values in the traceback::
|
||||
|
||||
$ py.test test_unittest_db.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 2 items
|
||||
|
||||
test_unittest_db.py FF
|
||||
@@ -101,7 +101,7 @@ the ``self.db`` values in the traceback::
|
||||
def test_method1(self):
|
||||
assert hasattr(self, "db")
|
||||
> assert 0, self.db # fail for demo purposes
|
||||
E AssertionError: <conftest.DummyDB instance at 0x269e5a8>
|
||||
E AssertionError: <conftest.DummyDB instance at 0x19fdf38>
|
||||
|
||||
test_unittest_db.py:9: AssertionError
|
||||
___________________________ MyTest.test_method2 ____________________________
|
||||
@@ -110,7 +110,7 @@ the ``self.db`` values in the traceback::
|
||||
|
||||
def test_method2(self):
|
||||
> assert 0, self.db # fail for demo purposes
|
||||
E AssertionError: <conftest.DummyDB instance at 0x269e5a8>
|
||||
E AssertionError: <conftest.DummyDB instance at 0x19fdf38>
|
||||
|
||||
test_unittest_db.py:12: AssertionError
|
||||
========================= 2 failed in 0.02 seconds =========================
|
||||
|
||||
@@ -44,8 +44,11 @@ Specifying tests / selecting tests
|
||||
Several test run options::
|
||||
|
||||
py.test test_mod.py # run tests in module
|
||||
py.test somepath # run all tests below path
|
||||
py.test -k string # only run tests whose names contain a string
|
||||
py.test somepath # run all tests below somepath
|
||||
py.test -k stringexpr # only run tests with names that match the
|
||||
# the "string expression", e.g. "MyClass and not method"
|
||||
# will select TestMyClass.test_something
|
||||
# but not TestMyClass.test_method_simple
|
||||
|
||||
Import 'pkg' and use its filesystem location to find and run tests::
|
||||
|
||||
@@ -133,7 +136,7 @@ by the `PyPy-test`_ web page to show test results over several revisions.
|
||||
.. _`PyPy-test`: http://buildbot.pypy.org/summary
|
||||
|
||||
|
||||
Sending test report to pocoo pastebin service
|
||||
Sending test report to online pastebin service
|
||||
-----------------------------------------------------
|
||||
|
||||
**Creating a URL for each test failure**::
|
||||
@@ -165,7 +168,7 @@ this acts as if you would call "py.test" from the command line.
|
||||
It will not raise ``SystemExit`` but return the exitcode instead.
|
||||
You can pass in options and arguments::
|
||||
|
||||
pytest.main(['x', 'mytestdir'])
|
||||
pytest.main(['-x', 'mytestdir'])
|
||||
|
||||
or pass in a string::
|
||||
|
||||
|
||||
@@ -13,8 +13,15 @@ names). While these setup/teardown methods are and will remain fully
|
||||
supported you may also use pytest's more powerful :ref:`fixture mechanism
|
||||
<fixture>` which leverages the concept of dependency injection, allowing
|
||||
for a more modular and more scalable approach for managing test state,
|
||||
especially for larger projects and for functional testing. It is safe
|
||||
to mix both fixture mechanisms.
|
||||
especially for larger projects and for functional testing. You can
|
||||
mix both fixture mechanisms in the same file but unittest-based
|
||||
test methods cannot receive fixture arguments.
|
||||
|
||||
.. note::
|
||||
|
||||
As of pytest-2.4, teardownX functions are not called if
|
||||
setupX existed and failed/was skipped. This harmonizes
|
||||
behaviour across all major python testing tools.
|
||||
|
||||
Module level setup/teardown
|
||||
--------------------------------------
|
||||
|
||||
131
doc/en/yieldfixture.txt
Normal file
131
doc/en/yieldfixture.txt
Normal file
@@ -0,0 +1,131 @@
|
||||
|
||||
.. _yieldfixture:
|
||||
|
||||
Fixture functions using "yield" / context manager integration
|
||||
---------------------------------------------------------------
|
||||
|
||||
.. versionadded:: 2.4
|
||||
|
||||
.. regendoc:wipe
|
||||
|
||||
pytest-2.4 allows fixture functions to seemlessly use a ``yield`` instead
|
||||
of a ``return`` statement to provide a fixture value while otherwise
|
||||
fully supporting all other fixture features.
|
||||
|
||||
.. note::
|
||||
|
||||
"yielding" fixture values is an experimental feature and its exact
|
||||
declaration may change later but earliest in a 2.5 release. You can thus
|
||||
safely use this feature in the 2.4 series but may need to adapt later.
|
||||
Test functions themselves will not need to change (as a general
|
||||
feature, they are ignorant of how fixtures are setup).
|
||||
|
||||
Let's look at a simple standalone-example using the new ``yield`` syntax::
|
||||
|
||||
# content of test_yield.py
|
||||
|
||||
import pytest
|
||||
|
||||
@pytest.yield_fixture
|
||||
def passwd():
|
||||
print ("\nsetup before yield")
|
||||
f = open("/etc/passwd")
|
||||
yield f.readlines()
|
||||
print ("teardown after yield")
|
||||
f.close()
|
||||
|
||||
def test_has_lines(passwd):
|
||||
print ("test called")
|
||||
assert passwd
|
||||
|
||||
In contrast to :ref:`finalization through registering callbacks
|
||||
<finalization>`, our fixture function used a ``yield``
|
||||
statement to provide the lines of the ``/etc/passwd`` file.
|
||||
The code after the ``yield`` statement serves as the teardown code,
|
||||
avoiding the indirection of registering a teardown callback function.
|
||||
|
||||
Let's run it with output capturing disabled::
|
||||
|
||||
$ py.test -q -s test_yield.py
|
||||
|
||||
setup before yield
|
||||
test called
|
||||
.teardown after yield
|
||||
|
||||
1 passed in 0.01 seconds
|
||||
|
||||
We can also seemlessly use the new syntax with ``with`` statements.
|
||||
Let's simplify the above ``passwd`` fixture::
|
||||
|
||||
# content of test_yield2.py
|
||||
|
||||
import pytest
|
||||
|
||||
@pytest.yield_fixture
|
||||
def passwd():
|
||||
with open("/etc/passwd") as f:
|
||||
yield f.readlines()
|
||||
|
||||
def test_has_lines(passwd):
|
||||
assert len(passwd) >= 1
|
||||
|
||||
The file ``f`` will be closed after the test finished execution
|
||||
because the Python ``file`` object supports finalization when
|
||||
the ``with`` statement ends.
|
||||
|
||||
Note that the new syntax is fully integrated with using ``scope``,
|
||||
``params`` and other fixture features. Changing existing
|
||||
fixture functions to use ``yield`` is thus straight forward.
|
||||
|
||||
Discussion and future considerations / feedback
|
||||
++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
The yield-syntax has been discussed by pytest users extensively.
|
||||
In general, the advantages of the using a ``yield`` fixture syntax are:
|
||||
|
||||
- easy provision of fixtures in conjunction with context managers.
|
||||
|
||||
- no need to register a callback, providing for more synchronous
|
||||
control flow in the fixture function. Also there is no need to accept
|
||||
the ``request`` object into the fixture function just for providing
|
||||
finalization code.
|
||||
|
||||
However, there are also limitations or foreseeable irritations:
|
||||
|
||||
- usually ``yield`` is typically used for producing multiple values.
|
||||
But fixture functions can only yield exactly one value.
|
||||
Yielding a second fixture value will get you an error.
|
||||
It's possible we can evolve pytest to allow for producing
|
||||
multiple values as an alternative to current parametrization.
|
||||
For now, you can just use the normal
|
||||
:ref:`fixture parametrization <fixture-parametrize>`
|
||||
mechanisms together with ``yield``-style fixtures.
|
||||
|
||||
- the ``yield`` syntax is similar to what
|
||||
:py:func:`contextlib.contextmanager` decorated functions
|
||||
provide. With pytest fixture functions, the "after yield" part will
|
||||
always be invoked, independently from the exception status
|
||||
of the test function which uses the fixture. The pytest
|
||||
behaviour makes sense if you consider that many different
|
||||
test functions might use a module or session scoped fixture.
|
||||
Some test functions might raise exceptions and others not,
|
||||
so how could pytest re-raise a single exception at the
|
||||
``yield`` point in the fixture function?
|
||||
|
||||
- lastly ``yield`` introduces more than one way to write
|
||||
fixture functions, so what's the obvious way to a newcomer?
|
||||
Newcomers reading the docs will see feature examples using the
|
||||
``return`` style so should use that, if in doubt.
|
||||
Others can start experimenting with writing yield-style fixtures
|
||||
and possibly help evolving them further.
|
||||
|
||||
Some developers also expressed their preference for
|
||||
rather introduce a new ``@pytest.yieldfixture`` decorator
|
||||
instead of a keyword argument, or for assuming the above
|
||||
yield-semantics automatically by introspecting if a fixture
|
||||
function is a generator. Depending on more experiences and
|
||||
feedback during the 2.4 cycle, we revisit theses issues.
|
||||
|
||||
If you want to feedback or participate in the ongoing
|
||||
discussion, please join our :ref:`contact channels`.
|
||||
you are most welcome.
|
||||
@@ -29,6 +29,8 @@ help:
|
||||
@echo " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " texinfo to make Texinfo files"
|
||||
@echo " info to make Texinfo files and run them through makeinfo"
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
@echo " text to make text files"
|
||||
@echo " man to make manual pages"
|
||||
@@ -140,3 +142,18 @@ doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
||||
|
||||
texinfo:
|
||||
mkdir -p $(BUILDDIR)/texinfo
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo
|
||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||
"(use \`make info' here to do that automatically)."
|
||||
|
||||
info:
|
||||
mkdir -p $(BUILDDIR)/texinfo
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo "Running Texinfo files through makeinfo..."
|
||||
make -C $(BUILDDIR)/texinfo info
|
||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||
|
||||
@@ -261,6 +261,19 @@ epub_copyright = u'2011, holger krekel et alii'
|
||||
#epub_tocdup = True
|
||||
|
||||
|
||||
# -- Options for texinfo output ------------------------------------------------
|
||||
|
||||
texinfo_documents = [
|
||||
(master_doc, 'pytest', 'pytest Documentation',
|
||||
('Holger Krekel@*Benjamin Peterson@*Ronny Pfannschmidt@*'
|
||||
'Floris Bruynooghe@*others'),
|
||||
'pytest',
|
||||
'simple powerful testing with Pytho',
|
||||
'Programming',
|
||||
1),
|
||||
]
|
||||
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {} # 'http://docs.python.org/': None}
|
||||
def setup(app):
|
||||
|
||||
@@ -72,3 +72,12 @@ Python モジュール (通常 python テストモジュールを含む) の doc
|
||||
mymodule.py .
|
||||
|
||||
========================= 1 passed in 0.02 seconds =========================
|
||||
|
||||
..
|
||||
It is possible to use fixtures using the ``getfixture`` helper::
|
||||
|
||||
それは ``getfixture`` ヘルパーを使ってフィクスチャを使用することが可能である::
|
||||
|
||||
# content of example.rst
|
||||
>>> tmp = getfixture('tmpdir')
|
||||
>>> ...
|
||||
|
||||
@@ -101,7 +101,7 @@ Yaml ファイルでテストを指定する基本的なサンプル
|
||||
|
||||
カスタムテストコレクションや実行処理の開発中、そのコレクションツリーをちょっと見るのもおもしろいです::
|
||||
|
||||
nonpython $ py.test --collectonly
|
||||
nonpython $ py.test --collect-only
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.1 -- pytest-2.2.4
|
||||
collecting ... collected 2 items
|
||||
|
||||
@@ -218,7 +218,7 @@ Robert Collins による標準ライブラリの unittest フレームワーク
|
||||
|
||||
ただテストを (実行せずに) 集めるだけなら、テスト関数の変数として 'advanced' と 'basic' もうまく表示されます::
|
||||
|
||||
$ py.test --collectonly test_scenarios.py
|
||||
$ py.test --collect-only test_scenarios.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.1 -- pytest-2.2.4
|
||||
collecting ... collected 2 items
|
||||
@@ -287,7 +287,7 @@ Robert Collins による標準ライブラリの unittest フレームワーク
|
||||
|
||||
コレクション時に先ほどの設定がどうなるかを最初に見てみましょう::
|
||||
|
||||
$ py.test test_backends.py --collectonly
|
||||
$ py.test test_backends.py --collect-only
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.1 -- pytest-2.2.4
|
||||
collecting ... collected 2 items
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
# run this with $ py.test --collectonly test_collectonly.py
|
||||
# run this with $ py.test --collect-only test_collectonly.py
|
||||
#
|
||||
def test_function():
|
||||
pass
|
||||
|
||||
@@ -68,7 +68,7 @@ ini ファイルで :confval:`norecursedirs` オプションを設定できま
|
||||
|
||||
テストコレクションは次のようになります::
|
||||
|
||||
$ py.test --collectonly
|
||||
$ py.test --collect-only
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.1 -- pytest-2.2.4
|
||||
collecting ... collected 2 items
|
||||
@@ -127,7 +127,7 @@ py.test がファイルシステムのパスから Python パッケージ名と
|
||||
|
||||
次のようにテストを実行せずにコレクションツリーをピークできます::
|
||||
|
||||
. $ py.test --collectonly pythoncollection.py
|
||||
. $ py.test --collect-only pythoncollection.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.1 -- pytest-2.2.4
|
||||
collecting ... collected 3 items
|
||||
|
||||
@@ -273,7 +273,7 @@ funcarg ファクトリー関数は、特別なテスト関数呼び出しに関
|
||||
|
||||
分かりやすいように ``numiter`` の値が ``9`` のときのみテストが失敗します。 ``pytest_generate_tests(metafunc)`` フックは、実際にテストを実行するときとは違うフェーズの、テストコレクションで呼ばれることに注意してください。では、テストコレクションがどうなるかをちょっと見てみましょう::
|
||||
|
||||
$ py.test --collectonly test_example.py
|
||||
$ py.test --collect-only test_example.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.1 -- pytest-2.2.4
|
||||
collecting ... collected 10 items
|
||||
|
||||
@@ -39,11 +39,11 @@ tox と継続的インテグレーションサーバーの利用
|
||||
..
|
||||
If you frequently release code to the public you
|
||||
may want to look into `tox`_, the virtualenv test automation
|
||||
tool and its `pytest support <http://codespeak.net/tox/example/pytest.html>`_.
|
||||
tool and its `pytest support <http://testrun.org/tox/latest/example/pytest.html>`_.
|
||||
The basic idea is to generate a JUnitXML file through the ``--junitxml=PATH`` option and have a continuous integration server like Jenkins_ pick it up
|
||||
and generate reports.
|
||||
|
||||
もし頻繁にコードを一般向けにリリースするなら、virtualenv のテスト自動化とその `pytest サポート <http://codespeak.net/tox/example/pytest.html>`_ を行う `tox`_ を調べてみたくなるでしょう。基本的な考え方は、 ``--junitxml=PATH`` オプションにより JUnitXML ファイルを生成します。そして Jenkins_ のような継続的インテグレーションサーバーがそのファイルを取得してレポートを生成します。
|
||||
もし頻繁にコードを一般向けにリリースするなら、virtualenv のテスト自動化とその `pytest サポート <http://testrun.org/tox/latest/example/pytest.html>`_ を行う `tox`_ を調べてみたくなるでしょう。基本的な考え方は、 ``--junitxml=PATH`` オプションにより JUnitXML ファイルを生成します。そして Jenkins_ のような継続的インテグレーションサーバーがそのファイルを取得してレポートを生成します。
|
||||
|
||||
.. _standalone:
|
||||
.. _`genscript method`:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user