Compare commits
160 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c7ae7f7a5 | ||
|
|
05c4ecf892 | ||
|
|
c5f9958783 | ||
|
|
7a90515d49 | ||
|
|
3ab94544b9 | ||
|
|
3c317dc35e | ||
|
|
b2cb93e06d | ||
|
|
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 | ||
|
|
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 | ||
|
|
d6f10d502c | ||
|
|
65d6ebe7d1 | ||
|
|
33cd414420 | ||
|
|
dba2a8bc64 | ||
|
|
f203401964 | ||
|
|
c64699bba6 | ||
|
|
002c5072af | ||
|
|
b3c8991b22 | ||
|
|
1a41c9c001 | ||
|
|
df444906d6 | ||
|
|
04754f6748 | ||
|
|
d5ad91c64f | ||
|
|
7e831b66ec | ||
|
|
ba9b27fcd3 | ||
|
|
ca281b7c1b | ||
|
|
fb173a97a8 | ||
|
|
983b2d2475 | ||
|
|
e7e5ee805f | ||
|
|
67f8dd0cf2 | ||
|
|
07cc48517d | ||
|
|
fce13c3e46 | ||
|
|
573599beb3 | ||
|
|
6ebf39e9a6 | ||
|
|
6b6080ae6c | ||
|
|
427cf6f66d | ||
|
|
2fc8ee0839 | ||
|
|
6ad16936bb | ||
|
|
bcb8dc71d2 | ||
|
|
b8277bfed8 | ||
|
|
2637326782 | ||
|
|
aa79c0a4b9 | ||
|
|
05c86aeb28 | ||
|
|
f28f073c7c | ||
|
|
036557ac18 | ||
|
|
1b61fbc8ed | ||
|
|
97f03edcd6 | ||
|
|
7e5efa0005 | ||
|
|
d55fc611c4 | ||
|
|
720fe3405b | ||
|
|
c894b2b459 | ||
|
|
6d5bf4b908 | ||
|
|
d4d213f83d | ||
|
|
289ee1c6ea | ||
|
|
f41f7fda68 | ||
|
|
9ed127b5da | ||
|
|
525b08bc5c | ||
|
|
fae34ca5e3 | ||
|
|
0852e84d9f | ||
|
|
76db624639 | ||
|
|
1e6ec9941c | ||
|
|
40a55a640c |
5
.hgtags
5
.hgtags
@@ -49,3 +49,8 @@ e5e1746a197f0398356a43fbe2eebac9690f795d 2.1.0
|
||||
92b916483c1e65a80dc80e3f7816b39e84b36a4d 2.2.2
|
||||
3c11c5c9776f3c678719161e96cc0a08169c1cb8 2.2.3
|
||||
ad9fe504a371ad8eb613052d58f229aa66f53527 2.2.4
|
||||
c27a60097767c16a54ae56d9669a77925b213b9b 2.3.0
|
||||
acf0e1477fb19a1d35a4e40242b77fa6af32eb17 2.3.1
|
||||
8738b828dec53937765db71951ef955cca4c51f6 2.3.2
|
||||
7fe44182c434f8ac89149a3c340479872a5d5ccb 2.3.3
|
||||
ef299e57f24218dbdd949498d7e660723636bcc3 2.3.4
|
||||
|
||||
2
AUTHORS
2
AUTHORS
@@ -23,3 +23,5 @@ Grig Gheorghiu
|
||||
Bob Ippolito
|
||||
Christian Tismer
|
||||
Daniel Nuri
|
||||
Graham Horler
|
||||
Andreas Zeidler
|
||||
|
||||
166
CHANGELOG
166
CHANGELOG
@@ -1,10 +1,170 @@
|
||||
Changes between 2.3.4 and 2.3.5dev
|
||||
-----------------------------------
|
||||
|
||||
- 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
|
||||
-----------------------------------
|
||||
|
||||
- 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.
|
||||
|
||||
Changes between 2.3.1 and 2.3.2
|
||||
-----------------------------------
|
||||
|
||||
- fix issue208 and fix issue29 use new py version to avoid long pauses
|
||||
when printing tracebacks in long modules
|
||||
|
||||
- fix issue205 - conftests in subdirs customizing
|
||||
pytest_pycollect_makemodule and pytest_pycollect_makeitem
|
||||
now work properly
|
||||
|
||||
- fix teardown-ordering for parametrized setups
|
||||
|
||||
- fix issue127 - better documentation for pytest_addoption
|
||||
and related objects.
|
||||
|
||||
- fix unittest behaviour: TestCase.runtest only called if there are
|
||||
test methods defined
|
||||
|
||||
- improve trial support: don't collect its empty
|
||||
unittest.TestCase.runTest() method
|
||||
|
||||
- "python setup.py test" now works with pytest itself
|
||||
|
||||
- fix/improve internal/packaging related bits:
|
||||
|
||||
- exception message check of test_nose.py now passes on python33 as well
|
||||
|
||||
- issue206 - fix test_assertrewrite.py to work when a global
|
||||
PYTHONDONTWRITEBYTECODE=1 is present
|
||||
|
||||
- add tox.ini to pytest distribution so that ignore-dirs and others config
|
||||
bits are properly distributed for maintainers who run pytest-own tests
|
||||
|
||||
Changes between 2.3.0 and 2.3.1
|
||||
-----------------------------------
|
||||
|
||||
- fix issue202 - fix regression: using "self" from fixture functions now
|
||||
works as expected (it's the same "self" instance that a test method
|
||||
which uses the fixture sees)
|
||||
|
||||
- skip pexpect using tests (test_pdb.py mostly) on freebsd* systems
|
||||
due to pexpect not supporting it properly (hanging)
|
||||
|
||||
- link to web pages from --markers output which provides help for
|
||||
pytest.mark.* usage.
|
||||
|
||||
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. Introduce new @pytest.setup
|
||||
marker to allow the writing of setup functions which accept funcargs.
|
||||
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
|
||||
@@ -14,7 +174,7 @@ Changes between 2.2.4 and 2.3.0
|
||||
- introduce re-ordering of tests by resource and parametrization setup
|
||||
which takes precedence to the usual file-ordering
|
||||
- fix issue185 monkeypatching time.time does not cause pytest to fail
|
||||
- fix issue172 duplicate call of pytest.setup-decoratored setup_module
|
||||
- fix issue172 duplicate call of pytest.fixture decoratored setup_module
|
||||
functions
|
||||
- fix junitxml=path construction so that if tests change the
|
||||
current working directory and the path is a relative path
|
||||
|
||||
125
ISSUES.txt
125
ISSUES.txt
@@ -48,59 +48,6 @@ if the signature of a decorated function does not match. XXX is it
|
||||
not sufficient to always allow non-matches?
|
||||
|
||||
|
||||
unify item/request classes, generalize items
|
||||
---------------------------------------------------------------
|
||||
tags: 2.4 wish
|
||||
|
||||
in lieu of extended parametrization and the new way to specify resource
|
||||
factories in terms of the parametrize decorator, consider unification
|
||||
of the item and request class. This also is connected with allowing
|
||||
funcargs in setup functions. Example of new item API:
|
||||
|
||||
item.getresource("db") # alias for request.getfuncargvalue
|
||||
item.addfinalizer(...)
|
||||
item.cached_setup(...)
|
||||
item.applymarker(...)
|
||||
|
||||
test classes/modules could then use this api via::
|
||||
|
||||
def pytest_runtest_setup(item):
|
||||
use item API ...
|
||||
|
||||
introduction of this new method needs to be _fully_ backward compatible -
|
||||
and the documentation needs to change along to mention this new way of
|
||||
doing things.
|
||||
|
||||
impl note: probably Request._fillfixtures would be called from the
|
||||
python plugins own pytest_runtest_setup(item) and would call
|
||||
item.getresource(X) for all X in the funcargs of a function.
|
||||
|
||||
XXX is it possible to even put the above item API to Nodes, i.e. also
|
||||
to Directorty/module/file/class collectors? Problem is that current
|
||||
funcarg factories presume they are called with a per-function (even
|
||||
per-funcarg-per-function) scope. Could there be small tweaks to the new
|
||||
API that lift this restriction?
|
||||
|
||||
consider::
|
||||
|
||||
def setup_class(cls, tmpdir):
|
||||
# would get a per-class tmpdir because tmpdir parametrization
|
||||
# would know that it is called with a class scope
|
||||
#
|
||||
#
|
||||
#
|
||||
this looks very difficult because those setup functions are also used
|
||||
by nose etc. Rather consider introduction of a new setup hook:
|
||||
|
||||
def setup_test(self, item):
|
||||
self.db = item.cached_setup(..., scope='class')
|
||||
self.tmpdir = item.getresource("tmpdir")
|
||||
|
||||
this should be compatible to unittest/nose and provide much of what
|
||||
"testresources" provide. XXX This would not allow full parametrization
|
||||
such that test function could be run multiple times with different
|
||||
values. See "parametrized attributes" issue.
|
||||
|
||||
allow parametrized attributes on classes
|
||||
--------------------------------------------------
|
||||
|
||||
@@ -383,3 +330,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,8 @@
|
||||
include CHANGELOG
|
||||
include README.txt
|
||||
include README.rst
|
||||
include setup.py
|
||||
include distribute_setup.py
|
||||
include tox.ini
|
||||
include LICENSE
|
||||
graft doc
|
||||
graft testing
|
||||
|
||||
36
README.rst
Normal file
36
README.rst
Normal file
@@ -0,0 +1,36 @@
|
||||
|
||||
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>`_.
|
||||
|
||||
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/
|
||||
|
||||
Copyright Holger Krekel and others, 2004-2012
|
||||
@@ -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.0'
|
||||
__version__ = '2.3.5'
|
||||
|
||||
@@ -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
|
||||
@@ -34,10 +35,11 @@ else:
|
||||
PYTEST_TAG = "%s-%s%s-PYTEST" % (impl, ver[0], ver[1])
|
||||
del ver, impl
|
||||
|
||||
PYC_EXT = ".py" + ("c" if __debug__ else "o")
|
||||
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."""
|
||||
@@ -187,12 +189,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:
|
||||
@@ -262,6 +295,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 +509,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)
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ BuiltinAssertionError = py.builtin.builtins.AssertionError
|
||||
# DebugInterpreter.
|
||||
_reprcompare = None
|
||||
|
||||
|
||||
def format_explanation(explanation):
|
||||
"""This formats an explanation
|
||||
|
||||
@@ -83,9 +84,9 @@ 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)
|
||||
@@ -93,72 +94,72 @@ def assertrepr_compare(op, left, right):
|
||||
issequence = lambda x: isinstance(x, (list, tuple))
|
||||
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 +167,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 +193,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 += ['Hiding %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'):
|
||||
|
||||
@@ -173,8 +173,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 +210,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()
|
||||
|
||||
@@ -19,7 +19,7 @@ def pytest_unconfigure(config):
|
||||
fin()
|
||||
|
||||
class Parser:
|
||||
""" Parser for command line arguments. """
|
||||
""" Parser for command line arguments and ini-file values. """
|
||||
|
||||
def __init__(self, usage=None, processopt=None):
|
||||
self._anonymous = OptionGroup("custom options", parser=self)
|
||||
@@ -38,9 +38,14 @@ class Parser:
|
||||
def getgroup(self, name, description="", after=None):
|
||||
""" get (or create) a named option Group.
|
||||
|
||||
:name: unique name of the option group.
|
||||
:name: name of the option group.
|
||||
:description: long description for --help output.
|
||||
:after: name of other group, used for ordering --help output.
|
||||
|
||||
The returned group object has an ``addoption`` method with the same
|
||||
signature as :py:func:`parser.addoption
|
||||
<_pytest.config.Parser.addoption>` but will be shown in the
|
||||
respective group in the output of ``pytest. --help``.
|
||||
"""
|
||||
for group in self._groups:
|
||||
if group.name == name:
|
||||
@@ -54,7 +59,19 @@ class Parser:
|
||||
return group
|
||||
|
||||
def addoption(self, *opts, **attrs):
|
||||
""" add an optparse-style option. """
|
||||
""" register a command line option.
|
||||
|
||||
:opts: option names, can be short or long options.
|
||||
:attrs: same attributes which the ``add_option()`` function of the
|
||||
`optparse library
|
||||
<http://docs.python.org/library/optparse.html#module-optparse>`_
|
||||
accepts.
|
||||
|
||||
After command line parsing options are available on the pytest config
|
||||
object via ``config.option.NAME`` where ``NAME`` is usually set
|
||||
by passing a ``dest`` attribute, for example
|
||||
``addoption("--long", dest="NAME", ...)``.
|
||||
"""
|
||||
self._anonymous.addoption(*opts, **attrs)
|
||||
|
||||
def parse(self, args):
|
||||
@@ -75,7 +92,15 @@ class Parser:
|
||||
return args
|
||||
|
||||
def addini(self, name, help, type=None, default=None):
|
||||
""" add an ini-file option with the given name and description. """
|
||||
""" register an ini-file option.
|
||||
|
||||
:name: name of the ini-variable
|
||||
:type: type of the variable, can be ``pathlist``, ``args`` or ``linelist``.
|
||||
:default: default value if no ini-file option exists but is queried.
|
||||
|
||||
The value of ini-variables can be retrieved via a call to
|
||||
:py:func:`config.getini(name) <_pytest.config.Config.getini>`.
|
||||
"""
|
||||
assert type in (None, "pathlist", "args", "linelist")
|
||||
self._inidict[name] = (help, type, default)
|
||||
self._ininames.append(name)
|
||||
@@ -156,7 +181,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:
|
||||
@@ -246,8 +271,8 @@ class CmdOptions(object):
|
||||
class Config(object):
|
||||
""" access to configuration values, pluginmanager and plugin hooks. """
|
||||
def __init__(self, pluginmanager=None):
|
||||
#: command line option values, usually added via parser.addoption(...)
|
||||
#: or parser.getgroup(...).addoption(...) calls
|
||||
#: access to command line option as attributes.
|
||||
#: (deprecated), use :py:func:`getoption() <_pytest.config.Config.getoption>` instead
|
||||
self.option = CmdOptions()
|
||||
self._parser = Parser(
|
||||
usage="usage: %prog [options] [file_or_dir] [file_or_dir] [...]",
|
||||
@@ -259,6 +284,7 @@ class Config(object):
|
||||
self._conftest = Conftest(onimport=self._onimportconftest)
|
||||
self.hook = self.pluginmanager.hook
|
||||
self._inicache = {}
|
||||
self._opt2dest = {}
|
||||
self._cleanup = []
|
||||
|
||||
@classmethod
|
||||
@@ -279,6 +305,9 @@ class Config(object):
|
||||
self.pluginmanager.consider_conftest(conftestmodule)
|
||||
|
||||
def _processopt(self, opt):
|
||||
for name in opt._short_opts + opt._long_opts:
|
||||
self._opt2dest[name] = opt.dest
|
||||
|
||||
if hasattr(opt, 'default') and opt.dest:
|
||||
if not hasattr(self.option, opt.dest):
|
||||
setattr(self.option, opt.dest, opt.default)
|
||||
@@ -357,8 +386,9 @@ class Config(object):
|
||||
x.append(line) # modifies the cached list inline
|
||||
|
||||
def getini(self, name):
|
||||
""" return configuration value from an ini file. If the
|
||||
specified name hasn't been registered through a prior ``parse.addini``
|
||||
""" return configuration value from an :ref:`ini file <inifiles>`. If the
|
||||
specified name hasn't been registered through a prior
|
||||
:py:func:`parser.addini <pytest.config.Parser.addini>`
|
||||
call (usually from a plugin), a ValueError is raised. """
|
||||
try:
|
||||
return self._inicache[name]
|
||||
@@ -412,8 +442,22 @@ class Config(object):
|
||||
self._checkconftest(name)
|
||||
return self._conftest.rget(name, path)
|
||||
|
||||
def getoption(self, name):
|
||||
""" return command line option value.
|
||||
|
||||
:arg name: name of the option. You may also specify
|
||||
the literal ``--OPT`` option instead of the "dest" option name.
|
||||
"""
|
||||
name = self._opt2dest.get(name, name)
|
||||
try:
|
||||
return getattr(self.option, name)
|
||||
except AttributeError:
|
||||
raise ValueError("no option named %r" % (name,))
|
||||
|
||||
def getvalue(self, name, path=None):
|
||||
""" return ``name`` value looked set from command line options.
|
||||
""" return command line option value.
|
||||
|
||||
:arg name: name of the command line option
|
||||
|
||||
(deprecated) if we can't find the option also lookup
|
||||
the name in a matching conftest file.
|
||||
@@ -435,6 +479,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("-")]
|
||||
@@ -445,20 +494,9 @@ 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 findupwards(current, basename):
|
||||
current = py.path.local(current)
|
||||
while 1:
|
||||
p = current.join(basename)
|
||||
if p.check():
|
||||
return p
|
||||
p = current.dirpath()
|
||||
if p == current:
|
||||
return
|
||||
current = p
|
||||
|
||||
|
||||
@@ -24,12 +24,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:
|
||||
@@ -329,7 +345,10 @@ def importplugin(importspec):
|
||||
#if str(e).find(name) == -1:
|
||||
# raise
|
||||
pass #
|
||||
__import__(importspec)
|
||||
try:
|
||||
__import__(importspec)
|
||||
except ImportError:
|
||||
raise ImportError(importspec)
|
||||
return sys.modules[importspec]
|
||||
|
||||
class MultiCall:
|
||||
|
||||
@@ -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):
|
||||
@@ -41,17 +42,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")
|
||||
@@ -70,9 +81,14 @@ class DoctestItem(pytest.Item):
|
||||
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):
|
||||
@@ -82,6 +98,11 @@ class DoctestModule(DoctestItem, pytest.File):
|
||||
module = self.config._conftest.importconftest(self.fspath)
|
||||
else:
|
||||
module = self.fspath.pyimport()
|
||||
# satisfy `FixtureRequest` constructor...
|
||||
self.funcargs = {}
|
||||
self._fixtureinfo = FuncFixtureInfo((), [], {})
|
||||
fixture_request = FixtureRequest(self)
|
||||
failed, tot = doctest.testmod(
|
||||
module, raise_on_error=True, verbose=0,
|
||||
extraglobs=dict(getfixture=fixture_request.getfuncargvalue),
|
||||
optionflags=doctest.ELLIPSIS)
|
||||
|
||||
@@ -23,8 +23,28 @@ def pytest_cmdline_preparse(config, args):
|
||||
"""modify command line arguments before option parsing. """
|
||||
|
||||
def pytest_addoption(parser):
|
||||
"""add optparse-style options and ini-style config values via calls
|
||||
to ``parser.addoption`` and ``parser.addini(...)``.
|
||||
"""register optparse-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.
|
||||
|
||||
:arg parser: To add command line options, call
|
||||
:py:func:`parser.addoption(...) <_pytest.config.Parser.addoption>`.
|
||||
To add ini-file values call :py:func:`parser.addini(...)
|
||||
<_pytest.config.Parser.addini>`.
|
||||
|
||||
Options can later be accessed through the
|
||||
:py:class:`config <_pytest.config.Config>` object, respectively:
|
||||
|
||||
- :py:func:`config.getoption(name) <_pytest.config.Config.getoption>` to
|
||||
retrieve the value of a command line option.
|
||||
|
||||
- :py:func:`config.getini(name) <_pytest.config.Config.getini>` to retrieve
|
||||
a value read from an ini-style file.
|
||||
|
||||
The config object is passed around on many internal objects via the ``.config``
|
||||
attribute or can be retrieved as the ``pytestconfig`` fixture or accessed
|
||||
via (deprecated) ``pytest.config``.
|
||||
"""
|
||||
|
||||
def pytest_cmdline_main(config):
|
||||
@@ -33,7 +53,7 @@ def pytest_cmdline_main(config):
|
||||
pytest_cmdline_main.firstresult = True
|
||||
|
||||
def pytest_configure(config):
|
||||
""" called after command line options have been parsed.
|
||||
""" called after command line options have been parsed
|
||||
and all plugins and initial conftest files been loaded.
|
||||
"""
|
||||
|
||||
|
||||
@@ -70,7 +70,8 @@ def pytest_addoption(parser):
|
||||
|
||||
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)
|
||||
|
||||
@@ -106,11 +107,20 @@ class LogXML(object):
|
||||
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 +129,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 +166,7 @@ class LogXML(object):
|
||||
message=skipreason
|
||||
))
|
||||
self.skipped += 1
|
||||
self._write_captured_output(report)
|
||||
|
||||
def pytest_runtest_logreport(self, report):
|
||||
if report.passed:
|
||||
|
||||
@@ -93,12 +93,14 @@ def wrap_session(config, doit):
|
||||
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:
|
||||
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)
|
||||
return session.exitstatus
|
||||
@@ -211,14 +213,16 @@ class Node(object):
|
||||
#: filesystem path where this node was collected from (can be None)
|
||||
self.fspath = getattr(parent, 'fspath', None)
|
||||
|
||||
#: fspath sensitive hook proxy used to call pytest hooks
|
||||
self.ihook = self.session.gethookproxy(self.fspath)
|
||||
|
||||
#: keywords/markers collected from all scopes
|
||||
self.keywords = NodeKeywords(self)
|
||||
|
||||
#self.extrainit()
|
||||
|
||||
@property
|
||||
def ihook(self):
|
||||
""" fspath sensitive hook proxy used to call pytest hooks"""
|
||||
return self.session.gethookproxy(self.fspath)
|
||||
|
||||
#def extrainit(self):
|
||||
# """"extra initialization after Node is initialized. Implemented
|
||||
# by some subclasses. """
|
||||
|
||||
@@ -7,12 +7,13 @@ def pytest_namespace():
|
||||
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). ")
|
||||
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 keywords. Example: -k 'test_method or test_other' "
|
||||
"matches all test functions whose name contains "
|
||||
"'test_method' or 'test_other'.")
|
||||
|
||||
group._addoption("-m",
|
||||
action="store", dest="markexpr", default="", metavar="MARKEXPR",
|
||||
@@ -51,7 +52,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:
|
||||
@@ -72,37 +73,26 @@ class BoolDict:
|
||||
def __getitem__(self, name):
|
||||
return name in self._mydict
|
||||
|
||||
class SubstringDict:
|
||||
def __init__(self, mydict):
|
||||
self._mydict = mydict
|
||||
def __getitem__(self, name):
|
||||
for key in self._mydict:
|
||||
if name in key:
|
||||
return True
|
||||
return False
|
||||
|
||||
def matchmark(colitem, matchexpr):
|
||||
return eval(matchexpr, {}, BoolDict(colitem.obj.__dict__))
|
||||
return eval(matchexpr, {}, BoolDict(colitem.keywords))
|
||||
|
||||
def matchkeyword(colitem, keywordexpr):
|
||||
keywordexpr = keywordexpr.replace("-", "not ")
|
||||
return eval(keywordexpr, {}, SubstringDict(colitem.keywords))
|
||||
|
||||
def pytest_configure(config):
|
||||
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
|
||||
a ``py.test.mark`` singleton instance. Example::
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
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)
|
||||
@@ -15,7 +17,7 @@ def pytest_runtest_makereport(__multicall__, item, call):
|
||||
|
||||
@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'):
|
||||
@@ -39,9 +43,18 @@ def pytest_make_collect_report(collector):
|
||||
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"):
|
||||
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()
|
||||
|
||||
@@ -61,24 +61,43 @@ class PdbInvoke:
|
||||
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]
|
||||
post_mortem(tb)
|
||||
rep._pdbshown = True
|
||||
return rep
|
||||
return _enter_pdb(item, call.excinfo, rep)
|
||||
|
||||
|
||||
|
||||
|
||||
def _enter_pdb(item, excinfo, 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")
|
||||
|
||||
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 +105,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()
|
||||
|
||||
@@ -195,10 +195,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
|
||||
@@ -422,7 +419,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)
|
||||
return py.std.subprocess.Popen(cmdargs,
|
||||
stdout=stdout, stderr=stderr, **kw)
|
||||
|
||||
def pytestmain(self, *args, **kwargs):
|
||||
class ResetCapturing:
|
||||
@@ -532,6 +530,8 @@ class TmpTestdir:
|
||||
pytest.skip("pypy-64 bit not supported")
|
||||
if sys.platform == "darwin":
|
||||
pytest.xfail("pexpect does not work reliably on darwin?!")
|
||||
if sys.platform.startswith("freebsd"):
|
||||
pytest.xfail("pexpect does not work reliably on freebsd")
|
||||
logfile = self.tmpdir.join("spawn.out")
|
||||
child = pexpect.spawn(cmd, logfile=logfile.open("w"))
|
||||
child.timeout = expect_timeout
|
||||
|
||||
@@ -12,6 +12,16 @@ cutdir = py.path.local(_pytest.__file__).dirpath()
|
||||
|
||||
callable = py.builtin.callable
|
||||
|
||||
def getimfunc(func):
|
||||
try:
|
||||
return func.__func__
|
||||
except AttributeError:
|
||||
try:
|
||||
return func.im_func
|
||||
except AttributeError:
|
||||
return func
|
||||
|
||||
|
||||
class FixtureFunctionMarker:
|
||||
def __init__(self, scope, params, autouse=False):
|
||||
self.scope = scope
|
||||
@@ -100,10 +110,12 @@ def pytest_configure(config):
|
||||
"times passing in multiple different argument value sets. Example: "
|
||||
"@parametrize('arg1', [1,2]) would lead to two calls of the decorated "
|
||||
"test function, one with arg1=1 and another with arg1=2."
|
||||
" see http://pytest.org/latest/parametrize.html for more info and "
|
||||
"examples."
|
||||
)
|
||||
config.addinivalue_line("markers",
|
||||
"usefixtures(fixturename1, fixturename2, ...): mark tests as needing "
|
||||
"all of the specified fixtures."
|
||||
"all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures "
|
||||
)
|
||||
|
||||
def pytest_sessionstart(session):
|
||||
@@ -149,8 +161,8 @@ def pytest_collect_file(path, parent):
|
||||
break
|
||||
else:
|
||||
return
|
||||
return parent.ihook.pytest_pycollect_makemodule(
|
||||
path=path, parent=parent)
|
||||
ihook = parent.session.gethookproxy(path)
|
||||
return ihook.pytest_pycollect_makemodule(path=path, parent=parent)
|
||||
|
||||
def pytest_pycollect_makemodule(path, parent):
|
||||
return Module(path, parent)
|
||||
@@ -163,10 +175,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:
|
||||
@@ -265,17 +277,17 @@ 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
|
||||
|
||||
def makeitem(self, name, obj):
|
||||
#assert self.ihook.fspath == self.fspath, self
|
||||
return self.ihook.pytest_pycollect_makeitem(
|
||||
collector=self, name=name, obj=obj)
|
||||
|
||||
@@ -382,6 +394,11 @@ class Module(pytest.File, PyCollector):
|
||||
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):
|
||||
@@ -515,7 +532,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:
|
||||
@@ -585,7 +602,7 @@ class CallSpec2(object):
|
||||
if valtype == "funcargs":
|
||||
self.params[arg] = id
|
||||
self._arg2scopenum[arg] = scopenum
|
||||
if val == _notexists:
|
||||
if val is _notexists:
|
||||
self._emptyparamspecified = True
|
||||
self._idlist.append(id)
|
||||
|
||||
@@ -630,8 +647,11 @@ class Metafunc(FuncargnamesCompatAttr):
|
||||
|
||||
:arg argnames: an argument name or a list of argument names
|
||||
|
||||
: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
|
||||
@@ -717,7 +737,7 @@ def idmaker(argnames, argvalues):
|
||||
this_id = []
|
||||
for nameindex, val in enumerate(valset):
|
||||
if not isinstance(val, (float, int, str)):
|
||||
this_id.append(argnames[nameindex]+str(valindex))
|
||||
this_id.append(str(argnames[nameindex])+str(valindex))
|
||||
else:
|
||||
this_id.append(str(val))
|
||||
idlist.append("-".join(this_id))
|
||||
@@ -893,23 +913,30 @@ class Function(FunctionMixin, pytest.Item, FuncargnamesCompatAttr):
|
||||
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):
|
||||
@@ -1184,8 +1211,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
|
||||
@@ -1211,6 +1240,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):
|
||||
@@ -1223,15 +1253,18 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||
__tracebackhide__ = False
|
||||
mp.setattr(self, "scope", scope)
|
||||
|
||||
# route request.addfinalizer to fixturedef
|
||||
mp.setattr(self, "addfinalizer", fixturedef.addfinalizer)
|
||||
|
||||
# perform the fixture call
|
||||
val = fixturedef.execute(request=self)
|
||||
|
||||
# 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.setattr(self, "addfinalizer", fixturedef.addfinalizer)
|
||||
# finally perform the fixture call
|
||||
val = fixturedef.execute(request=self)
|
||||
mp.undo()
|
||||
return val
|
||||
|
||||
@@ -1273,12 +1306,6 @@ scopenum_subfunction = scopes.index("subfunction")
|
||||
def scopemismatch(currentscope, newscope):
|
||||
return scopes.index(newscope) > scopes.index(currentscope)
|
||||
|
||||
def slice_kwargs(names, kwargs):
|
||||
new_kwargs = {}
|
||||
for name in names:
|
||||
new_kwargs[name] = kwargs[name]
|
||||
return new_kwargs
|
||||
|
||||
class FixtureLookupError(LookupError):
|
||||
""" could not return a requested Fixture (missing or invalid). """
|
||||
def __init__(self, argname, request, msg=None):
|
||||
@@ -1385,13 +1412,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:
|
||||
@@ -1442,7 +1469,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
|
||||
@@ -1496,6 +1523,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:
|
||||
@@ -1517,9 +1546,9 @@ 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 l:
|
||||
for fin in reversed(l):
|
||||
fin()
|
||||
|
||||
def parsefactories(self, node_or_obj, nodeid=None, unittest=False):
|
||||
@@ -1538,7 +1567,7 @@ class FixtureManager:
|
||||
continue
|
||||
# fixture functions have a pytest_funcarg__ prefix (pre-2.3 style)
|
||||
# or are "@pytest.fixture" marked
|
||||
marker = getattr(obj, "_pytestfixturefunction", None)
|
||||
marker = getfixturemarker(obj)
|
||||
if marker is None:
|
||||
if not name.startswith(self._argprefix):
|
||||
continue
|
||||
@@ -1586,9 +1615,9 @@ class FixtureManager:
|
||||
|
||||
class FixtureDef:
|
||||
""" A container for a factory definition. """
|
||||
def __init__(self, fixturenanager, baseid, argname, func, scope, params,
|
||||
def __init__(self, fixturemanager, baseid, argname, func, scope, params,
|
||||
unittest=False):
|
||||
self._fixturemanager = fixturenanager
|
||||
self._fixturemanager = fixturemanager
|
||||
self.baseid = baseid
|
||||
self.func = func
|
||||
self.argname = argname
|
||||
@@ -1598,7 +1627,6 @@ class FixtureDef:
|
||||
startindex = unittest and 1 or None
|
||||
self.argnames = getfuncargnames(func, startindex=startindex)
|
||||
self.unittest = unittest
|
||||
self.active = False
|
||||
self._finalizer = []
|
||||
|
||||
def addfinalizer(self, finalizer):
|
||||
@@ -1610,9 +1638,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 = {}
|
||||
@@ -1621,8 +1651,20 @@ class FixtureDef:
|
||||
if self.unittest:
|
||||
result = self.func(request.instance, **kwargs)
|
||||
else:
|
||||
result = self.func(**kwargs)
|
||||
self.active = True
|
||||
fixturefunc = self.func
|
||||
# the fixture function needs to be bound to the actual
|
||||
# request.instance so that code working with "self" behaves
|
||||
# as expected. XXX request.instance should maybe return None
|
||||
# instead of raising AttributeError
|
||||
try:
|
||||
if request.instance is not None:
|
||||
fixturefunc = getimfunc(self.func)
|
||||
if fixturefunc != self.func:
|
||||
fixturefunc = fixturefunc.__get__(request.instance)
|
||||
except AttributeError:
|
||||
pass
|
||||
result = fixturefunc(**kwargs)
|
||||
assert not hasattr(self, "cached_result")
|
||||
self.cached_result = result
|
||||
return result
|
||||
|
||||
@@ -1632,8 +1674,14 @@ class FixtureDef:
|
||||
def getfuncargnames(function, startindex=None):
|
||||
# XXX merge with main.py's varnames
|
||||
#assert not inspect.isclass(function)
|
||||
realfunction = function
|
||||
while hasattr(realfunction, "__wrapped__"):
|
||||
realfunction = realfunction.__wrapped__
|
||||
if startindex is None:
|
||||
startindex = inspect.ismethod(function) and 1 or 0
|
||||
if realfunction != function:
|
||||
startindex += len(getattr(function, "patchings", []))
|
||||
function = realfunction
|
||||
argnames = inspect.getargs(py.code.getrawcode(function))[0]
|
||||
defaults = getattr(function, 'func_defaults',
|
||||
getattr(function, '__defaults__', None)) or ()
|
||||
@@ -1716,6 +1764,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
|
||||
|
||||
|
||||
@@ -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):
|
||||
@@ -364,6 +372,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
|
||||
|
||||
|
||||
@@ -11,17 +11,18 @@ def pytest_addoption(parser):
|
||||
|
||||
def pytest_configure(config):
|
||||
config.addinivalue_line("markers",
|
||||
"skipif(*conditions): skip the given test function if evaluation "
|
||||
"of all conditions has a True value. Evaluation happens within the "
|
||||
"skipif(condition): skip the given test function if eval(condition) "
|
||||
"results in a True value. Evaluation happens within the "
|
||||
"module global context. Example: skipif('sys.platform == \"win32\"') "
|
||||
"skips the test if we are on the win32 platform. "
|
||||
"skips the test if we are on the win32 platform. see "
|
||||
"http://pytest.org/latest/skipping.html"
|
||||
)
|
||||
config.addinivalue_line("markers",
|
||||
"xfail(*conditions, reason=None, run=True): mark the the test function "
|
||||
"as an expected failure. Optionally specify a reason and run=False "
|
||||
"if you don't even want to execute the test function. Any positional "
|
||||
"condition strings will be evaluated (like with skipif) and if one is "
|
||||
"False the marker will not be applied."
|
||||
"xfail(condition, reason=None, run=True): mark the the test function "
|
||||
"as an expected failure if eval(condition) has a True value. "
|
||||
"Optionally specify a reason for better reporting and run=False if "
|
||||
"you don't even want to execute the test function. See "
|
||||
"http://pytest.org/latest/skipping.html"
|
||||
)
|
||||
|
||||
def pytest_namespace():
|
||||
@@ -85,7 +86,7 @@ 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")
|
||||
|
||||
@@ -57,7 +57,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())
|
||||
|
||||
@@ -331,7 +331,7 @@ class TerminalReporter:
|
||||
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.config.hook.pytest_terminal_summary(terminalreporter=self)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ class UnitTestCase(pytest.Class):
|
||||
loader = py.std.unittest.TestLoader()
|
||||
module = self.getparent(pytest.Module).obj
|
||||
cls = self.obj
|
||||
foundsomething = False
|
||||
for name in loader.getTestCaseNames(self.obj):
|
||||
x = getattr(self.obj, name)
|
||||
funcobj = getattr(x, 'im_func', x)
|
||||
@@ -35,17 +36,26 @@ class UnitTestCase(pytest.Class):
|
||||
if hasattr(funcobj, 'todo'):
|
||||
pytest.mark.xfail(reason=str(funcobj.todo))(funcobj)
|
||||
yield TestCaseFunction(name, parent=self)
|
||||
foundsomething = True
|
||||
|
||||
if getattr(self.obj, 'runTest', None) is not None:
|
||||
yield TestCaseFunction('runTest', parent=self)
|
||||
if not foundsomething:
|
||||
runtest = getattr(self.obj, 'runTest', None)
|
||||
if runtest is not None:
|
||||
ut = sys.modules.get("twisted.trial.unittest", None)
|
||||
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()
|
||||
|
||||
@@ -12,6 +12,8 @@ PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
SITETARGET=latest
|
||||
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
|
||||
|
||||
regen:
|
||||
@@ -29,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"
|
||||
@@ -40,10 +44,10 @@ clean:
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
|
||||
install: html
|
||||
rsync -avz _build/html/ pytest.org:/www/pytest.org/latest
|
||||
rsync -avz _build/html/ pytest.org:/www/pytest.org/$(SITETARGET)
|
||||
|
||||
installpdf: latexpdf
|
||||
@scp $(BUILDDIR)/latex/pytest.pdf pytest.org:/www/pytest.org/latest
|
||||
@scp $(BUILDDIR)/latex/pytest.pdf pytest.org:/www/pytest.org/$(SITETARGET)
|
||||
|
||||
installall: clean install installpdf
|
||||
@echo "done"
|
||||
@@ -140,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."
|
||||
|
||||
@@ -5,6 +5,10 @@ Release announcements
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
release-2.3.4
|
||||
release-2.3.3
|
||||
release-2.3.2
|
||||
release-2.3.1
|
||||
release-2.3.0
|
||||
release-2.2.4
|
||||
release-2.2.2
|
||||
|
||||
39
doc/en/announce/release-2.3.1.txt
Normal file
39
doc/en/announce/release-2.3.1.txt
Normal file
@@ -0,0 +1,39 @@
|
||||
pytest-2.3.1: fix regression with factory functions
|
||||
===========================================================================
|
||||
|
||||
pytest-2.3.1 is a quick follow-up release:
|
||||
|
||||
- fix issue202 - regression with fixture functions/funcarg factories:
|
||||
using "self" is now safe again and works as in 2.2.4. Thanks
|
||||
to Eduard Schettino for the quick bug report.
|
||||
|
||||
- disable pexpect pytest self tests on Freebsd - thanks Koob for the
|
||||
quick reporting
|
||||
|
||||
- fix/improve interactive docs with --markers
|
||||
|
||||
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.0 and 2.3.1
|
||||
-----------------------------------
|
||||
|
||||
- fix issue202 - fix regression: using "self" from fixture functions now
|
||||
works as expected (it's the same "self" instance that a test method
|
||||
which uses the fixture sees)
|
||||
|
||||
- skip pexpect using tests (test_pdb.py mostly) on freebsd* systems
|
||||
due to pexpect not supporting it properly (hanging)
|
||||
|
||||
- link to web pages from --markers output which provides help for
|
||||
pytest.mark.* usage.
|
||||
57
doc/en/announce/release-2.3.2.txt
Normal file
57
doc/en/announce/release-2.3.2.txt
Normal file
@@ -0,0 +1,57 @@
|
||||
pytest-2.3.2: some fixes and more traceback-printing speed
|
||||
===========================================================================
|
||||
|
||||
pytest-2.3.2 is a another stabilization release:
|
||||
|
||||
- issue 205: fixes a regression with conftest detection
|
||||
- issue 208/29: fixes traceback-printing speed in some bad cases
|
||||
- fix teardown-ordering for parametrized setups
|
||||
- fix unittest and trial compat behaviour with respect to runTest() methods
|
||||
- issue 206 and others: some improvements to packaging
|
||||
- fix issue127 and others: improve some docs
|
||||
|
||||
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.1 and 2.3.2
|
||||
-----------------------------------
|
||||
|
||||
- fix issue208 and fix issue29 use new py version to avoid long pauses
|
||||
when printing tracebacks in long modules
|
||||
|
||||
- fix issue205 - conftests in subdirs customizing
|
||||
pytest_pycollect_makemodule and pytest_pycollect_makeitem
|
||||
now work properly
|
||||
|
||||
- fix teardown-ordering for parametrized setups
|
||||
|
||||
- fix issue127 - better documentation for pytest_addoption
|
||||
and related objects.
|
||||
|
||||
- fix unittest behaviour: TestCase.runtest only called if there are
|
||||
test methods defined
|
||||
|
||||
- improve trial support: don't collect its empty
|
||||
unittest.TestCase.runTest() method
|
||||
|
||||
- "python setup.py test" now works with pytest itself
|
||||
|
||||
- fix/improve internal/packaging related bits:
|
||||
|
||||
- exception message check of test_nose.py now passes on python33 as well
|
||||
|
||||
- issue206 - fix test_assertrewrite.py to work when a global
|
||||
PYTHONDONTWRITEBYTECODE=1 is present
|
||||
|
||||
- add tox.ini to pytest distribution so that ignore-dirs and others config
|
||||
bits are properly distributed for maintainers who run pytest-own tests
|
||||
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
|
||||
|
||||
@@ -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.0
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 1 items
|
||||
|
||||
test_assert1.py F
|
||||
@@ -110,7 +110,7 @@ if you run this module::
|
||||
|
||||
$ py.test test_assert2.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.0
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 1 items
|
||||
|
||||
test_assert2.py F
|
||||
|
||||
@@ -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.0
|
||||
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 0x2d98050>
|
||||
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.0"
|
||||
version = release = "2.3.4.1"
|
||||
|
||||
import sys, os
|
||||
|
||||
@@ -53,7 +53,7 @@ master_doc = 'contents'
|
||||
|
||||
# General information about the project.
|
||||
project = u'pytest'
|
||||
copyright = u'2011, holger krekel et alii'
|
||||
copyright = u'2012, holger krekel'
|
||||
|
||||
|
||||
|
||||
@@ -197,7 +197,7 @@ htmlhelp_basename = 'pytestdoc'
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('contents', 'pytest.tex', u'pytest Documentation',
|
||||
u'holger krekel et alii', 'manual'),
|
||||
u'holger krekel, http://merlinux.eu', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
@@ -239,7 +239,7 @@ man_pages = [
|
||||
epub_title = u'pytest'
|
||||
epub_author = u'holger krekel at merlinux eu'
|
||||
epub_publisher = u'holger krekel at merlinux eu'
|
||||
epub_copyright = u'2011, holger krekel et alii'
|
||||
epub_copyright = u'2012, holger krekel et alii'
|
||||
|
||||
# The language of the text. It defaults to the language option
|
||||
# or en if the language is not set.
|
||||
@@ -273,6 +273,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 = {'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
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ configurations files by using the general help option::
|
||||
This will display command line and configuration file settings
|
||||
which were registered by installed plugins.
|
||||
|
||||
.. _inifiles:
|
||||
|
||||
How test configuration is read from configuration INI-files
|
||||
-------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -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.0
|
||||
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])
|
||||
|
||||
@@ -1,198 +0,0 @@
|
||||
|
||||
.. highlightlang:: python
|
||||
|
||||
.. _myapp:
|
||||
|
||||
Building an SSH connecting Application fixture
|
||||
==========================================================
|
||||
|
||||
The goal of this tutorial-example is to show how you can put efficient
|
||||
test support and fixture code in one place, allowing test modules and
|
||||
test functions to stay ignorant of importing, configuration or
|
||||
setup/teardown details.
|
||||
|
||||
The tutorial implements a simple ``RemoteInterpreter`` object that
|
||||
allows evaluation of python expressions. We are going to use
|
||||
the `execnet <http://codespeak.net/execnet>`_ package for the
|
||||
underlying cross-python bridge functionality.
|
||||
|
||||
|
||||
Step 1: Implementing a first test
|
||||
--------------------------------------------------------------
|
||||
|
||||
Let's write a simple test function using a not yet defined ``interp`` fixture::
|
||||
|
||||
# content of test_remoteinterpreter.py
|
||||
|
||||
def test_eval_simple(interp):
|
||||
assert interp.eval("6*9") == 42
|
||||
|
||||
The test function needs an argument named `interp` and therefore pytest will
|
||||
look for a :ref:`fixture function` that matches this name. We'll define it
|
||||
in a :ref:`local plugin <localplugin>` to make it available also to other
|
||||
test modules::
|
||||
|
||||
# content of conftest.py
|
||||
|
||||
from .remoteinterpreter import RemoteInterpreter
|
||||
|
||||
@pytest.fixture
|
||||
def interp(request):
|
||||
import execnet
|
||||
gw = execnet.makegateway()
|
||||
return RemoteInterpreter(gw)
|
||||
|
||||
To run the example we furthermore need to implement a RemoteInterpreter
|
||||
object which working with the injected execnet-gateway connection::
|
||||
|
||||
# content of remoteintepreter.py
|
||||
|
||||
class RemoteInterpreter:
|
||||
def __init__(self, gateway):
|
||||
self.gateway = gateway
|
||||
|
||||
def eval(self, expression):
|
||||
# execnet open a "gateway" to the remote process
|
||||
# which enables to remotely execute code and communicate
|
||||
# to and fro via channels
|
||||
ch = self.gateway.remote_exec("channel.send(%s)" % expression)
|
||||
return ch.receive()
|
||||
|
||||
That's it, we can now run the test::
|
||||
|
||||
$ py.test test_remoteinterpreter.py
|
||||
Traceback (most recent call last):
|
||||
File "/home/hpk/p/pytest/.tox/regen/bin/py.test", line 9, in <module>
|
||||
load_entry_point('pytest==2.3.0', 'console_scripts', 'py.test')()
|
||||
File "/home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/core.py", line 473, in main
|
||||
config = _prepareconfig(args, plugins)
|
||||
File "/home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/core.py", line 463, in _prepareconfig
|
||||
pluginmanager=_pluginmanager, args=args)
|
||||
File "/home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/core.py", line 422, in __call__
|
||||
return self._docall(methods, kwargs)
|
||||
File "/home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/core.py", line 433, in _docall
|
||||
res = mc.execute()
|
||||
File "/home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/core.py", line 351, in execute
|
||||
res = method(**kwargs)
|
||||
File "/home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/helpconfig.py", line 25, in pytest_cmdline_parse
|
||||
config = __multicall__.execute()
|
||||
File "/home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/core.py", line 351, in execute
|
||||
res = method(**kwargs)
|
||||
File "/home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/config.py", line 10, in pytest_cmdline_parse
|
||||
config.parse(args)
|
||||
File "/home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/config.py", line 344, in parse
|
||||
self._preparse(args)
|
||||
File "/home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/config.py", line 322, in _preparse
|
||||
self._setinitialconftest(args)
|
||||
File "/home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/config.py", line 301, in _setinitialconftest
|
||||
self._conftest.setinitial(args)
|
||||
File "/home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/config.py", line 160, in setinitial
|
||||
self._try_load_conftest(anchor)
|
||||
File "/home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/config.py", line 166, in _try_load_conftest
|
||||
self._path2confmods[None] = self.getconftestmodules(anchor)
|
||||
File "/home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/config.py", line 190, in getconftestmodules
|
||||
clist[:0] = self.getconftestmodules(dp)
|
||||
File "/home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/config.py", line 189, in getconftestmodules
|
||||
clist.append(self.importconftest(conftestpath))
|
||||
File "/home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/config.py", line 218, in importconftest
|
||||
self._conftestpath2mod[conftestpath] = mod = conftestpath.pyimport()
|
||||
File "/home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/py/_path/local.py", line 532, in pyimport
|
||||
__import__(modname)
|
||||
File "/tmp/doc-exec-286/conftest.py", line 2, in <module>
|
||||
from .remoteinterpreter import RemoteInterpreter
|
||||
ValueError: Attempted relative import in non-package
|
||||
|
||||
.. _`tut-cmdlineoption`:
|
||||
|
||||
Step 2: Adding command line configuration
|
||||
-----------------------------------------------------------
|
||||
|
||||
To add a command line option we update the ``conftest.py`` of
|
||||
the previous example and add a command line option which
|
||||
is passed on to the MyApp object::
|
||||
|
||||
# content of ./conftest.py
|
||||
import pytest
|
||||
from myapp import MyApp
|
||||
|
||||
def pytest_addoption(parser): # pytest hook called during initialisation
|
||||
parser.addoption("--ssh", action="store", default=None,
|
||||
help="specify ssh host to run tests with")
|
||||
|
||||
@pytest.fixture
|
||||
def mysetup(request): # "mysetup" factory function
|
||||
return MySetup(request.config)
|
||||
|
||||
class MySetup:
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
self.app = MyApp()
|
||||
|
||||
def getsshconnection(self):
|
||||
import execnet
|
||||
host = self.config.option.ssh
|
||||
if host is None:
|
||||
pytest.skip("specify ssh host with --ssh")
|
||||
return execnet.SshGateway(host)
|
||||
|
||||
|
||||
Now any test function can use the ``mysetup.getsshconnection()`` method
|
||||
like this::
|
||||
|
||||
# content of test_ssh.py
|
||||
class TestClass:
|
||||
def test_function(self, mysetup):
|
||||
conn = mysetup.getsshconnection()
|
||||
# work with conn
|
||||
|
||||
Running it yields::
|
||||
|
||||
$ py.test -q test_ssh.py -rs
|
||||
Traceback (most recent call last):
|
||||
File "/home/hpk/p/pytest/.tox/regen/bin/py.test", line 9, in <module>
|
||||
load_entry_point('pytest==2.3.0', 'console_scripts', 'py.test')()
|
||||
File "/home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/core.py", line 473, in main
|
||||
config = _prepareconfig(args, plugins)
|
||||
File "/home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/core.py", line 463, in _prepareconfig
|
||||
pluginmanager=_pluginmanager, args=args)
|
||||
File "/home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/core.py", line 422, in __call__
|
||||
return self._docall(methods, kwargs)
|
||||
File "/home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/core.py", line 433, in _docall
|
||||
res = mc.execute()
|
||||
File "/home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/core.py", line 351, in execute
|
||||
res = method(**kwargs)
|
||||
File "/home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/helpconfig.py", line 25, in pytest_cmdline_parse
|
||||
config = __multicall__.execute()
|
||||
File "/home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/core.py", line 351, in execute
|
||||
res = method(**kwargs)
|
||||
File "/home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/config.py", line 10, in pytest_cmdline_parse
|
||||
config.parse(args)
|
||||
File "/home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/config.py", line 344, in parse
|
||||
self._preparse(args)
|
||||
File "/home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/config.py", line 322, in _preparse
|
||||
self._setinitialconftest(args)
|
||||
File "/home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/config.py", line 301, in _setinitialconftest
|
||||
self._conftest.setinitial(args)
|
||||
File "/home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/config.py", line 160, in setinitial
|
||||
self._try_load_conftest(anchor)
|
||||
File "/home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/config.py", line 166, in _try_load_conftest
|
||||
self._path2confmods[None] = self.getconftestmodules(anchor)
|
||||
File "/home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/config.py", line 190, in getconftestmodules
|
||||
clist[:0] = self.getconftestmodules(dp)
|
||||
File "/home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/config.py", line 189, in getconftestmodules
|
||||
clist.append(self.importconftest(conftestpath))
|
||||
File "/home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/config.py", line 218, in importconftest
|
||||
self._conftestpath2mod[conftestpath] = mod = conftestpath.pyimport()
|
||||
File "/home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/py/_path/local.py", line 532, in pyimport
|
||||
__import__(modname)
|
||||
File "/tmp/doc-exec-286/conftest.py", line 2, in <module>
|
||||
from myapp import MyApp
|
||||
ImportError: No module named myapp
|
||||
|
||||
If you specify a command line option like ``py.test --ssh=python.org`` the test will execute as expected.
|
||||
|
||||
Note that neither the ``TestClass`` nor the ``test_function`` need to
|
||||
know anything about how to setup the test state. It is handled separately
|
||||
in the ``conftest.py`` file. It is easy
|
||||
to extend the ``mysetup`` object for further needs in the test code - and for use by any other test functions in the files and directories below the ``conftest.py`` file.
|
||||
|
||||
@@ -5,16 +5,22 @@ Usages and Examples
|
||||
===========================================
|
||||
|
||||
Here is a (growing) list of examples. :ref:`Contact <contact>` us if you
|
||||
need more examples or have questions. Also take a look at the :ref:`comprehensive documentation <toc>` which contains many example snippets as well.
|
||||
need more examples or have questions. Also take a look at the
|
||||
:ref:`comprehensive documentation <toc>` which contains many example
|
||||
snippets as well. Also, `pytest on stackoverflow.com
|
||||
<http://stackoverflow.com/search?q=pytest>`_ often comes with example
|
||||
answers.
|
||||
|
||||
Also, `pytest on stackoverflow.com <http://stackoverflow.com/search?q=pytest>`_
|
||||
is a primary continously updated source of pytest questions and answers
|
||||
which often contain examples. New Questions will usually be seen
|
||||
by pytest users or developers.
|
||||
For basic examples, see
|
||||
|
||||
.. note::
|
||||
- :doc:`../getting-started` for basic introductory examples
|
||||
- :ref:`assert` for basic assertion examples
|
||||
- :ref:`fixtures` for basic fixture/setup examples
|
||||
- :ref:`parametrize` for basic test function parametrization
|
||||
- :doc:`../unittest` for basic unittest integration
|
||||
- :doc:`../nose` for basic nosetests integration
|
||||
|
||||
see :doc:`../getting-started` for basic introductory examples
|
||||
The following examples aim at various use cases you might encounter.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
@@ -23,5 +29,6 @@ by pytest users or developers.
|
||||
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.0 -- /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.00 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.0 -- /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.00 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
|
||||
-------------------------------------
|
||||
@@ -65,13 +114,13 @@ You can ask which markers exist for your test suite - the list includes our just
|
||||
$ py.test --markers
|
||||
@pytest.mark.webtest: mark a test as a webtest.
|
||||
|
||||
@pytest.mark.skipif(*conditions): skip the given test function if evaluation of all conditions has a True value. Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform.
|
||||
@pytest.mark.skipif(condition): skip the given test function if eval(condition) results in a True value. Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform. see http://pytest.org/latest/skipping.html
|
||||
|
||||
@pytest.mark.xfail(*conditions, reason=None, run=True): mark the the test function as an expected failure. Optionally specify a reason and run=False if you don't even want to execute the test function. Any positional condition strings will be evaluated (like with skipif) and if one is False the marker will not be applied.
|
||||
@pytest.mark.xfail(condition, reason=None, run=True): mark the the test function as an expected failure if eval(condition) has a True value. Optionally specify a reason for better reporting and run=False if you don't even want to execute the test function. See http://pytest.org/latest/skipping.html
|
||||
|
||||
@pytest.mark.parametrize(argnames, argvalues): call a test function multiple times passing in multiple different argument value sets. Example: @parametrize('arg1', [1,2]) would lead to two calls of the decorated test function, one with arg1=1 and another with arg1=2.
|
||||
@pytest.mark.parametrize(argnames, argvalues): call a test function multiple times passing in multiple different argument value sets. Example: @parametrize('arg1', [1,2]) would lead to two calls of the decorated test function, one with arg1=1 and another with arg1=2. see http://pytest.org/latest/parametrize.html for more info and examples.
|
||||
|
||||
@pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures.
|
||||
@pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures
|
||||
|
||||
@pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible.
|
||||
|
||||
@@ -137,46 +186,6 @@ in which case it will be applied to all functions and
|
||||
methods defined in the module.
|
||||
|
||||
|
||||
Using ``-k TEXT`` to select tests
|
||||
----------------------------------------------------
|
||||
|
||||
You can use the ``-k`` command line option to only run tests with names matching
|
||||
the given argument::
|
||||
|
||||
$ py.test -k send_http # running with the above defined examples
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.0
|
||||
collected 4 items
|
||||
|
||||
test_server.py .
|
||||
|
||||
=================== 3 tests deselected by '-ksend_http' ====================
|
||||
================== 1 passed, 3 deselected in 0.01 seconds ==================
|
||||
|
||||
And you can also run all tests except the ones that match the keyword::
|
||||
|
||||
$ py.test -k-send_http
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.0
|
||||
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 ==================
|
||||
|
||||
Or to only select the class::
|
||||
|
||||
$ py.test -kTestClass
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.0
|
||||
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`:
|
||||
|
||||
@@ -194,7 +203,7 @@ specifies via named environments::
|
||||
|
||||
import pytest
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption("-E", dest="env", action="store", metavar="NAME",
|
||||
parser.addoption("-E", action="store", metavar="NAME",
|
||||
help="only run tests matching the environment NAME.")
|
||||
|
||||
def pytest_configure(config):
|
||||
@@ -206,7 +215,7 @@ specifies via named environments::
|
||||
envmarker = item.keywords.get("env", None)
|
||||
if envmarker is not None:
|
||||
envname = envmarker.args[0]
|
||||
if envname != item.config.option.env:
|
||||
if envname != item.config.getoption("-E"):
|
||||
pytest.skip("test requires env %r" % envname)
|
||||
|
||||
A test file using this local plugin::
|
||||
@@ -223,18 +232,18 @@ the test needs::
|
||||
|
||||
$ py.test -E stage2
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.0
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 1 items
|
||||
|
||||
test_someenv.py s
|
||||
|
||||
======================== 1 skipped in 0.00 seconds =========================
|
||||
======================== 1 skipped in 0.01 seconds =========================
|
||||
|
||||
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.0
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 1 items
|
||||
|
||||
test_someenv.py .
|
||||
@@ -246,13 +255,13 @@ The ``--markers`` option always gives you a list of available markers::
|
||||
$ py.test --markers
|
||||
@pytest.mark.env(name): mark test to run only on named environment
|
||||
|
||||
@pytest.mark.skipif(*conditions): skip the given test function if evaluation of all conditions has a True value. Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform.
|
||||
@pytest.mark.skipif(condition): skip the given test function if eval(condition) results in a True value. Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform. see http://pytest.org/latest/skipping.html
|
||||
|
||||
@pytest.mark.xfail(*conditions, reason=None, run=True): mark the the test function as an expected failure. Optionally specify a reason and run=False if you don't even want to execute the test function. Any positional condition strings will be evaluated (like with skipif) and if one is False the marker will not be applied.
|
||||
@pytest.mark.xfail(condition, reason=None, run=True): mark the the test function as an expected failure if eval(condition) has a True value. Optionally specify a reason for better reporting and run=False if you don't even want to execute the test function. See http://pytest.org/latest/skipping.html
|
||||
|
||||
@pytest.mark.parametrize(argnames, argvalues): call a test function multiple times passing in multiple different argument value sets. Example: @parametrize('arg1', [1,2]) would lead to two calls of the decorated test function, one with arg1=1 and another with arg1=2.
|
||||
@pytest.mark.parametrize(argnames, argvalues): call a test function multiple times passing in multiple different argument value sets. Example: @parametrize('arg1', [1,2]) would lead to two calls of the decorated test function, one with arg1=1 and another with arg1=2. see http://pytest.org/latest/parametrize.html for more info and examples.
|
||||
|
||||
@pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures.
|
||||
@pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures
|
||||
|
||||
@pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible.
|
||||
|
||||
@@ -351,12 +360,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.0
|
||||
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-288/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 +373,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.0
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 4 items
|
||||
|
||||
test_plat.py .
|
||||
@@ -373,3 +382,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.0
|
||||
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.03 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.0 -- /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.03 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
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.0
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 2 items
|
||||
<YamlFile 'test_simple.yml'>
|
||||
<YamlItem 'ok'>
|
||||
<YamlItem 'hello'>
|
||||
|
||||
============================= in 0.03 seconds =============================
|
||||
============================= in 0.05 seconds =============================
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
import pytest
|
||||
|
||||
def pytest_collect_file(path, parent):
|
||||
def pytest_collect_file(parent, path):
|
||||
if path.ext == ".yml" and path.basename.startswith("test"):
|
||||
return YamlFile(path, parent)
|
||||
|
||||
|
||||
class YamlFile(pytest.File):
|
||||
def collect(self):
|
||||
import yaml # we need a yaml parser, e.g. PyYAML
|
||||
@@ -17,7 +17,7 @@ class YamlItem(pytest.Item):
|
||||
def __init__(self, name, parent, spec):
|
||||
super(YamlItem, self).__init__(name, parent)
|
||||
self.spec = spec
|
||||
|
||||
|
||||
def runtest(self):
|
||||
for name, value in self.spec.items():
|
||||
# some custom test execution (dumb example follows)
|
||||
|
||||
@@ -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.0
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 4 items
|
||||
|
||||
test_scenarios.py ....
|
||||
@@ -116,7 +116,7 @@ If you just collect tests you'll also nicely see 'advanced' and 'basic' as varia
|
||||
|
||||
$ py.test --collectonly test_scenarios.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.0
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 4 items
|
||||
<Module 'test_scenarios.py'>
|
||||
<Class 'TestSampleWithScenarios'>
|
||||
@@ -180,7 +180,7 @@ Let's first see how it looks like at collection time::
|
||||
|
||||
$ py.test test_backends.py --collectonly
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.0
|
||||
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 0x2928878>
|
||||
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 0x18dd4d0>, 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.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ then the test collection looks like this::
|
||||
|
||||
$ py.test --collectonly
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.0
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 2 items
|
||||
<Module 'check_myapp.py'>
|
||||
<Class 'CheckMyApp'>
|
||||
@@ -51,7 +51,7 @@ then the test collection looks like this::
|
||||
<Function 'check_simple'>
|
||||
<Function 'check_complex'>
|
||||
|
||||
============================= in 0.00 seconds =============================
|
||||
============================= in 0.01 seconds =============================
|
||||
|
||||
Interpreting cmdline arguments as Python packages
|
||||
-----------------------------------------------------
|
||||
@@ -82,7 +82,7 @@ You can always peek at the collection tree without running tests like this::
|
||||
|
||||
. $ py.test --collectonly pythoncollection.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.0
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 3 items
|
||||
<Module 'pythoncollection.py'>
|
||||
<Function 'test_function'>
|
||||
@@ -91,7 +91,7 @@ You can always peek at the collection tree without running tests like this::
|
||||
<Function 'test_method'>
|
||||
<Function 'test_anothermethod'>
|
||||
|
||||
============================= in 0.00 seconds =============================
|
||||
============================= in 0.01 seconds =============================
|
||||
|
||||
customizing test collection to find all .py files
|
||||
---------------------------------------------------------
|
||||
@@ -135,7 +135,7 @@ interpreters and will leave out the setup.py file::
|
||||
|
||||
$ py.test --collectonly
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.0
|
||||
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.0
|
||||
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 0x2727750>
|
||||
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 0x26cf488>()
|
||||
E + and 43 = <function g at 0x26cf500>()
|
||||
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 0x27277d0>
|
||||
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 0x2727890>
|
||||
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 0x26cf7d0>()
|
||||
E + where 42 = <function f at 0x137caa0>()
|
||||
|
||||
failure_demo.py:38: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_eq_text _________________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x2727c10>
|
||||
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 0x26d1110>
|
||||
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 0x26d1190>
|
||||
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 0x26d1090>
|
||||
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 0x26c7ed0>
|
||||
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 0x26c7190>
|
||||
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 0x26c7410>
|
||||
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 0x26c78d0>
|
||||
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 0x26c7690>
|
||||
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 0x26c7510>
|
||||
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 0x26c3190>
|
||||
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 0x26c3d10>
|
||||
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 0x26c3d50>
|
||||
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 0x26c3790>
|
||||
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 0x26c3150>
|
||||
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 0x26c3850>.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 0x26c39d0>.b
|
||||
E + where <failure_demo.Foo object at 0x26c39d0> = <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 0x2890810>
|
||||
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 0x26c35d0>.b
|
||||
E + where <failure_demo.Foo object at 0x26c35d0> = <class 'failure_demo.Foo'>()
|
||||
E + and 2 = <failure_demo.Bar object at 0x26c3c50>.b
|
||||
E + where <failure_demo.Bar object at 0x26c3c50> = <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 0x2738c68>
|
||||
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/local/lib/python2.7/site-packages/_pytest/python.py:838>: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 0x27413b0>
|
||||
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 0x273a4d0>
|
||||
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 0x272d248>
|
||||
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 0x272df80>
|
||||
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 0x272ed88>
|
||||
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 0x2741758>
|
||||
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 0x2728d88>
|
||||
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 0x272ab90>
|
||||
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 0x272b998>
|
||||
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 0x2734ad0>('456')
|
||||
E + where <built-in method startswith of str object at 0x2734ad0> = '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 0x272a0e0>
|
||||
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 0x2734ad0>('456')
|
||||
E + where <built-in method startswith of str object at 0x2734ad0> = '123'.startswith
|
||||
E + where '123' = <function f at 0x27549b0>()
|
||||
E + and '456' = <function g at 0x27488c0>()
|
||||
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 0x273b7a0>
|
||||
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 0x272dd88>
|
||||
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 0x272dd88>.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 0x2729560>
|
||||
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 0x271f3b0>
|
||||
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.18 seconds =========================
|
||||
======================== 39 failed in 0.21 seconds =========================
|
||||
|
||||
@@ -33,7 +33,7 @@ provide the ``cmdopt`` through a :ref:`fixture function <fixture function>`::
|
||||
|
||||
@pytest.fixture
|
||||
def cmdopt(request):
|
||||
return request.config.option.cmdopt
|
||||
return request.config.getoption("--cmdopt")
|
||||
|
||||
Let's run this without supplying our new option::
|
||||
|
||||
@@ -106,7 +106,7 @@ directory with the above conftest.py::
|
||||
|
||||
$ py.test
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.0
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 0 items
|
||||
|
||||
============================= in 0.00 seconds =============================
|
||||
@@ -129,7 +129,7 @@ line option to control skipping of ``slow`` marked tests::
|
||||
help="run slow tests")
|
||||
|
||||
def pytest_runtest_setup(item):
|
||||
if 'slow' in item.keywords and not item.config.getvalue("runslow"):
|
||||
if 'slow' in item.keywords and not item.config.getoption("--runslow"):
|
||||
pytest.skip("need --runslow option to run")
|
||||
|
||||
We can now write a test module like this::
|
||||
@@ -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.0
|
||||
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-293/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.0
|
||||
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.0
|
||||
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.0 -- /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.0
|
||||
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.0
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 3 items
|
||||
|
||||
test_some_are_slow.py ...
|
||||
@@ -380,7 +380,7 @@ If we run this::
|
||||
|
||||
$ py.test -rx
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.0
|
||||
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 0x1db2638>
|
||||
self = <test_step.TestUserHandling instance at 0x282b8c0>
|
||||
|
||||
def test_modification(self):
|
||||
> assert 0
|
||||
@@ -403,3 +403,278 @@ If we run this::
|
||||
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
|
||||
@@ -25,10 +25,13 @@ how does py.test relate to twisted's trial?
|
||||
|
||||
Since some time py.test has builtin support for supporting tests
|
||||
written using trial. It does not itself start a reactor, however,
|
||||
and does not handle Deferreds returned from a test. Someone using
|
||||
these features might eventually write a dedicated ``pytest-twisted``
|
||||
plugin which will surely see strong support from the pytest development
|
||||
team.
|
||||
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
|
||||
return deferreds from pytest-style tests, allowing to use
|
||||
:ref:`fixtures` and other features.
|
||||
|
||||
how does py.test work with Django?
|
||||
++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
@@ -26,14 +26,14 @@ functions:
|
||||
fixtures.
|
||||
|
||||
* fixture management scales from simple unit to complex
|
||||
functional testing, allowing to parametrize fixtures or tests according
|
||||
to configuration and component options.
|
||||
functional testing, allowing to parametrize fixtures and tests according
|
||||
to configuration and component options, or to re-use fixtures
|
||||
across class, module or whole test session scopes.
|
||||
|
||||
In addition, pytest continues to support :ref:`xunitsetup` which it
|
||||
originally introduced in 2005. You can mix both styles, moving
|
||||
incrementally from classic to new style, if you prefer. You can also
|
||||
start out from existing :ref:`unittest.TestCase style <unittest.TestCase>`
|
||||
or :ref:`nose based <nosestyle>` projects.
|
||||
In addition, pytest continues to support :ref:`xunitsetup`. You can mix
|
||||
both styles, moving incrementally from classic to new style, as you
|
||||
prefer. You can also start out from existing :ref:`unittest.TestCase
|
||||
style <unittest.TestCase>` or :ref:`nose based <nosestyle>` projects.
|
||||
|
||||
.. _`funcargs`:
|
||||
.. _`funcarg mechanism`:
|
||||
@@ -47,7 +47,7 @@ Fixtures as Function arguments (funcargs)
|
||||
Test functions can receive fixture objects by naming them as an input
|
||||
argument. For each argument name, a fixture function with that name provides
|
||||
the fixture object. Fixture functions are registered by marking them with
|
||||
:py:func:`@pytest.fixture <pytest.fixture>`. Let's look at a simple
|
||||
:py:func:`@pytest.fixture <_pytest.python.fixture>`. Let's look at a simple
|
||||
self-contained test module containing a fixture and a test function
|
||||
using it::
|
||||
|
||||
@@ -65,13 +65,13 @@ using it::
|
||||
assert "merlinux" in msg
|
||||
assert 0 # for demo purposes
|
||||
|
||||
Here, the ``test_function`` needs the ``smtp`` fixture value. pytest
|
||||
will discover and call the ``@pytest.fixture`` marked ``smtp``
|
||||
fixture function. Running the test looks like this::
|
||||
Here, the ``test_ehlo`` needs the ``smtp`` fixture value. pytest
|
||||
will discover and call the :py:func:`@pytest.fixture <_pytest.python.fixture>`
|
||||
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.0
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 1 items
|
||||
|
||||
test_smtpsimple.py F
|
||||
@@ -79,7 +79,7 @@ fixture function. Running the test looks like this::
|
||||
================================= FAILURES =================================
|
||||
________________________________ test_ehlo _________________________________
|
||||
|
||||
smtp = <smtplib.SMTP instance at 0x1eade18>
|
||||
smtp = <smtplib.SMTP instance at 0x226cc20>
|
||||
|
||||
def test_ehlo(smtp):
|
||||
response, msg = smtp.ehlo()
|
||||
@@ -143,30 +143,32 @@ functions take the role of the *injector* and test functions are the
|
||||
|
||||
.. _smtpshared:
|
||||
|
||||
Working with a session-shared fixture
|
||||
Working with a module-shared fixture
|
||||
-----------------------------------------------------------------
|
||||
|
||||
.. regendoc:wipe
|
||||
|
||||
Fixtures requiring network access depend on connectivity and are
|
||||
usually time-expensive to create. Extending the previous example, we
|
||||
can add a ``scope='session'`` parameter to ``@pytest.fixture`` decorator.
|
||||
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 session. Multiple test functions 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 the directory can access the fixture function::
|
||||
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::
|
||||
|
||||
# content of conftest.py
|
||||
import pytest
|
||||
import smtplib
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
@pytest.fixture(scope="module")
|
||||
def smtp():
|
||||
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
|
||||
@@ -187,7 +189,7 @@ 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.0
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 2 items
|
||||
|
||||
test_module.py FF
|
||||
@@ -195,7 +197,7 @@ inspect what is going on and can now run the tests::
|
||||
================================= FAILURES =================================
|
||||
________________________________ test_ehlo _________________________________
|
||||
|
||||
smtp = <smtplib.SMTP instance at 0x29213b0>
|
||||
smtp = <smtplib.SMTP instance at 0x18a6368>
|
||||
|
||||
def test_ehlo(smtp):
|
||||
response = smtp.ehlo()
|
||||
@@ -207,7 +209,7 @@ inspect what is going on and can now run the tests::
|
||||
test_module.py:6: AssertionError
|
||||
________________________________ test_noop _________________________________
|
||||
|
||||
smtp = <smtplib.SMTP instance at 0x29213b0>
|
||||
smtp = <smtplib.SMTP instance at 0x18a6368>
|
||||
|
||||
def test_noop(smtp):
|
||||
response = smtp.noop()
|
||||
@@ -216,28 +218,39 @@ inspect what is going on and can now run the tests::
|
||||
E assert 0
|
||||
|
||||
test_module.py:11: AssertionError
|
||||
========================= 2 failed in 0.20 seconds =========================
|
||||
========================= 2 failed in 0.26 seconds =========================
|
||||
|
||||
You see the two ``assert 0`` failing and more importantly you can also see
|
||||
that the same (session-scoped) ``smtp`` object was passed into the two
|
||||
that the same (module-scoped) ``smtp`` object was passed into the two
|
||||
test functions because pytest shows the incoming argument values in the
|
||||
traceback. As a result, the two test functions using ``smtp`` run as
|
||||
quick as a single one because they reuse the same instance.
|
||||
|
||||
If you decide that you rather want to have a session-scoped ``smtp``
|
||||
instance, you can simply declare it::
|
||||
|
||||
@pytest.fixture(scope=``session``)
|
||||
def smtp(...):
|
||||
# the returned fixture value will be shared for
|
||||
# all tests needing it
|
||||
|
||||
.. _`request-context`:
|
||||
|
||||
Fixtures can interact with the requesting test context
|
||||
-------------------------------------------------------------
|
||||
|
||||
Using the special :py:class:`request <FixtureRequest>` argument,
|
||||
fixture functions can introspect the function, class or module for which
|
||||
they are invoked or can register finalizing (cleanup)
|
||||
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.
|
||||
|
||||
Further extending the previous ``smtp`` fixture example, let's try to
|
||||
read the server URL from the module namespace, also use module-caching and
|
||||
register a finalizer that closes the smtp connection after the last
|
||||
test finished execution::
|
||||
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::
|
||||
|
||||
# content of conftest.py
|
||||
import pytest
|
||||
@@ -258,12 +271,12 @@ using it has executed::
|
||||
|
||||
$ py.test -s -q --tb=no
|
||||
FF
|
||||
finalizing <smtplib.SMTP instance at 0x1a9d5a8>
|
||||
finalizing <smtplib.SMTP instance at 0x1e10248>
|
||||
|
||||
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 the test module itself did not need to change!
|
||||
Note that either case the test module itself does not need to change!
|
||||
|
||||
Let's quickly create another test module that actually sets the
|
||||
server URL and has a test to verify the fixture picks it up::
|
||||
@@ -285,11 +298,9 @@ Running it::
|
||||
> assert 0, smtp.helo()
|
||||
E AssertionError: (250, 'mail.python.org')
|
||||
|
||||
.. _`request`: :py:class:`_pytest.python.FixtureRequest`
|
||||
|
||||
.. _`fixture-parametrize`:
|
||||
|
||||
Parametrizing a session-shared fixture
|
||||
Parametrizing a fixture
|
||||
-----------------------------------------------------------------
|
||||
|
||||
Fixture functions can be parametrized in which case they will be called
|
||||
@@ -302,13 +313,13 @@ 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
|
||||
import smtplib
|
||||
|
||||
@pytest.fixture(scope="session",
|
||||
@pytest.fixture(scope="module",
|
||||
params=["merlinux.eu", "mail.python.org"])
|
||||
def smtp(request):
|
||||
smtp = smtplib.SMTP(request.param)
|
||||
@@ -318,7 +329,8 @@ through the special `request`_ object::
|
||||
request.addfinalizer(fin)
|
||||
return smtp
|
||||
|
||||
The main change is the declaration of ``params``, a list of values
|
||||
The main change is the declaration of ``params`` with
|
||||
:py:func:`@pytest.fixture <_pytest.python.fixture>`, a list of values
|
||||
for each of which the fixture function will execute and can access
|
||||
a value via ``request.param``. No test function code needs to change.
|
||||
So let's just do another run::
|
||||
@@ -328,7 +340,7 @@ So let's just do another run::
|
||||
================================= FAILURES =================================
|
||||
__________________________ test_ehlo[merlinux.eu] __________________________
|
||||
|
||||
smtp = <smtplib.SMTP instance at 0x1eae680>
|
||||
smtp = <smtplib.SMTP instance at 0x1b38a28>
|
||||
|
||||
def test_ehlo(smtp):
|
||||
response = smtp.ehlo()
|
||||
@@ -340,7 +352,7 @@ So let's just do another run::
|
||||
test_module.py:6: AssertionError
|
||||
__________________________ test_noop[merlinux.eu] __________________________
|
||||
|
||||
smtp = <smtplib.SMTP instance at 0x1eae680>
|
||||
smtp = <smtplib.SMTP instance at 0x1b38a28>
|
||||
|
||||
def test_noop(smtp):
|
||||
response = smtp.noop()
|
||||
@@ -351,18 +363,18 @@ So let's just do another run::
|
||||
test_module.py:11: AssertionError
|
||||
________________________ test_ehlo[mail.python.org] ________________________
|
||||
|
||||
smtp = <smtplib.SMTP instance at 0x1eb7fc8>
|
||||
smtp = <smtplib.SMTP instance at 0x1b496c8>
|
||||
|
||||
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 0x1eb7fc8>
|
||||
smtp = <smtplib.SMTP instance at 0x1b496c8>
|
||||
|
||||
def test_noop(smtp):
|
||||
response = smtp.noop()
|
||||
@@ -410,21 +422,23 @@ 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.0 -- /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_appsetup.py:12: test_smtp_exists[merlinux.eu] PASSED
|
||||
test_appsetup.py:12: test_smtp_exists[mail.python.org] PASSED
|
||||
|
||||
========================= 2 passed in 0.17 seconds =========================
|
||||
========================= 2 passed in 5.38 seconds =========================
|
||||
|
||||
Due to the parametrization of ``smtp`` the test will run twice with two
|
||||
different ``App`` instances and respective smtp servers. There is no
|
||||
need for the ``app`` fixture to be aware of the ``smtp`` parametrization
|
||||
as pytest will fully analyse the fixture dependency graph. Note also,
|
||||
that the ``app`` fixture has a scope of ``module`` but may use a
|
||||
session-scoped ``smtp`` from the example higher up: it is fine for
|
||||
fixtures to use "broader" scoped fixtures but not the other way round:
|
||||
as pytest will fully analyse the fixture dependency graph.
|
||||
|
||||
Note, that the ``app`` fixture has a scope of ``module`` and uses a
|
||||
module-scoped ``smtp`` fixture. The example would still work if ``smtp``
|
||||
was cached on a ``session`` scope: it is fine for fixtures to use
|
||||
"broader" scoped fixtures but not the other way round:
|
||||
A session-scoped fixture could not use a module-scoped one in a
|
||||
meaningful way.
|
||||
|
||||
@@ -473,7 +487,7 @@ Let's run the tests in verbose mode and with looking at the print-output::
|
||||
|
||||
$ py.test -v -s test_module.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.0 -- /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 8 items
|
||||
|
||||
test_module.py:16: test_0[1] PASSED
|
||||
|
||||
@@ -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.0, imported from /home/hpk/p/pytest/.tox/regen/local/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.0
|
||||
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 0x1f8a6c8>
|
||||
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-1409/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-1409/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`.
|
||||
|
||||
@@ -73,7 +73,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,7 +85,7 @@ 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
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
|
||||
.. _features:
|
||||
|
||||
pytest: makes you write better programs
|
||||
pytest: helps you write better programs
|
||||
=============================================
|
||||
|
||||
.. note:: Upcoming: `professional testing with pytest and tox <http://www.python-academy.com/courses/specialtopics/python_course_testing.html>`_ , 24th-26th June 2013, Leipzig.
|
||||
|
||||
|
||||
**a mature full-featured Python testing tool**
|
||||
|
||||
- runs on Posix/Windows, Python 2.4-3.3, PyPy and Jython-2.5.1
|
||||
@@ -34,8 +37,9 @@ pytest: makes you write better programs
|
||||
|
||||
**integrates many common testing methods**:
|
||||
|
||||
- can run many ``nose``, ``unittest.py`` and ``doctest.py`` style
|
||||
tests, including running testcases made for Django and trial
|
||||
- multi-paradigm: pytest can run many ``nose``, ``unittest.py`` and
|
||||
``doctest.py`` style test suites, including running testcases made for
|
||||
Django and trial
|
||||
- supports :ref:`good integration practises <goodpractises>`
|
||||
- supports extended :ref:`xUnit style setup <xunitsetup>`
|
||||
- supports domain-specific :ref:`non-python tests`
|
||||
|
||||
@@ -53,7 +53,7 @@ which will thus run three times::
|
||||
|
||||
$ py.test
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.0
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 3 items
|
||||
|
||||
test_expectation.py ..F
|
||||
@@ -135,8 +135,8 @@ Let's also run with a stringinput that will lead to a failing test::
|
||||
|
||||
def test_valid_string(stringinput):
|
||||
> assert stringinput.isalpha()
|
||||
E assert <built-in method isalpha of str object at 0x2b7d7a85a030>()
|
||||
E + where <built-in method isalpha of str object at 0x2b7d7a85a030> = '!'.isalpha
|
||||
E assert <built-in method isalpha of str object at 0x2ba729dab300>()
|
||||
E + where <built-in method isalpha of str object at 0x2ba729dab300> = '!'.isalpha
|
||||
|
||||
test_strings.py:3: AssertionError
|
||||
|
||||
@@ -149,7 +149,7 @@ listlist::
|
||||
$ py.test -q -rs test_strings.py
|
||||
s
|
||||
========================= short test summary info ==========================
|
||||
SKIP [1] /home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/python.py:947: got empty parameter set, function test_valid_string at /tmp/doc-exec-259/test_strings.py:1
|
||||
SKIP [1] /home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/python.py:974: got empty parameter set, function test_valid_string at /tmp/doc-exec-240/test_strings.py:1
|
||||
|
||||
For further examples, you might want to look at :ref:`more
|
||||
parametrization examples <paramexamples>`.
|
||||
|
||||
@@ -5,7 +5,7 @@ Working with plugins and conftest files
|
||||
|
||||
py.test implements all aspects of configuration, collection, running and reporting by calling `well specified hooks`_. Virtually any Python module can be registered as a plugin. It can implement any number of hook functions (usually two or three) which all have a ``pytest_`` prefix, making hook functions easy to distinguish and find. There are three basic location types:
|
||||
|
||||
* `builtin plugins`_: loaded from py.test's own ``pytest/plugin`` directory.
|
||||
* `builtin plugins`_: loaded from py.test's internal ``_pytest`` directory.
|
||||
* `external plugins`_: modules discovered through `setuptools entry points`_
|
||||
* `conftest.py plugins`_: modules auto-discovered in test directories
|
||||
|
||||
@@ -71,6 +71,10 @@ there is no need to activate it. Here is a initial list of known plugins:
|
||||
* `pytest-django <http://pypi.python.org/pypi/pytest-django>`_: write tests
|
||||
for `django`_ apps, using pytest integration.
|
||||
|
||||
* `pytest-twisted <http://pypi.python.org/pypi/pytest-twisted>`_: write tests
|
||||
for `twisted <http://twistedmatrix.com>`_ apps, starting a reactor and
|
||||
processing deferreds from test functions.
|
||||
|
||||
* `pytest-capturelog <http://pypi.python.org/pypi/pytest-capturelog>`_:
|
||||
to capture and assert about messages from the logging module
|
||||
|
||||
@@ -151,6 +155,8 @@ If a package is installed this way, py.test will load
|
||||
``myproject.pluginmodule`` as a plugin which can define
|
||||
`well specified hooks`_.
|
||||
|
||||
.. _`pluginorder`:
|
||||
|
||||
Plugin discovery order at tool startup
|
||||
--------------------------------------------
|
||||
|
||||
@@ -171,6 +177,7 @@ py.test loads plugin modules at tool startup in the following way:
|
||||
* by recursively loading all plugins specified by the
|
||||
``pytest_plugins`` variable in ``conftest.py`` files
|
||||
|
||||
|
||||
Requiring/Loading plugins in a test module or conftest file
|
||||
-------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ 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
|
||||
* `sentry <https://getsentry.com/welcome/>`_, realtime app-maintenance and exception tracking
|
||||
* `tox <http://codespeak.net/tox>`_, virtualenv/Hudson integration tool
|
||||
* `PIDA <http://pida.co.uk>`_ framework for integrated development
|
||||
* `PyPM <http://code.activestate.com/pypm/>`_ ActiveState's package manager
|
||||
@@ -18,7 +19,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
|
||||
|
||||
@@ -132,7 +132,7 @@ 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.0
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.5
|
||||
collected 6 items
|
||||
|
||||
xfail_demo.py xxxxxx
|
||||
@@ -149,7 +149,7 @@ Running it with the report-on-xfail option gives this output::
|
||||
XFAIL xfail_demo.py::test_hello6
|
||||
reason: reason
|
||||
|
||||
======================== 6 xfailed in 0.03 seconds =========================
|
||||
======================== 6 xfailed in 0.05 seconds =========================
|
||||
|
||||
.. _`evaluation of skipif/xfail conditions`:
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ Talks and Tutorials
|
||||
|
||||
.. _`funcargs`: funcargs.html
|
||||
|
||||
.. note:: Upcoming: `professional testing with pytest and tox <`http://www.python-academy.com/courses/specialtopics/python_course_testing.html>`_ , 24th-26th June 2013, Leipzig.
|
||||
|
||||
Tutorial examples and blog postings
|
||||
---------------------------------------------
|
||||
|
||||
@@ -12,6 +14,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 +62,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.0
|
||||
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-1410/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/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.0
|
||||
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 0x2e36bd8>
|
||||
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 0x2e36bd8>
|
||||
E AssertionError: <conftest.DummyDB instance at 0x19fdf38>
|
||||
|
||||
test_unittest_db.py:12: AssertionError
|
||||
========================= 2 failed in 0.02 seconds =========================
|
||||
|
||||
@@ -133,7 +133,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 +165,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::
|
||||
|
||||
|
||||
@@ -5,13 +5,16 @@
|
||||
classic xunit-style setup
|
||||
========================================
|
||||
|
||||
.. note::
|
||||
|
||||
This section describes the classic way how you can implement setup and
|
||||
teardown on a per-module/class/function basis. It remains fully
|
||||
supported but it is recommended to rather use the more flexible,
|
||||
more modular and more scalable :ref:`fixture functions
|
||||
<fixture>` for implementing for fixing test state for your tests.
|
||||
This section describes a classic and popular way how you can implement
|
||||
fixtures (setup and teardown test state) on a per-module/class/function basis.
|
||||
pytest started supporting these methods around 2005 and subsequently
|
||||
nose and the standard library introduced them (under slightly different
|
||||
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.
|
||||
|
||||
Module level setup/teardown
|
||||
--------------------------------------
|
||||
|
||||
@@ -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')
|
||||
>>> ...
|
||||
|
||||
@@ -26,7 +26,7 @@ py.test を使っているプロジェクトを紹介します (:ref:`contact`
|
||||
* `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
|
||||
@@ -59,7 +59,7 @@ py.test を使っているプロジェクトを紹介します (:ref:`contact`
|
||||
* `mwlib <http://pypi.python.org/pypi/mwlib>`_: mediawiki のパーサーとユーティリティライブラリ
|
||||
* `The Translate Toolkit <http://translate.sourceforge.net/wiki/toolkit/index>`_: ローカライズと変換
|
||||
* `execnet <http://codespeak.net/execnet>`_: 高速な multi-Python デプロイ
|
||||
* `pylib <http://pylib.org>`_: クロスプラットフォームのパス、IO、動的コードライブラリ
|
||||
* `pylib <http://py.rtfd.org>`_: クロスプラットフォームのパス、IO、動的コードライブラリ
|
||||
* `Pacha <http://pacha.cafepais.com/>`_: 5分でできる構成管理
|
||||
* `bbfreeze <http://pypi.python.org/pypi/bbfreeze>`_: Python スクリプトから単独で実行できる実行可能ファイルの作成
|
||||
* `pdb++ <http://bitbucket.org/antocuni/pdb>`_: PDB の手の込んだバージョン
|
||||
|
||||
@@ -97,6 +97,6 @@
|
||||
|
||||
``py.test`` は、ローカルマシン上で分散テストを行うとき、全ての一時データが basetemp ディレクトリの配下で実行されてテスト毎に一意になるよう、サブプロセスに対しても basetemp ディレクトリをちゃんと設定します。
|
||||
|
||||
.. _`py.path.local`: http://pylib.org/path.html
|
||||
.. _`py.path.local`: http://py.rtfd.org/path.html
|
||||
|
||||
|
||||
|
||||
@@ -276,7 +276,7 @@ Python コードから直接 ``py.test`` を呼び出せます::
|
||||
|
||||
これはコマンドラインから "py.test" を呼び出すように動作します。 ``SystemExit`` を発生させない代わりに終了コードを返します。次のようにオプションと引数を渡します::
|
||||
|
||||
pytest.main(['x', 'mytestdir'])
|
||||
pytest.main(['-x', 'mytestdir'])
|
||||
|
||||
..
|
||||
or pass in a string::
|
||||
|
||||
44
setup.py
44
setup.py
@@ -1,38 +1,27 @@
|
||||
import os, sys
|
||||
try:
|
||||
from setuptools import setup
|
||||
from setuptools import setup, Command
|
||||
except ImportError:
|
||||
from distribute_setup import use_setuptools
|
||||
use_setuptools()
|
||||
from setuptools import setup
|
||||
from setuptools import setup, Command
|
||||
|
||||
long_description = """
|
||||
cross-project testing tool for Python.
|
||||
|
||||
Platforms: Linux, Win32, OSX
|
||||
|
||||
Interpreters: Python versions 2.4 through to 3.3, Jython 2.5.1 and PyPy-1.9
|
||||
|
||||
Bugs and issues: http://bitbucket.org/hpk42/pytest/issues/
|
||||
|
||||
Web page: http://pytest.org
|
||||
|
||||
(c) Holger Krekel and others, 2004-2012
|
||||
"""
|
||||
long_description = open("README.rst").read()
|
||||
def main():
|
||||
setup(
|
||||
name='pytest',
|
||||
description='py.test: simple powerful testing with Python',
|
||||
long_description = long_description,
|
||||
version='2.3.0',
|
||||
version='2.3.5',
|
||||
url='http://pytest.org',
|
||||
license='MIT license',
|
||||
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
|
||||
author='Holger Krekel, Benjamin Peterson, Ronny Pfannschmidt, Floris Bruynooghe and others',
|
||||
author_email='holger at merlinux.eu',
|
||||
entry_points= make_entry_points(),
|
||||
cmdclass = {'test': PyTest},
|
||||
# the following should be enabled for release
|
||||
install_requires=['py>=1.4.10'],
|
||||
install_requires=['py>=1.4.13dev6'],
|
||||
classifiers=['Development Status :: 6 - Mature',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
@@ -42,8 +31,10 @@ def main():
|
||||
'Topic :: Software Development :: Testing',
|
||||
'Topic :: Software Development :: Libraries',
|
||||
'Topic :: Utilities',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3'],
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 3'] + [
|
||||
("Programming Language :: Python :: %s" % x) for x in
|
||||
"2.4 2.5 2.6 2.7 3.0 3.1 3.2 3.3".split()],
|
||||
packages=['_pytest', '_pytest.assertion'],
|
||||
py_modules=['pytest'],
|
||||
zip_safe=False,
|
||||
@@ -69,5 +60,20 @@ def make_entry_points():
|
||||
l = ["%s = %s" % (x, points[x]) for x in keys]
|
||||
return {'console_scripts': l}
|
||||
|
||||
|
||||
class PyTest(Command):
|
||||
user_options = []
|
||||
def initialize_options(self):
|
||||
pass
|
||||
def finalize_options(self):
|
||||
pass
|
||||
def run(self):
|
||||
import sys,subprocess
|
||||
PPATH=[x for x in os.environ.get("PYTHONPATH", "").split(":") if x]
|
||||
PPATH.insert(0, os.getcwd())
|
||||
os.environ["PYTHONPATH"] = ":".join(PPATH)
|
||||
errno = subprocess.call([sys.executable, 'pytest.py'])
|
||||
raise SystemExit(errno)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@@ -294,6 +294,21 @@ class TestGeneralUsage:
|
||||
])
|
||||
assert 'sessionstarttime' not in result.stderr.str()
|
||||
|
||||
@pytest.mark.parametrize('lookfor', ['test_fun.py', 'test_fun.py::test_a'])
|
||||
def test_issue134_report_syntaxerror_when_collecting_member(self, testdir, lookfor):
|
||||
testdir.makepyfile(test_fun="""
|
||||
def test_a():
|
||||
pass
|
||||
def""")
|
||||
result = testdir.runpytest(lookfor)
|
||||
result.stdout.fnmatch_lines(['*SyntaxError*'])
|
||||
if '::' in lookfor:
|
||||
result.stderr.fnmatch_lines([
|
||||
'*ERROR*',
|
||||
])
|
||||
assert result.ret == 4 # usage error only if item not found
|
||||
|
||||
|
||||
class TestInvocationVariants:
|
||||
def test_earlyinit(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
@@ -490,6 +505,8 @@ class TestDurations:
|
||||
source = """
|
||||
import time
|
||||
frag = 0.02
|
||||
def test_something():
|
||||
pass
|
||||
def test_2():
|
||||
time.sleep(frag*5)
|
||||
def test_1():
|
||||
|
||||
@@ -66,7 +66,8 @@ def pytest_generate_tests(metafunc):
|
||||
metafunc.addcall(funcargs={name: val})
|
||||
elif 'anypython' in metafunc.fixturenames:
|
||||
for name in ('python2.4', 'python2.5', 'python2.6',
|
||||
'python2.7', 'python3.1', 'pypy', 'jython'):
|
||||
'python2.7', 'python3.2', "python3.3",
|
||||
'pypy', 'jython'):
|
||||
metafunc.addcall(id=name, param=name)
|
||||
|
||||
# XXX copied from execnet's conftest.py - needs to be merged
|
||||
|
||||
746
testing/python/collect.py
Normal file
746
testing/python/collect.py
Normal file
@@ -0,0 +1,746 @@
|
||||
import pytest, py, sys
|
||||
from _pytest import python as funcargs
|
||||
from _pytest.python import FixtureLookupError
|
||||
|
||||
class TestModule:
|
||||
def test_failing_import(self, testdir):
|
||||
modcol = testdir.getmodulecol("import alksdjalskdjalkjals")
|
||||
pytest.raises(ImportError, modcol.collect)
|
||||
pytest.raises(ImportError, modcol.collect)
|
||||
|
||||
def test_import_duplicate(self, testdir):
|
||||
a = testdir.mkdir("a")
|
||||
b = testdir.mkdir("b")
|
||||
p = a.ensure("test_whatever.py")
|
||||
p.pyimport()
|
||||
del py.std.sys.modules['test_whatever']
|
||||
b.ensure("test_whatever.py")
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines([
|
||||
"*import*mismatch*",
|
||||
"*imported*test_whatever*",
|
||||
"*%s*" % a.join("test_whatever.py"),
|
||||
"*not the same*",
|
||||
"*%s*" % b.join("test_whatever.py"),
|
||||
"*HINT*",
|
||||
])
|
||||
|
||||
def test_syntax_error_in_module(self, testdir):
|
||||
modcol = testdir.getmodulecol("this is a syntax error")
|
||||
pytest.raises(modcol.CollectError, modcol.collect)
|
||||
pytest.raises(modcol.CollectError, modcol.collect)
|
||||
|
||||
def test_module_considers_pluginmanager_at_import(self, testdir):
|
||||
modcol = testdir.getmodulecol("pytest_plugins='xasdlkj',")
|
||||
pytest.raises(ImportError, "modcol.obj")
|
||||
|
||||
class TestClass:
|
||||
def test_class_with_init_skip_collect(self, testdir):
|
||||
modcol = testdir.getmodulecol("""
|
||||
class TestClass1:
|
||||
def __init__(self):
|
||||
pass
|
||||
class TestClass2(object):
|
||||
def __init__(self):
|
||||
pass
|
||||
""")
|
||||
l = modcol.collect()
|
||||
assert len(l) == 2
|
||||
|
||||
for classcol in l:
|
||||
pytest.raises(pytest.skip.Exception, classcol.collect)
|
||||
|
||||
def test_class_subclassobject(self, testdir):
|
||||
testdir.getmodulecol("""
|
||||
class test(object):
|
||||
pass
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines([
|
||||
"*collected 0*",
|
||||
])
|
||||
|
||||
def test_setup_teardown_class_as_classmethod(self, testdir):
|
||||
testdir.makepyfile(test_mod1="""
|
||||
class TestClassMethod:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
pass
|
||||
def test_1(self):
|
||||
pass
|
||||
@classmethod
|
||||
def teardown_class(cls):
|
||||
pass
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines([
|
||||
"*1 passed*",
|
||||
])
|
||||
|
||||
|
||||
class TestGenerator:
|
||||
def test_generative_functions(self, testdir):
|
||||
modcol = testdir.getmodulecol("""
|
||||
def func1(arg, arg2):
|
||||
assert arg == arg2
|
||||
|
||||
def test_gen():
|
||||
yield func1, 17, 3*5
|
||||
yield func1, 42, 6*7
|
||||
""")
|
||||
colitems = modcol.collect()
|
||||
assert len(colitems) == 1
|
||||
gencol = colitems[0]
|
||||
assert isinstance(gencol, pytest.Generator)
|
||||
gencolitems = gencol.collect()
|
||||
assert len(gencolitems) == 2
|
||||
assert isinstance(gencolitems[0], pytest.Function)
|
||||
assert isinstance(gencolitems[1], pytest.Function)
|
||||
assert gencolitems[0].name == '[0]'
|
||||
assert gencolitems[0].obj.__name__ == 'func1'
|
||||
|
||||
def test_generative_methods(self, testdir):
|
||||
modcol = testdir.getmodulecol("""
|
||||
def func1(arg, arg2):
|
||||
assert arg == arg2
|
||||
class TestGenMethods:
|
||||
def test_gen(self):
|
||||
yield func1, 17, 3*5
|
||||
yield func1, 42, 6*7
|
||||
""")
|
||||
gencol = modcol.collect()[0].collect()[0].collect()[0]
|
||||
assert isinstance(gencol, pytest.Generator)
|
||||
gencolitems = gencol.collect()
|
||||
assert len(gencolitems) == 2
|
||||
assert isinstance(gencolitems[0], pytest.Function)
|
||||
assert isinstance(gencolitems[1], pytest.Function)
|
||||
assert gencolitems[0].name == '[0]'
|
||||
assert gencolitems[0].obj.__name__ == 'func1'
|
||||
|
||||
def test_generative_functions_with_explicit_names(self, testdir):
|
||||
modcol = testdir.getmodulecol("""
|
||||
def func1(arg, arg2):
|
||||
assert arg == arg2
|
||||
|
||||
def test_gen():
|
||||
yield "seventeen", func1, 17, 3*5
|
||||
yield "fortytwo", func1, 42, 6*7
|
||||
""")
|
||||
colitems = modcol.collect()
|
||||
assert len(colitems) == 1
|
||||
gencol = colitems[0]
|
||||
assert isinstance(gencol, pytest.Generator)
|
||||
gencolitems = gencol.collect()
|
||||
assert len(gencolitems) == 2
|
||||
assert isinstance(gencolitems[0], pytest.Function)
|
||||
assert isinstance(gencolitems[1], pytest.Function)
|
||||
assert gencolitems[0].name == "['seventeen']"
|
||||
assert gencolitems[0].obj.__name__ == 'func1'
|
||||
assert gencolitems[1].name == "['fortytwo']"
|
||||
assert gencolitems[1].obj.__name__ == 'func1'
|
||||
|
||||
def test_generative_functions_unique_explicit_names(self, testdir):
|
||||
# generative
|
||||
modcol = testdir.getmodulecol("""
|
||||
def func(): pass
|
||||
def test_gen():
|
||||
yield "name", func
|
||||
yield "name", func
|
||||
""")
|
||||
colitems = modcol.collect()
|
||||
assert len(colitems) == 1
|
||||
gencol = colitems[0]
|
||||
assert isinstance(gencol, pytest.Generator)
|
||||
pytest.raises(ValueError, "gencol.collect()")
|
||||
|
||||
def test_generative_methods_with_explicit_names(self, testdir):
|
||||
modcol = testdir.getmodulecol("""
|
||||
def func1(arg, arg2):
|
||||
assert arg == arg2
|
||||
class TestGenMethods:
|
||||
def test_gen(self):
|
||||
yield "m1", func1, 17, 3*5
|
||||
yield "m2", func1, 42, 6*7
|
||||
""")
|
||||
gencol = modcol.collect()[0].collect()[0].collect()[0]
|
||||
assert isinstance(gencol, pytest.Generator)
|
||||
gencolitems = gencol.collect()
|
||||
assert len(gencolitems) == 2
|
||||
assert isinstance(gencolitems[0], pytest.Function)
|
||||
assert isinstance(gencolitems[1], pytest.Function)
|
||||
assert gencolitems[0].name == "['m1']"
|
||||
assert gencolitems[0].obj.__name__ == 'func1'
|
||||
assert gencolitems[1].name == "['m2']"
|
||||
assert gencolitems[1].obj.__name__ == 'func1'
|
||||
|
||||
def test_order_of_execution_generator_same_codeline(self, testdir, tmpdir):
|
||||
o = testdir.makepyfile("""
|
||||
def test_generative_order_of_execution():
|
||||
import py, pytest
|
||||
test_list = []
|
||||
expected_list = list(range(6))
|
||||
|
||||
def list_append(item):
|
||||
test_list.append(item)
|
||||
|
||||
def assert_order_of_execution():
|
||||
py.builtin.print_('expected order', expected_list)
|
||||
py.builtin.print_('but got ', test_list)
|
||||
assert test_list == expected_list
|
||||
|
||||
for i in expected_list:
|
||||
yield list_append, i
|
||||
yield assert_order_of_execution
|
||||
""")
|
||||
reprec = testdir.inline_run(o)
|
||||
passed, skipped, failed = reprec.countoutcomes()
|
||||
assert passed == 7
|
||||
assert not skipped and not failed
|
||||
|
||||
def test_order_of_execution_generator_different_codeline(self, testdir):
|
||||
o = testdir.makepyfile("""
|
||||
def test_generative_tests_different_codeline():
|
||||
import py, pytest
|
||||
test_list = []
|
||||
expected_list = list(range(3))
|
||||
|
||||
def list_append_2():
|
||||
test_list.append(2)
|
||||
|
||||
def list_append_1():
|
||||
test_list.append(1)
|
||||
|
||||
def list_append_0():
|
||||
test_list.append(0)
|
||||
|
||||
def assert_order_of_execution():
|
||||
py.builtin.print_('expected order', expected_list)
|
||||
py.builtin.print_('but got ', test_list)
|
||||
assert test_list == expected_list
|
||||
|
||||
yield list_append_0
|
||||
yield list_append_1
|
||||
yield list_append_2
|
||||
yield assert_order_of_execution
|
||||
""")
|
||||
reprec = testdir.inline_run(o)
|
||||
passed, skipped, failed = reprec.countoutcomes()
|
||||
assert passed == 4
|
||||
assert not skipped and not failed
|
||||
|
||||
def test_setupstate_is_preserved_134(self, testdir):
|
||||
# yield-based tests are messy wrt to setupstate because
|
||||
# during collection they already invoke setup functions
|
||||
# and then again when they are run. For now, we want to make sure
|
||||
# that the old 1.3.4 behaviour is preserved such that all
|
||||
# yielded functions all share the same "self" instance that
|
||||
# has been used during collection.
|
||||
o = testdir.makepyfile("""
|
||||
setuplist = []
|
||||
class TestClass:
|
||||
def setup_method(self, func):
|
||||
#print "setup_method", self, func
|
||||
setuplist.append(self)
|
||||
self.init = 42
|
||||
|
||||
def teardown_method(self, func):
|
||||
self.init = None
|
||||
|
||||
def test_func1(self):
|
||||
pass
|
||||
|
||||
def test_func2(self):
|
||||
yield self.func2
|
||||
yield self.func2
|
||||
|
||||
def func2(self):
|
||||
assert self.init
|
||||
|
||||
def test_setuplist():
|
||||
# once for test_func2 during collection
|
||||
# once for test_func1 during test run
|
||||
# once for test_func2 during test run
|
||||
#print setuplist
|
||||
assert len(setuplist) == 3, len(setuplist)
|
||||
assert setuplist[0] == setuplist[2], setuplist
|
||||
assert setuplist[1] != setuplist[2], setuplist
|
||||
""")
|
||||
reprec = testdir.inline_run(o, '-v')
|
||||
passed, skipped, failed = reprec.countoutcomes()
|
||||
assert passed == 4
|
||||
assert not skipped and not failed
|
||||
|
||||
|
||||
class TestFunction:
|
||||
def test_getmodulecollector(self, testdir):
|
||||
item = testdir.getitem("def test_func(): pass")
|
||||
modcol = item.getparent(pytest.Module)
|
||||
assert isinstance(modcol, pytest.Module)
|
||||
assert hasattr(modcol.obj, 'test_func')
|
||||
|
||||
def test_function_equality(self, testdir, tmpdir):
|
||||
from _pytest.python import FixtureManager
|
||||
config = testdir.parseconfigure()
|
||||
session = testdir.Session(config)
|
||||
session._fixturemanager = FixtureManager(session)
|
||||
def func1():
|
||||
pass
|
||||
def func2():
|
||||
pass
|
||||
f1 = pytest.Function(name="name", parent=session, config=config,
|
||||
args=(1,), callobj=func1)
|
||||
f2 = pytest.Function(name="name",config=config,
|
||||
args=(1,), callobj=func2, parent=session)
|
||||
assert not f1 == f2
|
||||
assert f1 != f2
|
||||
f3 = pytest.Function(name="name", parent=session, config=config,
|
||||
args=(1,2), callobj=func2)
|
||||
assert not f3 == f2
|
||||
assert f3 != f2
|
||||
|
||||
assert not f3 == f1
|
||||
assert f3 != f1
|
||||
|
||||
f1_b = pytest.Function(name="name", parent=session, config=config,
|
||||
args=(1,), callobj=func1)
|
||||
assert f1 == f1_b
|
||||
assert not f1 != f1_b
|
||||
|
||||
def test_issue197_parametrize_emptyset(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
import pytest
|
||||
@pytest.mark.parametrize('arg', [])
|
||||
def test_function(arg):
|
||||
pass
|
||||
""")
|
||||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(skipped=1)
|
||||
|
||||
def test_issue213_parametrize_value_no_equal(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
import pytest
|
||||
class A:
|
||||
def __eq__(self, other):
|
||||
raise ValueError("not possible")
|
||||
@pytest.mark.parametrize('arg', [A()])
|
||||
def test_function(arg):
|
||||
assert arg.__class__.__name__ == "A"
|
||||
""")
|
||||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(passed=1)
|
||||
|
||||
def test_function_equality_with_callspec(self, testdir, tmpdir):
|
||||
items = testdir.getitems("""
|
||||
import pytest
|
||||
@pytest.mark.parametrize('arg', [1,2])
|
||||
def test_function(arg):
|
||||
pass
|
||||
""")
|
||||
assert items[0] != items[1]
|
||||
assert not (items[0] == items[1])
|
||||
|
||||
def test_pyfunc_call(self, testdir):
|
||||
item = testdir.getitem("def test_func(): raise ValueError")
|
||||
config = item.config
|
||||
class MyPlugin1:
|
||||
def pytest_pyfunc_call(self, pyfuncitem):
|
||||
raise ValueError
|
||||
class MyPlugin2:
|
||||
def pytest_pyfunc_call(self, pyfuncitem):
|
||||
return True
|
||||
config.pluginmanager.register(MyPlugin1())
|
||||
config.pluginmanager.register(MyPlugin2())
|
||||
config.hook.pytest_pyfunc_call(pyfuncitem=item)
|
||||
|
||||
class TestSorting:
|
||||
def test_check_equality(self, testdir):
|
||||
modcol = testdir.getmodulecol("""
|
||||
def test_pass(): pass
|
||||
def test_fail(): assert 0
|
||||
""")
|
||||
fn1 = testdir.collect_by_name(modcol, "test_pass")
|
||||
assert isinstance(fn1, pytest.Function)
|
||||
fn2 = testdir.collect_by_name(modcol, "test_pass")
|
||||
assert isinstance(fn2, pytest.Function)
|
||||
|
||||
assert fn1 == fn2
|
||||
assert fn1 != modcol
|
||||
if py.std.sys.version_info < (3, 0):
|
||||
assert cmp(fn1, fn2) == 0
|
||||
assert hash(fn1) == hash(fn2)
|
||||
|
||||
fn3 = testdir.collect_by_name(modcol, "test_fail")
|
||||
assert isinstance(fn3, pytest.Function)
|
||||
assert not (fn1 == fn3)
|
||||
assert fn1 != fn3
|
||||
|
||||
for fn in fn1,fn2,fn3:
|
||||
assert fn != 3
|
||||
assert fn != modcol
|
||||
assert fn != [1,2,3]
|
||||
assert [1,2,3] != fn
|
||||
assert modcol != fn
|
||||
|
||||
def test_allow_sane_sorting_for_decorators(self, testdir):
|
||||
modcol = testdir.getmodulecol("""
|
||||
def dec(f):
|
||||
g = lambda: f(2)
|
||||
g.place_as = f
|
||||
return g
|
||||
|
||||
|
||||
def test_b(y):
|
||||
pass
|
||||
test_b = dec(test_b)
|
||||
|
||||
def test_a(y):
|
||||
pass
|
||||
test_a = dec(test_a)
|
||||
""")
|
||||
colitems = modcol.collect()
|
||||
assert len(colitems) == 2
|
||||
assert [item.name for item in colitems] == ['test_b', 'test_a']
|
||||
|
||||
|
||||
class TestConftestCustomization:
|
||||
def test_pytest_pycollect_module(self, testdir):
|
||||
testdir.makeconftest("""
|
||||
import pytest
|
||||
class MyModule(pytest.Module):
|
||||
pass
|
||||
def pytest_pycollect_makemodule(path, parent):
|
||||
if path.basename == "test_xyz.py":
|
||||
return MyModule(path, parent)
|
||||
""")
|
||||
testdir.makepyfile("def test_some(): pass")
|
||||
testdir.makepyfile(test_xyz="def test_func(): pass")
|
||||
result = testdir.runpytest("--collectonly")
|
||||
result.stdout.fnmatch_lines([
|
||||
"*<Module*test_pytest*",
|
||||
"*<MyModule*xyz*",
|
||||
])
|
||||
|
||||
def test_customized_pymakemodule_issue205_subdir(self, testdir):
|
||||
b = testdir.mkdir("a").mkdir("b")
|
||||
b.join("conftest.py").write(py.code.Source("""
|
||||
def pytest_pycollect_makemodule(__multicall__):
|
||||
mod = __multicall__.execute()
|
||||
mod.obj.hello = "world"
|
||||
return mod
|
||||
"""))
|
||||
b.join("test_module.py").write(py.code.Source("""
|
||||
def test_hello():
|
||||
assert hello == "world"
|
||||
"""))
|
||||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(passed=1)
|
||||
|
||||
def test_customized_pymakeitem(self, testdir):
|
||||
b = testdir.mkdir("a").mkdir("b")
|
||||
b.join("conftest.py").write(py.code.Source("""
|
||||
def pytest_pycollect_makeitem(__multicall__):
|
||||
result = __multicall__.execute()
|
||||
if result:
|
||||
for func in result:
|
||||
func._some123 = "world"
|
||||
return result
|
||||
"""))
|
||||
b.join("test_module.py").write(py.code.Source("""
|
||||
import pytest
|
||||
|
||||
@pytest.fixture()
|
||||
def obj(request):
|
||||
return request.node._some123
|
||||
def test_hello(obj):
|
||||
assert obj == "world"
|
||||
"""))
|
||||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(passed=1)
|
||||
|
||||
def test_pytest_pycollect_makeitem(self, testdir):
|
||||
testdir.makeconftest("""
|
||||
import pytest
|
||||
class MyFunction(pytest.Function):
|
||||
pass
|
||||
def pytest_pycollect_makeitem(collector, name, obj):
|
||||
if name == "some":
|
||||
return MyFunction(name, collector)
|
||||
""")
|
||||
testdir.makepyfile("def some(): pass")
|
||||
result = testdir.runpytest("--collectonly")
|
||||
result.stdout.fnmatch_lines([
|
||||
"*MyFunction*some*",
|
||||
])
|
||||
|
||||
def test_makeitem_non_underscore(self, testdir, monkeypatch):
|
||||
modcol = testdir.getmodulecol("def _hello(): pass")
|
||||
l = []
|
||||
monkeypatch.setattr(pytest.Module, 'makeitem',
|
||||
lambda self, name, obj: l.append(name))
|
||||
l = modcol.collect()
|
||||
assert '_hello' not in l
|
||||
|
||||
def test_setup_only_available_in_subdir(testdir):
|
||||
sub1 = testdir.mkpydir("sub1")
|
||||
sub2 = testdir.mkpydir("sub2")
|
||||
sub1.join("conftest.py").write(py.code.Source("""
|
||||
import pytest
|
||||
def pytest_runtest_setup(item):
|
||||
assert item.fspath.purebasename == "test_in_sub1"
|
||||
def pytest_runtest_call(item):
|
||||
assert item.fspath.purebasename == "test_in_sub1"
|
||||
def pytest_runtest_teardown(item):
|
||||
assert item.fspath.purebasename == "test_in_sub1"
|
||||
"""))
|
||||
sub2.join("conftest.py").write(py.code.Source("""
|
||||
import pytest
|
||||
def pytest_runtest_setup(item):
|
||||
assert item.fspath.purebasename == "test_in_sub2"
|
||||
def pytest_runtest_call(item):
|
||||
assert item.fspath.purebasename == "test_in_sub2"
|
||||
def pytest_runtest_teardown(item):
|
||||
assert item.fspath.purebasename == "test_in_sub2"
|
||||
"""))
|
||||
sub1.join("test_in_sub1.py").write("def test_1(): pass")
|
||||
sub2.join("test_in_sub2.py").write("def test_2(): pass")
|
||||
result = testdir.runpytest("-v", "-s")
|
||||
result.stdout.fnmatch_lines([
|
||||
"*2 passed*"
|
||||
])
|
||||
|
||||
def test_modulecol_roundtrip(testdir):
|
||||
modcol = testdir.getmodulecol("pass", withinit=True)
|
||||
trail = modcol.nodeid
|
||||
newcol = modcol.session.perform_collect([trail], genitems=0)[0]
|
||||
assert modcol.name == newcol.name
|
||||
|
||||
|
||||
class TestTracebackCutting:
|
||||
def test_skip_simple(self):
|
||||
excinfo = pytest.raises(pytest.skip.Exception, 'pytest.skip("xxx")')
|
||||
assert excinfo.traceback[-1].frame.code.name == "skip"
|
||||
assert excinfo.traceback[-1].ishidden()
|
||||
|
||||
def test_traceback_argsetup(self, testdir):
|
||||
testdir.makeconftest("""
|
||||
def pytest_funcarg__hello(request):
|
||||
raise ValueError("xyz")
|
||||
""")
|
||||
p = testdir.makepyfile("def test(hello): pass")
|
||||
result = testdir.runpytest(p)
|
||||
assert result.ret != 0
|
||||
out = result.stdout.str()
|
||||
assert out.find("xyz") != -1
|
||||
assert out.find("conftest.py:2: ValueError") != -1
|
||||
numentries = out.count("_ _ _") # separator for traceback entries
|
||||
assert numentries == 0
|
||||
|
||||
result = testdir.runpytest("--fulltrace", p)
|
||||
out = result.stdout.str()
|
||||
assert out.find("conftest.py:2: ValueError") != -1
|
||||
numentries = out.count("_ _ _ _") # separator for traceback entries
|
||||
assert numentries > 3
|
||||
|
||||
def test_traceback_error_during_import(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
x = 1
|
||||
x = 2
|
||||
x = 17
|
||||
asd
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
assert result.ret != 0
|
||||
out = result.stdout.str()
|
||||
assert "x = 1" not in out
|
||||
assert "x = 2" not in out
|
||||
result.stdout.fnmatch_lines([
|
||||
">*asd*",
|
||||
"E*NameError*",
|
||||
])
|
||||
result = testdir.runpytest("--fulltrace")
|
||||
out = result.stdout.str()
|
||||
assert "x = 1" in out
|
||||
assert "x = 2" in out
|
||||
result.stdout.fnmatch_lines([
|
||||
">*asd*",
|
||||
"E*NameError*",
|
||||
])
|
||||
|
||||
class TestReportInfo:
|
||||
def test_itemreport_reportinfo(self, testdir, linecomp):
|
||||
testdir.makeconftest("""
|
||||
import pytest
|
||||
class MyFunction(pytest.Function):
|
||||
def reportinfo(self):
|
||||
return "ABCDE", 42, "custom"
|
||||
def pytest_pycollect_makeitem(collector, name, obj):
|
||||
if name == "test_func":
|
||||
return MyFunction(name, parent=collector)
|
||||
""")
|
||||
item = testdir.getitem("def test_func(): pass")
|
||||
runner = item.config.pluginmanager.getplugin("runner")
|
||||
assert item.location == ("ABCDE", 42, "custom")
|
||||
|
||||
def test_func_reportinfo(self, testdir):
|
||||
item = testdir.getitem("def test_func(): pass")
|
||||
fspath, lineno, modpath = item.reportinfo()
|
||||
assert fspath == item.fspath
|
||||
assert lineno == 0
|
||||
assert modpath == "test_func"
|
||||
|
||||
def test_class_reportinfo(self, testdir):
|
||||
modcol = testdir.getmodulecol("""
|
||||
# lineno 0
|
||||
class TestClass:
|
||||
def test_hello(self): pass
|
||||
""")
|
||||
classcol = testdir.collect_by_name(modcol, "TestClass")
|
||||
fspath, lineno, msg = classcol.reportinfo()
|
||||
assert fspath == modcol.fspath
|
||||
assert lineno == 1
|
||||
assert msg == "TestClass"
|
||||
|
||||
def test_generator_reportinfo(self, testdir):
|
||||
modcol = testdir.getmodulecol("""
|
||||
# lineno 0
|
||||
def test_gen():
|
||||
def check(x):
|
||||
assert x
|
||||
yield check, 3
|
||||
""")
|
||||
gencol = testdir.collect_by_name(modcol, "test_gen")
|
||||
fspath, lineno, modpath = gencol.reportinfo()
|
||||
assert fspath == modcol.fspath
|
||||
assert lineno == 1
|
||||
assert modpath == "test_gen"
|
||||
|
||||
genitem = gencol.collect()[0]
|
||||
fspath, lineno, modpath = genitem.reportinfo()
|
||||
assert fspath == modcol.fspath
|
||||
assert lineno == 2
|
||||
assert modpath == "test_gen[0]"
|
||||
"""
|
||||
def test_func():
|
||||
pass
|
||||
def test_genfunc():
|
||||
def check(x):
|
||||
pass
|
||||
yield check, 3
|
||||
class TestClass:
|
||||
def test_method(self):
|
||||
pass
|
||||
"""
|
||||
|
||||
|
||||
def test_customized_python_discovery(testdir):
|
||||
testdir.makeini("""
|
||||
[pytest]
|
||||
python_files=check_*.py
|
||||
python_classes=Check
|
||||
python_functions=check
|
||||
""")
|
||||
p = testdir.makepyfile("""
|
||||
def check_simple():
|
||||
pass
|
||||
class CheckMyApp:
|
||||
def check_meth(self):
|
||||
pass
|
||||
""")
|
||||
p2 = p.new(basename=p.basename.replace("test", "check"))
|
||||
p.move(p2)
|
||||
result = testdir.runpytest("--collectonly", "-s")
|
||||
result.stdout.fnmatch_lines([
|
||||
"*check_customized*",
|
||||
"*check_simple*",
|
||||
"*CheckMyApp*",
|
||||
"*check_meth*",
|
||||
])
|
||||
|
||||
result = testdir.runpytest()
|
||||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines([
|
||||
"*2 passed*",
|
||||
])
|
||||
|
||||
|
||||
def test_customized_python_discovery_functions(testdir):
|
||||
testdir.makeini("""
|
||||
[pytest]
|
||||
python_functions=_test
|
||||
""")
|
||||
p = testdir.makepyfile("""
|
||||
def _test_underscore():
|
||||
pass
|
||||
""")
|
||||
result = testdir.runpytest("--collectonly", "-s")
|
||||
result.stdout.fnmatch_lines([
|
||||
"*_test_underscore*",
|
||||
])
|
||||
|
||||
result = testdir.runpytest()
|
||||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines([
|
||||
"*1 passed*",
|
||||
])
|
||||
|
||||
|
||||
def test_collector_attributes(testdir):
|
||||
testdir.makeconftest("""
|
||||
import pytest
|
||||
def pytest_pycollect_makeitem(collector):
|
||||
assert collector.Function == pytest.Function
|
||||
assert collector.Class == pytest.Class
|
||||
assert collector.Instance == pytest.Instance
|
||||
assert collector.Module == pytest.Module
|
||||
""")
|
||||
testdir.makepyfile("""
|
||||
def test_hello():
|
||||
pass
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines([
|
||||
"*1 passed*",
|
||||
])
|
||||
|
||||
def test_customize_through_attributes(testdir):
|
||||
testdir.makeconftest("""
|
||||
import pytest
|
||||
class MyFunction(pytest.Function):
|
||||
pass
|
||||
class MyInstance(pytest.Instance):
|
||||
Function = MyFunction
|
||||
class MyClass(pytest.Class):
|
||||
Instance = MyInstance
|
||||
|
||||
def pytest_pycollect_makeitem(collector, name, obj):
|
||||
if name.startswith("MyTestClass"):
|
||||
return MyClass(name, parent=collector)
|
||||
""")
|
||||
testdir.makepyfile("""
|
||||
class MyTestClass:
|
||||
def test_hello(self):
|
||||
pass
|
||||
""")
|
||||
result = testdir.runpytest("--collectonly")
|
||||
result.stdout.fnmatch_lines([
|
||||
"*MyClass*",
|
||||
"*MyInstance*",
|
||||
"*MyFunction*test_hello*",
|
||||
])
|
||||
|
||||
|
||||
def test_unorderable_types(testdir):
|
||||
testdir.makepyfile("""
|
||||
class TestJoinEmpty:
|
||||
pass
|
||||
|
||||
def make_test():
|
||||
class Test:
|
||||
pass
|
||||
Test.__name__ = "TestFoo"
|
||||
return Test
|
||||
TestFoo = make_test()
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
assert "TypeError" not in result.stdout.str()
|
||||
assert result.ret == 0
|
||||
File diff suppressed because it is too large
Load Diff
151
testing/python/integration.py
Normal file
151
testing/python/integration.py
Normal file
@@ -0,0 +1,151 @@
|
||||
import pytest, py, sys
|
||||
|
||||
class TestOEJSKITSpecials:
|
||||
def test_funcarg_non_pycollectobj(self, testdir): # rough jstests usage
|
||||
testdir.makeconftest("""
|
||||
import pytest
|
||||
def pytest_pycollect_makeitem(collector, name, obj):
|
||||
if name == "MyClass":
|
||||
return MyCollector(name, parent=collector)
|
||||
class MyCollector(pytest.Collector):
|
||||
def reportinfo(self):
|
||||
return self.fspath, 3, "xyz"
|
||||
""")
|
||||
modcol = testdir.getmodulecol("""
|
||||
def pytest_funcarg__arg1(request):
|
||||
return 42
|
||||
class MyClass:
|
||||
pass
|
||||
""")
|
||||
# this hook finds funcarg factories
|
||||
rep = modcol.ihook.pytest_make_collect_report(collector=modcol)
|
||||
clscol = rep.result[0]
|
||||
clscol.obj = lambda arg1: None
|
||||
clscol.funcargs = {}
|
||||
pytest._fillfuncargs(clscol)
|
||||
assert clscol.funcargs['arg1'] == 42
|
||||
|
||||
def test_autouse_fixture(self, testdir): # rough jstests usage
|
||||
testdir.makeconftest("""
|
||||
import pytest
|
||||
def pytest_pycollect_makeitem(collector, name, obj):
|
||||
if name == "MyClass":
|
||||
return MyCollector(name, parent=collector)
|
||||
class MyCollector(pytest.Collector):
|
||||
def reportinfo(self):
|
||||
return self.fspath, 3, "xyz"
|
||||
""")
|
||||
modcol = testdir.getmodulecol("""
|
||||
import pytest
|
||||
@pytest.fixture(autouse=True)
|
||||
def hello():
|
||||
pass
|
||||
def pytest_funcarg__arg1(request):
|
||||
return 42
|
||||
class MyClass:
|
||||
pass
|
||||
""")
|
||||
# this hook finds funcarg factories
|
||||
rep = modcol.ihook.pytest_make_collect_report(collector=modcol)
|
||||
clscol = rep.result[0]
|
||||
clscol.obj = lambda: None
|
||||
clscol.funcargs = {}
|
||||
pytest._fillfuncargs(clscol)
|
||||
assert not clscol.funcargs
|
||||
|
||||
|
||||
class TestMockDecoration:
|
||||
def test_wrapped_getfuncargnames(self):
|
||||
from _pytest.python import getfuncargnames
|
||||
def wrap(f):
|
||||
def func():
|
||||
pass
|
||||
func.__wrapped__ = f
|
||||
return func
|
||||
@wrap
|
||||
def f(x):
|
||||
pass
|
||||
l = getfuncargnames(f)
|
||||
assert l == ("x",)
|
||||
|
||||
def test_wrapped_getfuncargnames_patching(self):
|
||||
from _pytest.python import getfuncargnames
|
||||
def wrap(f):
|
||||
def func():
|
||||
pass
|
||||
func.__wrapped__ = f
|
||||
func.patchings = ["qwe"]
|
||||
return func
|
||||
@wrap
|
||||
def f(x, y, z):
|
||||
pass
|
||||
l = getfuncargnames(f)
|
||||
assert l == ("y", "z")
|
||||
|
||||
def test_unittest_mock(self, testdir):
|
||||
pytest.importorskip("unittest.mock")
|
||||
testdir.makepyfile("""
|
||||
import unittest.mock
|
||||
class T(unittest.TestCase):
|
||||
@unittest.mock.patch("os.path.abspath")
|
||||
def test_hello(self, abspath):
|
||||
import os
|
||||
os.path.abspath("hello")
|
||||
abspath.assert_any_call("hello")
|
||||
""")
|
||||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(passed=1)
|
||||
|
||||
def test_mock(self, testdir):
|
||||
pytest.importorskip("mock", "1.0.1")
|
||||
testdir.makepyfile("""
|
||||
import os
|
||||
import unittest
|
||||
import mock
|
||||
|
||||
class T(unittest.TestCase):
|
||||
@mock.patch("os.path.abspath")
|
||||
def test_hello(self, abspath):
|
||||
os.path.abspath("hello")
|
||||
abspath.assert_any_call("hello")
|
||||
@mock.patch("os.path.abspath")
|
||||
@mock.patch("os.path.normpath")
|
||||
def test_someting(normpath, abspath, tmpdir):
|
||||
abspath.return_value = "this"
|
||||
os.path.normpath(os.path.abspath("hello"))
|
||||
normpath.assert_any_call("this")
|
||||
""")
|
||||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(passed=2)
|
||||
|
||||
|
||||
class TestReRunTests:
|
||||
def test_rerun(self, testdir):
|
||||
testdir.makeconftest("""
|
||||
from _pytest.runner import runtestprotocol
|
||||
def pytest_runtest_protocol(item, nextitem):
|
||||
runtestprotocol(item, log=False, nextitem=nextitem)
|
||||
runtestprotocol(item, log=True, nextitem=nextitem)
|
||||
""")
|
||||
testdir.makepyfile("""
|
||||
import pytest
|
||||
count = 0
|
||||
req = None
|
||||
@pytest.fixture
|
||||
def fix(request):
|
||||
global count, req
|
||||
assert request != req
|
||||
req = request
|
||||
print ("fix count %s" % count)
|
||||
count += 1
|
||||
def test_fix(fix):
|
||||
pass
|
||||
""")
|
||||
result = testdir.runpytest("-s")
|
||||
result.stdout.fnmatch_lines("""
|
||||
*fix count 0*
|
||||
*fix count 1*
|
||||
""")
|
||||
result.stdout.fnmatch_lines("""
|
||||
*2 passed*
|
||||
""")
|
||||
580
testing/python/metafunc.py
Normal file
580
testing/python/metafunc.py
Normal file
@@ -0,0 +1,580 @@
|
||||
|
||||
import pytest, py, sys
|
||||
from _pytest import python as funcargs
|
||||
from _pytest.python import FixtureLookupError
|
||||
|
||||
class TestMetafunc:
|
||||
def Metafunc(self, func):
|
||||
# the unit tests of this class check if things work correctly
|
||||
# on the funcarg level, so we don't need a full blown
|
||||
# initiliazation
|
||||
class FixtureInfo:
|
||||
name2fixturedefs = None
|
||||
def __init__(self, names):
|
||||
self.names_closure = names
|
||||
names = funcargs.getfuncargnames(func)
|
||||
fixtureinfo = FixtureInfo(names)
|
||||
return funcargs.Metafunc(func, fixtureinfo, None)
|
||||
|
||||
def test_no_funcargs(self, testdir):
|
||||
def function(): pass
|
||||
metafunc = self.Metafunc(function)
|
||||
assert not metafunc.fixturenames
|
||||
repr(metafunc._calls)
|
||||
|
||||
def test_function_basic(self):
|
||||
def func(arg1, arg2="qwe"): pass
|
||||
metafunc = self.Metafunc(func)
|
||||
assert len(metafunc.fixturenames) == 1
|
||||
assert 'arg1' in metafunc.fixturenames
|
||||
assert metafunc.function is func
|
||||
assert metafunc.cls is None
|
||||
|
||||
def test_addcall_no_args(self):
|
||||
def func(arg1): pass
|
||||
metafunc = self.Metafunc(func)
|
||||
metafunc.addcall()
|
||||
assert len(metafunc._calls) == 1
|
||||
call = metafunc._calls[0]
|
||||
assert call.id == "0"
|
||||
assert not hasattr(call, 'param')
|
||||
|
||||
def test_addcall_id(self):
|
||||
def func(arg1): pass
|
||||
metafunc = self.Metafunc(func)
|
||||
pytest.raises(ValueError, "metafunc.addcall(id=None)")
|
||||
|
||||
metafunc.addcall(id=1)
|
||||
pytest.raises(ValueError, "metafunc.addcall(id=1)")
|
||||
pytest.raises(ValueError, "metafunc.addcall(id='1')")
|
||||
metafunc.addcall(id=2)
|
||||
assert len(metafunc._calls) == 2
|
||||
assert metafunc._calls[0].id == "1"
|
||||
assert metafunc._calls[1].id == "2"
|
||||
|
||||
def test_addcall_param(self):
|
||||
def func(arg1): pass
|
||||
metafunc = self.Metafunc(func)
|
||||
class obj: pass
|
||||
metafunc.addcall(param=obj)
|
||||
metafunc.addcall(param=obj)
|
||||
metafunc.addcall(param=1)
|
||||
assert len(metafunc._calls) == 3
|
||||
assert metafunc._calls[0].getparam("arg1") == obj
|
||||
assert metafunc._calls[1].getparam("arg1") == obj
|
||||
assert metafunc._calls[2].getparam("arg1") == 1
|
||||
|
||||
def test_addcall_funcargs(self):
|
||||
def func(x): pass
|
||||
metafunc = self.Metafunc(func)
|
||||
class obj: pass
|
||||
metafunc.addcall(funcargs={"x": 2})
|
||||
metafunc.addcall(funcargs={"x": 3})
|
||||
pytest.raises(pytest.fail.Exception, "metafunc.addcall({'xyz': 0})")
|
||||
assert len(metafunc._calls) == 2
|
||||
assert metafunc._calls[0].funcargs == {'x': 2}
|
||||
assert metafunc._calls[1].funcargs == {'x': 3}
|
||||
assert not hasattr(metafunc._calls[1], 'param')
|
||||
|
||||
def test_parametrize_error(self):
|
||||
def func(x, y): pass
|
||||
metafunc = self.Metafunc(func)
|
||||
metafunc.parametrize("x", [1,2])
|
||||
pytest.raises(ValueError, lambda: metafunc.parametrize("x", [5,6]))
|
||||
pytest.raises(ValueError, lambda: metafunc.parametrize("x", [5,6]))
|
||||
metafunc.parametrize("y", [1,2])
|
||||
pytest.raises(ValueError, lambda: metafunc.parametrize("y", [5,6]))
|
||||
pytest.raises(ValueError, lambda: metafunc.parametrize("y", [5,6]))
|
||||
|
||||
def test_parametrize_and_id(self):
|
||||
def func(x, y): pass
|
||||
metafunc = self.Metafunc(func)
|
||||
|
||||
metafunc.parametrize("x", [1,2], ids=['basic', 'advanced'])
|
||||
metafunc.parametrize("y", ["abc", "def"])
|
||||
ids = [x.id for x in metafunc._calls]
|
||||
assert ids == ["basic-abc", "basic-def", "advanced-abc", "advanced-def"]
|
||||
|
||||
def test_parametrize_with_userobjects(self):
|
||||
def func(x, y): pass
|
||||
metafunc = self.Metafunc(func)
|
||||
class A:
|
||||
pass
|
||||
metafunc.parametrize("x", [A(), A()])
|
||||
metafunc.parametrize("y", list("ab"))
|
||||
assert metafunc._calls[0].id == "x0-a"
|
||||
assert metafunc._calls[1].id == "x0-b"
|
||||
assert metafunc._calls[2].id == "x1-a"
|
||||
assert metafunc._calls[3].id == "x1-b"
|
||||
|
||||
@pytest.mark.issue250
|
||||
def test_idmaker_autoname(self):
|
||||
from _pytest.python import idmaker
|
||||
result = idmaker(("a", "b"), [("string", 1.0),
|
||||
("st-ring", 2.0)])
|
||||
assert result == ["string-1.0", "st-ring-2.0"]
|
||||
|
||||
result = idmaker(("a", "b"), [(object(), 1.0),
|
||||
(object(), object())])
|
||||
assert result == ["a0-1.0", "a1-b1"]
|
||||
# unicode mixing, issue250
|
||||
result = idmaker((py.builtin._totext("a"), "b"), [({}, '\xc3\xb4')])
|
||||
assert result == ['a0-\xc3\xb4']
|
||||
|
||||
|
||||
def test_addcall_and_parametrize(self):
|
||||
def func(x, y): pass
|
||||
metafunc = self.Metafunc(func)
|
||||
metafunc.addcall({'x': 1})
|
||||
metafunc.parametrize('y', [2,3])
|
||||
assert len(metafunc._calls) == 2
|
||||
assert metafunc._calls[0].funcargs == {'x': 1, 'y': 2}
|
||||
assert metafunc._calls[1].funcargs == {'x': 1, 'y': 3}
|
||||
assert metafunc._calls[0].id == "0-2"
|
||||
assert metafunc._calls[1].id == "0-3"
|
||||
|
||||
def test_parametrize_indirect(self):
|
||||
def func(x, y): pass
|
||||
metafunc = self.Metafunc(func)
|
||||
metafunc.parametrize('x', [1], indirect=True)
|
||||
metafunc.parametrize('y', [2,3], indirect=True)
|
||||
metafunc.parametrize('unnamed', [1], indirect=True)
|
||||
assert len(metafunc._calls) == 2
|
||||
assert metafunc._calls[0].funcargs == {}
|
||||
assert metafunc._calls[1].funcargs == {}
|
||||
assert metafunc._calls[0].params == dict(x=1,y=2, unnamed=1)
|
||||
assert metafunc._calls[1].params == dict(x=1,y=3, unnamed=1)
|
||||
|
||||
def test_addcalls_and_parametrize_indirect(self):
|
||||
def func(x, y): pass
|
||||
metafunc = self.Metafunc(func)
|
||||
metafunc.addcall(param="123")
|
||||
metafunc.parametrize('x', [1], indirect=True)
|
||||
metafunc.parametrize('y', [2,3], indirect=True)
|
||||
assert len(metafunc._calls) == 2
|
||||
assert metafunc._calls[0].funcargs == {}
|
||||
assert metafunc._calls[1].funcargs == {}
|
||||
assert metafunc._calls[0].params == dict(x=1,y=2)
|
||||
assert metafunc._calls[1].params == dict(x=1,y=3)
|
||||
|
||||
def test_parametrize_functional(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
def pytest_generate_tests(metafunc):
|
||||
metafunc.parametrize('x', [1,2], indirect=True)
|
||||
metafunc.parametrize('y', [2])
|
||||
def pytest_funcarg__x(request):
|
||||
return request.param * 10
|
||||
def pytest_funcarg__y(request):
|
||||
return request.param
|
||||
|
||||
def test_simple(x,y):
|
||||
assert x in (10,20)
|
||||
assert y == 2
|
||||
""")
|
||||
result = testdir.runpytest("-v")
|
||||
result.stdout.fnmatch_lines([
|
||||
"*test_simple*1-2*",
|
||||
"*test_simple*2-2*",
|
||||
"*2 passed*",
|
||||
])
|
||||
|
||||
def test_parametrize_onearg(self):
|
||||
metafunc = self.Metafunc(lambda x: None)
|
||||
metafunc.parametrize("x", [1,2])
|
||||
assert len(metafunc._calls) == 2
|
||||
assert metafunc._calls[0].funcargs == dict(x=1)
|
||||
assert metafunc._calls[0].id == "1"
|
||||
assert metafunc._calls[1].funcargs == dict(x=2)
|
||||
assert metafunc._calls[1].id == "2"
|
||||
|
||||
def test_parametrize_onearg_indirect(self):
|
||||
metafunc = self.Metafunc(lambda x: None)
|
||||
metafunc.parametrize("x", [1,2], indirect=True)
|
||||
assert metafunc._calls[0].params == dict(x=1)
|
||||
assert metafunc._calls[0].id == "1"
|
||||
assert metafunc._calls[1].params == dict(x=2)
|
||||
assert metafunc._calls[1].id == "2"
|
||||
|
||||
def test_parametrize_twoargs(self):
|
||||
metafunc = self.Metafunc(lambda x,y: None)
|
||||
metafunc.parametrize(("x", "y"), [(1,2), (3,4)])
|
||||
assert len(metafunc._calls) == 2
|
||||
assert metafunc._calls[0].funcargs == dict(x=1, y=2)
|
||||
assert metafunc._calls[0].id == "1-2"
|
||||
assert metafunc._calls[1].funcargs == dict(x=3, y=4)
|
||||
assert metafunc._calls[1].id == "3-4"
|
||||
|
||||
def test_parametrize_multiple_times(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
import pytest
|
||||
pytestmark = pytest.mark.parametrize("x", [1,2])
|
||||
def test_func(x):
|
||||
assert 0, x
|
||||
class TestClass:
|
||||
pytestmark = pytest.mark.parametrize("y", [3,4])
|
||||
def test_meth(self, x, y):
|
||||
assert 0, x
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
assert result.ret == 1
|
||||
result.stdout.fnmatch_lines([
|
||||
"*6 fail*",
|
||||
])
|
||||
|
||||
def test_parametrize_class_scenarios(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
# same as doc/en/example/parametrize scenario example
|
||||
def pytest_generate_tests(metafunc):
|
||||
idlist = []
|
||||
argvalues = []
|
||||
for scenario in metafunc.cls.scenarios:
|
||||
idlist.append(scenario[0])
|
||||
items = scenario[1].items()
|
||||
argnames = [x[0] for x in items]
|
||||
argvalues.append(([x[1] for x in items]))
|
||||
metafunc.parametrize(argnames, argvalues, ids=idlist, scope="class")
|
||||
|
||||
class Test(object):
|
||||
scenarios = [['1', {'arg': {1: 2}, "arg2": "value2"}],
|
||||
['2', {'arg':'value2', "arg2": "value2"}]]
|
||||
|
||||
def test_1(self, arg, arg2):
|
||||
pass
|
||||
|
||||
def test_2(self, arg2, arg):
|
||||
pass
|
||||
|
||||
def test_3(self, arg, arg2):
|
||||
pass
|
||||
""")
|
||||
result = testdir.runpytest("-v")
|
||||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines("""
|
||||
*test_1*1*
|
||||
*test_2*1*
|
||||
*test_3*1*
|
||||
*test_1*2*
|
||||
*test_2*2*
|
||||
*test_3*2*
|
||||
*6 passed*
|
||||
""")
|
||||
|
||||
class TestMetafuncFunctional:
|
||||
def test_attributes(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
# assumes that generate/provide runs in the same process
|
||||
import py, pytest
|
||||
def pytest_generate_tests(metafunc):
|
||||
metafunc.addcall(param=metafunc)
|
||||
|
||||
def pytest_funcarg__metafunc(request):
|
||||
assert request._pyfuncitem._genid == "0"
|
||||
return request.param
|
||||
|
||||
def test_function(metafunc, pytestconfig):
|
||||
assert metafunc.config == pytestconfig
|
||||
assert metafunc.module.__name__ == __name__
|
||||
assert metafunc.function == test_function
|
||||
assert metafunc.cls is None
|
||||
|
||||
class TestClass:
|
||||
def test_method(self, metafunc, pytestconfig):
|
||||
assert metafunc.config == pytestconfig
|
||||
assert metafunc.module.__name__ == __name__
|
||||
if py.std.sys.version_info > (3, 0):
|
||||
unbound = TestClass.test_method
|
||||
else:
|
||||
unbound = TestClass.test_method.im_func
|
||||
# XXX actually have an unbound test function here?
|
||||
assert metafunc.function == unbound
|
||||
assert metafunc.cls == TestClass
|
||||
""")
|
||||
result = testdir.runpytest(p, "-v")
|
||||
result.stdout.fnmatch_lines([
|
||||
"*2 passed in*",
|
||||
])
|
||||
|
||||
def test_addcall_with_two_funcargs_generators(self, testdir):
|
||||
testdir.makeconftest("""
|
||||
def pytest_generate_tests(metafunc):
|
||||
assert "arg1" in metafunc.fixturenames
|
||||
metafunc.addcall(funcargs=dict(arg1=1, arg2=2))
|
||||
""")
|
||||
p = testdir.makepyfile("""
|
||||
def pytest_generate_tests(metafunc):
|
||||
metafunc.addcall(funcargs=dict(arg1=1, arg2=1))
|
||||
|
||||
class TestClass:
|
||||
def test_myfunc(self, arg1, arg2):
|
||||
assert arg1 == arg2
|
||||
""")
|
||||
result = testdir.runpytest("-v", p)
|
||||
result.stdout.fnmatch_lines([
|
||||
"*test_myfunc*0*PASS*",
|
||||
"*test_myfunc*1*FAIL*",
|
||||
"*1 failed, 1 passed*"
|
||||
])
|
||||
|
||||
def test_two_functions(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
def pytest_generate_tests(metafunc):
|
||||
metafunc.addcall(param=10)
|
||||
metafunc.addcall(param=20)
|
||||
|
||||
def pytest_funcarg__arg1(request):
|
||||
return request.param
|
||||
|
||||
def test_func1(arg1):
|
||||
assert arg1 == 10
|
||||
def test_func2(arg1):
|
||||
assert arg1 in (10, 20)
|
||||
""")
|
||||
result = testdir.runpytest("-v", p)
|
||||
result.stdout.fnmatch_lines([
|
||||
"*test_func1*0*PASS*",
|
||||
"*test_func1*1*FAIL*",
|
||||
"*test_func2*PASS*",
|
||||
"*1 failed, 3 passed*"
|
||||
])
|
||||
|
||||
def test_noself_in_method(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
def pytest_generate_tests(metafunc):
|
||||
assert 'xyz' not in metafunc.fixturenames
|
||||
|
||||
class TestHello:
|
||||
def test_hello(xyz):
|
||||
pass
|
||||
""")
|
||||
result = testdir.runpytest(p)
|
||||
result.stdout.fnmatch_lines([
|
||||
"*1 pass*",
|
||||
])
|
||||
|
||||
|
||||
def test_generate_plugin_and_module(self, testdir):
|
||||
testdir.makeconftest("""
|
||||
def pytest_generate_tests(metafunc):
|
||||
assert "arg1" in metafunc.fixturenames
|
||||
metafunc.addcall(id="world", param=(2,100))
|
||||
""")
|
||||
p = testdir.makepyfile("""
|
||||
def pytest_generate_tests(metafunc):
|
||||
metafunc.addcall(param=(1,1), id="hello")
|
||||
|
||||
def pytest_funcarg__arg1(request):
|
||||
return request.param[0]
|
||||
def pytest_funcarg__arg2(request):
|
||||
return request.param[1]
|
||||
|
||||
class TestClass:
|
||||
def test_myfunc(self, arg1, arg2):
|
||||
assert arg1 == arg2
|
||||
""")
|
||||
result = testdir.runpytest("-v", p)
|
||||
result.stdout.fnmatch_lines([
|
||||
"*test_myfunc*hello*PASS*",
|
||||
"*test_myfunc*world*FAIL*",
|
||||
"*1 failed, 1 passed*"
|
||||
])
|
||||
|
||||
def test_generate_tests_in_class(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
class TestClass:
|
||||
def pytest_generate_tests(self, metafunc):
|
||||
metafunc.addcall(funcargs={'hello': 'world'}, id="hello")
|
||||
|
||||
def test_myfunc(self, hello):
|
||||
assert hello == "world"
|
||||
""")
|
||||
result = testdir.runpytest("-v", p)
|
||||
result.stdout.fnmatch_lines([
|
||||
"*test_myfunc*hello*PASS*",
|
||||
"*1 passed*"
|
||||
])
|
||||
|
||||
def test_two_functions_not_same_instance(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
def pytest_generate_tests(metafunc):
|
||||
metafunc.addcall({'arg1': 10})
|
||||
metafunc.addcall({'arg1': 20})
|
||||
|
||||
class TestClass:
|
||||
def test_func(self, arg1):
|
||||
assert not hasattr(self, 'x')
|
||||
self.x = 1
|
||||
""")
|
||||
result = testdir.runpytest("-v", p)
|
||||
result.stdout.fnmatch_lines([
|
||||
"*test_func*0*PASS*",
|
||||
"*test_func*1*PASS*",
|
||||
"*2 pass*",
|
||||
])
|
||||
|
||||
def test_issue28_setup_method_in_generate_tests(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
def pytest_generate_tests(metafunc):
|
||||
metafunc.addcall({'arg1': 1})
|
||||
|
||||
class TestClass:
|
||||
def test_method(self, arg1):
|
||||
assert arg1 == self.val
|
||||
def setup_method(self, func):
|
||||
self.val = 1
|
||||
""")
|
||||
result = testdir.runpytest(p)
|
||||
result.stdout.fnmatch_lines([
|
||||
"*1 pass*",
|
||||
])
|
||||
|
||||
def test_parametrize_functional2(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
def pytest_generate_tests(metafunc):
|
||||
metafunc.parametrize("arg1", [1,2])
|
||||
metafunc.parametrize("arg2", [4,5])
|
||||
def test_hello(arg1, arg2):
|
||||
assert 0, (arg1, arg2)
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines([
|
||||
"*(1, 4)*",
|
||||
"*(1, 5)*",
|
||||
"*(2, 4)*",
|
||||
"*(2, 5)*",
|
||||
"*4 failed*",
|
||||
])
|
||||
|
||||
def test_parametrize_and_inner_getfuncargvalue(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
def pytest_generate_tests(metafunc):
|
||||
metafunc.parametrize("arg1", [1], indirect=True)
|
||||
metafunc.parametrize("arg2", [10], indirect=True)
|
||||
|
||||
def pytest_funcarg__arg1(request):
|
||||
x = request.getfuncargvalue("arg2")
|
||||
return x + request.param
|
||||
|
||||
def pytest_funcarg__arg2(request):
|
||||
return request.param
|
||||
|
||||
def test_func1(arg1, arg2):
|
||||
assert arg1 == 11
|
||||
""")
|
||||
result = testdir.runpytest("-v", p)
|
||||
result.stdout.fnmatch_lines([
|
||||
"*test_func1*1*PASS*",
|
||||
"*1 passed*"
|
||||
])
|
||||
|
||||
def test_parametrize_on_setup_arg(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
def pytest_generate_tests(metafunc):
|
||||
assert "arg1" in metafunc.fixturenames
|
||||
metafunc.parametrize("arg1", [1], indirect=True)
|
||||
|
||||
def pytest_funcarg__arg1(request):
|
||||
return request.param
|
||||
|
||||
def pytest_funcarg__arg2(request, arg1):
|
||||
return 10 * arg1
|
||||
|
||||
def test_func(arg2):
|
||||
assert arg2 == 10
|
||||
""")
|
||||
result = testdir.runpytest("-v", p)
|
||||
result.stdout.fnmatch_lines([
|
||||
"*test_func*1*PASS*",
|
||||
"*1 passed*"
|
||||
])
|
||||
|
||||
def test_parametrize_with_ids(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
import pytest
|
||||
def pytest_generate_tests(metafunc):
|
||||
metafunc.parametrize(("a", "b"), [(1,1), (1,2)],
|
||||
ids=["basic", "advanced"])
|
||||
|
||||
def test_function(a, b):
|
||||
assert a == b
|
||||
""")
|
||||
result = testdir.runpytest("-v")
|
||||
assert result.ret == 1
|
||||
result.stdout.fnmatch_lines_random([
|
||||
"*test_function*basic*PASSED",
|
||||
"*test_function*advanced*FAILED",
|
||||
])
|
||||
|
||||
def test_parametrize_without_ids(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
import pytest
|
||||
def pytest_generate_tests(metafunc):
|
||||
metafunc.parametrize(("a", "b"),
|
||||
[(1,object()), (1.3,object())])
|
||||
|
||||
def test_function(a, b):
|
||||
assert 1
|
||||
""")
|
||||
result = testdir.runpytest("-v")
|
||||
result.stdout.fnmatch_lines("""
|
||||
*test_function*1-b0*
|
||||
*test_function*1.3-b1*
|
||||
""")
|
||||
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("scope", "length"),
|
||||
[("module", 2), ("function", 4)])
|
||||
def test_parametrize_scope_overrides(self, testdir, scope, length):
|
||||
testdir.makepyfile("""
|
||||
import pytest
|
||||
l = []
|
||||
def pytest_generate_tests(metafunc):
|
||||
if "arg" in metafunc.funcargnames:
|
||||
metafunc.parametrize("arg", [1,2], indirect=True,
|
||||
scope=%r)
|
||||
def pytest_funcarg__arg(request):
|
||||
l.append(request.param)
|
||||
return request.param
|
||||
def test_hello(arg):
|
||||
assert arg in (1,2)
|
||||
def test_world(arg):
|
||||
assert arg in (1,2)
|
||||
def test_checklength():
|
||||
assert len(l) == %d
|
||||
""" % (scope, length))
|
||||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(passed=5)
|
||||
|
||||
def test_usefixtures_seen_in_generate_tests(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
import pytest
|
||||
def pytest_generate_tests(metafunc):
|
||||
assert "abc" in metafunc.fixturenames
|
||||
metafunc.parametrize("abc", [1])
|
||||
|
||||
@pytest.mark.usefixtures("abc")
|
||||
def test_function():
|
||||
pass
|
||||
""")
|
||||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(passed=1)
|
||||
|
||||
def test_generate_tests_only_done_in_subdir(self, testdir):
|
||||
sub1 = testdir.mkpydir("sub1")
|
||||
sub2 = testdir.mkpydir("sub2")
|
||||
sub1.join("conftest.py").write(py.code.Source("""
|
||||
def pytest_generate_tests(metafunc):
|
||||
assert metafunc.function.__name__ == "test_1"
|
||||
"""))
|
||||
sub2.join("conftest.py").write(py.code.Source("""
|
||||
def pytest_generate_tests(metafunc):
|
||||
assert metafunc.function.__name__ == "test_2"
|
||||
"""))
|
||||
sub1.join("test_in_sub1.py").write("def test_1(): pass")
|
||||
sub2.join("test_in_sub2.py").write("def test_2(): pass")
|
||||
result = testdir.runpytest("-v", "-s", sub1, sub2, sub1)
|
||||
result.stdout.fnmatch_lines([
|
||||
"*3 passed*"
|
||||
])
|
||||
|
||||
|
||||
64
testing/python/raises.py
Normal file
64
testing/python/raises.py
Normal file
@@ -0,0 +1,64 @@
|
||||
import pytest
|
||||
|
||||
class TestRaises:
|
||||
def test_raises(self):
|
||||
source = "int('qwe')"
|
||||
excinfo = pytest.raises(ValueError, source)
|
||||
code = excinfo.traceback[-1].frame.code
|
||||
s = str(code.fullsource)
|
||||
assert s == source
|
||||
|
||||
def test_raises_exec(self):
|
||||
pytest.raises(ValueError, "a,x = []")
|
||||
|
||||
def test_raises_syntax_error(self):
|
||||
pytest.raises(SyntaxError, "qwe qwe qwe")
|
||||
|
||||
def test_raises_function(self):
|
||||
pytest.raises(ValueError, int, 'hello')
|
||||
|
||||
def test_raises_callable_no_exception(self):
|
||||
class A:
|
||||
def __call__(self):
|
||||
pass
|
||||
try:
|
||||
pytest.raises(ValueError, A())
|
||||
except pytest.raises.Exception:
|
||||
pass
|
||||
|
||||
def test_raises_flip_builtin_AssertionError(self):
|
||||
# we replace AssertionError on python level
|
||||
# however c code might still raise the builtin one
|
||||
from _pytest.assertion.util import BuiltinAssertionError
|
||||
pytest.raises(AssertionError,"""
|
||||
raise BuiltinAssertionError
|
||||
""")
|
||||
|
||||
@pytest.mark.skipif('sys.version < "2.5"')
|
||||
def test_raises_as_contextmanager(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
from __future__ import with_statement
|
||||
import py, pytest
|
||||
|
||||
def test_simple():
|
||||
with pytest.raises(ZeroDivisionError) as excinfo:
|
||||
assert isinstance(excinfo, py.code.ExceptionInfo)
|
||||
1/0
|
||||
print (excinfo)
|
||||
assert excinfo.type == ZeroDivisionError
|
||||
|
||||
def test_noraise():
|
||||
with pytest.raises(pytest.raises.Exception):
|
||||
with pytest.raises(ValueError):
|
||||
int()
|
||||
|
||||
def test_raise_wrong_exception_passes_by():
|
||||
with pytest.raises(ZeroDivisionError):
|
||||
with pytest.raises(ValueError):
|
||||
1/0
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines([
|
||||
'*3 passed*',
|
||||
])
|
||||
|
||||
@@ -6,6 +6,18 @@ from _pytest.assertion import reinterpret, util
|
||||
|
||||
needsnewassert = pytest.mark.skipif("sys.version_info < (2,6)")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_config():
|
||||
class Config(object):
|
||||
verbose = False
|
||||
def getoption(self, name):
|
||||
if name == 'verbose':
|
||||
return self.verbose
|
||||
raise KeyError('Not mocked out: %s' % name)
|
||||
return Config()
|
||||
|
||||
|
||||
def interpret(expr):
|
||||
return reinterpret.reinterpret(expr, py.code.Frame(sys._getframe(1)))
|
||||
|
||||
@@ -32,8 +44,11 @@ class TestBinReprIntegration:
|
||||
"*test_check*PASS*",
|
||||
])
|
||||
|
||||
def callequal(left, right):
|
||||
return plugin.pytest_assertrepr_compare('==', left, right)
|
||||
def callequal(left, right, verbose=False):
|
||||
config = mock_config()
|
||||
config.verbose = verbose
|
||||
return plugin.pytest_assertrepr_compare(config, '==', left, right)
|
||||
|
||||
|
||||
class TestAssert_reprcompare:
|
||||
def test_different_types(self):
|
||||
@@ -48,6 +63,17 @@ class TestAssert_reprcompare:
|
||||
assert '- spam' in diff
|
||||
assert '+ eggs' in diff
|
||||
|
||||
def test_text_skipping(self):
|
||||
lines = callequal('a'*50 + 'spam', 'a'*50 + 'eggs')
|
||||
assert 'Skipping' in lines[1]
|
||||
for line in lines:
|
||||
assert 'a'*50 not in line
|
||||
|
||||
def test_text_skipping_verbose(self):
|
||||
lines = callequal('a'*50 + 'spam', 'a'*50 + 'eggs', verbose=True)
|
||||
assert '- ' + 'a'*50 + 'spam' in lines
|
||||
assert '+ ' + 'a'*50 + 'eggs' in lines
|
||||
|
||||
def test_multiline_text_diff(self):
|
||||
left = 'foo\nspam\nbar'
|
||||
right = 'foo\neggs\nbar'
|
||||
@@ -73,6 +99,11 @@ class TestAssert_reprcompare:
|
||||
expl = callequal(set([0, 1]), set([0, 2]))
|
||||
assert len(expl) > 1
|
||||
|
||||
def test_frozenzet(self):
|
||||
expl = callequal(frozenset([0, 1]), set([0, 2]))
|
||||
print (expl)
|
||||
assert len(expl) > 1
|
||||
|
||||
def test_list_tuples(self):
|
||||
expl = callequal([], [(1,2)])
|
||||
assert len(expl) > 1
|
||||
@@ -103,6 +134,19 @@ class TestAssert_reprcompare:
|
||||
expl = ' '.join(callequal('foo', 'bar'))
|
||||
assert 'raised in repr()' not in expl
|
||||
|
||||
def test_python25_compile_issue257(testdir):
|
||||
testdir.makepyfile("""
|
||||
def test_rewritten():
|
||||
assert 1 == 2
|
||||
# some comment
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
assert result.ret == 1
|
||||
result.stdout.fnmatch_lines("""
|
||||
*E*assert 1 == 2*
|
||||
*1 failed*
|
||||
""")
|
||||
|
||||
@needsnewassert
|
||||
def test_rewritten(testdir):
|
||||
testdir.makepyfile("""
|
||||
@@ -111,8 +155,9 @@ def test_rewritten(testdir):
|
||||
""")
|
||||
assert testdir.runpytest().ret == 0
|
||||
|
||||
def test_reprcompare_notin():
|
||||
detail = plugin.pytest_assertrepr_compare('not in', 'foo', 'aaafoobbb')[1:]
|
||||
def test_reprcompare_notin(mock_config):
|
||||
detail = plugin.pytest_assertrepr_compare(
|
||||
mock_config, 'not in', 'foo', 'aaafoobbb')[1:]
|
||||
assert detail == ["'foo' is contained here:", ' aaafoobbb', '? +++']
|
||||
|
||||
@needsnewassert
|
||||
@@ -164,7 +209,7 @@ def test_assert_compare_truncate_longmessage(testdir):
|
||||
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines([
|
||||
"*too verbose, truncated*",
|
||||
"*truncated*use*-vv*",
|
||||
])
|
||||
|
||||
|
||||
@@ -275,3 +320,17 @@ def test_warn_missing(testdir):
|
||||
result.stderr.fnmatch_lines([
|
||||
"*WARNING*assert statements are not executed*",
|
||||
])
|
||||
|
||||
def test_recursion_source_decode(testdir):
|
||||
testdir.makepyfile("""
|
||||
def test_something():
|
||||
pass
|
||||
""")
|
||||
testdir.makeini("""
|
||||
[pytest]
|
||||
python_files = *.py
|
||||
""")
|
||||
result = testdir.runpytest("--collectonly")
|
||||
result.stdout.fnmatch_lines("""
|
||||
<Module*>
|
||||
""")
|
||||
|
||||
@@ -107,7 +107,15 @@ class TestAssertionRewrite:
|
||||
assert getmsg(f) == "assert False"
|
||||
def f():
|
||||
assert a_global
|
||||
assert getmsg(f, {"a_global" : False}) == "assert a_global"
|
||||
assert getmsg(f, {"a_global" : False}) == "assert False"
|
||||
def f():
|
||||
assert sys == 42
|
||||
assert getmsg(f, {"sys" : sys}) == "assert sys == 42"
|
||||
def f():
|
||||
assert cls == 42
|
||||
class X(object):
|
||||
pass
|
||||
assert getmsg(f, {"cls" : X}) == "assert cls == 42"
|
||||
|
||||
def test_assert_already_has_message(self):
|
||||
def f():
|
||||
@@ -232,7 +240,7 @@ class TestAssertionRewrite:
|
||||
def test_attribute(self):
|
||||
class X(object):
|
||||
g = 3
|
||||
ns = {"X" : X, "x" : X()}
|
||||
ns = {"x" : X}
|
||||
def f():
|
||||
assert not x.g
|
||||
assert getmsg(f, ns) == """assert not 3
|
||||
@@ -354,18 +362,21 @@ def test_no_bytecode():
|
||||
@pytest.mark.skipif('"__pypy__" in sys.modules')
|
||||
def test_pyc_vs_pyo(self, testdir, monkeypatch):
|
||||
testdir.makepyfile("""
|
||||
import pytest
|
||||
def test_optimized():
|
||||
"hello"
|
||||
assert test_optimized.__doc__ is None""")
|
||||
import pytest
|
||||
def test_optimized():
|
||||
"hello"
|
||||
assert test_optimized.__doc__ is None"""
|
||||
)
|
||||
p = py.path.local.make_numbered_dir(prefix="runpytest-", keep=None,
|
||||
rootdir=testdir.tmpdir)
|
||||
tmp = "--basetemp=%s" % p
|
||||
monkeypatch.setenv("PYTHONOPTIMIZE", "2")
|
||||
monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", raising=False)
|
||||
assert testdir.runpybin("py.test", tmp).ret == 0
|
||||
tagged = "test_pyc_vs_pyo." + PYTEST_TAG
|
||||
assert tagged + ".pyo" in os.listdir("__pycache__")
|
||||
monkeypatch.undo()
|
||||
monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", raising=False)
|
||||
assert testdir.runpybin("py.test", tmp).ret == 1
|
||||
assert tagged + ".pyc" in os.listdir("__pycache__")
|
||||
|
||||
@@ -383,3 +394,11 @@ def test_rewritten():
|
||||
b = content.encode("utf-8")
|
||||
testdir.tmpdir.join("test_newlines.py").write(b, "wb")
|
||||
assert testdir.runpytest().ret == 0
|
||||
|
||||
@pytest.mark.skipif("sys.version_info[0] >= 3")
|
||||
def test_assume_ascii(self, testdir):
|
||||
content = "u'\xe2\x99\xa5'"
|
||||
testdir.tmpdir.join("test_encoding.py").write(content, "wb")
|
||||
res = testdir.runpytest()
|
||||
assert res.ret != 0
|
||||
assert "SyntaxError: Non-ASCII character" in res.stdout.str()
|
||||
|
||||
@@ -437,6 +437,18 @@ class TestCaptureFixture:
|
||||
])
|
||||
assert result.ret == 2
|
||||
|
||||
@pytest.mark.xfail("sys.version_info < (2,7)")
|
||||
@pytest.mark.issue14
|
||||
def test_capture_and_logging(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
import logging
|
||||
def test_log(capsys):
|
||||
logging.error('x')
|
||||
""")
|
||||
result = testdir.runpytest(p)
|
||||
assert 'closed' not in result.stderr.str()
|
||||
|
||||
|
||||
def test_setup_failure_does_not_kill_capturing(testdir):
|
||||
sub1 = testdir.mkpydir("sub1")
|
||||
sub1.join("conftest.py").write(py.code.Source("""
|
||||
|
||||
@@ -103,6 +103,16 @@ class TestConfigAPI:
|
||||
assert config.getvalue("x", o) == 1
|
||||
pytest.raises(KeyError, 'config.getvalue("y", o)')
|
||||
|
||||
def test_config_getoption(self, testdir):
|
||||
testdir.makeconftest("""
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption("--hello", "-X", dest="hello")
|
||||
""")
|
||||
config = testdir.parseconfig("--hello=this")
|
||||
for x in ("hello", "--hello", "-X"):
|
||||
assert config.getoption(x) == "this"
|
||||
pytest.raises(ValueError, "config.getoption('qweqwe')")
|
||||
|
||||
def test_config_getvalueorskip(self, testdir):
|
||||
config = testdir.parseconfig()
|
||||
pytest.raises(pytest.skip.Exception,
|
||||
@@ -304,3 +314,9 @@ def test_cmdline_processargs_simple(testdir):
|
||||
"*pytest*",
|
||||
"*-h*",
|
||||
])
|
||||
|
||||
|
||||
@pytest.mark.skipif("sys.platform == 'win32'")
|
||||
def test_toolongargs_issue224(testdir):
|
||||
result = testdir.runpytest("-m", "hello" * 500)
|
||||
assert result.ret == 0
|
||||
|
||||
@@ -612,6 +612,19 @@ class TestTracer:
|
||||
assert names == ['hello', ' line1', ' line2',
|
||||
' line3', ' line4', ' line5', 'last']
|
||||
|
||||
def test_readable_output_dictargs(self):
|
||||
from _pytest.core import TagTracer
|
||||
rootlogger = TagTracer()
|
||||
|
||||
out = rootlogger.format_message(['test'], [1])
|
||||
assert out == ['1 [test]\n']
|
||||
|
||||
out2= rootlogger.format_message(['test'], ['test', {'a':1}])
|
||||
assert out2 ==[
|
||||
'test [test]\n',
|
||||
' a: 1\n'
|
||||
]
|
||||
|
||||
def test_setprocessor(self):
|
||||
from _pytest.core import TagTracer
|
||||
rootlogger = TagTracer()
|
||||
|
||||
@@ -59,6 +59,26 @@ class TestDoctests:
|
||||
"*UNEXPECTED*ZeroDivision*",
|
||||
])
|
||||
|
||||
def test_doctest_linedata_missing(self, testdir):
|
||||
testdir.tmpdir.join('hello.py').write(py.code.Source("""
|
||||
class Fun(object):
|
||||
@property
|
||||
def test(self):
|
||||
'''
|
||||
>>> a = 1
|
||||
>>> 1/0
|
||||
'''
|
||||
"""))
|
||||
result = testdir.runpytest("--doctest-modules")
|
||||
result.stdout.fnmatch_lines([
|
||||
"*hello*",
|
||||
"*EXAMPLE LOCATION UNKNOWN, not showing all tests of that example*",
|
||||
"*1/0*",
|
||||
"*UNEXPECTED*ZeroDivision*",
|
||||
"*1 failed*",
|
||||
])
|
||||
|
||||
|
||||
def test_doctest_unex_importerror(self, testdir):
|
||||
testdir.tmpdir.join("hello.py").write(py.code.Source("""
|
||||
import asdalsdkjaslkdjasd
|
||||
@@ -124,3 +144,23 @@ class TestDoctests:
|
||||
" 1",
|
||||
"*test_txtfile_failing.txt:2: DocTestFailure"
|
||||
])
|
||||
|
||||
def test_txtfile_with_fixtures(self, testdir):
|
||||
p = testdir.maketxtfile("""
|
||||
>>> dir = getfixture('tmpdir')
|
||||
>>> type(dir).__name__
|
||||
'LocalPath'
|
||||
""")
|
||||
reprec = testdir.inline_run(p, )
|
||||
reprec.assertoutcome(passed=1)
|
||||
|
||||
def test_doctestmodule_with_fixtures(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
'''
|
||||
>>> dir = getfixture('tmpdir')
|
||||
>>> type(dir).__name__
|
||||
'LocalPath'
|
||||
'''
|
||||
""")
|
||||
reprec = testdir.inline_run(p, "--doctest-modules")
|
||||
reprec.assertoutcome(passed=1)
|
||||
|
||||
@@ -3,7 +3,8 @@ import subprocess
|
||||
|
||||
|
||||
def pytest_funcarg__standalone(request):
|
||||
return request.cached_setup(scope="module", setup=lambda: Standalone(request))
|
||||
return request.cached_setup(scope="module",
|
||||
setup=lambda: Standalone(request))
|
||||
|
||||
class Standalone:
|
||||
def __init__(self, request):
|
||||
|
||||
@@ -76,5 +76,6 @@ def test_PYTEST_DEBUG(testdir, monkeypatch):
|
||||
result = testdir.runpytest()
|
||||
assert result.ret == 0
|
||||
result.stderr.fnmatch_lines([
|
||||
"*registered*PluginManager*"
|
||||
"*pytest_plugin_registered*",
|
||||
"*manager*PluginManager*"
|
||||
])
|
||||
|
||||
@@ -282,12 +282,53 @@ class TestPython:
|
||||
if not sys.platform.startswith("java"):
|
||||
assert "hx" in fnode.toxml()
|
||||
|
||||
def test_pass_captures_stdout(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
def test_pass():
|
||||
print('hello-stdout')
|
||||
""")
|
||||
result, dom = runandparse(testdir)
|
||||
node = dom.getElementsByTagName("testsuite")[0]
|
||||
pnode = node.getElementsByTagName("testcase")[0]
|
||||
systemout = pnode.getElementsByTagName("system-out")[0]
|
||||
assert "hello-stdout" in systemout.toxml()
|
||||
|
||||
def test_pass_captures_stderr(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
import sys
|
||||
def test_pass():
|
||||
sys.stderr.write('hello-stderr')
|
||||
""")
|
||||
result, dom = runandparse(testdir)
|
||||
node = dom.getElementsByTagName("testsuite")[0]
|
||||
pnode = node.getElementsByTagName("testcase")[0]
|
||||
systemout = pnode.getElementsByTagName("system-err")[0]
|
||||
assert "hello-stderr" in systemout.toxml()
|
||||
|
||||
def test_mangle_testnames():
|
||||
from _pytest.junitxml import mangle_testnames
|
||||
names = ["a/pything.py", "Class", "()", "method"]
|
||||
newnames = mangle_testnames(names)
|
||||
assert newnames == ["a.pything", "Class", "method"]
|
||||
|
||||
def test_dont_configure_on_slaves(tmpdir):
|
||||
gotten = []
|
||||
class FakeConfig:
|
||||
def __init__(self):
|
||||
self.pluginmanager = self
|
||||
self.option = self
|
||||
junitprefix = None
|
||||
#XXX: shouldnt need tmpdir ?
|
||||
xmlpath = str(tmpdir.join('junix.xml'))
|
||||
register = gotten.append
|
||||
fake_config = FakeConfig()
|
||||
from _pytest import junitxml
|
||||
junitxml.pytest_configure(fake_config)
|
||||
assert len(gotten) == 1
|
||||
FakeConfig.slaveinput = None
|
||||
junitxml.pytest_configure(fake_config)
|
||||
assert len(gotten) == 1
|
||||
|
||||
|
||||
class TestNonPython:
|
||||
def test_summing_simple(self, testdir):
|
||||
|
||||
@@ -137,6 +137,48 @@ def test_mark_option(spec, testdir):
|
||||
assert len(passed) == len(passed_result)
|
||||
assert list(passed) == list(passed_result)
|
||||
|
||||
@pytest.mark.multi(spec=[
|
||||
("interface", ("test_interface",)),
|
||||
("not interface", ("test_nointer",)),
|
||||
])
|
||||
def test_mark_option_custom(spec, testdir):
|
||||
testdir.makeconftest("""
|
||||
import pytest
|
||||
def pytest_collection_modifyitems(items):
|
||||
for item in items:
|
||||
if "interface" in item.nodeid:
|
||||
item.keywords["interface"] = pytest.mark.interface
|
||||
""")
|
||||
testdir.makepyfile("""
|
||||
def test_interface():
|
||||
pass
|
||||
def test_nointer():
|
||||
pass
|
||||
""")
|
||||
opt, passed_result = spec
|
||||
rec = testdir.inline_run("-m", opt)
|
||||
passed, skipped, fail = rec.listoutcomes()
|
||||
passed = [x.nodeid.split("::")[-1] for x in passed]
|
||||
assert len(passed) == len(passed_result)
|
||||
assert list(passed) == list(passed_result)
|
||||
|
||||
@pytest.mark.multi(spec=[
|
||||
("interface", ("test_interface",)),
|
||||
("not interface", ("test_nointer",)),
|
||||
])
|
||||
def test_keyword_option_custom(spec, testdir):
|
||||
testdir.makepyfile("""
|
||||
def test_interface():
|
||||
pass
|
||||
def test_nointer():
|
||||
pass
|
||||
""")
|
||||
opt, passed_result = spec
|
||||
rec = testdir.inline_run("-k", opt)
|
||||
passed, skipped, fail = rec.listoutcomes()
|
||||
passed = [x.nodeid.split("::")[-1] for x in passed]
|
||||
assert len(passed) == len(passed_result)
|
||||
assert list(passed) == list(passed_result)
|
||||
|
||||
class TestFunctional:
|
||||
|
||||
@@ -237,6 +279,25 @@ class TestFunctional:
|
||||
assert l[1].args == ()
|
||||
assert l[2].args == ("pos1", )
|
||||
|
||||
@pytest.mark.xfail(reason='unfixed')
|
||||
def test_merging_markers_deep(self, testdir):
|
||||
# issue 199 - propagate markers into nested classes
|
||||
p = testdir.makepyfile("""
|
||||
import pytest
|
||||
class TestA:
|
||||
pytestmark = pytest.mark.a
|
||||
def test_b(self):
|
||||
assert True
|
||||
class TestC:
|
||||
# this one didnt get marked
|
||||
def test_d(self):
|
||||
assert True
|
||||
""")
|
||||
items, rec = testdir.inline_genitems(p)
|
||||
for item in items:
|
||||
print (item, item.keywords)
|
||||
assert 'a' in item.keywords
|
||||
|
||||
def test_mark_with_wrong_marker(self, testdir):
|
||||
reprec = testdir.inline_runsource("""
|
||||
import pytest
|
||||
@@ -323,11 +384,11 @@ class TestKeywordSelection:
|
||||
for keyword in ['test_one', 'est_on']:
|
||||
#yield check, keyword, 'test_one'
|
||||
check(keyword, 'test_one')
|
||||
check('TestClass.test', 'test_method_one')
|
||||
check('TestClass and test', 'test_method_one')
|
||||
|
||||
@pytest.mark.parametrize("keyword", [
|
||||
'xxx', 'xxx test_2', 'TestClass', 'xxx -test_1',
|
||||
'TestClass test_2', 'xxx TestClass test_2'])
|
||||
'xxx', 'xxx and test_2', 'TestClass', 'xxx and -test_1',
|
||||
'TestClass and test_2', 'xxx and TestClass and test_2'])
|
||||
def test_select_extra_keywords(self, testdir, keyword):
|
||||
p = testdir.makepyfile(test_select="""
|
||||
def test_1():
|
||||
@@ -367,7 +428,6 @@ class TestKeywordSelection:
|
||||
item = dlist[0].items[0]
|
||||
assert item.name == "test_one"
|
||||
|
||||
|
||||
def test_keyword_extra(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
def test_one():
|
||||
|
||||
@@ -35,6 +35,12 @@ def test_setup_func_with_setup_decorator():
|
||||
assert not l
|
||||
|
||||
|
||||
def test_setup_func_not_callable():
|
||||
from _pytest.nose import call_optional
|
||||
class A:
|
||||
f = 1
|
||||
call_optional(A(), "f")
|
||||
|
||||
def test_nose_setup_func(testdir):
|
||||
p = testdir.makepyfile("""
|
||||
from nose.tools import with_setup
|
||||
@@ -85,7 +91,7 @@ def test_nose_setup_func_failure(testdir):
|
||||
""")
|
||||
result = testdir.runpytest(p, '-p', 'nose')
|
||||
result.stdout.fnmatch_lines([
|
||||
"*TypeError: <lambda>() takes exactly 1*0 given*"
|
||||
"*TypeError: <lambda>()*"
|
||||
])
|
||||
|
||||
|
||||
@@ -97,21 +103,13 @@ def test_nose_setup_func_failure_2(testdir):
|
||||
my_teardown = 2
|
||||
|
||||
def test_hello():
|
||||
print (l)
|
||||
assert l == [1]
|
||||
|
||||
def test_world():
|
||||
print (l)
|
||||
assert l == [1,2]
|
||||
assert l == []
|
||||
|
||||
test_hello.setup = my_setup
|
||||
test_hello.teardown = my_teardown
|
||||
""")
|
||||
result = testdir.runpytest(p, '-p', 'nose')
|
||||
result.stdout.fnmatch_lines([
|
||||
"*TypeError: 'int' object is not callable*"
|
||||
])
|
||||
|
||||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(passed=1)
|
||||
|
||||
def test_nose_setup_partial(testdir):
|
||||
py.test.importorskip("functools")
|
||||
@@ -282,3 +280,53 @@ def test_nose_setup_ordering(testdir):
|
||||
result.stdout.fnmatch_lines([
|
||||
"*1 passed*",
|
||||
])
|
||||
|
||||
|
||||
def test_apiwrapper_problem_issue260(testdir):
|
||||
# this would end up trying a call a optional teardown on the class
|
||||
# for plain unittests we dont want nose behaviour
|
||||
testdir.makepyfile("""
|
||||
import unittest
|
||||
class TestCase(unittest.TestCase):
|
||||
def setup(self):
|
||||
#should not be called in unittest testcases
|
||||
assert 0, 'setup'
|
||||
def teardown(self):
|
||||
#should not be called in unittest testcases
|
||||
assert 0, 'teardown'
|
||||
def setUp(self):
|
||||
print('setup')
|
||||
def tearDown(self):
|
||||
print('teardown')
|
||||
def test_fun(self):
|
||||
pass
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines("*1 passed*")
|
||||
|
||||
@pytest.mark.skipif("sys.version_info < (2,6)")
|
||||
def test_setup_teardown_linking_issue265(testdir):
|
||||
# we accidentally didnt integrate nose setupstate with normal setupstate
|
||||
# this test ensures that won't happen again
|
||||
testdir.makepyfile('''
|
||||
import pytest
|
||||
|
||||
class TestGeneric(object):
|
||||
def test_nothing(self):
|
||||
"""Tests the API of the implementation (for generic and specialized)."""
|
||||
|
||||
@pytest.mark.skipif("True", reason=
|
||||
"Skip tests to check if teardown is skipped as well.")
|
||||
class TestSkipTeardown(TestGeneric):
|
||||
|
||||
def setup(self):
|
||||
"""Sets up my specialized implementation for $COOL_PLATFORM."""
|
||||
raise Exception("should not call setup for skipped tests")
|
||||
|
||||
def teardown(self):
|
||||
"""Undoes the setup."""
|
||||
raise Exception("should not call teardown for skipped tests")
|
||||
''')
|
||||
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines("*1 skipped*")
|
||||
|
||||
@@ -106,6 +106,22 @@ class TestPDB:
|
||||
if child.isalive():
|
||||
child.wait()
|
||||
|
||||
def test_pdb_and_capsys(self, testdir):
|
||||
p1 = testdir.makepyfile("""
|
||||
import pytest
|
||||
def test_1(capsys):
|
||||
print ("hello1")
|
||||
pytest.set_trace()
|
||||
""")
|
||||
child = testdir.spawn_pytest(str(p1))
|
||||
child.expect("test_1")
|
||||
child.send("capsys.readouterr()\n")
|
||||
child.expect("hello1")
|
||||
child.sendeof()
|
||||
rest = child.read()
|
||||
if child.isalive():
|
||||
child.wait()
|
||||
|
||||
def test_pdb_interaction_doctest(self, testdir):
|
||||
p1 = testdir.makepyfile("""
|
||||
import pytest
|
||||
|
||||
@@ -378,6 +378,10 @@ def test_runtest_in_module_ordering(testdir):
|
||||
])
|
||||
|
||||
|
||||
def test_outcomeexception_exceptionattributes():
|
||||
outcome = runner.OutcomeException('test')
|
||||
assert outcome.args[0] == outcome.msg
|
||||
|
||||
def test_pytest_exit():
|
||||
try:
|
||||
py.test.exit("hello")
|
||||
|
||||
@@ -39,6 +39,20 @@ class TestEvaluator:
|
||||
expl = ev.getexplanation()
|
||||
assert expl == "condition: hasattr(os, 'sep')"
|
||||
|
||||
@pytest.mark.skipif('sys.version_info[0] >= 3')
|
||||
def test_marked_one_arg_unicode(self, testdir):
|
||||
item = testdir.getitem("""
|
||||
import pytest
|
||||
@pytest.mark.xyz(u"hasattr(os, 'sep')")
|
||||
def test_func():
|
||||
pass
|
||||
""")
|
||||
ev = MarkEvaluator(item, 'xyz')
|
||||
assert ev
|
||||
assert ev.istrue()
|
||||
expl = ev.getexplanation()
|
||||
assert expl == "condition: hasattr(os, 'sep')"
|
||||
|
||||
def test_marked_one_arg_with_reason(self, testdir):
|
||||
item = testdir.getitem("""
|
||||
import pytest
|
||||
@@ -551,8 +565,8 @@ def test_direct_gives_error(testdir):
|
||||
def test_default_markers(testdir):
|
||||
result = testdir.runpytest("--markers")
|
||||
result.stdout.fnmatch_lines([
|
||||
"*skipif(*conditions)*skip*",
|
||||
"*xfail(*conditions, reason=None, run=True)*expected failure*",
|
||||
"*skipif(*condition)*skip*",
|
||||
"*xfail(*condition, reason=None, run=True)*expected failure*",
|
||||
])
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import py, pytest
|
||||
import os
|
||||
|
||||
from _pytest.tmpdir import pytest_funcarg__tmpdir, TempdirHandler
|
||||
from _pytest.tmpdir import tmpdir, TempdirHandler
|
||||
|
||||
def test_funcarg(testdir):
|
||||
testdir.makepyfile("""
|
||||
@@ -16,12 +16,13 @@ def test_funcarg(testdir):
|
||||
# pytest_unconfigure has deleted the TempdirHandler already
|
||||
config = item.config
|
||||
config._tmpdirhandler = TempdirHandler(config)
|
||||
p = pytest_funcarg__tmpdir(item)
|
||||
item._initrequest()
|
||||
p = tmpdir(item._request)
|
||||
assert p.check()
|
||||
bn = p.basename.strip("0123456789")
|
||||
assert bn.endswith("test_func_a_")
|
||||
item.name = "qwe/\\abc"
|
||||
p = pytest_funcarg__tmpdir(item)
|
||||
p = tmpdir(item._request)
|
||||
assert p.check()
|
||||
bn = p.basename.strip("0123456789")
|
||||
assert bn == "qwe__abc"
|
||||
|
||||
@@ -18,12 +18,21 @@ def test_runTest_method(testdir):
|
||||
testpath=testdir.makepyfile("""
|
||||
import unittest
|
||||
pytest_plugins = "pytest_unittest"
|
||||
class MyTestCase(unittest.TestCase):
|
||||
class MyTestCaseWithRunTest(unittest.TestCase):
|
||||
def runTest(self):
|
||||
self.assertEquals('foo', 'foo')
|
||||
class MyTestCaseWithoutRunTest(unittest.TestCase):
|
||||
def runTest(self):
|
||||
self.assertEquals('foo', 'foo')
|
||||
def test_something(self):
|
||||
pass
|
||||
""")
|
||||
reprec = testdir.inline_run(testpath)
|
||||
assert reprec.matchreport('runTest').passed
|
||||
result = testdir.runpytest("-v")
|
||||
result.stdout.fnmatch_lines("""
|
||||
*MyTestCaseWithRunTest.runTest*
|
||||
*MyTestCaseWithoutRunTest.test_something*
|
||||
*2 passed*
|
||||
""")
|
||||
|
||||
def test_isclasscheck_issue53(testdir):
|
||||
testpath = testdir.makepyfile("""
|
||||
@@ -87,6 +96,25 @@ def test_teardown(testdir):
|
||||
assert passed == 2
|
||||
assert passed + skipped + failed == 2
|
||||
|
||||
@pytest.mark.skipif("sys.version_info < (3,1)")
|
||||
def test_unittest_skip_issue148(testdir):
|
||||
testpath = testdir.makepyfile("""
|
||||
import unittest
|
||||
|
||||
@unittest.skip("hello")
|
||||
class MyTestCase(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(self):
|
||||
xxx
|
||||
def test_one(self):
|
||||
pass
|
||||
@classmethod
|
||||
def tearDownClass(self):
|
||||
xxx
|
||||
""")
|
||||
reprec = testdir.inline_run(testpath)
|
||||
reprec.assertoutcome(skipped=1)
|
||||
|
||||
def test_method_and_teardown_failing_reporting(testdir):
|
||||
testdir.makepyfile("""
|
||||
import unittest, pytest
|
||||
@@ -268,7 +296,27 @@ def test_testfunction_skip_property(testdir):
|
||||
|
||||
class TestTrialUnittest:
|
||||
def setup_class(cls):
|
||||
pytest.importorskip("twisted.trial.unittest")
|
||||
cls.ut = pytest.importorskip("twisted.trial.unittest")
|
||||
|
||||
def test_trial_testcase_runtest_not_collected(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
from twisted.trial.unittest import TestCase
|
||||
|
||||
class TC(TestCase):
|
||||
def test_hello(self):
|
||||
pass
|
||||
""")
|
||||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(passed=1)
|
||||
testdir.makepyfile("""
|
||||
from twisted.trial.unittest import TestCase
|
||||
|
||||
class TC(TestCase):
|
||||
def runTest(self):
|
||||
pass
|
||||
""")
|
||||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(passed=1)
|
||||
|
||||
def test_trial_exceptions_with_skips(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
@@ -313,7 +361,7 @@ class TestTrialUnittest:
|
||||
"*i2wanto*",
|
||||
"*sys.version_info*",
|
||||
"*skip_in_method*",
|
||||
"*5 skipped*3 xfail*1 xpass*",
|
||||
"*4 skipped*3 xfail*1 xpass*",
|
||||
])
|
||||
|
||||
def test_trial_error(self, testdir):
|
||||
|
||||
37
tox.ini
37
tox.ini
@@ -1,8 +1,8 @@
|
||||
[tox]
|
||||
distshare={homedir}/.tox/distshare
|
||||
envlist=py26,py27,py31,py32,py33,py27-xdist,py25,trial
|
||||
envlist=py25,py26,py27,py27-nobyte,py32,py33,py27-xdist,trial
|
||||
indexserver=
|
||||
pypi = http://pypi.python.org/simple
|
||||
pypi = https://pypi.python.org/simple
|
||||
testrun = http://pypi.testrun.org
|
||||
default = http://pypi.testrun.org
|
||||
|
||||
@@ -16,22 +16,31 @@ deps=
|
||||
[testenv:genscript]
|
||||
changedir=.
|
||||
commands= py.test --genscript=pytest1
|
||||
deps=py>=1.4.0
|
||||
|
||||
[testenv:py27-xdist]
|
||||
changedir=.
|
||||
basepython=python2.7
|
||||
deps=pytest-xdist
|
||||
:pypi:mock
|
||||
:pypi:nose
|
||||
commands=
|
||||
py.test -n3 -rfsxX \
|
||||
--ignore .tox --junitxml={envlogdir}/junit-{envname}.xml testing
|
||||
--junitxml={envlogdir}/junit-{envname}.xml testing
|
||||
|
||||
[testenv:py27-nobyte]
|
||||
changedir=.
|
||||
basepython=python2.7
|
||||
deps=pytest-xdist
|
||||
setenv=
|
||||
PYTHONDONTWRITEBYTECODE=1
|
||||
commands=
|
||||
py.test -n3 -rfsxX \
|
||||
--junitxml={envlogdir}/junit-{envname}.xml []
|
||||
|
||||
[testenv:trial]
|
||||
changedir=.
|
||||
basepython=python2.6
|
||||
deps=:pypi:twisted
|
||||
:pypi:pexpect
|
||||
py>=1.4.5.dev1
|
||||
commands=
|
||||
py.test -rsxf testing/test_unittest.py \
|
||||
--junitxml={envlogdir}/junit-{envname}.xml {posargs:testing/test_unittest.py}
|
||||
@@ -40,6 +49,13 @@ changedir=.
|
||||
commands=py.test --doctest-modules _pytest
|
||||
deps=
|
||||
|
||||
[testenv:py32]
|
||||
deps=
|
||||
:pypi:nose
|
||||
|
||||
[testenv:py33]
|
||||
deps=
|
||||
:pypi:nose
|
||||
|
||||
[testenv:doc]
|
||||
basepython=python
|
||||
@@ -58,6 +74,7 @@ deps=:pypi:sphinx
|
||||
:pypi:PyYAML
|
||||
commands=
|
||||
rm -rf /tmp/doc-exec*
|
||||
#pip install pytest==2.3.4
|
||||
make regen
|
||||
|
||||
[testenv:py31]
|
||||
@@ -69,12 +86,6 @@ commands=
|
||||
py.test -n3 -rfsxX \
|
||||
--junitxml={envlogdir}/junit-{envname}.xml []
|
||||
|
||||
[testenv:py32]
|
||||
deps=py>=1.4.0
|
||||
|
||||
[testenv:py33]
|
||||
deps=py>=1.4.0
|
||||
|
||||
[testenv:jython]
|
||||
changedir=testing
|
||||
commands=
|
||||
@@ -87,7 +98,7 @@ plugins=pytester
|
||||
#--pyargs --doctest-modules --ignore=.tox
|
||||
addopts= -rxs
|
||||
rsyncdirs=tox.ini pytest.py _pytest testing
|
||||
python_files=test_*.py *_test.py
|
||||
python_files=test_*.py *_test.py testing/*/*.py
|
||||
python_classes=Test Acceptance
|
||||
python_functions=test
|
||||
pep8ignore = E401 E225 E261 E128 E124 E302
|
||||
|
||||
Reference in New Issue
Block a user