commit
cd5a89bec9
1
.hgtags
1
.hgtags
|
@ -12,3 +12,4 @@
|
||||||
c63f35c266cbb26dad6b87b5e115d65685adf448 1.0.0b8
|
c63f35c266cbb26dad6b87b5e115d65685adf448 1.0.0b8
|
||||||
c63f35c266cbb26dad6b87b5e115d65685adf448 1.0.0b8
|
c63f35c266cbb26dad6b87b5e115d65685adf448 1.0.0b8
|
||||||
0eaa0fdf2ba0163cf534dc2eff4ba2e5fc66c261 1.0.0b8
|
0eaa0fdf2ba0163cf534dc2eff4ba2e5fc66c261 1.0.0b8
|
||||||
|
e2a60653cb490aeed81bbbd83c070b99401c211c 1.0.0b9
|
||||||
|
|
34
CHANGELOG
34
CHANGELOG
|
@ -1,3 +1,37 @@
|
||||||
|
Changes between 1.0.0b8 and 1.0.0b9
|
||||||
|
=====================================
|
||||||
|
|
||||||
|
* cleanly handle and report final teardown of test setup
|
||||||
|
|
||||||
|
* fix svn-1.6 compat issue with py.path.svnwc().versioned()
|
||||||
|
(thanks Wouter Vanden Hove)
|
||||||
|
|
||||||
|
* setup/teardown or collection problems now show as ERRORs
|
||||||
|
or with big "E"'s in the progress lines. they are reported
|
||||||
|
and counted separately.
|
||||||
|
|
||||||
|
* dist-testing: properly handle test items that get locally
|
||||||
|
collected but cannot be collected on the remote side - often
|
||||||
|
due to platform/dependency reasons
|
||||||
|
|
||||||
|
* simplified py.test.mark API - see keyword plugin documentation
|
||||||
|
|
||||||
|
* integrate better with logging: capturing now by default captures
|
||||||
|
test functions and their immediate setup/teardown in a single stream
|
||||||
|
|
||||||
|
* capsys and capfd funcargs now have a readouterr() and a close() method
|
||||||
|
(underlyingly py.io.StdCapture/FD objects are used which grew a
|
||||||
|
readouterr() method as well to return snapshots of captured out/err)
|
||||||
|
|
||||||
|
* make assert-reinterpretation work better with comparisons not
|
||||||
|
returning bools (reported with numpy from thanks maciej fijalkowski)
|
||||||
|
|
||||||
|
* reworked per-test output capturing into the pytest_iocapture.py plugin
|
||||||
|
and thus removed capturing code from config object
|
||||||
|
|
||||||
|
* item.repr_failure(excinfo) instead of item.repr_failure(excinfo, outerr)
|
||||||
|
|
||||||
|
|
||||||
Changes between 1.0.0b7 and 1.0.0b8
|
Changes between 1.0.0b7 and 1.0.0b8
|
||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
|
|
5
MANIFEST
5
MANIFEST
|
@ -29,7 +29,6 @@ doc/test/extend.txt
|
||||||
doc/test/features.txt
|
doc/test/features.txt
|
||||||
doc/test/funcargs.txt
|
doc/test/funcargs.txt
|
||||||
doc/test/plugin/doctest.txt
|
doc/test/plugin/doctest.txt
|
||||||
doc/test/plugin/execnetcleanup.txt
|
|
||||||
doc/test/plugin/figleaf.txt
|
doc/test/plugin/figleaf.txt
|
||||||
doc/test/plugin/hooklog.txt
|
doc/test/plugin/hooklog.txt
|
||||||
doc/test/plugin/hookspec.txt
|
doc/test/plugin/hookspec.txt
|
||||||
|
@ -37,13 +36,12 @@ doc/test/plugin/index.txt
|
||||||
doc/test/plugin/iocapture.txt
|
doc/test/plugin/iocapture.txt
|
||||||
doc/test/plugin/keyword.txt
|
doc/test/plugin/keyword.txt
|
||||||
doc/test/plugin/monkeypatch.txt
|
doc/test/plugin/monkeypatch.txt
|
||||||
|
doc/test/plugin/oejskit.txt
|
||||||
doc/test/plugin/pdb.txt
|
doc/test/plugin/pdb.txt
|
||||||
doc/test/plugin/pocoo.txt
|
doc/test/plugin/pocoo.txt
|
||||||
doc/test/plugin/pytester.txt
|
|
||||||
doc/test/plugin/recwarn.txt
|
doc/test/plugin/recwarn.txt
|
||||||
doc/test/plugin/restdoc.txt
|
doc/test/plugin/restdoc.txt
|
||||||
doc/test/plugin/resultlog.txt
|
doc/test/plugin/resultlog.txt
|
||||||
doc/test/plugin/runner.txt
|
|
||||||
doc/test/plugin/terminal.txt
|
doc/test/plugin/terminal.txt
|
||||||
doc/test/plugin/unittest.txt
|
doc/test/plugin/unittest.txt
|
||||||
doc/test/plugin/xfail.txt
|
doc/test/plugin/xfail.txt
|
||||||
|
@ -356,6 +354,7 @@ py/test/plugin/pytest_terminal.py
|
||||||
py/test/plugin/pytest_tmpdir.py
|
py/test/plugin/pytest_tmpdir.py
|
||||||
py/test/plugin/pytest_unittest.py
|
py/test/plugin/pytest_unittest.py
|
||||||
py/test/plugin/pytest_xfail.py
|
py/test/plugin/pytest_xfail.py
|
||||||
|
py/test/plugin/test_pytest_iocapture.py
|
||||||
py/test/plugin/test_pytest_runner.py
|
py/test/plugin/test_pytest_runner.py
|
||||||
py/test/plugin/test_pytest_runner_xunit.py
|
py/test/plugin/test_pytest_runner_xunit.py
|
||||||
py/test/plugin/test_pytest_terminal.py
|
py/test/plugin/test_pytest_terminal.py
|
||||||
|
|
|
@ -237,11 +237,15 @@ class/function names of a test function are put into the set
|
||||||
of keywords for a given test. You can specify additional
|
of keywords for a given test. You can specify additional
|
||||||
kewords like this::
|
kewords like this::
|
||||||
|
|
||||||
@py.test.mark(webtest=True)
|
@py.test.mark.webtest
|
||||||
def test_send_http():
|
def test_send_http():
|
||||||
...
|
...
|
||||||
|
|
||||||
and then use those keywords to select tests.
|
and then use those keywords to select tests. See the `pytest_keyword`_
|
||||||
|
plugin for more information.
|
||||||
|
|
||||||
|
.. _`pytest_keyword`: plugin/keyword.html
|
||||||
|
|
||||||
|
|
||||||
disabling a test class
|
disabling a test class
|
||||||
----------------------
|
----------------------
|
||||||
|
|
|
@ -37,8 +37,4 @@ Do you find the above documentation or the plugin itself lacking?
|
||||||
|
|
||||||
Further information: extend_ documentation, other plugins_ or contact_.
|
Further information: extend_ documentation, other plugins_ or contact_.
|
||||||
|
|
||||||
.. _`pytest_doctest.py`: http://bitbucket.org/hpk42/py-trunk/raw/85fe614ab05f301f206935d11a477df184cbbce6/py/test/plugin/pytest_doctest.py
|
.. include:: links.txt
|
||||||
.. _`extend`: ../extend.html
|
|
||||||
.. _`plugins`: index.html
|
|
||||||
.. _`contact`: ../../contact.html
|
|
||||||
.. _`checkout the py.test development version`: ../../download.html#checkout
|
|
||||||
|
|
|
@ -32,8 +32,4 @@ Do you find the above documentation or the plugin itself lacking?
|
||||||
|
|
||||||
Further information: extend_ documentation, other plugins_ or contact_.
|
Further information: extend_ documentation, other plugins_ or contact_.
|
||||||
|
|
||||||
.. _`pytest_figleaf.py`: http://bitbucket.org/hpk42/py-trunk/raw/85fe614ab05f301f206935d11a477df184cbbce6/py/test/plugin/pytest_figleaf.py
|
.. include:: links.txt
|
||||||
.. _`extend`: ../extend.html
|
|
||||||
.. _`plugins`: index.html
|
|
||||||
.. _`contact`: ../../contact.html
|
|
||||||
.. _`checkout the py.test development version`: ../../download.html#checkout
|
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
|
||||||
|
pytest_hooklog plugin
|
||||||
|
=====================
|
||||||
|
|
||||||
|
log invocations of extension hooks to a file.
|
||||||
|
|
||||||
|
.. contents::
|
||||||
|
:local:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
command line options
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
|
||||||
|
``--hooklog=HOOKLOG``
|
||||||
|
write hook calls to the given file.
|
||||||
|
|
||||||
|
Start improving this plugin in 30 seconds
|
||||||
|
=========================================
|
||||||
|
|
||||||
|
|
||||||
|
Do you find the above documentation or the plugin itself lacking?
|
||||||
|
|
||||||
|
1. Download `pytest_hooklog.py`_ plugin source code
|
||||||
|
2. put it somewhere as ``pytest_hooklog.py`` into your import path
|
||||||
|
3. a subsequent ``py.test`` run will use your local version
|
||||||
|
|
||||||
|
Further information: extend_ documentation, other plugins_ or contact_.
|
||||||
|
|
||||||
|
.. include:: links.txt
|
|
@ -92,6 +92,14 @@ hook specification sourcecode
|
||||||
def pytest_runtest_logreport(rep):
|
def pytest_runtest_logreport(rep):
|
||||||
""" process item test report. """
|
""" process item test report. """
|
||||||
|
|
||||||
|
# special handling for final teardown - somewhat internal for now
|
||||||
|
def pytest__teardown_final(session):
|
||||||
|
""" called before test session finishes. """
|
||||||
|
pytest__teardown_final.firstresult = True
|
||||||
|
|
||||||
|
def pytest__teardown_final_logerror(rep):
|
||||||
|
""" called if runtest_teardown_final failed. """
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# test session related hooks
|
# test session related hooks
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
@ -163,3 +171,4 @@ hook specification sourcecode
|
||||||
def pytest_trace(category, msg):
|
def pytest_trace(category, msg):
|
||||||
""" called for debug info. """
|
""" called for debug info. """
|
||||||
|
|
||||||
|
.. include:: links.txt
|
||||||
|
|
|
@ -8,7 +8,7 @@ figleaf_ write and report coverage data with 'figleaf'.
|
||||||
|
|
||||||
monkeypatch_ safely patch object attributes, dicts and environment variables.
|
monkeypatch_ safely patch object attributes, dicts and environment variables.
|
||||||
|
|
||||||
iocapture_ convenient capturing of writes to stdout/stderror streams and file descriptors.
|
iocapture_ configurable per-test stdout/stderr capturing mechanisms.
|
||||||
|
|
||||||
recwarn_ helpers for asserting deprecation and other warnings.
|
recwarn_ helpers for asserting deprecation and other warnings.
|
||||||
|
|
||||||
|
@ -35,15 +35,14 @@ resultlog_ resultlog plugin for machine-readable logging of test results.
|
||||||
terminal_ Implements terminal reporting of the full testing process.
|
terminal_ Implements terminal reporting of the full testing process.
|
||||||
|
|
||||||
|
|
||||||
.. _`xfail`: xfail.html
|
internal plugins / core functionality
|
||||||
.. _`figleaf`: figleaf.html
|
=====================================
|
||||||
.. _`monkeypatch`: monkeypatch.html
|
|
||||||
.. _`iocapture`: iocapture.html
|
pdb_ interactive debugging with the Python Debugger.
|
||||||
.. _`recwarn`: recwarn.html
|
|
||||||
.. _`unittest`: unittest.html
|
keyword_ mark test functions with keywords that may hold values.
|
||||||
.. _`doctest`: doctest.html
|
|
||||||
.. _`oejskit`: oejskit.html
|
hooklog_ log invocations of extension hooks to a file.
|
||||||
.. _`restdoc`: restdoc.html
|
|
||||||
.. _`pocoo`: pocoo.html
|
|
||||||
.. _`resultlog`: resultlog.html
|
.. include:: links.txt
|
||||||
.. _`terminal`: terminal.html
|
|
||||||
|
|
|
@ -2,34 +2,90 @@
|
||||||
pytest_iocapture plugin
|
pytest_iocapture plugin
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
convenient capturing of writes to stdout/stderror streams and file descriptors.
|
configurable per-test stdout/stderr capturing mechanisms.
|
||||||
|
|
||||||
.. contents::
|
.. contents::
|
||||||
:local:
|
:local:
|
||||||
|
|
||||||
Example Usage
|
This plugin captures stdout/stderr output for each test separately.
|
||||||
----------------------
|
In case of test failures this captured output is shown grouped
|
||||||
|
togtther with the test.
|
||||||
|
|
||||||
You can use the `capsys funcarg`_ to capture writes
|
The plugin also provides test function arguments that help to
|
||||||
to stdout and stderr streams by using it in a test
|
assert stdout/stderr output from within your tests, see the
|
||||||
likes this:
|
`funcarg example`_.
|
||||||
|
|
||||||
|
|
||||||
|
Capturing of input/output streams during tests
|
||||||
|
---------------------------------------------------
|
||||||
|
|
||||||
|
By default ``sys.stdout`` and ``sys.stderr`` are substituted with
|
||||||
|
temporary streams during the execution of tests and setup/teardown code.
|
||||||
|
During the whole testing process it will re-use the same temporary
|
||||||
|
streams allowing to play well with the logging module which easily
|
||||||
|
takes ownership on these streams.
|
||||||
|
|
||||||
|
Also, 'sys.stdin' is substituted with a file-like "null" object that
|
||||||
|
does not return any values. This is to immediately error out
|
||||||
|
on tests that wait on reading something from stdin.
|
||||||
|
|
||||||
|
You can influence output capturing mechanisms from the command line::
|
||||||
|
|
||||||
|
py.test -s # disable all capturing
|
||||||
|
py.test --capture=sys # set StringIO() to each of sys.stdout/stderr
|
||||||
|
py.test --capture=fd # capture stdout/stderr on Filedescriptors 1/2
|
||||||
|
|
||||||
|
If you set capturing values in a conftest file like this::
|
||||||
|
|
||||||
|
# conftest.py
|
||||||
|
conf_capture = 'fd'
|
||||||
|
|
||||||
|
then all tests in that directory will execute with "fd" style capturing.
|
||||||
|
|
||||||
|
sys-level capturing
|
||||||
|
------------------------------------------
|
||||||
|
|
||||||
|
Capturing on 'sys' level means that ``sys.stdout`` and ``sys.stderr``
|
||||||
|
will be replaced with StringIO() objects.
|
||||||
|
|
||||||
|
FD-level capturing and subprocesses
|
||||||
|
------------------------------------------
|
||||||
|
|
||||||
|
The ``fd`` based method means that writes going to system level files
|
||||||
|
based on the standard file descriptors will be captured, for example
|
||||||
|
writes such as ``os.write(1, 'hello')`` will be captured properly.
|
||||||
|
Capturing on fd-level will include output generated from
|
||||||
|
any subprocesses created during a test.
|
||||||
|
|
||||||
|
.. _`funcarg example`:
|
||||||
|
|
||||||
|
Example Usage of the capturing Function arguments
|
||||||
|
---------------------------------------------------
|
||||||
|
|
||||||
|
You can use the `capsys funcarg`_ and `capfd funcarg`_ to
|
||||||
|
capture writes to stdout and stderr streams. Using the
|
||||||
|
funcargs frees your test from having to care about setting/resetting
|
||||||
|
the old streams and also interacts well with py.test's own
|
||||||
|
per-test capturing. Here is an example test function:
|
||||||
|
|
||||||
.. sourcecode:: python
|
.. sourcecode:: python
|
||||||
|
|
||||||
def test_myoutput(capsys):
|
def test_myoutput(capsys):
|
||||||
print "hello"
|
print "hello"
|
||||||
print >>sys.stderr, "world"
|
print >>sys.stderr, "world"
|
||||||
out, err = capsys.reset()
|
out, err = capsys.readouterr()
|
||||||
assert out == "hello\n"
|
assert out == "hello\n"
|
||||||
assert err == "world\n"
|
assert err == "world\n"
|
||||||
print "next"
|
print "next"
|
||||||
out, err = capsys.reset()
|
out, err = capsys.readouterr()
|
||||||
assert out == "next\n"
|
assert out == "next\n"
|
||||||
|
|
||||||
The ``reset()`` call returns a tuple and will restart
|
The ``readouterr()`` call snapshots the output so far -
|
||||||
capturing so that you can successively check for output.
|
and capturing will be continued. After the test
|
||||||
After the test function finishes the original streams
|
function finishes the original streams will
|
||||||
will be restored.
|
be restored. If you want to capture on
|
||||||
|
the filedescriptor level you can use the ``capfd`` function
|
||||||
|
argument which offers the same interface.
|
||||||
|
|
||||||
.. _`capsys funcarg`:
|
.. _`capsys funcarg`:
|
||||||
|
|
||||||
|
@ -38,8 +94,8 @@ the 'capsys' test function argument
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
|
|
||||||
captures writes to sys.stdout/sys.stderr and makes
|
captures writes to sys.stdout/sys.stderr and makes
|
||||||
them available successively via a ``capsys.reset()`` method
|
them available successively via a ``capsys.readouterr()`` method
|
||||||
which returns a ``(out, err)`` tuple of captured strings.
|
which returns a ``(out, err)`` tuple of captured snapshot strings.
|
||||||
|
|
||||||
.. _`capfd funcarg`:
|
.. _`capfd funcarg`:
|
||||||
|
|
||||||
|
@ -48,8 +104,17 @@ the 'capfd' test function argument
|
||||||
----------------------------------
|
----------------------------------
|
||||||
|
|
||||||
captures writes to file descriptors 1 and 2 and makes
|
captures writes to file descriptors 1 and 2 and makes
|
||||||
them available successively via a ``capsys.reset()`` method
|
snapshotted ``(out, err)`` string tuples available
|
||||||
which returns a ``(out, err)`` tuple of captured strings.
|
via the ``capsys.readouterr()`` method.
|
||||||
|
|
||||||
|
command line options
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
|
||||||
|
``-s``
|
||||||
|
shortcut for --capture=no.
|
||||||
|
``--capture=capture``
|
||||||
|
set IO capturing method during tests: sys|fd|no.
|
||||||
|
|
||||||
Start improving this plugin in 30 seconds
|
Start improving this plugin in 30 seconds
|
||||||
=========================================
|
=========================================
|
||||||
|
@ -63,8 +128,4 @@ Do you find the above documentation or the plugin itself lacking?
|
||||||
|
|
||||||
Further information: extend_ documentation, other plugins_ or contact_.
|
Further information: extend_ documentation, other plugins_ or contact_.
|
||||||
|
|
||||||
.. _`pytest_iocapture.py`: http://bitbucket.org/hpk42/py-trunk/raw/85fe614ab05f301f206935d11a477df184cbbce6/py/test/plugin/pytest_iocapture.py
|
.. include:: links.txt
|
||||||
.. _`extend`: ../extend.html
|
|
||||||
.. _`plugins`: index.html
|
|
||||||
.. _`contact`: ../../contact.html
|
|
||||||
.. _`checkout the py.test development version`: ../../download.html#checkout
|
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
|
||||||
|
pytest_keyword plugin
|
||||||
|
=====================
|
||||||
|
|
||||||
|
mark test functions with keywords that may hold values.
|
||||||
|
|
||||||
|
.. contents::
|
||||||
|
:local:
|
||||||
|
|
||||||
|
Marking functions and setting rich attributes
|
||||||
|
----------------------------------------------------
|
||||||
|
|
||||||
|
By default, all filename parts and class/function names of a test
|
||||||
|
function are put into the set of keywords for a given test. You can
|
||||||
|
specify additional kewords like this::
|
||||||
|
|
||||||
|
@py.test.mark.webtest
|
||||||
|
def test_send_http():
|
||||||
|
...
|
||||||
|
|
||||||
|
This will set an attribute 'webtest' on the given test function
|
||||||
|
and by default all such attributes signal keywords. You can
|
||||||
|
also set values in this attribute which you could read from
|
||||||
|
a hook in order to do something special with respect to
|
||||||
|
the test function::
|
||||||
|
|
||||||
|
@py.test.mark.timeout(seconds=5)
|
||||||
|
def test_receive():
|
||||||
|
...
|
||||||
|
|
||||||
|
This will set the "timeout" attribute with a Marker object
|
||||||
|
that has a 'seconds' attribute.
|
||||||
|
|
||||||
|
Start improving this plugin in 30 seconds
|
||||||
|
=========================================
|
||||||
|
|
||||||
|
|
||||||
|
Do you find the above documentation or the plugin itself lacking?
|
||||||
|
|
||||||
|
1. Download `pytest_keyword.py`_ plugin source code
|
||||||
|
2. put it somewhere as ``pytest_keyword.py`` into your import path
|
||||||
|
3. a subsequent ``py.test`` run will use your local version
|
||||||
|
|
||||||
|
Further information: extend_ documentation, other plugins_ or contact_.
|
||||||
|
|
||||||
|
.. include:: links.txt
|
|
@ -0,0 +1,33 @@
|
||||||
|
.. _`pytest_recwarn.py`: http://bitbucket.org/hpk42/py-trunk/raw/69bd12627e4d304c89c2003842703ccb10dfe838/py/test/plugin/pytest_recwarn.py
|
||||||
|
.. _`pytest_iocapture.py`: http://bitbucket.org/hpk42/py-trunk/raw/69bd12627e4d304c89c2003842703ccb10dfe838/py/test/plugin/pytest_iocapture.py
|
||||||
|
.. _`pytest_monkeypatch.py`: http://bitbucket.org/hpk42/py-trunk/raw/69bd12627e4d304c89c2003842703ccb10dfe838/py/test/plugin/pytest_monkeypatch.py
|
||||||
|
.. _`plugins`: index.html
|
||||||
|
.. _`pytest_doctest.py`: http://bitbucket.org/hpk42/py-trunk/raw/69bd12627e4d304c89c2003842703ccb10dfe838/py/test/plugin/pytest_doctest.py
|
||||||
|
.. _`terminal`: terminal.html
|
||||||
|
.. _`hooklog`: hooklog.html
|
||||||
|
.. _`pytest_restdoc.py`: http://bitbucket.org/hpk42/py-trunk/raw/69bd12627e4d304c89c2003842703ccb10dfe838/py/test/plugin/pytest_restdoc.py
|
||||||
|
.. _`xfail`: xfail.html
|
||||||
|
.. _`pytest_pocoo.py`: http://bitbucket.org/hpk42/py-trunk/raw/69bd12627e4d304c89c2003842703ccb10dfe838/py/test/plugin/pytest_pocoo.py
|
||||||
|
.. _`pytest_keyword.py`: http://bitbucket.org/hpk42/py-trunk/raw/69bd12627e4d304c89c2003842703ccb10dfe838/py/test/plugin/pytest_keyword.py
|
||||||
|
.. _`pytest_figleaf.py`: http://bitbucket.org/hpk42/py-trunk/raw/69bd12627e4d304c89c2003842703ccb10dfe838/py/test/plugin/pytest_figleaf.py
|
||||||
|
.. _`pytest_hooklog.py`: http://bitbucket.org/hpk42/py-trunk/raw/69bd12627e4d304c89c2003842703ccb10dfe838/py/test/plugin/pytest_hooklog.py
|
||||||
|
.. _`contact`: ../../contact.html
|
||||||
|
.. _`pocoo`: pocoo.html
|
||||||
|
.. _`checkout the py.test development version`: ../../download.html#checkout
|
||||||
|
.. _`oejskit`: oejskit.html
|
||||||
|
.. _`unittest`: unittest.html
|
||||||
|
.. _`iocapture`: iocapture.html
|
||||||
|
.. _`pytest_xfail.py`: http://bitbucket.org/hpk42/py-trunk/raw/69bd12627e4d304c89c2003842703ccb10dfe838/py/test/plugin/pytest_xfail.py
|
||||||
|
.. _`figleaf`: figleaf.html
|
||||||
|
.. _`extend`: ../extend.html
|
||||||
|
.. _`pytest_terminal.py`: http://bitbucket.org/hpk42/py-trunk/raw/69bd12627e4d304c89c2003842703ccb10dfe838/py/test/plugin/pytest_terminal.py
|
||||||
|
.. _`recwarn`: recwarn.html
|
||||||
|
.. _`pytest_pdb.py`: http://bitbucket.org/hpk42/py-trunk/raw/69bd12627e4d304c89c2003842703ccb10dfe838/py/test/plugin/pytest_pdb.py
|
||||||
|
.. _`monkeypatch`: monkeypatch.html
|
||||||
|
.. _`resultlog`: resultlog.html
|
||||||
|
.. _`keyword`: keyword.html
|
||||||
|
.. _`restdoc`: restdoc.html
|
||||||
|
.. _`pytest_unittest.py`: http://bitbucket.org/hpk42/py-trunk/raw/69bd12627e4d304c89c2003842703ccb10dfe838/py/test/plugin/pytest_unittest.py
|
||||||
|
.. _`doctest`: doctest.html
|
||||||
|
.. _`pytest_resultlog.py`: http://bitbucket.org/hpk42/py-trunk/raw/69bd12627e4d304c89c2003842703ccb10dfe838/py/test/plugin/pytest_resultlog.py
|
||||||
|
.. _`pdb`: pdb.html
|
|
@ -58,8 +58,4 @@ Do you find the above documentation or the plugin itself lacking?
|
||||||
|
|
||||||
Further information: extend_ documentation, other plugins_ or contact_.
|
Further information: extend_ documentation, other plugins_ or contact_.
|
||||||
|
|
||||||
.. _`pytest_monkeypatch.py`: http://bitbucket.org/hpk42/py-trunk/raw/85fe614ab05f301f206935d11a477df184cbbce6/py/test/plugin/pytest_monkeypatch.py
|
.. include:: links.txt
|
||||||
.. _`extend`: ../extend.html
|
|
||||||
.. _`plugins`: index.html
|
|
||||||
.. _`contact`: ../../contact.html
|
|
||||||
.. _`checkout the py.test development version`: ../../download.html#checkout
|
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
|
||||||
|
pytest_pdb plugin
|
||||||
|
=================
|
||||||
|
|
||||||
|
interactive debugging with the Python Debugger.
|
||||||
|
|
||||||
|
.. contents::
|
||||||
|
:local:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
command line options
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
|
||||||
|
``--pdb``
|
||||||
|
start pdb (the Python debugger) on errors.
|
||||||
|
|
||||||
|
Start improving this plugin in 30 seconds
|
||||||
|
=========================================
|
||||||
|
|
||||||
|
|
||||||
|
Do you find the above documentation or the plugin itself lacking?
|
||||||
|
|
||||||
|
1. Download `pytest_pdb.py`_ plugin source code
|
||||||
|
2. put it somewhere as ``pytest_pdb.py`` into your import path
|
||||||
|
3. a subsequent ``py.test`` run will use your local version
|
||||||
|
|
||||||
|
Further information: extend_ documentation, other plugins_ or contact_.
|
||||||
|
|
||||||
|
.. include:: links.txt
|
|
@ -28,8 +28,4 @@ Do you find the above documentation or the plugin itself lacking?
|
||||||
|
|
||||||
Further information: extend_ documentation, other plugins_ or contact_.
|
Further information: extend_ documentation, other plugins_ or contact_.
|
||||||
|
|
||||||
.. _`pytest_pocoo.py`: http://bitbucket.org/hpk42/py-trunk/raw/85fe614ab05f301f206935d11a477df184cbbce6/py/test/plugin/pytest_pocoo.py
|
.. include:: links.txt
|
||||||
.. _`extend`: ../extend.html
|
|
||||||
.. _`plugins`: index.html
|
|
||||||
.. _`contact`: ../../contact.html
|
|
||||||
.. _`checkout the py.test development version`: ../../download.html#checkout
|
|
||||||
|
|
|
@ -58,8 +58,4 @@ Do you find the above documentation or the plugin itself lacking?
|
||||||
|
|
||||||
Further information: extend_ documentation, other plugins_ or contact_.
|
Further information: extend_ documentation, other plugins_ or contact_.
|
||||||
|
|
||||||
.. _`pytest_recwarn.py`: http://bitbucket.org/hpk42/py-trunk/raw/85fe614ab05f301f206935d11a477df184cbbce6/py/test/plugin/pytest_recwarn.py
|
.. include:: links.txt
|
||||||
.. _`extend`: ../extend.html
|
|
||||||
.. _`plugins`: index.html
|
|
||||||
.. _`contact`: ../../contact.html
|
|
||||||
.. _`checkout the py.test development version`: ../../download.html#checkout
|
|
||||||
|
|
|
@ -32,8 +32,4 @@ Do you find the above documentation or the plugin itself lacking?
|
||||||
|
|
||||||
Further information: extend_ documentation, other plugins_ or contact_.
|
Further information: extend_ documentation, other plugins_ or contact_.
|
||||||
|
|
||||||
.. _`pytest_restdoc.py`: http://bitbucket.org/hpk42/py-trunk/raw/85fe614ab05f301f206935d11a477df184cbbce6/py/test/plugin/pytest_restdoc.py
|
.. include:: links.txt
|
||||||
.. _`extend`: ../extend.html
|
|
||||||
.. _`plugins`: index.html
|
|
||||||
.. _`contact`: ../../contact.html
|
|
||||||
.. _`checkout the py.test development version`: ../../download.html#checkout
|
|
||||||
|
|
|
@ -28,8 +28,4 @@ Do you find the above documentation or the plugin itself lacking?
|
||||||
|
|
||||||
Further information: extend_ documentation, other plugins_ or contact_.
|
Further information: extend_ documentation, other plugins_ or contact_.
|
||||||
|
|
||||||
.. _`pytest_resultlog.py`: http://bitbucket.org/hpk42/py-trunk/raw/85fe614ab05f301f206935d11a477df184cbbce6/py/test/plugin/pytest_resultlog.py
|
.. include:: links.txt
|
||||||
.. _`extend`: ../extend.html
|
|
||||||
.. _`plugins`: index.html
|
|
||||||
.. _`contact`: ../../contact.html
|
|
||||||
.. _`checkout the py.test development version`: ../../download.html#checkout
|
|
||||||
|
|
|
@ -21,8 +21,4 @@ Do you find the above documentation or the plugin itself lacking?
|
||||||
|
|
||||||
Further information: extend_ documentation, other plugins_ or contact_.
|
Further information: extend_ documentation, other plugins_ or contact_.
|
||||||
|
|
||||||
.. _`pytest_terminal.py`: http://bitbucket.org/hpk42/py-trunk/raw/85fe614ab05f301f206935d11a477df184cbbce6/py/test/plugin/pytest_terminal.py
|
.. include:: links.txt
|
||||||
.. _`extend`: ../extend.html
|
|
||||||
.. _`plugins`: index.html
|
|
||||||
.. _`contact`: ../../contact.html
|
|
||||||
.. _`checkout the py.test development version`: ../../download.html#checkout
|
|
||||||
|
|
|
@ -31,8 +31,4 @@ Do you find the above documentation or the plugin itself lacking?
|
||||||
|
|
||||||
Further information: extend_ documentation, other plugins_ or contact_.
|
Further information: extend_ documentation, other plugins_ or contact_.
|
||||||
|
|
||||||
.. _`pytest_unittest.py`: http://bitbucket.org/hpk42/py-trunk/raw/85fe614ab05f301f206935d11a477df184cbbce6/py/test/plugin/pytest_unittest.py
|
.. include:: links.txt
|
||||||
.. _`extend`: ../extend.html
|
|
||||||
.. _`plugins`: index.html
|
|
||||||
.. _`contact`: ../../contact.html
|
|
||||||
.. _`checkout the py.test development version`: ../../download.html#checkout
|
|
||||||
|
|
|
@ -33,8 +33,4 @@ Do you find the above documentation or the plugin itself lacking?
|
||||||
|
|
||||||
Further information: extend_ documentation, other plugins_ or contact_.
|
Further information: extend_ documentation, other plugins_ or contact_.
|
||||||
|
|
||||||
.. _`pytest_xfail.py`: http://bitbucket.org/hpk42/py-trunk/raw/85fe614ab05f301f206935d11a477df184cbbce6/py/test/plugin/pytest_xfail.py
|
.. include:: links.txt
|
||||||
.. _`extend`: ../extend.html
|
|
||||||
.. _`plugins`: index.html
|
|
||||||
.. _`contact`: ../../contact.html
|
|
||||||
.. _`checkout the py.test development version`: ../../download.html#checkout
|
|
||||||
|
|
|
@ -10,6 +10,8 @@ plugins = [
|
||||||
'unittest doctest oejskit restdoc'),
|
'unittest doctest oejskit restdoc'),
|
||||||
('Plugins for generic reporting and failure logging',
|
('Plugins for generic reporting and failure logging',
|
||||||
'pocoo resultlog terminal',),
|
'pocoo resultlog terminal',),
|
||||||
|
('internal plugins / core functionality',
|
||||||
|
'pdb keyword hooklog')
|
||||||
#('internal plugins / core functionality',
|
#('internal plugins / core functionality',
|
||||||
# #'pdb keyword hooklog runner execnetcleanup # pytester',
|
# #'pdb keyword hooklog runner execnetcleanup # pytester',
|
||||||
# 'pdb keyword hooklog runner execnetcleanup' # pytester',
|
# 'pdb keyword hooklog runner execnetcleanup' # pytester',
|
||||||
|
@ -26,6 +28,8 @@ def warn(*args):
|
||||||
print >>sys.stderr, "WARN:", msg
|
print >>sys.stderr, "WARN:", msg
|
||||||
|
|
||||||
class RestWriter:
|
class RestWriter:
|
||||||
|
_all_links = {}
|
||||||
|
|
||||||
def __init__(self, target):
|
def __init__(self, target):
|
||||||
self.target = py.path.local(target)
|
self.target = py.path.local(target)
|
||||||
self.links = []
|
self.links = []
|
||||||
|
@ -90,9 +94,23 @@ class RestWriter:
|
||||||
|
|
||||||
def write_links(self):
|
def write_links(self):
|
||||||
self.Print()
|
self.Print()
|
||||||
|
self.Print(".. include:: links.txt")
|
||||||
for link in self.links:
|
for link in self.links:
|
||||||
#warn(repr(self.link))
|
key = link[0]
|
||||||
self.Print(".. _`%s`: %s" % (link[0], link[1]))
|
if key in self._all_links:
|
||||||
|
assert self._all_links[key] == link[1], (key, link[1])
|
||||||
|
else:
|
||||||
|
self._all_links[key] = link[1]
|
||||||
|
|
||||||
|
def write_all_links(cls, linkpath):
|
||||||
|
p = linkpath.new(basename="links.txt")
|
||||||
|
p_writer = RestWriter(p)
|
||||||
|
p_writer.out = p_writer.target.open("w")
|
||||||
|
for name, value in cls._all_links.items():
|
||||||
|
p_writer.Print(".. _`%s`: %s" % (name, value))
|
||||||
|
p_writer.out.close()
|
||||||
|
del p_writer.out
|
||||||
|
write_all_links = classmethod(write_all_links)
|
||||||
|
|
||||||
def make(self, **kwargs):
|
def make(self, **kwargs):
|
||||||
self.out = self.target.open("w")
|
self.out = self.target.open("w")
|
||||||
|
@ -264,3 +282,5 @@ if __name__ == "__main__":
|
||||||
ov = HookSpec(testdir.join("plugin", "hookspec.txt"))
|
ov = HookSpec(testdir.join("plugin", "hookspec.txt"))
|
||||||
ov.make(config=_config)
|
ov.make(config=_config)
|
||||||
|
|
||||||
|
RestWriter.write_all_links(testdir.join("plugin", "links.txt"))
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ For questions please check out http://pylib.org/contact.html
|
||||||
from initpkg import initpkg
|
from initpkg import initpkg
|
||||||
trunk = None
|
trunk = None
|
||||||
|
|
||||||
version = trunk or "1.0.0b8"
|
version = trunk or "1.0.0b9"
|
||||||
|
|
||||||
initpkg(__name__,
|
initpkg(__name__,
|
||||||
description = "py.test and pylib: advanced testing tool and networking lib",
|
description = "py.test and pylib: advanced testing tool and networking lib",
|
||||||
|
|
|
@ -43,3 +43,13 @@ def getsocketspec(config=None):
|
||||||
if spec.socket:
|
if spec.socket:
|
||||||
return spec
|
return spec
|
||||||
py.test.skip("need '--gx socket=...'")
|
py.test.skip("need '--gx socket=...'")
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_generate_tests(metafunc):
|
||||||
|
multi = getattr(metafunc.function, 'multi', None)
|
||||||
|
if multi is None:
|
||||||
|
return
|
||||||
|
assert len(multi.__dict__) == 1
|
||||||
|
for name, l in multi.__dict__.items():
|
||||||
|
for val in l:
|
||||||
|
metafunc.addcall(funcargs={name: val})
|
||||||
|
|
|
@ -22,21 +22,52 @@ class Capture(object):
|
||||||
call = classmethod(call)
|
call = classmethod(call)
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
""" reset sys.stdout and sys.stderr and return captured output
|
""" reset sys.stdout/stderr and return captured output as strings. """
|
||||||
as strings and restore sys.stdout/err.
|
if hasattr(self, '_suspended'):
|
||||||
"""
|
outfile = self._kwargs['out']
|
||||||
x, y = self.done()
|
errfile = self._kwargs['err']
|
||||||
outerr = x.read(), y.read()
|
del self._kwargs
|
||||||
x.close()
|
else:
|
||||||
y.close()
|
outfile, errfile = self.done()
|
||||||
|
out, err = "", ""
|
||||||
|
if outfile:
|
||||||
|
out = outfile.read()
|
||||||
|
outfile.close()
|
||||||
|
if errfile and errfile != outfile:
|
||||||
|
err = errfile.read()
|
||||||
|
errfile.close()
|
||||||
|
return out, err
|
||||||
|
|
||||||
|
def suspend(self):
|
||||||
|
""" return current snapshot captures, memorize tempfiles. """
|
||||||
|
assert not hasattr(self, '_suspended')
|
||||||
|
self._suspended = True
|
||||||
|
outerr = self.readouterr()
|
||||||
|
outfile, errfile = self.done()
|
||||||
|
self._kwargs['out'] = outfile
|
||||||
|
self._kwargs['err'] = errfile
|
||||||
return outerr
|
return outerr
|
||||||
|
|
||||||
|
def resume(self):
|
||||||
|
""" resume capturing with original temp files. """
|
||||||
|
assert self._suspended
|
||||||
|
self._initialize(**self._kwargs)
|
||||||
|
del self._suspended
|
||||||
|
|
||||||
|
|
||||||
class StdCaptureFD(Capture):
|
class StdCaptureFD(Capture):
|
||||||
""" This class allows to capture writes to FD1 and FD2
|
""" This class allows to capture writes to FD1 and FD2
|
||||||
and may connect a NULL file to FD0 (and prevent
|
and may connect a NULL file to FD0 (and prevent
|
||||||
reads from sys.stdin)
|
reads from sys.stdin)
|
||||||
"""
|
"""
|
||||||
def __init__(self, out=True, err=True, mixed=False, in_=True, patchsys=True):
|
def __init__(self, out=True, err=True,
|
||||||
|
mixed=False, in_=True, patchsys=True):
|
||||||
|
self._kwargs = locals().copy()
|
||||||
|
del self._kwargs['self']
|
||||||
|
self._initialize(**self._kwargs)
|
||||||
|
|
||||||
|
def _initialize(self, out=True, err=True,
|
||||||
|
mixed=False, in_=True, patchsys=True):
|
||||||
if in_:
|
if in_:
|
||||||
self._oldin = (sys.stdin, os.dup(0))
|
self._oldin = (sys.stdin, os.dup(0))
|
||||||
sys.stdin = DontReadFromInput()
|
sys.stdin = DontReadFromInput()
|
||||||
|
@ -44,12 +75,17 @@ class StdCaptureFD(Capture):
|
||||||
os.dup2(fd, 0)
|
os.dup2(fd, 0)
|
||||||
os.close(fd)
|
os.close(fd)
|
||||||
if out:
|
if out:
|
||||||
self.out = py.io.FDCapture(1)
|
tmpfile = None
|
||||||
|
if isinstance(out, file):
|
||||||
|
tmpfile = out
|
||||||
|
self.out = py.io.FDCapture(1, tmpfile=tmpfile)
|
||||||
if patchsys:
|
if patchsys:
|
||||||
self.out.setasfile('stdout')
|
self.out.setasfile('stdout')
|
||||||
if err:
|
if err:
|
||||||
if mixed and out:
|
if mixed and out:
|
||||||
tmpfile = self.out.tmpfile
|
tmpfile = self.out.tmpfile
|
||||||
|
elif isinstance(err, file):
|
||||||
|
tmpfile = err
|
||||||
else:
|
else:
|
||||||
tmpfile = None
|
tmpfile = None
|
||||||
self.err = py.io.FDCapture(2, tmpfile=tmpfile)
|
self.err = py.io.FDCapture(2, tmpfile=tmpfile)
|
||||||
|
@ -61,11 +97,11 @@ class StdCaptureFD(Capture):
|
||||||
if hasattr(self, 'out'):
|
if hasattr(self, 'out'):
|
||||||
outfile = self.out.done()
|
outfile = self.out.done()
|
||||||
else:
|
else:
|
||||||
outfile = StringIO()
|
outfile = None
|
||||||
if hasattr(self, 'err'):
|
if hasattr(self, 'err'):
|
||||||
errfile = self.err.done()
|
errfile = self.err.done()
|
||||||
else:
|
else:
|
||||||
errfile = StringIO()
|
errfile = None
|
||||||
if hasattr(self, '_oldin'):
|
if hasattr(self, '_oldin'):
|
||||||
oldsys, oldfd = self._oldin
|
oldsys, oldfd = self._oldin
|
||||||
os.dup2(oldfd, 0)
|
os.dup2(oldfd, 0)
|
||||||
|
@ -73,6 +109,20 @@ class StdCaptureFD(Capture):
|
||||||
sys.stdin = oldsys
|
sys.stdin = oldsys
|
||||||
return outfile, errfile
|
return outfile, errfile
|
||||||
|
|
||||||
|
def readouterr(self):
|
||||||
|
""" return snapshot value of stdout/stderr capturings. """
|
||||||
|
l = []
|
||||||
|
for name in ('out', 'err'):
|
||||||
|
res = ""
|
||||||
|
if hasattr(self, name):
|
||||||
|
f = getattr(self, name).tmpfile
|
||||||
|
f.seek(0)
|
||||||
|
res = f.read()
|
||||||
|
f.truncate(0)
|
||||||
|
f.seek(0)
|
||||||
|
l.append(res)
|
||||||
|
return l
|
||||||
|
|
||||||
class StdCapture(Capture):
|
class StdCapture(Capture):
|
||||||
""" This class allows to capture writes to sys.stdout|stderr "in-memory"
|
""" This class allows to capture writes to sys.stdout|stderr "in-memory"
|
||||||
and will raise errors on tries to read from sys.stdin. It only
|
and will raise errors on tries to read from sys.stdin. It only
|
||||||
|
@ -80,21 +130,28 @@ class StdCapture(Capture):
|
||||||
touch underlying File Descriptors (use StdCaptureFD for that).
|
touch underlying File Descriptors (use StdCaptureFD for that).
|
||||||
"""
|
"""
|
||||||
def __init__(self, out=True, err=True, in_=True, mixed=False):
|
def __init__(self, out=True, err=True, in_=True, mixed=False):
|
||||||
|
self._kwargs = locals().copy()
|
||||||
|
del self._kwargs['self']
|
||||||
|
self._initialize(**self._kwargs)
|
||||||
|
|
||||||
|
def _initialize(self, out, err, in_, mixed):
|
||||||
self._out = out
|
self._out = out
|
||||||
self._err = err
|
self._err = err
|
||||||
self._in = in_
|
self._in = in_
|
||||||
if out:
|
if out:
|
||||||
self.oldout = sys.stdout
|
self._oldout = sys.stdout
|
||||||
sys.stdout = self.newout = StringIO()
|
if not hasattr(out, 'write'):
|
||||||
|
out = StringIO()
|
||||||
|
sys.stdout = self.out = out
|
||||||
if err:
|
if err:
|
||||||
self.olderr = sys.stderr
|
self._olderr = sys.stderr
|
||||||
if out and mixed:
|
if out and mixed:
|
||||||
newerr = self.newout
|
err = self.out
|
||||||
else:
|
elif not hasattr(err, 'write'):
|
||||||
newerr = StringIO()
|
err = StringIO()
|
||||||
sys.stderr = self.newerr = newerr
|
sys.stderr = self.err = err
|
||||||
if in_:
|
if in_:
|
||||||
self.oldin = sys.stdin
|
self._oldin = sys.stdin
|
||||||
sys.stdin = self.newin = DontReadFromInput()
|
sys.stdin = self.newin = DontReadFromInput()
|
||||||
|
|
||||||
def done(self):
|
def done(self):
|
||||||
|
@ -102,28 +159,39 @@ class StdCapture(Capture):
|
||||||
o,e = sys.stdout, sys.stderr
|
o,e = sys.stdout, sys.stderr
|
||||||
if self._out:
|
if self._out:
|
||||||
try:
|
try:
|
||||||
sys.stdout = self.oldout
|
sys.stdout = self._oldout
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise IOError("stdout capturing already reset")
|
raise IOError("stdout capturing already reset")
|
||||||
del self.oldout
|
del self._oldout
|
||||||
outfile = self.newout
|
outfile = self.out
|
||||||
outfile.seek(0)
|
outfile.seek(0)
|
||||||
else:
|
else:
|
||||||
outfile = StringIO()
|
outfile = None
|
||||||
if self._err:
|
if self._err:
|
||||||
try:
|
try:
|
||||||
sys.stderr = self.olderr
|
sys.stderr = self._olderr
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise IOError("stderr capturing already reset")
|
raise IOError("stderr capturing already reset")
|
||||||
del self.olderr
|
del self._olderr
|
||||||
errfile = self.newerr
|
errfile = self.err
|
||||||
errfile.seek(0)
|
errfile.seek(0)
|
||||||
else:
|
else:
|
||||||
errfile = StringIO()
|
errfile = None
|
||||||
if self._in:
|
if self._in:
|
||||||
sys.stdin = self.oldin
|
sys.stdin = self._oldin
|
||||||
return outfile, errfile
|
return outfile, errfile
|
||||||
|
|
||||||
|
def readouterr(self):
|
||||||
|
""" return snapshot value of stdout/stderr capturings. """
|
||||||
|
out = err = ""
|
||||||
|
if self._out:
|
||||||
|
out = sys.stdout.getvalue()
|
||||||
|
sys.stdout.truncate(0)
|
||||||
|
if self._err:
|
||||||
|
err = sys.stderr.getvalue()
|
||||||
|
sys.stderr.truncate(0)
|
||||||
|
return out, err
|
||||||
|
|
||||||
class DontReadFromInput:
|
class DontReadFromInput:
|
||||||
"""Temporary stub class. Ideally when stdin is accessed, the
|
"""Temporary stub class. Ideally when stdin is accessed, the
|
||||||
capturing should be turned off, with possibly all data captured
|
capturing should be turned off, with possibly all data captured
|
||||||
|
|
|
@ -30,6 +30,19 @@ class TestStdCapture:
|
||||||
assert out == "hello world\n"
|
assert out == "hello world\n"
|
||||||
assert err == "hello error\n"
|
assert err == "hello error\n"
|
||||||
|
|
||||||
|
def test_capturing_readouterr(self):
|
||||||
|
cap = self.getcapture()
|
||||||
|
try:
|
||||||
|
print "hello world"
|
||||||
|
print >>sys.stderr, "hello error"
|
||||||
|
out, err = cap.readouterr()
|
||||||
|
assert out == "hello world\n"
|
||||||
|
assert err == "hello error\n"
|
||||||
|
print >>sys.stderr, "error2"
|
||||||
|
finally:
|
||||||
|
out, err = cap.reset()
|
||||||
|
assert err == "error2\n"
|
||||||
|
|
||||||
def test_capturing_mixed(self):
|
def test_capturing_mixed(self):
|
||||||
cap = self.getcapture(mixed=True)
|
cap = self.getcapture(mixed=True)
|
||||||
print "hello",
|
print "hello",
|
||||||
|
@ -43,7 +56,7 @@ class TestStdCapture:
|
||||||
cap = self.getcapture()
|
cap = self.getcapture()
|
||||||
print "hello"
|
print "hello"
|
||||||
cap.reset()
|
cap.reset()
|
||||||
py.test.raises(EnvironmentError, "cap.reset()")
|
py.test.raises(Exception, "cap.reset()")
|
||||||
|
|
||||||
def test_capturing_modify_sysouterr_in_between(self):
|
def test_capturing_modify_sysouterr_in_between(self):
|
||||||
oldout = sys.stdout
|
oldout = sys.stdout
|
||||||
|
@ -67,7 +80,7 @@ class TestStdCapture:
|
||||||
cap2 = self.getcapture()
|
cap2 = self.getcapture()
|
||||||
print "cap2"
|
print "cap2"
|
||||||
out2, err2 = cap2.reset()
|
out2, err2 = cap2.reset()
|
||||||
py.test.raises(EnvironmentError, "cap2.reset()")
|
py.test.raises(Exception, "cap2.reset()")
|
||||||
out1, err1 = cap1.reset()
|
out1, err1 = cap1.reset()
|
||||||
assert out1 == "cap1\n"
|
assert out1 == "cap1\n"
|
||||||
assert out2 == "cap2\n"
|
assert out2 == "cap2\n"
|
||||||
|
@ -104,6 +117,24 @@ class TestStdCapture:
|
||||||
py.test.raises(IOError, "sys.stdin.read()")
|
py.test.raises(IOError, "sys.stdin.read()")
|
||||||
out, err = cap.reset()
|
out, err = cap.reset()
|
||||||
|
|
||||||
|
def test_suspend_resume(self):
|
||||||
|
cap = self.getcapture(out=True, err=False, in_=False)
|
||||||
|
try:
|
||||||
|
print "hello"
|
||||||
|
sys.stderr.write("error\n")
|
||||||
|
out, err = cap.suspend()
|
||||||
|
assert out == "hello\n"
|
||||||
|
assert not err
|
||||||
|
print "in between"
|
||||||
|
sys.stderr.write("in between\n")
|
||||||
|
cap.resume()
|
||||||
|
print "after"
|
||||||
|
sys.stderr.write("error_after\n")
|
||||||
|
finally:
|
||||||
|
out, err = cap.reset()
|
||||||
|
assert out == "after\n"
|
||||||
|
assert not err
|
||||||
|
|
||||||
class TestStdCaptureFD(TestStdCapture):
|
class TestStdCaptureFD(TestStdCapture):
|
||||||
def getcapture(self, **kw):
|
def getcapture(self, **kw):
|
||||||
return py.io.StdCaptureFD(**kw)
|
return py.io.StdCaptureFD(**kw)
|
||||||
|
@ -150,10 +181,14 @@ def test_callcapture_nofd():
|
||||||
os.write(1, "hello")
|
os.write(1, "hello")
|
||||||
os.write(2, "hello")
|
os.write(2, "hello")
|
||||||
print x
|
print x
|
||||||
print >>py.std.sys.stderr, y
|
print >>sys.stderr, y
|
||||||
return 42
|
return 42
|
||||||
|
|
||||||
res, out, err = py.io.StdCapture.call(func, 3, y=4)
|
capfd = py.io.StdCaptureFD(patchsys=False)
|
||||||
|
try:
|
||||||
|
res, out, err = py.io.StdCapture.call(func, 3, y=4)
|
||||||
|
finally:
|
||||||
|
capfd.reset()
|
||||||
assert res == 42
|
assert res == 42
|
||||||
assert out.startswith("3")
|
assert out.startswith("3")
|
||||||
assert err.startswith("4")
|
assert err.startswith("4")
|
||||||
|
|
|
@ -122,6 +122,10 @@ class Compare(Interpretable):
|
||||||
expr = Interpretable(self.expr)
|
expr = Interpretable(self.expr)
|
||||||
expr.eval(frame)
|
expr.eval(frame)
|
||||||
for operation, expr2 in self.ops:
|
for operation, expr2 in self.ops:
|
||||||
|
if hasattr(self, 'result'):
|
||||||
|
# shortcutting in chained expressions
|
||||||
|
if not frame.is_true(self.result):
|
||||||
|
break
|
||||||
expr2 = Interpretable(expr2)
|
expr2 = Interpretable(expr2)
|
||||||
expr2.eval(frame)
|
expr2.eval(frame)
|
||||||
self.explanation = "%s %s %s" % (
|
self.explanation = "%s %s %s" % (
|
||||||
|
@ -135,8 +139,6 @@ class Compare(Interpretable):
|
||||||
raise
|
raise
|
||||||
except:
|
except:
|
||||||
raise Failure(self)
|
raise Failure(self)
|
||||||
if not frame.is_true(self.result):
|
|
||||||
break
|
|
||||||
expr = expr2
|
expr = expr2
|
||||||
|
|
||||||
class And(Interpretable):
|
class And(Interpretable):
|
||||||
|
|
|
@ -131,3 +131,26 @@ def test_inconsistent_assert_result(testdir):
|
||||||
s = result.stdout.str()
|
s = result.stdout.str()
|
||||||
assert s.find("re-run") != -1
|
assert s.find("re-run") != -1
|
||||||
|
|
||||||
|
def test_twoarg_comparison_does_not_call_nonzero():
|
||||||
|
# this arises e.g. in numpy array comparisons
|
||||||
|
class X(object):
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __nonzero__(self):
|
||||||
|
raise ValueError
|
||||||
|
|
||||||
|
def all(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def f():
|
||||||
|
a = X()
|
||||||
|
b = X()
|
||||||
|
assert (a == b).all()
|
||||||
|
|
||||||
|
excinfo = getexcinfo(AssertionError, f)
|
||||||
|
msg = getmsg(excinfo)
|
||||||
|
print msg
|
||||||
|
assert "re-run" not in msg
|
||||||
|
assert "ValueError" not in msg
|
||||||
|
|
||||||
|
|
|
@ -454,6 +454,8 @@ recursively. """
|
||||||
except py.process.cmdexec.Error, e:
|
except py.process.cmdexec.Error, e:
|
||||||
if e.err.find('is not a working copy')!=-1:
|
if e.err.find('is not a working copy')!=-1:
|
||||||
return False
|
return False
|
||||||
|
if e.err.lower().find('not a versioned resource') != -1:
|
||||||
|
return False
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -11,12 +11,12 @@ def test_waitfinish_removes_tempdir():
|
||||||
ff.waitfinish()
|
ff.waitfinish()
|
||||||
assert not ff.tempdir.check()
|
assert not ff.tempdir.check()
|
||||||
|
|
||||||
def test_tempdir_gets_gc_collected():
|
def test_tempdir_gets_gc_collected(monkeypatch):
|
||||||
|
monkeypatch.setattr(os, 'fork', lambda: os.getpid())
|
||||||
ff = py.process.ForkedFunc(boxf1)
|
ff = py.process.ForkedFunc(boxf1)
|
||||||
assert ff.tempdir.check()
|
assert ff.tempdir.check()
|
||||||
ff.__del__()
|
ff.__del__()
|
||||||
assert not ff.tempdir.check()
|
assert not ff.tempdir.check()
|
||||||
os.waitpid(ff.pid, 0)
|
|
||||||
|
|
||||||
def test_basic_forkedfunc():
|
def test_basic_forkedfunc():
|
||||||
result = py.process.ForkedFunc(boxf1).waitfinish()
|
result = py.process.ForkedFunc(boxf1).waitfinish()
|
||||||
|
|
|
@ -4,7 +4,6 @@ Collectors and test Items form a tree
|
||||||
that is usually built iteratively.
|
that is usually built iteratively.
|
||||||
"""
|
"""
|
||||||
import py
|
import py
|
||||||
from py.__.test.outcome import Skipped
|
|
||||||
|
|
||||||
def configproperty(name):
|
def configproperty(name):
|
||||||
def fget(self):
|
def fget(self):
|
||||||
|
@ -31,6 +30,10 @@ class Node(object):
|
||||||
self.config = getattr(parent, 'config', None)
|
self.config = getattr(parent, 'config', None)
|
||||||
self.fspath = getattr(parent, 'fspath', None)
|
self.fspath = getattr(parent, 'fspath', None)
|
||||||
|
|
||||||
|
def _checkcollectable(self):
|
||||||
|
if not hasattr(self, 'fspath'):
|
||||||
|
self.parent._memocollect() # to reraise exception
|
||||||
|
|
||||||
#
|
#
|
||||||
# note to myself: Pickling is uh.
|
# note to myself: Pickling is uh.
|
||||||
#
|
#
|
||||||
|
@ -44,6 +47,7 @@ class Node(object):
|
||||||
except Exception:
|
except Exception:
|
||||||
# seems our parent can't collect us
|
# seems our parent can't collect us
|
||||||
# so let's be somewhat operable
|
# so let's be somewhat operable
|
||||||
|
# _checkcollectable() is to tell outsiders about the fact
|
||||||
self.name = name
|
self.name = name
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.config = parent.config
|
self.config = parent.config
|
||||||
|
@ -247,7 +251,8 @@ class Node(object):
|
||||||
return col._getitembynames(names)
|
return col._getitembynames(names)
|
||||||
_fromtrail = staticmethod(_fromtrail)
|
_fromtrail = staticmethod(_fromtrail)
|
||||||
|
|
||||||
def _repr_failure_py(self, excinfo, outerr):
|
def _repr_failure_py(self, excinfo, outerr=None):
|
||||||
|
assert outerr is None, "XXX deprecated"
|
||||||
excinfo.traceback = self._prunetraceback(excinfo.traceback)
|
excinfo.traceback = self._prunetraceback(excinfo.traceback)
|
||||||
# XXX temporary hack: getrepr() should not take a 'style' argument
|
# XXX temporary hack: getrepr() should not take a 'style' argument
|
||||||
# at all; it should record all data in all cases, and the style
|
# at all; it should record all data in all cases, and the style
|
||||||
|
@ -256,13 +261,9 @@ class Node(object):
|
||||||
style = "short"
|
style = "short"
|
||||||
else:
|
else:
|
||||||
style = "long"
|
style = "long"
|
||||||
repr = excinfo.getrepr(funcargs=True,
|
return excinfo.getrepr(funcargs=True,
|
||||||
showlocals=self.config.option.showlocals,
|
showlocals=self.config.option.showlocals,
|
||||||
style=style)
|
style=style)
|
||||||
for secname, content in zip(["out", "err"], outerr):
|
|
||||||
if content:
|
|
||||||
repr.addsection("Captured std%s" % secname, content.rstrip())
|
|
||||||
return repr
|
|
||||||
|
|
||||||
repr_failure = _repr_failure_py
|
repr_failure = _repr_failure_py
|
||||||
shortfailurerepr = "F"
|
shortfailurerepr = "F"
|
||||||
|
@ -291,9 +292,10 @@ class Collector(Node):
|
||||||
if colitem.name == name:
|
if colitem.name == name:
|
||||||
return colitem
|
return colitem
|
||||||
|
|
||||||
def repr_failure(self, excinfo, outerr):
|
def repr_failure(self, excinfo, outerr=None):
|
||||||
""" represent a failure. """
|
""" represent a failure. """
|
||||||
return self._repr_failure_py(excinfo, outerr)
|
assert outerr is None, "XXX deprecated"
|
||||||
|
return self._repr_failure_py(excinfo)
|
||||||
|
|
||||||
def _memocollect(self):
|
def _memocollect(self):
|
||||||
""" internal helper method to cache results of calling collect(). """
|
""" internal helper method to cache results of calling collect(). """
|
||||||
|
|
|
@ -240,20 +240,6 @@ class Config(object):
|
||||||
finally:
|
finally:
|
||||||
config_per_process = py.test.config = oldconfig
|
config_per_process = py.test.config = oldconfig
|
||||||
|
|
||||||
def _getcapture(self, path=None):
|
|
||||||
if self.option.nocapture:
|
|
||||||
iocapture = "no"
|
|
||||||
else:
|
|
||||||
iocapture = self.getvalue("iocapture", path=path)
|
|
||||||
if iocapture == "fd":
|
|
||||||
return py.io.StdCaptureFD()
|
|
||||||
elif iocapture == "sys":
|
|
||||||
return py.io.StdCapture()
|
|
||||||
elif iocapture == "no":
|
|
||||||
return py.io.StdCapture(out=False, err=False, in_=False)
|
|
||||||
else:
|
|
||||||
raise self.Error("unknown io capturing: " + iocapture)
|
|
||||||
|
|
||||||
def getxspecs(self):
|
def getxspecs(self):
|
||||||
xspeclist = []
|
xspeclist = []
|
||||||
for xspec in self.getvalue("tx"):
|
for xspec in self.getvalue("tx"):
|
||||||
|
@ -287,29 +273,6 @@ class Config(object):
|
||||||
roots.append(pydir)
|
roots.append(pydir)
|
||||||
return roots
|
return roots
|
||||||
|
|
||||||
def guardedcall(self, func):
|
|
||||||
excinfo = result = None
|
|
||||||
capture = self._getcapture()
|
|
||||||
try:
|
|
||||||
try:
|
|
||||||
result = func()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
raise
|
|
||||||
except:
|
|
||||||
excinfo = py.code.ExceptionInfo()
|
|
||||||
finally:
|
|
||||||
stdout, stderr = capture.reset()
|
|
||||||
return CallResult(result, excinfo, stdout, stderr)
|
|
||||||
|
|
||||||
class CallResult:
|
|
||||||
def __init__(self, result, excinfo, stdout, stderr):
|
|
||||||
self.stdout = stdout
|
|
||||||
self.stderr = stderr
|
|
||||||
self.outerr = (self.stdout, self.stderr)
|
|
||||||
self.excinfo = excinfo
|
|
||||||
if excinfo is None:
|
|
||||||
self.result = result
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# helpers
|
# helpers
|
||||||
#
|
#
|
||||||
|
|
|
@ -10,5 +10,6 @@ Generator = py.test.collect.Generator
|
||||||
Function = py.test.collect.Function
|
Function = py.test.collect.Function
|
||||||
Instance = py.test.collect.Instance
|
Instance = py.test.collect.Instance
|
||||||
|
|
||||||
pytest_plugins = "default runner terminal keyword xfail tmpdir execnetcleanup monkeypatch recwarn pdb unittest".split()
|
pytest_plugins = "default runner iocapture terminal keyword xfail tmpdir execnetcleanup monkeypatch recwarn pdb unittest".split()
|
||||||
|
|
||||||
|
conf_capture = "fd"
|
||||||
|
|
|
@ -11,6 +11,13 @@ from py.__.test.dist.nodemanage import NodeManager
|
||||||
|
|
||||||
import Queue
|
import Queue
|
||||||
|
|
||||||
|
debug_file = None # open('/tmp/loop.log', 'w')
|
||||||
|
def debug(*args):
|
||||||
|
if debug_file is not None:
|
||||||
|
s = " ".join(map(str, args))
|
||||||
|
debug_file.write(s+"\n")
|
||||||
|
debug_file.flush()
|
||||||
|
|
||||||
class LoopState(object):
|
class LoopState(object):
|
||||||
def __init__(self, dsession, colitems):
|
def __init__(self, dsession, colitems):
|
||||||
self.dsession = dsession
|
self.dsession = dsession
|
||||||
|
@ -23,9 +30,14 @@ class LoopState(object):
|
||||||
self.shuttingdown = False
|
self.shuttingdown = False
|
||||||
self.testsfailed = False
|
self.testsfailed = False
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<LoopState exitstatus=%r shuttingdown=%r len(colitems)=%d>" % (
|
||||||
|
self.exitstatus, self.shuttingdown, len(self.colitems))
|
||||||
|
|
||||||
def pytest_runtest_logreport(self, rep):
|
def pytest_runtest_logreport(self, rep):
|
||||||
if rep.item in self.dsession.item2nodes:
|
if rep.item in self.dsession.item2nodes:
|
||||||
self.dsession.removeitem(rep.item, rep.node)
|
if rep.when != "teardown": # otherwise we have already managed it
|
||||||
|
self.dsession.removeitem(rep.item, rep.node)
|
||||||
if rep.failed:
|
if rep.failed:
|
||||||
self.testsfailed = True
|
self.testsfailed = True
|
||||||
|
|
||||||
|
@ -39,9 +51,14 @@ class LoopState(object):
|
||||||
def pytest_testnodedown(self, node, error=None):
|
def pytest_testnodedown(self, node, error=None):
|
||||||
pending = self.dsession.removenode(node)
|
pending = self.dsession.removenode(node)
|
||||||
if pending:
|
if pending:
|
||||||
crashitem = pending[0]
|
if error:
|
||||||
self.dsession.handle_crashitem(crashitem, node)
|
crashitem = pending[0]
|
||||||
self.colitems.extend(pending[1:])
|
debug("determined crashitem", crashitem)
|
||||||
|
self.dsession.handle_crashitem(crashitem, node)
|
||||||
|
# XXX recovery handling for "each"?
|
||||||
|
# currently pending items are not retried
|
||||||
|
if self.dsession.config.option.dist == "load":
|
||||||
|
self.colitems.extend(pending[1:])
|
||||||
|
|
||||||
def pytest_rescheduleitems(self, items):
|
def pytest_rescheduleitems(self, items):
|
||||||
self.colitems.extend(items)
|
self.colitems.extend(items)
|
||||||
|
@ -115,6 +132,9 @@ class DSession(Session):
|
||||||
if eventname == "pytest_testnodedown":
|
if eventname == "pytest_testnodedown":
|
||||||
self.config.hook.pytest_testnodedown(**kwargs)
|
self.config.hook.pytest_testnodedown(**kwargs)
|
||||||
self.removenode(kwargs['node'])
|
self.removenode(kwargs['node'])
|
||||||
|
elif eventname == "pytest_runtest_logreport":
|
||||||
|
# might be some teardown report
|
||||||
|
self.config.hook.pytest_runtest_logreport(**kwargs)
|
||||||
if not self.node2pending:
|
if not self.node2pending:
|
||||||
# finished
|
# finished
|
||||||
if loopstate.testsfailed:
|
if loopstate.testsfailed:
|
||||||
|
@ -200,7 +220,9 @@ class DSession(Session):
|
||||||
node.sendlist(sending)
|
node.sendlist(sending)
|
||||||
pending.extend(sending)
|
pending.extend(sending)
|
||||||
for item in sending:
|
for item in sending:
|
||||||
self.item2nodes.setdefault(item, []).append(node)
|
nodes = self.item2nodes.setdefault(item, [])
|
||||||
|
assert node not in nodes
|
||||||
|
nodes.append(node)
|
||||||
self.config.hook.pytest_itemstart(item=item, node=node)
|
self.config.hook.pytest_itemstart(item=item, node=node)
|
||||||
tosend[:] = tosend[room:] # update inplace
|
tosend[:] = tosend[room:] # update inplace
|
||||||
if tosend:
|
if tosend:
|
||||||
|
@ -237,7 +259,8 @@ class DSession(Session):
|
||||||
nodes.remove(node)
|
nodes.remove(node)
|
||||||
if not nodes:
|
if not nodes:
|
||||||
del self.item2nodes[item]
|
del self.item2nodes[item]
|
||||||
self.node2pending[node].remove(item)
|
pending = self.node2pending[node]
|
||||||
|
pending.remove(item)
|
||||||
|
|
||||||
def handle_crashitem(self, item, node):
|
def handle_crashitem(self, item, node):
|
||||||
runner = item.config.pluginmanager.getplugin("runner")
|
runner = item.config.pluginmanager.getplugin("runner")
|
||||||
|
|
|
@ -69,7 +69,8 @@ class ImmutablePickler:
|
||||||
pickler = MyPickler(f, self._protocol, uneven=self.uneven)
|
pickler = MyPickler(f, self._protocol, uneven=self.uneven)
|
||||||
pickler.memo = self._picklememo
|
pickler.memo = self._picklememo
|
||||||
pickler.dump(obj)
|
pickler.dump(obj)
|
||||||
self._updateunpicklememo()
|
if obj is not None:
|
||||||
|
self._updateunpicklememo()
|
||||||
#print >>debug, "dumped", obj
|
#print >>debug, "dumped", obj
|
||||||
#print >>debug, "picklememo", self._picklememo
|
#print >>debug, "picklememo", self._picklememo
|
||||||
return f.getvalue()
|
return f.getvalue()
|
||||||
|
|
|
@ -7,7 +7,7 @@ XSpec = py.execnet.XSpec
|
||||||
def run(item, node, excinfo=None):
|
def run(item, node, excinfo=None):
|
||||||
runner = item.config.pluginmanager.getplugin("runner")
|
runner = item.config.pluginmanager.getplugin("runner")
|
||||||
rep = runner.ItemTestReport(item=item,
|
rep = runner.ItemTestReport(item=item,
|
||||||
excinfo=excinfo, when="call", outerr=("", ""))
|
excinfo=excinfo, when="call")
|
||||||
rep.node = node
|
rep.node = node
|
||||||
return rep
|
return rep
|
||||||
|
|
||||||
|
@ -155,6 +155,45 @@ class TestDSession:
|
||||||
dumpqueue(session.queue)
|
dumpqueue(session.queue)
|
||||||
assert loopstate.exitstatus == outcome.EXIT_NOHOSTS
|
assert loopstate.exitstatus == outcome.EXIT_NOHOSTS
|
||||||
|
|
||||||
|
def test_removeitem_from_failing_teardown(self, testdir):
|
||||||
|
# teardown reports only come in when they signal a failure
|
||||||
|
# internal session-management should basically ignore them
|
||||||
|
# XXX probably it'S best to invent a new error hook for
|
||||||
|
# teardown/setup related failures
|
||||||
|
modcol = testdir.getmodulecol("""
|
||||||
|
def test_one():
|
||||||
|
pass
|
||||||
|
def teardown_function(function):
|
||||||
|
assert 0
|
||||||
|
""")
|
||||||
|
item1, = modcol.collect()
|
||||||
|
|
||||||
|
# setup a session with two nodes
|
||||||
|
session = DSession(item1.config)
|
||||||
|
node1, node2 = MockNode(), MockNode()
|
||||||
|
session.addnode(node1)
|
||||||
|
session.addnode(node2)
|
||||||
|
|
||||||
|
# have one test pending for a node that goes down
|
||||||
|
session.senditems_each([item1])
|
||||||
|
nodes = session.item2nodes[item1]
|
||||||
|
class rep:
|
||||||
|
failed = True
|
||||||
|
item = item1
|
||||||
|
node = nodes[0]
|
||||||
|
when = "call"
|
||||||
|
session.queueevent("pytest_runtest_logreport", rep=rep)
|
||||||
|
reprec = testdir.getreportrecorder(session)
|
||||||
|
print session.item2nodes
|
||||||
|
loopstate = session._initloopstate([])
|
||||||
|
assert len(session.item2nodes[item1]) == 2
|
||||||
|
session.loop_once(loopstate)
|
||||||
|
assert len(session.item2nodes[item1]) == 1
|
||||||
|
rep.when = "teardown"
|
||||||
|
session.queueevent("pytest_runtest_logreport", rep=rep)
|
||||||
|
session.loop_once(loopstate)
|
||||||
|
assert len(session.item2nodes[item1]) == 1
|
||||||
|
|
||||||
def test_testnodedown_causes_reschedule_pending(self, testdir):
|
def test_testnodedown_causes_reschedule_pending(self, testdir):
|
||||||
modcol = testdir.getmodulecol("""
|
modcol = testdir.getmodulecol("""
|
||||||
def test_crash():
|
def test_crash():
|
||||||
|
@ -173,7 +212,8 @@ class TestDSession:
|
||||||
# have one test pending for a node that goes down
|
# have one test pending for a node that goes down
|
||||||
session.senditems_load([item1, item2])
|
session.senditems_load([item1, item2])
|
||||||
node = session.item2nodes[item1] [0]
|
node = session.item2nodes[item1] [0]
|
||||||
session.queueevent("pytest_testnodedown", node=node, error=None)
|
item1.config.option.dist = "load"
|
||||||
|
session.queueevent("pytest_testnodedown", node=node, error="xyz")
|
||||||
reprec = testdir.getreportrecorder(session)
|
reprec = testdir.getreportrecorder(session)
|
||||||
print session.item2nodes
|
print session.item2nodes
|
||||||
loopstate = session._initloopstate([])
|
loopstate = session._initloopstate([])
|
||||||
|
@ -367,13 +407,36 @@ class TestDSession:
|
||||||
assert node.gateway.spec.popen
|
assert node.gateway.spec.popen
|
||||||
#XXX eq.geteventargs("pytest_sessionfinish")
|
#XXX eq.geteventargs("pytest_sessionfinish")
|
||||||
|
|
||||||
@py.test.mark.xfail
|
def test_collected_function_causes_remote_skip(testdir):
|
||||||
def test_collected_function_causes_remote_skip_at_module_level(self, testdir):
|
sub = testdir.mkpydir("testing")
|
||||||
p = testdir.makepyfile("""
|
sub.join("test_module.py").write(py.code.Source("""
|
||||||
import py
|
import py
|
||||||
py.test.importorskip("xyz")
|
path = py.path.local(%r)
|
||||||
def test_func():
|
if path.check():
|
||||||
pass
|
path.remove()
|
||||||
""")
|
else:
|
||||||
# we need to be able to collect test_func locally but not in the subprocess
|
py.test.skip("remote skip")
|
||||||
XXX
|
def test_func():
|
||||||
|
pass
|
||||||
|
def test_func2():
|
||||||
|
pass
|
||||||
|
""" % str(sub.ensure("somefile"))))
|
||||||
|
result = testdir.runpytest('-v', '--dist=each', '--tx=popen')
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
"*2 skipped*"
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_teardownfails_one_function(testdir):
|
||||||
|
p = testdir.makepyfile("""
|
||||||
|
def test_func():
|
||||||
|
pass
|
||||||
|
def teardown_function(function):
|
||||||
|
assert 0
|
||||||
|
""")
|
||||||
|
result = testdir.runpytest(p, '--dist=each', '--tx=popen')
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
"*def teardown_function(function):*",
|
||||||
|
"*1 passed*1 error*"
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ class EventQueue:
|
||||||
|
|
||||||
class MySetup:
|
class MySetup:
|
||||||
def __init__(self, request):
|
def __init__(self, request):
|
||||||
|
self.id = 0
|
||||||
self.request = request
|
self.request = request
|
||||||
|
|
||||||
def geteventargs(self, eventname, timeout=2.0):
|
def geteventargs(self, eventname, timeout=2.0):
|
||||||
|
@ -45,6 +46,8 @@ class MySetup:
|
||||||
self.queue = py.std.Queue.Queue()
|
self.queue = py.std.Queue.Queue()
|
||||||
self.xspec = py.execnet.XSpec("popen")
|
self.xspec = py.execnet.XSpec("popen")
|
||||||
self.gateway = py.execnet.makegateway(self.xspec)
|
self.gateway = py.execnet.makegateway(self.xspec)
|
||||||
|
self.id += 1
|
||||||
|
self.gateway.id = str(self.id)
|
||||||
self.node = TXNode(self.gateway, self.config, putevent=self.queue.put)
|
self.node = TXNode(self.gateway, self.config, putevent=self.queue.put)
|
||||||
assert not self.node.channel.isclosed()
|
assert not self.node.channel.isclosed()
|
||||||
return self.node
|
return self.node
|
||||||
|
|
|
@ -21,6 +21,11 @@ class TXNode(object):
|
||||||
self.channel.setcallback(self.callback, endmarker=self.ENDMARK)
|
self.channel.setcallback(self.callback, endmarker=self.ENDMARK)
|
||||||
self._down = False
|
self._down = False
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
id = self.gateway.id
|
||||||
|
status = self._down and 'true' or 'false'
|
||||||
|
return "<TXNode %r down=%s>" %(id, status)
|
||||||
|
|
||||||
def notify(self, eventname, *args, **kwargs):
|
def notify(self, eventname, *args, **kwargs):
|
||||||
assert not args
|
assert not args
|
||||||
self.putevent((eventname, args, kwargs))
|
self.putevent((eventname, args, kwargs))
|
||||||
|
@ -115,6 +120,7 @@ class SlaveNode(object):
|
||||||
self.config.basetemp = py.path.local(basetemp)
|
self.config.basetemp = py.path.local(basetemp)
|
||||||
self.config.pluginmanager.do_configure(self.config)
|
self.config.pluginmanager.do_configure(self.config)
|
||||||
self.config.pluginmanager.register(self)
|
self.config.pluginmanager.register(self)
|
||||||
|
self.runner = self.config.pluginmanager.getplugin("pytest_runner")
|
||||||
self.sendevent("slaveready")
|
self.sendevent("slaveready")
|
||||||
try:
|
try:
|
||||||
while 1:
|
while 1:
|
||||||
|
@ -124,12 +130,26 @@ class SlaveNode(object):
|
||||||
break
|
break
|
||||||
if isinstance(task, list):
|
if isinstance(task, list):
|
||||||
for item in task:
|
for item in task:
|
||||||
item.config.hook.pytest_runtest_protocol(item=item)
|
self.run_single(item=item)
|
||||||
else:
|
else:
|
||||||
task.config.hook.pytest_runtest_protocol(item=task)
|
self.run_single(item=task)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
raise
|
raise
|
||||||
except:
|
except:
|
||||||
er = py.code.ExceptionInfo().getrepr(funcargs=True, showlocals=True)
|
er = py.code.ExceptionInfo().getrepr(funcargs=True, showlocals=True)
|
||||||
self.sendevent("pytest_internalerror", excrepr=er)
|
self.sendevent("pytest_internalerror", excrepr=er)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
def run_single(self, item):
|
||||||
|
call = self.runner.CallInfo(item._checkcollectable, when='setup')
|
||||||
|
if call.excinfo:
|
||||||
|
# likely it is not collectable here because of
|
||||||
|
# platform/import-dependency induced skips
|
||||||
|
# XXX somewhat ugly shortcuts - also makes a collection
|
||||||
|
# failure into an ItemTestReport - this might confuse
|
||||||
|
# pytest_runtest_logreport hooks
|
||||||
|
rep = self.runner.pytest_runtest_makereport(item=item, call=call)
|
||||||
|
self.pytest_runtest_logreport(rep)
|
||||||
|
return
|
||||||
|
item.config.hook.pytest_runtest_protocol(item=item)
|
||||||
|
|
||||||
|
|
|
@ -86,6 +86,14 @@ pytest_runtest_makereport.firstresult = True
|
||||||
def pytest_runtest_logreport(rep):
|
def pytest_runtest_logreport(rep):
|
||||||
""" process item test report. """
|
""" process item test report. """
|
||||||
|
|
||||||
|
# special handling for final teardown - somewhat internal for now
|
||||||
|
def pytest__teardown_final(session):
|
||||||
|
""" called before test session finishes. """
|
||||||
|
pytest__teardown_final.firstresult = True
|
||||||
|
|
||||||
|
def pytest__teardown_final_logerror(rep):
|
||||||
|
""" called if runtest_teardown_final failed. """
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# test session related hooks
|
# test session related hooks
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
|
|
@ -60,9 +60,6 @@ def pytest_addoption(parser):
|
||||||
action="store", dest="tbstyle", default='long',
|
action="store", dest="tbstyle", default='long',
|
||||||
type="choice", choices=['long', 'short', 'no'],
|
type="choice", choices=['long', 'short', 'no'],
|
||||||
help="traceback verboseness (long/short/no).")
|
help="traceback verboseness (long/short/no).")
|
||||||
group._addoption('-s',
|
|
||||||
action="store_true", dest="nocapture", default=False,
|
|
||||||
help="disable catching of stdout/stderr during test run.")
|
|
||||||
group._addoption('-p', action="append", dest="plugin", default = [],
|
group._addoption('-p', action="append", dest="plugin", default = [],
|
||||||
help=("load the specified plugin after command line parsing. "))
|
help=("load the specified plugin after command line parsing. "))
|
||||||
group._addoption('-f', '--looponfail',
|
group._addoption('-f', '--looponfail',
|
||||||
|
@ -84,9 +81,6 @@ def pytest_addoption(parser):
|
||||||
help="don't cut any tracebacks (default is to cut).")
|
help="don't cut any tracebacks (default is to cut).")
|
||||||
group.addoption('--basetemp', dest="basetemp", default=None, metavar="dir",
|
group.addoption('--basetemp', dest="basetemp", default=None, metavar="dir",
|
||||||
help="base temporary directory for this test run.")
|
help="base temporary directory for this test run.")
|
||||||
group._addoption('--iocapture', action="store", default="fd", metavar="method",
|
|
||||||
type="choice", choices=['fd', 'sys', 'no'],
|
|
||||||
help="set iocapturing method: fd|sys|no.")
|
|
||||||
group.addoption('--debug',
|
group.addoption('--debug',
|
||||||
action="store_true", dest="debug", default=False,
|
action="store_true", dest="debug", default=False,
|
||||||
help="generate and show debugging information.")
|
help="generate and show debugging information.")
|
||||||
|
|
|
@ -45,7 +45,7 @@ class DoctestItem(py.test.collect.Item):
|
||||||
super(DoctestItem, self).__init__(name=name, parent=parent)
|
super(DoctestItem, self).__init__(name=name, parent=parent)
|
||||||
self.fspath = path
|
self.fspath = path
|
||||||
|
|
||||||
def repr_failure(self, excinfo, outerr):
|
def repr_failure(self, excinfo):
|
||||||
if excinfo.errisinstance(py.compat.doctest.DocTestFailure):
|
if excinfo.errisinstance(py.compat.doctest.DocTestFailure):
|
||||||
doctestfailure = excinfo.value
|
doctestfailure = excinfo.value
|
||||||
example = doctestfailure.example
|
example = doctestfailure.example
|
||||||
|
@ -67,9 +67,9 @@ class DoctestItem(py.test.collect.Item):
|
||||||
return ReprFailDoctest(reprlocation, lines)
|
return ReprFailDoctest(reprlocation, lines)
|
||||||
elif excinfo.errisinstance(py.compat.doctest.UnexpectedException):
|
elif excinfo.errisinstance(py.compat.doctest.UnexpectedException):
|
||||||
excinfo = py.code.ExceptionInfo(excinfo.value.exc_info)
|
excinfo = py.code.ExceptionInfo(excinfo.value.exc_info)
|
||||||
return super(DoctestItem, self).repr_failure(excinfo, outerr)
|
return super(DoctestItem, self).repr_failure(excinfo)
|
||||||
else:
|
else:
|
||||||
return super(DoctestItem, self).repr_failure(excinfo, outerr)
|
return super(DoctestItem, self).repr_failure(excinfo)
|
||||||
|
|
||||||
class DoctestTextfile(DoctestItem):
|
class DoctestTextfile(DoctestItem):
|
||||||
def runtest(self):
|
def runtest(self):
|
||||||
|
|
|
@ -1,97 +1,254 @@
|
||||||
"""
|
"""
|
||||||
convenient capturing of writes to stdout/stderror streams and file descriptors.
|
configurable per-test stdout/stderr capturing mechanisms.
|
||||||
|
|
||||||
Example Usage
|
This plugin captures stdout/stderr output for each test separately.
|
||||||
----------------------
|
In case of test failures this captured output is shown grouped
|
||||||
|
togtther with the test.
|
||||||
|
|
||||||
You can use the `capsys funcarg`_ to capture writes
|
The plugin also provides test function arguments that help to
|
||||||
to stdout and stderr streams by using it in a test
|
assert stdout/stderr output from within your tests, see the
|
||||||
likes this:
|
`funcarg example`_.
|
||||||
|
|
||||||
|
|
||||||
|
Capturing of input/output streams during tests
|
||||||
|
---------------------------------------------------
|
||||||
|
|
||||||
|
By default ``sys.stdout`` and ``sys.stderr`` are substituted with
|
||||||
|
temporary streams during the execution of tests and setup/teardown code.
|
||||||
|
During the whole testing process it will re-use the same temporary
|
||||||
|
streams allowing to play well with the logging module which easily
|
||||||
|
takes ownership on these streams.
|
||||||
|
|
||||||
|
Also, 'sys.stdin' is substituted with a file-like "null" object that
|
||||||
|
does not return any values. This is to immediately error out
|
||||||
|
on tests that wait on reading something from stdin.
|
||||||
|
|
||||||
|
You can influence output capturing mechanisms from the command line::
|
||||||
|
|
||||||
|
py.test -s # disable all capturing
|
||||||
|
py.test --capture=sys # set StringIO() to each of sys.stdout/stderr
|
||||||
|
py.test --capture=fd # capture stdout/stderr on Filedescriptors 1/2
|
||||||
|
|
||||||
|
If you set capturing values in a conftest file like this::
|
||||||
|
|
||||||
|
# conftest.py
|
||||||
|
conf_capture = 'fd'
|
||||||
|
|
||||||
|
then all tests in that directory will execute with "fd" style capturing.
|
||||||
|
|
||||||
|
sys-level capturing
|
||||||
|
------------------------------------------
|
||||||
|
|
||||||
|
Capturing on 'sys' level means that ``sys.stdout`` and ``sys.stderr``
|
||||||
|
will be replaced with StringIO() objects.
|
||||||
|
|
||||||
|
FD-level capturing and subprocesses
|
||||||
|
------------------------------------------
|
||||||
|
|
||||||
|
The ``fd`` based method means that writes going to system level files
|
||||||
|
based on the standard file descriptors will be captured, for example
|
||||||
|
writes such as ``os.write(1, 'hello')`` will be captured properly.
|
||||||
|
Capturing on fd-level will include output generated from
|
||||||
|
any subprocesses created during a test.
|
||||||
|
|
||||||
|
.. _`funcarg example`:
|
||||||
|
|
||||||
|
Example Usage of the capturing Function arguments
|
||||||
|
---------------------------------------------------
|
||||||
|
|
||||||
|
You can use the `capsys funcarg`_ and `capfd funcarg`_ to
|
||||||
|
capture writes to stdout and stderr streams. Using the
|
||||||
|
funcargs frees your test from having to care about setting/resetting
|
||||||
|
the old streams and also interacts well with py.test's own
|
||||||
|
per-test capturing. Here is an example test function:
|
||||||
|
|
||||||
.. sourcecode:: python
|
.. sourcecode:: python
|
||||||
|
|
||||||
def test_myoutput(capsys):
|
def test_myoutput(capsys):
|
||||||
print "hello"
|
print "hello"
|
||||||
print >>sys.stderr, "world"
|
print >>sys.stderr, "world"
|
||||||
out, err = capsys.reset()
|
out, err = capsys.readouterr()
|
||||||
assert out == "hello\\n"
|
assert out == "hello\\n"
|
||||||
assert err == "world\\n"
|
assert err == "world\\n"
|
||||||
print "next"
|
print "next"
|
||||||
out, err = capsys.reset()
|
out, err = capsys.readouterr()
|
||||||
assert out == "next\\n"
|
assert out == "next\\n"
|
||||||
|
|
||||||
The ``reset()`` call returns a tuple and will restart
|
The ``readouterr()`` call snapshots the output so far -
|
||||||
capturing so that you can successively check for output.
|
and capturing will be continued. After the test
|
||||||
After the test function finishes the original streams
|
function finishes the original streams will
|
||||||
will be restored.
|
be restored. If you want to capture on
|
||||||
|
the filedescriptor level you can use the ``capfd`` function
|
||||||
|
argument which offers the same interface.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import py
|
import py
|
||||||
|
|
||||||
|
def pytest_addoption(parser):
|
||||||
|
group = parser.getgroup("general")
|
||||||
|
group._addoption('-s', action="store_const", const="no", dest="capture",
|
||||||
|
help="shortcut for --capture=no.")
|
||||||
|
group._addoption('--capture', action="store", default=None,
|
||||||
|
metavar="capture", type="choice", choices=['fd', 'sys', 'no'],
|
||||||
|
help="set IO capturing method during tests: sys|fd|no.")
|
||||||
|
|
||||||
|
def addouterr(rep, outerr):
|
||||||
|
repr = getattr(rep, 'longrepr', None)
|
||||||
|
if not hasattr(repr, 'addsection'):
|
||||||
|
return
|
||||||
|
for secname, content in zip(["out", "err"], outerr):
|
||||||
|
if content:
|
||||||
|
repr.addsection("Captured std%s" % secname, content.rstrip())
|
||||||
|
|
||||||
|
def pytest_configure(config):
|
||||||
|
config.pluginmanager.register(CaptureManager(), 'capturemanager')
|
||||||
|
|
||||||
|
class CaptureManager:
|
||||||
|
def __init__(self):
|
||||||
|
self._method2capture = {}
|
||||||
|
|
||||||
|
def _startcapture(self, method):
|
||||||
|
if method == "fd":
|
||||||
|
return py.io.StdCaptureFD()
|
||||||
|
elif method == "sys":
|
||||||
|
return py.io.StdCapture()
|
||||||
|
else:
|
||||||
|
raise ValueError("unknown capturing method: %r" % method)
|
||||||
|
|
||||||
|
def _getmethod(self, config, fspath):
|
||||||
|
if config.option.capture:
|
||||||
|
return config.option.capture
|
||||||
|
return config._conftest.rget("conf_capture", path=fspath)
|
||||||
|
|
||||||
|
def resumecapture_item(self, item):
|
||||||
|
method = self._getmethod(item.config, item.fspath)
|
||||||
|
if not hasattr(item, 'outerr'):
|
||||||
|
item.outerr = ('', '') # we accumulate outerr on the item
|
||||||
|
return self.resumecapture(method)
|
||||||
|
|
||||||
|
def resumecapture(self, method):
|
||||||
|
if hasattr(self, '_capturing'):
|
||||||
|
raise ValueError("cannot resume, already capturing with %r" %
|
||||||
|
(self._capturing,))
|
||||||
|
if method != "no":
|
||||||
|
cap = self._method2capture.get(method)
|
||||||
|
if cap is None:
|
||||||
|
cap = self._startcapture(method)
|
||||||
|
self._method2capture[method] = cap
|
||||||
|
else:
|
||||||
|
cap.resume()
|
||||||
|
self._capturing = method
|
||||||
|
|
||||||
|
def suspendcapture(self):
|
||||||
|
self.deactivate_funcargs()
|
||||||
|
method = self._capturing
|
||||||
|
if method != "no":
|
||||||
|
cap = self._method2capture[method]
|
||||||
|
outerr = cap.suspend()
|
||||||
|
else:
|
||||||
|
outerr = "", ""
|
||||||
|
del self._capturing
|
||||||
|
return outerr
|
||||||
|
|
||||||
|
def activate_funcargs(self, pyfuncitem):
|
||||||
|
if not hasattr(pyfuncitem, 'funcargs'):
|
||||||
|
return
|
||||||
|
assert not hasattr(self, '_capturing_funcargs')
|
||||||
|
l = []
|
||||||
|
for name, obj in pyfuncitem.funcargs.items():
|
||||||
|
if name in ('capsys', 'capfd'):
|
||||||
|
obj._start()
|
||||||
|
l.append(obj)
|
||||||
|
if l:
|
||||||
|
self._capturing_funcargs = l
|
||||||
|
|
||||||
|
def deactivate_funcargs(self):
|
||||||
|
if hasattr(self, '_capturing_funcargs'):
|
||||||
|
for capfuncarg in self._capturing_funcargs:
|
||||||
|
capfuncarg._finalize()
|
||||||
|
del self._capturing_funcargs
|
||||||
|
|
||||||
|
def pytest_make_collect_report(self, __call__, collector):
|
||||||
|
method = self._getmethod(collector.config, collector.fspath)
|
||||||
|
self.resumecapture(method)
|
||||||
|
try:
|
||||||
|
rep = __call__.execute(firstresult=True)
|
||||||
|
finally:
|
||||||
|
outerr = self.suspendcapture()
|
||||||
|
addouterr(rep, outerr)
|
||||||
|
return rep
|
||||||
|
|
||||||
|
def pytest_runtest_setup(self, item):
|
||||||
|
self.resumecapture_item(item)
|
||||||
|
|
||||||
|
def pytest_runtest_call(self, item):
|
||||||
|
self.resumecapture_item(item)
|
||||||
|
self.activate_funcargs(item)
|
||||||
|
|
||||||
|
def pytest_runtest_teardown(self, item):
|
||||||
|
self.resumecapture_item(item)
|
||||||
|
|
||||||
|
def pytest_runtest_teardown(self, item):
|
||||||
|
self.resumecapture_item(item)
|
||||||
|
|
||||||
|
def pytest__teardown_final(self, __call__, session):
|
||||||
|
method = self._getmethod(session.config, None)
|
||||||
|
self.resumecapture(method)
|
||||||
|
try:
|
||||||
|
rep = __call__.execute(firstresult=True)
|
||||||
|
finally:
|
||||||
|
outerr = self.suspendcapture()
|
||||||
|
if rep:
|
||||||
|
addouterr(rep, outerr)
|
||||||
|
return rep
|
||||||
|
|
||||||
|
def pytest_keyboard_interrupt(self, excinfo):
|
||||||
|
if hasattr(self, '_capturing'):
|
||||||
|
self.suspendcapture()
|
||||||
|
|
||||||
|
def pytest_runtest_makereport(self, __call__, item, call):
|
||||||
|
self.deactivate_funcargs()
|
||||||
|
rep = __call__.execute(firstresult=True)
|
||||||
|
outerr = self.suspendcapture()
|
||||||
|
outerr = (item.outerr[0] + outerr[0], item.outerr[1] + outerr[1])
|
||||||
|
if not rep.passed:
|
||||||
|
addouterr(rep, outerr)
|
||||||
|
if not rep.passed or rep.when == "teardown":
|
||||||
|
outerr = ('', '')
|
||||||
|
item.outerr = outerr
|
||||||
|
return rep
|
||||||
|
|
||||||
def pytest_funcarg__capsys(request):
|
def pytest_funcarg__capsys(request):
|
||||||
"""captures writes to sys.stdout/sys.stderr and makes
|
"""captures writes to sys.stdout/sys.stderr and makes
|
||||||
them available successively via a ``capsys.reset()`` method
|
them available successively via a ``capsys.readouterr()`` method
|
||||||
which returns a ``(out, err)`` tuple of captured strings.
|
which returns a ``(out, err)`` tuple of captured snapshot strings.
|
||||||
"""
|
"""
|
||||||
capture = Capture(py.io.StdCapture)
|
return CaptureFuncarg(request, py.io.StdCapture)
|
||||||
request.addfinalizer(capture.finalize)
|
|
||||||
return capture
|
|
||||||
|
|
||||||
def pytest_funcarg__capfd(request):
|
def pytest_funcarg__capfd(request):
|
||||||
"""captures writes to file descriptors 1 and 2 and makes
|
"""captures writes to file descriptors 1 and 2 and makes
|
||||||
them available successively via a ``capsys.reset()`` method
|
snapshotted ``(out, err)`` string tuples available
|
||||||
which returns a ``(out, err)`` tuple of captured strings.
|
via the ``capsys.readouterr()`` method.
|
||||||
"""
|
"""
|
||||||
capture = Capture(py.io.StdCaptureFD)
|
return CaptureFuncarg(request, py.io.StdCaptureFD)
|
||||||
request.addfinalizer(capture.finalize)
|
|
||||||
return capture
|
|
||||||
|
|
||||||
def pytest_pyfunc_call(pyfuncitem):
|
|
||||||
if hasattr(pyfuncitem, 'funcargs'):
|
|
||||||
for funcarg, value in pyfuncitem.funcargs.items():
|
|
||||||
if funcarg == "capsys" or funcarg == "capfd":
|
|
||||||
value.reset()
|
|
||||||
|
|
||||||
class Capture:
|
class CaptureFuncarg:
|
||||||
_capture = None
|
def __init__(self, request, captureclass):
|
||||||
def __init__(self, captureclass):
|
self._cclass = captureclass
|
||||||
self._captureclass = captureclass
|
#request.addfinalizer(self._finalize)
|
||||||
|
|
||||||
def finalize(self):
|
def _start(self):
|
||||||
if self._capture:
|
self.capture = self._cclass()
|
||||||
self._capture.reset()
|
|
||||||
|
|
||||||
def reset(self):
|
def _finalize(self):
|
||||||
res = None
|
if hasattr(self, 'capture'):
|
||||||
if self._capture:
|
self.capture.reset()
|
||||||
res = self._capture.reset()
|
del self.capture
|
||||||
self._capture = self._captureclass()
|
|
||||||
return res
|
|
||||||
|
|
||||||
class TestCapture:
|
def readouterr(self):
|
||||||
def test_std_functional(self, testdir):
|
return self.capture.readouterr()
|
||||||
reprec = testdir.inline_runsource("""
|
|
||||||
def test_hello(capsys):
|
|
||||||
print 42
|
|
||||||
out, err = capsys.reset()
|
|
||||||
assert out.startswith("42")
|
|
||||||
""")
|
|
||||||
reprec.assertoutcome(passed=1)
|
|
||||||
|
|
||||||
def test_stdfd_functional(self, testdir):
|
|
||||||
reprec = testdir.inline_runsource("""
|
|
||||||
def test_hello(capfd):
|
|
||||||
import os
|
|
||||||
os.write(1, "42")
|
|
||||||
out, err = capfd.reset()
|
|
||||||
assert out.startswith("42")
|
|
||||||
""")
|
|
||||||
reprec.assertoutcome(passed=1)
|
|
||||||
|
|
||||||
def test_funcall_yielded_no_funcargs(self, testdir):
|
|
||||||
reprec = testdir.inline_runsource("""
|
|
||||||
def test_hello():
|
|
||||||
yield lambda: None
|
|
||||||
""")
|
|
||||||
reprec.assertoutcome(passed=1)
|
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.capture.reset()
|
||||||
|
del self.capture
|
||||||
|
|
|
@ -1,70 +1,86 @@
|
||||||
"""
|
"""
|
||||||
py.test.mark / keyword plugin
|
mark test functions with keywords that may hold values.
|
||||||
|
|
||||||
|
Marking functions and setting rich attributes
|
||||||
|
----------------------------------------------------
|
||||||
|
|
||||||
|
By default, all filename parts and class/function names of a test
|
||||||
|
function are put into the set of keywords for a given test. You can
|
||||||
|
specify additional kewords like this::
|
||||||
|
|
||||||
|
@py.test.mark.webtest
|
||||||
|
def test_send_http():
|
||||||
|
...
|
||||||
|
|
||||||
|
This will set an attribute 'webtest' on the given test function
|
||||||
|
and by default all such attributes signal keywords. You can
|
||||||
|
also set values in this attribute which you could read from
|
||||||
|
a hook in order to do something special with respect to
|
||||||
|
the test function::
|
||||||
|
|
||||||
|
@py.test.mark.timeout(seconds=5)
|
||||||
|
def test_receive():
|
||||||
|
...
|
||||||
|
|
||||||
|
This will set the "timeout" attribute with a Marker object
|
||||||
|
that has a 'seconds' attribute.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import py
|
import py
|
||||||
|
|
||||||
def pytest_namespace():
|
def pytest_namespace():
|
||||||
mark = KeywordDecorator({})
|
return {'mark': Mark()}
|
||||||
return {'mark': mark}
|
|
||||||
|
|
||||||
class KeywordDecorator:
|
|
||||||
""" decorator for setting function attributes. """
|
|
||||||
def __init__(self, keywords, lastname=None):
|
|
||||||
self._keywords = keywords
|
|
||||||
self._lastname = lastname
|
|
||||||
|
|
||||||
def __call__(self, func=None, **kwargs):
|
|
||||||
if func is None:
|
|
||||||
kw = self._keywords.copy()
|
|
||||||
kw.update(kwargs)
|
|
||||||
return KeywordDecorator(kw)
|
|
||||||
elif not hasattr(func, 'func_dict'):
|
|
||||||
kw = self._keywords.copy()
|
|
||||||
name = self._lastname
|
|
||||||
if name is None:
|
|
||||||
name = "mark"
|
|
||||||
kw[name] = func
|
|
||||||
return KeywordDecorator(kw)
|
|
||||||
func.func_dict.update(self._keywords)
|
|
||||||
return func
|
|
||||||
|
|
||||||
|
class Mark(object):
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
if name[0] == "_":
|
if name[0] == "_":
|
||||||
raise AttributeError(name)
|
raise AttributeError(name)
|
||||||
kw = self._keywords.copy()
|
return MarkerDecorator(name)
|
||||||
kw[name] = True
|
|
||||||
return self.__class__(kw, lastname=name)
|
class MarkerDecorator:
|
||||||
|
""" decorator for setting function attributes. """
|
||||||
|
def __init__(self, name):
|
||||||
|
self.markname = name
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
d = self.__dict__.copy()
|
||||||
|
name = d.pop('markname')
|
||||||
|
return "<MarkerDecorator %r %r>" %(name, d)
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
if not args:
|
||||||
|
if hasattr(self, 'kwargs'):
|
||||||
|
raise TypeError("double mark-keywords?")
|
||||||
|
self.kwargs = kwargs.copy()
|
||||||
|
return self
|
||||||
|
else:
|
||||||
|
if not len(args) == 1 or not hasattr(args[0], 'func_dict'):
|
||||||
|
raise TypeError("need exactly one function to decorate, "
|
||||||
|
"got %r" %(args,))
|
||||||
|
func = args[0]
|
||||||
|
mh = MarkHolder(getattr(self, 'kwargs', {}))
|
||||||
|
setattr(func, self.markname, mh)
|
||||||
|
return func
|
||||||
|
|
||||||
|
class MarkHolder:
|
||||||
|
def __init__(self, kwargs):
|
||||||
|
self.__dict__.update(kwargs)
|
||||||
|
|
||||||
|
def test_pytest_mark_api():
|
||||||
|
mark = Mark()
|
||||||
|
py.test.raises(TypeError, "mark(x=3)")
|
||||||
|
|
||||||
def test_pytest_mark_getattr():
|
|
||||||
mark = KeywordDecorator({})
|
|
||||||
def f(): pass
|
def f(): pass
|
||||||
|
|
||||||
mark.hello(f)
|
mark.hello(f)
|
||||||
assert f.hello == True
|
assert f.hello
|
||||||
|
|
||||||
mark.hello("test")(f)
|
mark.world(x=3, y=4)(f)
|
||||||
assert f.hello == "test"
|
assert f.world
|
||||||
|
assert f.world.x == 3
|
||||||
|
assert f.world.y == 4
|
||||||
|
|
||||||
py.test.raises(AttributeError, "mark._hello")
|
py.test.raises(TypeError, "mark.some(x=3)(f=5)")
|
||||||
py.test.raises(AttributeError, "mark.__str__")
|
|
||||||
|
|
||||||
def test_pytest_mark_call():
|
|
||||||
mark = KeywordDecorator({})
|
|
||||||
def f(): pass
|
|
||||||
mark(x=3)(f)
|
|
||||||
assert f.x == 3
|
|
||||||
def g(): pass
|
|
||||||
mark(g)
|
|
||||||
assert not g.func_dict
|
|
||||||
|
|
||||||
mark.hello(f)
|
|
||||||
assert f.hello == True
|
|
||||||
|
|
||||||
mark.hello("test")(f)
|
|
||||||
assert f.hello == "test"
|
|
||||||
|
|
||||||
mark("x1")(f)
|
|
||||||
assert f.mark == "x1"
|
|
||||||
|
|
||||||
def test_mark_plugin(testdir):
|
def test_mark_plugin(testdir):
|
||||||
p = testdir.makepyfile("""
|
p = testdir.makepyfile("""
|
||||||
|
|
|
@ -23,11 +23,18 @@ def pytest_configure(config):
|
||||||
class PdbInvoke:
|
class PdbInvoke:
|
||||||
def pytest_runtest_makereport(self, item, call):
|
def pytest_runtest_makereport(self, item, call):
|
||||||
if call.excinfo and not call.excinfo.errisinstance(Skipped):
|
if call.excinfo and not call.excinfo.errisinstance(Skipped):
|
||||||
|
# XXX hack hack hack to play well with capturing
|
||||||
|
capman = item.config.pluginmanager.impname2plugin['capturemanager']
|
||||||
|
capman.suspendcapture()
|
||||||
|
|
||||||
tw = py.io.TerminalWriter()
|
tw = py.io.TerminalWriter()
|
||||||
repr = call.excinfo.getrepr()
|
repr = call.excinfo.getrepr()
|
||||||
repr.toterminal(tw)
|
repr.toterminal(tw)
|
||||||
post_mortem(call.excinfo._excinfo[2])
|
post_mortem(call.excinfo._excinfo[2])
|
||||||
|
|
||||||
|
# XXX hack end
|
||||||
|
capman.resumecapture_item(item)
|
||||||
|
|
||||||
class Pdb(py.std.pdb.Pdb):
|
class Pdb(py.std.pdb.Pdb):
|
||||||
def do_list(self, arg):
|
def do_list(self, arg):
|
||||||
self.lastcmd = 'list'
|
self.lastcmd = 'list'
|
||||||
|
|
|
@ -130,6 +130,11 @@ class TmpTestdir:
|
||||||
def mkdir(self, name):
|
def mkdir(self, name):
|
||||||
return self.tmpdir.mkdir(name)
|
return self.tmpdir.mkdir(name)
|
||||||
|
|
||||||
|
def mkpydir(self, name):
|
||||||
|
p = self.mkdir(name)
|
||||||
|
p.ensure("__init__.py")
|
||||||
|
return p
|
||||||
|
|
||||||
def genitems(self, colitems):
|
def genitems(self, colitems):
|
||||||
return list(self.session.genitems(colitems))
|
return list(self.session.genitems(colitems))
|
||||||
|
|
||||||
|
@ -296,6 +301,9 @@ class TmpTestdir:
|
||||||
assert script.check()
|
assert script.check()
|
||||||
return py.std.sys.executable, script
|
return py.std.sys.executable, script
|
||||||
|
|
||||||
|
def runpython(self, script):
|
||||||
|
return self.run(py.std.sys.executable, script)
|
||||||
|
|
||||||
def runpytest(self, *args):
|
def runpytest(self, *args):
|
||||||
p = py.path.local.make_numbered_dir(prefix="runpytest-",
|
p = py.path.local.make_numbered_dir(prefix="runpytest-",
|
||||||
keep=None, rootdir=self.tmpdir)
|
keep=None, rootdir=self.tmpdir)
|
||||||
|
@ -515,7 +523,6 @@ def test_testdir_runs_with_plugin(testdir):
|
||||||
def pytest_funcarg__venv(request):
|
def pytest_funcarg__venv(request):
|
||||||
p = request.config.mktemp(request.function.__name__, numbered=True)
|
p = request.config.mktemp(request.function.__name__, numbered=True)
|
||||||
venv = VirtualEnv(str(p))
|
venv = VirtualEnv(str(p))
|
||||||
venv.create()
|
|
||||||
return venv
|
return venv
|
||||||
|
|
||||||
def pytest_funcarg__py_setup(request):
|
def pytest_funcarg__py_setup(request):
|
||||||
|
@ -528,13 +535,14 @@ def pytest_funcarg__py_setup(request):
|
||||||
class SetupBuilder:
|
class SetupBuilder:
|
||||||
def __init__(self, setup_path):
|
def __init__(self, setup_path):
|
||||||
self.setup_path = setup_path
|
self.setup_path = setup_path
|
||||||
|
assert setup_path.check()
|
||||||
|
|
||||||
def make_sdist(self, destdir=None):
|
def make_sdist(self, destdir=None):
|
||||||
temp = py.path.local.mkdtemp()
|
temp = py.path.local.mkdtemp()
|
||||||
try:
|
try:
|
||||||
args = ['python', str(self.setup_path), 'sdist',
|
args = ['python', str(self.setup_path), 'sdist',
|
||||||
'--dist-dir', str(temp)]
|
'--dist-dir', str(temp)]
|
||||||
subprocess.check_call(args)
|
subcall(args)
|
||||||
l = temp.listdir('py-*')
|
l = temp.listdir('py-*')
|
||||||
assert len(l) == 1
|
assert len(l) == 1
|
||||||
sdist = l[0]
|
sdist = l[0]
|
||||||
|
@ -549,6 +557,11 @@ class SetupBuilder:
|
||||||
finally:
|
finally:
|
||||||
temp.remove()
|
temp.remove()
|
||||||
|
|
||||||
|
def subcall(args):
|
||||||
|
if hasattr(subprocess, 'check_call'):
|
||||||
|
subprocess.check_call(args)
|
||||||
|
else:
|
||||||
|
subprocess.call(args)
|
||||||
# code taken from Ronny Pfannenschmidt's virtualenvmanager
|
# code taken from Ronny Pfannenschmidt's virtualenvmanager
|
||||||
|
|
||||||
class VirtualEnv(object):
|
class VirtualEnv(object):
|
||||||
|
@ -562,22 +575,22 @@ class VirtualEnv(object):
|
||||||
def _cmd(self, name):
|
def _cmd(self, name):
|
||||||
return os.path.join(self.path, 'bin', name)
|
return os.path.join(self.path, 'bin', name)
|
||||||
|
|
||||||
@property
|
def ensure(self):
|
||||||
def valid(self):
|
if not os.path.exists(self._cmd('python')):
|
||||||
return os.path.exists(self._cmd('python'))
|
self.create()
|
||||||
|
|
||||||
def create(self, sitepackages=False):
|
def create(self, sitepackages=False):
|
||||||
args = ['virtualenv', self.path]
|
args = ['virtualenv', self.path]
|
||||||
if not sitepackages:
|
if not sitepackages:
|
||||||
args.append('--no-site-packages')
|
args.append('--no-site-packages')
|
||||||
subprocess.check_call(args)
|
subcall(args)
|
||||||
|
|
||||||
def makegateway(self):
|
def makegateway(self):
|
||||||
python = self._cmd('python')
|
python = self._cmd('python')
|
||||||
return py.execnet.makegateway("popen//python=%s" %(python,))
|
return py.execnet.makegateway("popen//python=%s" %(python,))
|
||||||
|
|
||||||
def pcall(self, cmd, *args, **kw):
|
def pcall(self, cmd, *args, **kw):
|
||||||
assert self.valid
|
self.ensure()
|
||||||
return subprocess.call([
|
return subprocess.call([
|
||||||
self._cmd(cmd)
|
self._cmd(cmd)
|
||||||
] + list(args),
|
] + list(args),
|
||||||
|
|
|
@ -105,12 +105,14 @@ class ReSTSyntaxTest(py.test.collect.Item):
|
||||||
|
|
||||||
def register_pygments(self):
|
def register_pygments(self):
|
||||||
# taken from pygments-main/external/rst-directive.py
|
# taken from pygments-main/external/rst-directive.py
|
||||||
|
from docutils.parsers.rst import directives
|
||||||
try:
|
try:
|
||||||
from pygments.formatters import HtmlFormatter
|
from pygments.formatters import HtmlFormatter
|
||||||
except ImportError:
|
except ImportError:
|
||||||
def pygments_directive(name, arguments, options, content, lineno,
|
def pygments_directive(name, arguments, options, content, lineno,
|
||||||
content_offset, block_text, state, state_machine):
|
content_offset, block_text, state, state_machine):
|
||||||
return []
|
return []
|
||||||
|
pygments_directive.options = {}
|
||||||
else:
|
else:
|
||||||
# The default formatter
|
# The default formatter
|
||||||
DEFAULT = HtmlFormatter(noclasses=True)
|
DEFAULT = HtmlFormatter(noclasses=True)
|
||||||
|
@ -120,7 +122,6 @@ class ReSTSyntaxTest(py.test.collect.Item):
|
||||||
}
|
}
|
||||||
|
|
||||||
from docutils import nodes
|
from docutils import nodes
|
||||||
from docutils.parsers.rst import directives
|
|
||||||
|
|
||||||
from pygments import highlight
|
from pygments import highlight
|
||||||
from pygments.lexers import get_lexer_by_name, TextLexer
|
from pygments.lexers import get_lexer_by_name, TextLexer
|
||||||
|
@ -137,10 +138,10 @@ class ReSTSyntaxTest(py.test.collect.Item):
|
||||||
parsed = highlight(u'\n'.join(content), lexer, formatter)
|
parsed = highlight(u'\n'.join(content), lexer, formatter)
|
||||||
return [nodes.raw('', parsed, format='html')]
|
return [nodes.raw('', parsed, format='html')]
|
||||||
|
|
||||||
|
pygments_directive.options = dict([(key, directives.flag) for key in VARIANTS])
|
||||||
|
|
||||||
pygments_directive.arguments = (1, 0, 1)
|
pygments_directive.arguments = (1, 0, 1)
|
||||||
pygments_directive.content = 1
|
pygments_directive.content = 1
|
||||||
pygments_directive.options = dict([(key, directives.flag) for key in VARIANTS])
|
|
||||||
|
|
||||||
directives.register_directive('sourcecode', pygments_directive)
|
directives.register_directive('sourcecode', pygments_directive)
|
||||||
|
|
||||||
def resolve_linkrole(self, name, text, check=True):
|
def resolve_linkrole(self, name, text, check=True):
|
||||||
|
|
|
@ -20,26 +20,21 @@ def pytest_configure(config):
|
||||||
config._setupstate = SetupState()
|
config._setupstate = SetupState()
|
||||||
|
|
||||||
def pytest_sessionfinish(session, exitstatus):
|
def pytest_sessionfinish(session, exitstatus):
|
||||||
# XXX see above
|
|
||||||
if hasattr(session.config, '_setupstate'):
|
if hasattr(session.config, '_setupstate'):
|
||||||
session.config._setupstate.teardown_all()
|
hook = session.config.hook
|
||||||
# prevent logging module atexit handler from choking on
|
rep = hook.pytest__teardown_final(session=session)
|
||||||
# its attempt to close already closed streams
|
if rep:
|
||||||
# see http://bugs.python.org/issue6333
|
hook.pytest__teardown_final_logerror(rep=rep)
|
||||||
mod = py.std.sys.modules.get("logging", None)
|
|
||||||
if mod is not None:
|
|
||||||
mod.raiseExceptions = False
|
|
||||||
|
|
||||||
def pytest_make_collect_report(collector):
|
def pytest_make_collect_report(collector):
|
||||||
call = collector.config.guardedcall(
|
result = excinfo = None
|
||||||
lambda: collector._memocollect()
|
try:
|
||||||
)
|
result = collector._memocollect()
|
||||||
result = None
|
except KeyboardInterrupt:
|
||||||
if not call.excinfo:
|
raise
|
||||||
result = call.result
|
except:
|
||||||
return CollectReport(collector, result, call.excinfo, call.outerr)
|
excinfo = py.code.ExceptionInfo()
|
||||||
|
return CollectReport(collector, result, excinfo)
|
||||||
return report
|
|
||||||
|
|
||||||
def pytest_runtest_protocol(item):
|
def pytest_runtest_protocol(item):
|
||||||
if item.config.getvalue("boxed"):
|
if item.config.getvalue("boxed"):
|
||||||
|
@ -66,40 +61,52 @@ def pytest_runtest_call(item):
|
||||||
item.runtest()
|
item.runtest()
|
||||||
|
|
||||||
def pytest_runtest_makereport(item, call):
|
def pytest_runtest_makereport(item, call):
|
||||||
return ItemTestReport(item, call.excinfo, call.when, call.outerr)
|
return ItemTestReport(item, call.excinfo, call.when)
|
||||||
|
|
||||||
def pytest_runtest_teardown(item):
|
def pytest_runtest_teardown(item):
|
||||||
item.config._setupstate.teardown_exact(item)
|
item.config._setupstate.teardown_exact(item)
|
||||||
|
|
||||||
|
def pytest__teardown_final(session):
|
||||||
|
call = CallInfo(session.config._setupstate.teardown_all, when="teardown")
|
||||||
|
if call.excinfo:
|
||||||
|
rep = TeardownErrorReport(call.excinfo)
|
||||||
|
return rep
|
||||||
|
|
||||||
|
def pytest_report_teststatus(rep):
|
||||||
|
if rep.when in ("setup", "teardown"):
|
||||||
|
if rep.failed:
|
||||||
|
# category, shortletter, verbose-word
|
||||||
|
return "error", "E", "ERROR"
|
||||||
|
elif rep.skipped:
|
||||||
|
return "skipped", "s", "SKIPPED"
|
||||||
|
else:
|
||||||
|
return "", "", ""
|
||||||
#
|
#
|
||||||
# Implementation
|
# Implementation
|
||||||
|
|
||||||
def call_and_report(item, when, log=True):
|
def call_and_report(item, when, log=True):
|
||||||
call = RuntestHookCall(item, when)
|
call = call_runtest_hook(item, when)
|
||||||
hook = item.config.hook
|
hook = item.config.hook
|
||||||
report = hook.pytest_runtest_makereport(item=item, call=call)
|
report = hook.pytest_runtest_makereport(item=item, call=call)
|
||||||
if log and (when == "call" or not report.passed):
|
if log and (when == "call" or not report.passed):
|
||||||
hook.pytest_runtest_logreport(rep=report)
|
hook.pytest_runtest_logreport(rep=report)
|
||||||
return report
|
return report
|
||||||
|
|
||||||
|
def call_runtest_hook(item, when):
|
||||||
|
hookname = "pytest_runtest_" + when
|
||||||
|
hook = getattr(item.config.hook, hookname)
|
||||||
|
return CallInfo(lambda: hook(item=item), when=when)
|
||||||
|
|
||||||
class RuntestHookCall:
|
class CallInfo:
|
||||||
excinfo = None
|
excinfo = None
|
||||||
_prefix = "pytest_runtest_"
|
def __init__(self, func, when):
|
||||||
def __init__(self, item, when):
|
|
||||||
self.when = when
|
self.when = when
|
||||||
hookname = self._prefix + when
|
|
||||||
hook = getattr(item.config.hook, hookname)
|
|
||||||
capture = item.config._getcapture()
|
|
||||||
try:
|
try:
|
||||||
try:
|
self.result = func()
|
||||||
self.result = hook(item=item)
|
except KeyboardInterrupt:
|
||||||
except KeyboardInterrupt:
|
raise
|
||||||
raise
|
except:
|
||||||
except:
|
self.excinfo = py.code.ExceptionInfo()
|
||||||
self.excinfo = py.code.ExceptionInfo()
|
|
||||||
finally:
|
|
||||||
self.outerr = capture.reset()
|
|
||||||
|
|
||||||
def forked_run_report(item):
|
def forked_run_report(item):
|
||||||
# for now, we run setup/teardown in the subprocess
|
# for now, we run setup/teardown in the subprocess
|
||||||
|
@ -149,10 +156,9 @@ class BaseReport(object):
|
||||||
class ItemTestReport(BaseReport):
|
class ItemTestReport(BaseReport):
|
||||||
failed = passed = skipped = False
|
failed = passed = skipped = False
|
||||||
|
|
||||||
def __init__(self, item, excinfo=None, when=None, outerr=None):
|
def __init__(self, item, excinfo=None, when=None):
|
||||||
self.item = item
|
self.item = item
|
||||||
self.when = when
|
self.when = when
|
||||||
self.outerr = outerr
|
|
||||||
if item and when != "setup":
|
if item and when != "setup":
|
||||||
self.keywords = item.readkeywords()
|
self.keywords = item.readkeywords()
|
||||||
else:
|
else:
|
||||||
|
@ -173,32 +179,42 @@ class ItemTestReport(BaseReport):
|
||||||
elif excinfo.errisinstance(Skipped):
|
elif excinfo.errisinstance(Skipped):
|
||||||
self.skipped = True
|
self.skipped = True
|
||||||
shortrepr = "s"
|
shortrepr = "s"
|
||||||
longrepr = self.item._repr_failure_py(excinfo, outerr)
|
longrepr = self.item._repr_failure_py(excinfo)
|
||||||
else:
|
else:
|
||||||
self.failed = True
|
self.failed = True
|
||||||
shortrepr = self.item.shortfailurerepr
|
shortrepr = self.item.shortfailurerepr
|
||||||
if self.when == "call":
|
if self.when == "call":
|
||||||
longrepr = self.item.repr_failure(excinfo, outerr)
|
longrepr = self.item.repr_failure(excinfo)
|
||||||
else: # exception in setup or teardown
|
else: # exception in setup or teardown
|
||||||
longrepr = self.item._repr_failure_py(excinfo, outerr)
|
longrepr = self.item._repr_failure_py(excinfo)
|
||||||
shortrepr = shortrepr.lower()
|
shortrepr = shortrepr.lower()
|
||||||
self.shortrepr = shortrepr
|
self.shortrepr = shortrepr
|
||||||
self.longrepr = longrepr
|
self.longrepr = longrepr
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
status = (self.passed and "passed" or
|
||||||
|
self.skipped and "skipped" or
|
||||||
|
self.failed and "failed" or
|
||||||
|
"CORRUPT")
|
||||||
|
l = [repr(self.item.name), "when=%r" % self.when, "outcome %r" % status,]
|
||||||
|
if hasattr(self, 'node'):
|
||||||
|
l.append("txnode=%s" % self.node.gateway.id)
|
||||||
|
info = " " .join(map(str, l))
|
||||||
|
return "<ItemTestReport %s>" % info
|
||||||
|
|
||||||
def getnode(self):
|
def getnode(self):
|
||||||
return self.item
|
return self.item
|
||||||
|
|
||||||
class CollectReport(BaseReport):
|
class CollectReport(BaseReport):
|
||||||
skipped = failed = passed = False
|
skipped = failed = passed = False
|
||||||
|
|
||||||
def __init__(self, collector, result, excinfo=None, outerr=None):
|
def __init__(self, collector, result, excinfo=None):
|
||||||
self.collector = collector
|
self.collector = collector
|
||||||
if not excinfo:
|
if not excinfo:
|
||||||
self.passed = True
|
self.passed = True
|
||||||
self.result = result
|
self.result = result
|
||||||
else:
|
else:
|
||||||
self.outerr = outerr
|
self.longrepr = self.collector._repr_failure_py(excinfo)
|
||||||
self.longrepr = self.collector._repr_failure_py(excinfo, outerr)
|
|
||||||
if excinfo.errisinstance(Skipped):
|
if excinfo.errisinstance(Skipped):
|
||||||
self.skipped = True
|
self.skipped = True
|
||||||
self.reason = str(excinfo.value)
|
self.reason = str(excinfo.value)
|
||||||
|
@ -208,6 +224,13 @@ class CollectReport(BaseReport):
|
||||||
def getnode(self):
|
def getnode(self):
|
||||||
return self.collector
|
return self.collector
|
||||||
|
|
||||||
|
class TeardownErrorReport(BaseReport):
|
||||||
|
skipped = passed = False
|
||||||
|
failed = True
|
||||||
|
when = "teardown"
|
||||||
|
def __init__(self, excinfo):
|
||||||
|
self.longrepr = excinfo.getrepr(funcargs=True)
|
||||||
|
|
||||||
class SetupState(object):
|
class SetupState(object):
|
||||||
""" shared state for setting up/tearing down test items or collectors. """
|
""" shared state for setting up/tearing down test items or collectors. """
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
|
@ -166,11 +166,14 @@ class TerminalReporter:
|
||||||
fspath, lineno, msg = self._getreportinfo(item)
|
fspath, lineno, msg = self._getreportinfo(item)
|
||||||
self.write_fspath_result(fspath, "")
|
self.write_fspath_result(fspath, "")
|
||||||
|
|
||||||
|
def pytest__teardown_final_logerror(self, rep):
|
||||||
|
self.stats.setdefault("error", []).append(rep)
|
||||||
|
|
||||||
def pytest_runtest_logreport(self, rep):
|
def pytest_runtest_logreport(self, rep):
|
||||||
if rep.passed and rep.when in ("setup", "teardown"):
|
|
||||||
return
|
|
||||||
fspath = rep.item.fspath
|
|
||||||
cat, letter, word = self.getcategoryletterword(rep)
|
cat, letter, word = self.getcategoryletterword(rep)
|
||||||
|
if not letter and not word:
|
||||||
|
# probably passed setup/teardown
|
||||||
|
return
|
||||||
if isinstance(word, tuple):
|
if isinstance(word, tuple):
|
||||||
word, markup = word
|
word, markup = word
|
||||||
else:
|
else:
|
||||||
|
@ -194,9 +197,9 @@ class TerminalReporter:
|
||||||
def pytest_collectreport(self, rep):
|
def pytest_collectreport(self, rep):
|
||||||
if not rep.passed:
|
if not rep.passed:
|
||||||
if rep.failed:
|
if rep.failed:
|
||||||
self.stats.setdefault("failed", []).append(rep)
|
self.stats.setdefault("error", []).append(rep)
|
||||||
msg = rep.longrepr.reprcrash.message
|
msg = rep.longrepr.reprcrash.message
|
||||||
self.write_fspath_result(rep.collector.fspath, "F")
|
self.write_fspath_result(rep.collector.fspath, "E")
|
||||||
elif rep.skipped:
|
elif rep.skipped:
|
||||||
self.stats.setdefault("skipped", []).append(rep)
|
self.stats.setdefault("skipped", []).append(rep)
|
||||||
self.write_fspath_result(rep.collector.fspath, "S")
|
self.write_fspath_result(rep.collector.fspath, "S")
|
||||||
|
@ -237,6 +240,7 @@ class TerminalReporter:
|
||||||
__call__.execute()
|
__call__.execute()
|
||||||
self._tw.line("")
|
self._tw.line("")
|
||||||
if exitstatus in (0, 1, 2):
|
if exitstatus in (0, 1, 2):
|
||||||
|
self.summary_errors()
|
||||||
self.summary_failures()
|
self.summary_failures()
|
||||||
self.summary_skips()
|
self.summary_skips()
|
||||||
self.config.hook.pytest_terminal_summary(terminalreporter=self)
|
self.config.hook.pytest_terminal_summary(terminalreporter=self)
|
||||||
|
@ -288,9 +292,11 @@ class TerminalReporter:
|
||||||
def _getfailureheadline(self, rep):
|
def _getfailureheadline(self, rep):
|
||||||
if hasattr(rep, "collector"):
|
if hasattr(rep, "collector"):
|
||||||
return str(rep.collector.fspath)
|
return str(rep.collector.fspath)
|
||||||
else:
|
elif hasattr(rep, 'item'):
|
||||||
fspath, lineno, msg = self._getreportinfo(rep.item)
|
fspath, lineno, msg = self._getreportinfo(rep.item)
|
||||||
return msg
|
return msg
|
||||||
|
else:
|
||||||
|
return "test session"
|
||||||
|
|
||||||
def _getreportinfo(self, item):
|
def _getreportinfo(self, item):
|
||||||
try:
|
try:
|
||||||
|
@ -312,16 +318,39 @@ class TerminalReporter:
|
||||||
for rep in self.stats['failed']:
|
for rep in self.stats['failed']:
|
||||||
msg = self._getfailureheadline(rep)
|
msg = self._getfailureheadline(rep)
|
||||||
self.write_sep("_", msg)
|
self.write_sep("_", msg)
|
||||||
if hasattr(rep, 'node'):
|
self.write_platinfo(rep)
|
||||||
self.write_line(self.gateway2info.get(
|
|
||||||
rep.node.gateway, "node %r (platinfo not found? strange)")
|
|
||||||
[:self._tw.fullwidth-1])
|
|
||||||
rep.toterminal(self._tw)
|
rep.toterminal(self._tw)
|
||||||
|
|
||||||
|
def summary_errors(self):
|
||||||
|
if 'error' in self.stats and self.config.option.tbstyle != "no":
|
||||||
|
self.write_sep("=", "ERRORS")
|
||||||
|
for rep in self.stats['error']:
|
||||||
|
msg = self._getfailureheadline(rep)
|
||||||
|
if not hasattr(rep, 'when'):
|
||||||
|
# collect
|
||||||
|
msg = "ERROR during collection " + msg
|
||||||
|
elif rep.when == "setup":
|
||||||
|
msg = "ERROR at setup of " + msg
|
||||||
|
elif rep.when == "teardown":
|
||||||
|
msg = "ERROR at teardown of " + msg
|
||||||
|
self.write_sep("_", msg)
|
||||||
|
self.write_platinfo(rep)
|
||||||
|
rep.toterminal(self._tw)
|
||||||
|
|
||||||
|
def write_platinfo(self, rep):
|
||||||
|
if hasattr(rep, 'node'):
|
||||||
|
self.write_line(self.gateway2info.get(
|
||||||
|
rep.node.gateway,
|
||||||
|
"node %r (platinfo not found? strange)")
|
||||||
|
[:self._tw.fullwidth-1])
|
||||||
|
|
||||||
def summary_stats(self):
|
def summary_stats(self):
|
||||||
session_duration = py.std.time.time() - self._sessionstarttime
|
session_duration = py.std.time.time() - self._sessionstarttime
|
||||||
|
|
||||||
keys = "failed passed skipped deselected".split()
|
keys = "failed passed skipped deselected".split()
|
||||||
|
for key in self.stats.keys():
|
||||||
|
if key not in keys:
|
||||||
|
keys.append(key)
|
||||||
parts = []
|
parts = []
|
||||||
for key in keys:
|
for key in keys:
|
||||||
val = self.stats.get(key, None)
|
val = self.stats.get(key, None)
|
||||||
|
|
|
@ -0,0 +1,307 @@
|
||||||
|
import py, os, sys
|
||||||
|
from py.__.test.plugin.pytest_iocapture import CaptureManager
|
||||||
|
|
||||||
|
class TestCaptureManager:
|
||||||
|
|
||||||
|
def test_configure_per_fspath(self, testdir):
|
||||||
|
config = testdir.parseconfig(testdir.tmpdir)
|
||||||
|
assert config.getvalue("capture") is None
|
||||||
|
capman = CaptureManager()
|
||||||
|
assert capman._getmethod(config, None) == "fd" # default
|
||||||
|
|
||||||
|
for name in ('no', 'fd', 'sys'):
|
||||||
|
sub = testdir.tmpdir.mkdir("dir" + name)
|
||||||
|
sub.ensure("__init__.py")
|
||||||
|
sub.join("conftest.py").write('conf_capture = %r' % name)
|
||||||
|
assert capman._getmethod(config, sub.join("test_hello.py")) == name
|
||||||
|
|
||||||
|
@py.test.mark.multi(method=['no', 'fd', 'sys'])
|
||||||
|
def test_capturing_basic_api(self, method):
|
||||||
|
capouter = py.io.StdCaptureFD()
|
||||||
|
old = sys.stdout, sys.stderr, sys.stdin
|
||||||
|
try:
|
||||||
|
capman = CaptureManager()
|
||||||
|
capman.resumecapture(method)
|
||||||
|
print "hello"
|
||||||
|
out, err = capman.suspendcapture()
|
||||||
|
if method == "no":
|
||||||
|
assert old == (sys.stdout, sys.stderr, sys.stdin)
|
||||||
|
else:
|
||||||
|
assert out == "hello\n"
|
||||||
|
capman.resumecapture(method)
|
||||||
|
out, err = capman.suspendcapture()
|
||||||
|
assert not out and not err
|
||||||
|
finally:
|
||||||
|
capouter.reset()
|
||||||
|
|
||||||
|
def test_juggle_capturings(self, testdir):
|
||||||
|
capouter = py.io.StdCaptureFD()
|
||||||
|
try:
|
||||||
|
config = testdir.parseconfig(testdir.tmpdir)
|
||||||
|
capman = CaptureManager()
|
||||||
|
capman.resumecapture("fd")
|
||||||
|
py.test.raises(ValueError, 'capman.resumecapture("fd")')
|
||||||
|
py.test.raises(ValueError, 'capman.resumecapture("sys")')
|
||||||
|
os.write(1, "hello\n")
|
||||||
|
out, err = capman.suspendcapture()
|
||||||
|
assert out == "hello\n"
|
||||||
|
capman.resumecapture("sys")
|
||||||
|
os.write(1, "hello\n")
|
||||||
|
print >>sys.stderr, "world"
|
||||||
|
out, err = capman.suspendcapture()
|
||||||
|
assert not out
|
||||||
|
assert err == "world\n"
|
||||||
|
finally:
|
||||||
|
capouter.reset()
|
||||||
|
|
||||||
|
def test_collect_capturing(testdir):
|
||||||
|
p = testdir.makepyfile("""
|
||||||
|
print "collect %s failure" % 13
|
||||||
|
import xyz42123
|
||||||
|
""")
|
||||||
|
result = testdir.runpytest(p)
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
"*Captured stdout*",
|
||||||
|
"*collect 13 failure*",
|
||||||
|
])
|
||||||
|
|
||||||
|
class TestPerTestCapturing:
|
||||||
|
def test_capture_and_fixtures(self, testdir):
|
||||||
|
p = testdir.makepyfile("""
|
||||||
|
def setup_module(mod):
|
||||||
|
print "setup module"
|
||||||
|
def setup_function(function):
|
||||||
|
print "setup", function.__name__
|
||||||
|
def test_func1():
|
||||||
|
print "in func1"
|
||||||
|
assert 0
|
||||||
|
def test_func2():
|
||||||
|
print "in func2"
|
||||||
|
assert 0
|
||||||
|
""")
|
||||||
|
result = testdir.runpytest(p)
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
"setup module*",
|
||||||
|
"setup test_func1*",
|
||||||
|
"in func1*",
|
||||||
|
"setup test_func2*",
|
||||||
|
"in func2*",
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_no_carry_over(self, testdir):
|
||||||
|
p = testdir.makepyfile("""
|
||||||
|
def test_func1():
|
||||||
|
print "in func1"
|
||||||
|
def test_func2():
|
||||||
|
print "in func2"
|
||||||
|
assert 0
|
||||||
|
""")
|
||||||
|
result = testdir.runpytest(p)
|
||||||
|
s = result.stdout.str()
|
||||||
|
assert "in func1" not in s
|
||||||
|
assert "in func2" in s
|
||||||
|
|
||||||
|
|
||||||
|
def test_teardown_capturing(self, testdir):
|
||||||
|
p = testdir.makepyfile("""
|
||||||
|
def setup_function(function):
|
||||||
|
print "setup func1"
|
||||||
|
def teardown_function(function):
|
||||||
|
print "teardown func1"
|
||||||
|
assert 0
|
||||||
|
def test_func1():
|
||||||
|
print "in func1"
|
||||||
|
pass
|
||||||
|
""")
|
||||||
|
result = testdir.runpytest(p)
|
||||||
|
assert result.stdout.fnmatch_lines([
|
||||||
|
'*teardown_function*',
|
||||||
|
'*Captured stdout*',
|
||||||
|
"setup func1*",
|
||||||
|
"in func1*",
|
||||||
|
"teardown func1*",
|
||||||
|
#"*1 fixture failure*"
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_teardown_final_capturing(self, testdir):
|
||||||
|
p = testdir.makepyfile("""
|
||||||
|
def teardown_module(mod):
|
||||||
|
print "teardown module"
|
||||||
|
assert 0
|
||||||
|
def test_func():
|
||||||
|
pass
|
||||||
|
""")
|
||||||
|
result = testdir.runpytest(p)
|
||||||
|
assert result.stdout.fnmatch_lines([
|
||||||
|
"*def teardown_module(mod):*",
|
||||||
|
"*Captured stdout*",
|
||||||
|
"*teardown module*",
|
||||||
|
"*1 error*",
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_capturing_outerr(self, testdir):
|
||||||
|
p1 = testdir.makepyfile("""
|
||||||
|
import sys
|
||||||
|
def test_capturing():
|
||||||
|
print 42
|
||||||
|
print >>sys.stderr, 23
|
||||||
|
def test_capturing_error():
|
||||||
|
print 1
|
||||||
|
print >>sys.stderr, 2
|
||||||
|
raise ValueError
|
||||||
|
""")
|
||||||
|
result = testdir.runpytest(p1)
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
"*test_capturing_outerr.py .F",
|
||||||
|
"====* FAILURES *====",
|
||||||
|
"____*____",
|
||||||
|
"*test_capturing_outerr.py:8: ValueError",
|
||||||
|
"*--- Captured stdout ---*",
|
||||||
|
"1",
|
||||||
|
"*--- Captured stderr ---*",
|
||||||
|
"2",
|
||||||
|
])
|
||||||
|
|
||||||
|
class TestLoggingInteraction:
|
||||||
|
def test_logging_stream_ownership(self, testdir):
|
||||||
|
p = testdir.makepyfile("""
|
||||||
|
def test_logging():
|
||||||
|
import logging
|
||||||
|
import StringIO
|
||||||
|
stream = StringIO.StringIO()
|
||||||
|
logging.basicConfig(stream=stream)
|
||||||
|
stream.close() # to free memory/release resources
|
||||||
|
""")
|
||||||
|
result = testdir.runpytest(p)
|
||||||
|
result.stderr.str().find("atexit") == -1
|
||||||
|
|
||||||
|
def test_capturing_and_logging_fundamentals(self, testdir):
|
||||||
|
# here we check a fundamental feature
|
||||||
|
rootdir = str(py.path.local(py.__file__).dirpath().dirpath())
|
||||||
|
p = testdir.makepyfile("""
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, %r)
|
||||||
|
import py, logging
|
||||||
|
cap = py.io.StdCaptureFD(out=False, in_=False)
|
||||||
|
logging.warn("hello1")
|
||||||
|
outerr = cap.suspend()
|
||||||
|
|
||||||
|
print "suspeneded and captured", outerr
|
||||||
|
|
||||||
|
logging.warn("hello2")
|
||||||
|
|
||||||
|
cap.resume()
|
||||||
|
logging.warn("hello3")
|
||||||
|
|
||||||
|
outerr = cap.suspend()
|
||||||
|
print "suspend2 and captured", outerr
|
||||||
|
""" % rootdir)
|
||||||
|
result = testdir.runpython(p)
|
||||||
|
assert result.stdout.fnmatch_lines([
|
||||||
|
"suspeneded and captured*hello1*",
|
||||||
|
"suspend2 and captured*hello2*WARNING:root:hello3*",
|
||||||
|
])
|
||||||
|
assert "atexit" not in result.stderr.str()
|
||||||
|
|
||||||
|
|
||||||
|
def test_logging_and_immediate_setupteardown(self, testdir):
|
||||||
|
p = testdir.makepyfile("""
|
||||||
|
import logging
|
||||||
|
def setup_function(function):
|
||||||
|
logging.warn("hello1")
|
||||||
|
|
||||||
|
def test_logging():
|
||||||
|
logging.warn("hello2")
|
||||||
|
assert 0
|
||||||
|
|
||||||
|
def teardown_function(function):
|
||||||
|
logging.warn("hello3")
|
||||||
|
assert 0
|
||||||
|
""")
|
||||||
|
for optargs in (('--capture=sys',), ('--capture=fd',)):
|
||||||
|
print optargs
|
||||||
|
result = testdir.runpytest(p, *optargs)
|
||||||
|
s = result.stdout.str()
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
"*WARN*hello3", # errors show first!
|
||||||
|
"*WARN*hello1",
|
||||||
|
"*WARN*hello2",
|
||||||
|
])
|
||||||
|
# verify proper termination
|
||||||
|
assert "closed" not in s
|
||||||
|
|
||||||
|
@py.test.mark.xfail
|
||||||
|
def test_logging_and_crossscope_fixtures(self, testdir):
|
||||||
|
# XXX also needs final teardown reporting to work!
|
||||||
|
p = testdir.makepyfile("""
|
||||||
|
import logging
|
||||||
|
def setup_module(function):
|
||||||
|
logging.warn("hello1")
|
||||||
|
|
||||||
|
def test_logging():
|
||||||
|
logging.warn("hello2")
|
||||||
|
assert 0
|
||||||
|
|
||||||
|
def teardown_module(function):
|
||||||
|
logging.warn("hello3")
|
||||||
|
assert 0
|
||||||
|
""")
|
||||||
|
for optargs in (('--iocapture=sys',), ('--iocapture=fd',)):
|
||||||
|
print optargs
|
||||||
|
result = testdir.runpytest(p, *optargs)
|
||||||
|
s = result.stdout.str()
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
"*WARN*hello1",
|
||||||
|
"*WARN*hello2",
|
||||||
|
"*WARN*hello3",
|
||||||
|
])
|
||||||
|
# verify proper termination
|
||||||
|
assert "closed" not in s
|
||||||
|
|
||||||
|
class TestCaptureFuncarg:
|
||||||
|
def test_std_functional(self, testdir):
|
||||||
|
reprec = testdir.inline_runsource("""
|
||||||
|
def test_hello(capsys):
|
||||||
|
print 42
|
||||||
|
out, err = capsys.readouterr()
|
||||||
|
assert out.startswith("42")
|
||||||
|
""")
|
||||||
|
reprec.assertoutcome(passed=1)
|
||||||
|
|
||||||
|
def test_stdfd_functional(self, testdir):
|
||||||
|
reprec = testdir.inline_runsource("""
|
||||||
|
def test_hello(capfd):
|
||||||
|
import os
|
||||||
|
os.write(1, "42")
|
||||||
|
out, err = capfd.readouterr()
|
||||||
|
assert out.startswith("42")
|
||||||
|
capfd.close()
|
||||||
|
""")
|
||||||
|
reprec.assertoutcome(passed=1)
|
||||||
|
|
||||||
|
def test_partial_setup_failure(self, testdir):
|
||||||
|
p = testdir.makepyfile("""
|
||||||
|
def test_hello(capfd, missingarg):
|
||||||
|
pass
|
||||||
|
""")
|
||||||
|
result = testdir.runpytest(p)
|
||||||
|
assert result.stdout.fnmatch_lines([
|
||||||
|
"*test_partial_setup_failure*",
|
||||||
|
"*1 error*",
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_keyboardinterrupt_disables_capturing(self, testdir):
|
||||||
|
p = testdir.makepyfile("""
|
||||||
|
def test_hello(capfd):
|
||||||
|
import os
|
||||||
|
os.write(1, "42")
|
||||||
|
raise KeyboardInterrupt()
|
||||||
|
""")
|
||||||
|
result = testdir.runpytest(p)
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
"*KEYBOARD INTERRUPT*"
|
||||||
|
])
|
||||||
|
assert result.ret == 2
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -126,7 +126,7 @@ class BaseFunctionalTests:
|
||||||
testdir.makepyfile(conftest="""
|
testdir.makepyfile(conftest="""
|
||||||
import py
|
import py
|
||||||
class Function(py.test.collect.Function):
|
class Function(py.test.collect.Function):
|
||||||
def repr_failure(self, excinfo, outerr):
|
def repr_failure(self, excinfo):
|
||||||
return "hello"
|
return "hello"
|
||||||
""")
|
""")
|
||||||
reports = testdir.runitem("""
|
reports = testdir.runitem("""
|
||||||
|
@ -143,7 +143,7 @@ class BaseFunctionalTests:
|
||||||
#assert rep.failed.where.path.basename == "test_func.py"
|
#assert rep.failed.where.path.basename == "test_func.py"
|
||||||
#assert rep.failed.failurerepr == "hello"
|
#assert rep.failed.failurerepr == "hello"
|
||||||
|
|
||||||
def test_failure_in_setup_function_ignores_custom_failure_repr(self, testdir):
|
def test_failure_in_setup_function_ignores_custom_repr(self, testdir):
|
||||||
testdir.makepyfile(conftest="""
|
testdir.makepyfile(conftest="""
|
||||||
import py
|
import py
|
||||||
class Function(py.test.collect.Function):
|
class Function(py.test.collect.Function):
|
||||||
|
@ -168,21 +168,6 @@ class BaseFunctionalTests:
|
||||||
#assert rep.outcome.where.path.basename == "test_func.py"
|
#assert rep.outcome.where.path.basename == "test_func.py"
|
||||||
#assert instanace(rep.failed.failurerepr, PythonFailureRepr)
|
#assert instanace(rep.failed.failurerepr, PythonFailureRepr)
|
||||||
|
|
||||||
def test_capture_in_func(self, testdir):
|
|
||||||
reports = testdir.runitem("""
|
|
||||||
import sys
|
|
||||||
def setup_function(func):
|
|
||||||
print "in setup"
|
|
||||||
def test_func():
|
|
||||||
print "in function"
|
|
||||||
assert 0
|
|
||||||
def teardown_function(func):
|
|
||||||
print "in teardown"
|
|
||||||
""")
|
|
||||||
assert reports[0].outerr[0] == "in setup\n"
|
|
||||||
assert reports[1].outerr[0] == "in function\n"
|
|
||||||
assert reports[2].outerr[0] == "in teardown\n"
|
|
||||||
|
|
||||||
def test_systemexit_does_not_bail_out(self, testdir):
|
def test_systemexit_does_not_bail_out(self, testdir):
|
||||||
try:
|
try:
|
||||||
reports = testdir.runitem("""
|
reports = testdir.runitem("""
|
||||||
|
@ -208,6 +193,23 @@ class BaseFunctionalTests:
|
||||||
else:
|
else:
|
||||||
py.test.fail("did not raise")
|
py.test.fail("did not raise")
|
||||||
|
|
||||||
|
@py.test.mark.xfail
|
||||||
|
def test_capture_per_func(self, testdir):
|
||||||
|
reports = testdir.runitem("""
|
||||||
|
import sys
|
||||||
|
def setup_function(func):
|
||||||
|
print "in setup"
|
||||||
|
def test_func():
|
||||||
|
print "in function"
|
||||||
|
assert 0
|
||||||
|
def teardown_function(func):
|
||||||
|
print "in teardown"
|
||||||
|
""")
|
||||||
|
assert reports[0].outerr[0] == "in setup\n"
|
||||||
|
assert reports[1].outerr[0] == "in function\n"
|
||||||
|
assert reports[2].outerr[0] == "in teardown\n"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class TestExecutionNonForked(BaseFunctionalTests):
|
class TestExecutionNonForked(BaseFunctionalTests):
|
||||||
def getrunner(self):
|
def getrunner(self):
|
||||||
|
@ -287,16 +289,3 @@ def test_functional_boxed(testdir):
|
||||||
"*1 failed*"
|
"*1 failed*"
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_logging_interaction(testdir):
|
|
||||||
p = testdir.makepyfile("""
|
|
||||||
def test_logging():
|
|
||||||
import logging
|
|
||||||
import StringIO
|
|
||||||
stream = StringIO.StringIO()
|
|
||||||
logging.basicConfig(stream=stream)
|
|
||||||
stream.close() # to free memory/release resources
|
|
||||||
""")
|
|
||||||
result = testdir.runpytest(p)
|
|
||||||
assert result.stderr.str().find("atexit") == -1
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -89,9 +89,10 @@ class TestTerminal:
|
||||||
p = testdir.makepyfile("import xyz")
|
p = testdir.makepyfile("import xyz")
|
||||||
result = testdir.runpytest(*option._getcmdargs())
|
result = testdir.runpytest(*option._getcmdargs())
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
"*test_collect_fail.py F*",
|
"*test_collect_fail.py E*",
|
||||||
"> import xyz",
|
"> import xyz",
|
||||||
"E ImportError: No module named xyz",
|
"E ImportError: No module named xyz",
|
||||||
|
"*1 error*",
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_internalerror(self, testdir, linecomp):
|
def test_internalerror(self, testdir, linecomp):
|
||||||
|
@ -357,3 +358,62 @@ def test_repr_python_version(monkeypatch):
|
||||||
py.std.sys.version_info = x = (2,3)
|
py.std.sys.version_info = x = (2,3)
|
||||||
assert repr_pythonversion() == str(x)
|
assert repr_pythonversion() == str(x)
|
||||||
|
|
||||||
|
class TestFixtureReporting:
|
||||||
|
def test_setup_fixture_error(self, testdir):
|
||||||
|
p = testdir.makepyfile("""
|
||||||
|
def setup_function(function):
|
||||||
|
print "setup func"
|
||||||
|
assert 0
|
||||||
|
def test_nada():
|
||||||
|
pass
|
||||||
|
""")
|
||||||
|
result = testdir.runpytest()
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
"*ERROR at setup of test_nada*",
|
||||||
|
"*setup_function(function):*",
|
||||||
|
"*setup func*",
|
||||||
|
"*assert 0*",
|
||||||
|
"*1 error*",
|
||||||
|
])
|
||||||
|
assert result.ret != 0
|
||||||
|
|
||||||
|
def test_teardown_fixture_error(self, testdir):
|
||||||
|
p = testdir.makepyfile("""
|
||||||
|
def test_nada():
|
||||||
|
pass
|
||||||
|
def teardown_function(function):
|
||||||
|
print "teardown func"
|
||||||
|
assert 0
|
||||||
|
""")
|
||||||
|
result = testdir.runpytest()
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
"*ERROR at teardown*",
|
||||||
|
"*teardown_function(function):*",
|
||||||
|
"*assert 0*",
|
||||||
|
"*Captured stdout*",
|
||||||
|
"*teardown func*",
|
||||||
|
"*1 passed*1 error*",
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_teardown_fixture_error_and_test_failure(self, testdir):
|
||||||
|
p = testdir.makepyfile("""
|
||||||
|
def test_fail():
|
||||||
|
assert 0, "failingfunc"
|
||||||
|
|
||||||
|
def teardown_function(function):
|
||||||
|
print "teardown func"
|
||||||
|
assert False
|
||||||
|
""")
|
||||||
|
result = testdir.runpytest()
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
"*ERROR at teardown of test_fail*",
|
||||||
|
"*teardown_function(function):*",
|
||||||
|
"*assert False*",
|
||||||
|
"*Captured stdout*",
|
||||||
|
"*teardown func*",
|
||||||
|
|
||||||
|
"*test_fail*",
|
||||||
|
"*def test_fail():",
|
||||||
|
"*failingfunc*",
|
||||||
|
"*1 failed*1 error*",
|
||||||
|
])
|
||||||
|
|
|
@ -271,8 +271,9 @@ class FunctionMixin(PyobjMixin):
|
||||||
traceback = ntraceback.filter()
|
traceback = ntraceback.filter()
|
||||||
return traceback
|
return traceback
|
||||||
|
|
||||||
def repr_failure(self, excinfo, outerr):
|
def repr_failure(self, excinfo, outerr=None):
|
||||||
return self._repr_failure_py(excinfo, outerr)
|
assert outerr is None, "XXX outerr usage is deprecated"
|
||||||
|
return self._repr_failure_py(excinfo)
|
||||||
|
|
||||||
shortfailurerepr = "F"
|
shortfailurerepr = "F"
|
||||||
|
|
||||||
|
|
|
@ -227,28 +227,6 @@ class TestGeneralUsage:
|
||||||
"*test_traceback_failure.py:4: AssertionError"
|
"*test_traceback_failure.py:4: AssertionError"
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_capturing_outerr(self, testdir):
|
|
||||||
p1 = testdir.makepyfile("""
|
|
||||||
import sys
|
|
||||||
def test_capturing():
|
|
||||||
print 42
|
|
||||||
print >>sys.stderr, 23
|
|
||||||
def test_capturing_error():
|
|
||||||
print 1
|
|
||||||
print >>sys.stderr, 2
|
|
||||||
raise ValueError
|
|
||||||
""")
|
|
||||||
result = testdir.runpytest(p1)
|
|
||||||
result.stdout.fnmatch_lines([
|
|
||||||
"*test_capturing_outerr.py .F",
|
|
||||||
"====* FAILURES *====",
|
|
||||||
"____*____",
|
|
||||||
"*test_capturing_outerr.py:8: ValueError",
|
|
||||||
"*--- Captured stdout ---*",
|
|
||||||
"1",
|
|
||||||
"*--- Captured stderr ---*",
|
|
||||||
"2",
|
|
||||||
])
|
|
||||||
|
|
||||||
def test_showlocals(self, testdir):
|
def test_showlocals(self, testdir):
|
||||||
p1 = testdir.makepyfile("""
|
p1 = testdir.makepyfile("""
|
||||||
|
|
|
@ -212,38 +212,6 @@ class TestConfigApi_getcolitems:
|
||||||
for col in col.listchain():
|
for col in col.listchain():
|
||||||
assert col.config is config
|
assert col.config is config
|
||||||
|
|
||||||
|
|
||||||
class TestGuardedCall:
|
|
||||||
def test_guardedcall_ok(self, testdir):
|
|
||||||
config = testdir.parseconfig()
|
|
||||||
def myfunc(x):
|
|
||||||
print x
|
|
||||||
print >>py.std.sys.stderr, "hello"
|
|
||||||
return 7
|
|
||||||
call = config.guardedcall(lambda: myfunc(3))
|
|
||||||
assert call.excinfo is None
|
|
||||||
assert call.result == 7
|
|
||||||
assert call.stdout.startswith("3")
|
|
||||||
assert call.stderr.startswith("hello")
|
|
||||||
|
|
||||||
def test_guardedcall_fail(self, testdir):
|
|
||||||
config = testdir.parseconfig()
|
|
||||||
def myfunc(x):
|
|
||||||
print x
|
|
||||||
raise ValueError(17)
|
|
||||||
call = config.guardedcall(lambda: myfunc(3))
|
|
||||||
assert call.excinfo
|
|
||||||
assert call.excinfo.type == ValueError
|
|
||||||
assert not hasattr(call, 'result')
|
|
||||||
assert call.stdout.startswith("3")
|
|
||||||
assert not call.stderr
|
|
||||||
|
|
||||||
def test_guardedcall_keyboardinterrupt(self, testdir):
|
|
||||||
config = testdir.parseconfig()
|
|
||||||
def myfunc():
|
|
||||||
raise KeyboardInterrupt
|
|
||||||
py.test.raises(KeyboardInterrupt, config.guardedcall, myfunc)
|
|
||||||
|
|
||||||
class TestOptionEffects:
|
class TestOptionEffects:
|
||||||
def test_boxed_option_default(self, testdir):
|
def test_boxed_option_default(self, testdir):
|
||||||
tmpdir = testdir.tmpdir.ensure("subdir", dir=1)
|
tmpdir = testdir.tmpdir.ensure("subdir", dir=1)
|
||||||
|
@ -258,29 +226,6 @@ class TestOptionEffects:
|
||||||
config = py.test.config._reparse([testdir.tmpdir])
|
config = py.test.config._reparse([testdir.tmpdir])
|
||||||
assert not config.option.boxed
|
assert not config.option.boxed
|
||||||
|
|
||||||
def test_config_iocapturing(self, testdir):
|
|
||||||
config = testdir.parseconfig(testdir.tmpdir)
|
|
||||||
assert config.getvalue("iocapture")
|
|
||||||
tmpdir = testdir.tmpdir.ensure("sub-with-conftest", dir=1)
|
|
||||||
tmpdir.join("conftest.py").write(py.code.Source("""
|
|
||||||
pytest_option_iocapture = "no"
|
|
||||||
"""))
|
|
||||||
config = py.test.config._reparse([tmpdir])
|
|
||||||
assert config.getvalue("iocapture") == "no"
|
|
||||||
capture = config._getcapture()
|
|
||||||
assert isinstance(capture, py.io.StdCapture)
|
|
||||||
assert not capture._out
|
|
||||||
assert not capture._err
|
|
||||||
assert not capture._in
|
|
||||||
assert isinstance(capture, py.io.StdCapture)
|
|
||||||
for opt, cls in (("sys", py.io.StdCapture),
|
|
||||||
("fd", py.io.StdCaptureFD),
|
|
||||||
):
|
|
||||||
config.option.iocapture = opt
|
|
||||||
capture = config._getcapture()
|
|
||||||
assert isinstance(capture, cls)
|
|
||||||
|
|
||||||
|
|
||||||
class TestConfig_gettopdir:
|
class TestConfig_gettopdir:
|
||||||
def test_gettopdir(self, testdir):
|
def test_gettopdir(self, testdir):
|
||||||
from py.__.test.config import gettopdir
|
from py.__.test.config import gettopdir
|
||||||
|
|
|
@ -194,7 +194,7 @@ class TestRequest:
|
||||||
""")
|
""")
|
||||||
result = testdir.runpytest(p)
|
result = testdir.runpytest(p)
|
||||||
assert result.stdout.fnmatch_lines([
|
assert result.stdout.fnmatch_lines([
|
||||||
"*1 failed*1 passed*"
|
"*1 passed*1 error*"
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_request_getmodulepath(self, testdir):
|
def test_request_getmodulepath(self, testdir):
|
||||||
|
|
|
@ -1,9 +1,16 @@
|
||||||
import py
|
import py
|
||||||
|
|
||||||
def test_make_sdist_and_run_it(py_setup, venv):
|
def test_make_sdist_and_run_it(capfd, py_setup, venv):
|
||||||
sdist = py_setup.make_sdist(venv.path)
|
try:
|
||||||
venv.easy_install(str(sdist))
|
sdist = py_setup.make_sdist(venv.path)
|
||||||
gw = venv.makegateway()
|
venv.easy_install(str(sdist))
|
||||||
ch = gw.remote_exec("import py ; channel.send(py.__version__)")
|
gw = venv.makegateway()
|
||||||
version = ch.receive()
|
ch = gw.remote_exec("import py ; channel.send(py.__version__)")
|
||||||
assert version == py.__version__
|
version = ch.receive()
|
||||||
|
assert version == py.__version__
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
raise
|
||||||
|
except:
|
||||||
|
print capfd.readouterr()
|
||||||
|
raise
|
||||||
|
capfd.close()
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
import py
|
import py
|
||||||
from py.__.test import parseopt
|
from py.__.test import parseopt
|
||||||
|
|
||||||
pytest_plugins = 'pytest_iocapture'
|
|
||||||
|
|
||||||
class TestParser:
|
class TestParser:
|
||||||
def test_init(self, capsys):
|
def test_init(self, capsys):
|
||||||
parser = parseopt.Parser(usage="xyz")
|
parser = parseopt.Parser(usage="xyz")
|
||||||
py.test.raises(SystemExit, 'parser.parse(["-h"])')
|
py.test.raises(SystemExit, 'parser.parse(["-h"])')
|
||||||
out, err = capsys.reset()
|
out, err = capsys.readouterr()
|
||||||
assert out.find("xyz") != -1
|
assert out.find("xyz") != -1
|
||||||
|
|
||||||
def test_group_add_and_get(self):
|
def test_group_add_and_get(self):
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -31,7 +31,7 @@ def main():
|
||||||
name='py',
|
name='py',
|
||||||
description='py.test and pylib: advanced testing tool and networking lib',
|
description='py.test and pylib: advanced testing tool and networking lib',
|
||||||
long_description = long_description,
|
long_description = long_description,
|
||||||
version= trunk or '1.0.0b8',
|
version= trunk or '1.0.0b9',
|
||||||
url='http://pylib.org',
|
url='http://pylib.org',
|
||||||
license='MIT license',
|
license='MIT license',
|
||||||
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
|
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
|
||||||
|
|
Loading…
Reference in New Issue