start reorganizing docs, write more docs, shift plugin docs, to proper documentation,
use sphinx, remove old docs ... work in progress. --HG-- branch : trunk
This commit is contained in:
@@ -1,87 +1,5 @@
|
||||
"""
|
||||
configurable per-test stdout/stderr capturing mechanisms.
|
||||
|
||||
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.
|
||||
|
||||
The plugin also provides test function arguments that help to
|
||||
assert stdout/stderr output from within your tests, see the
|
||||
`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 # replace sys.stdout/stderr with in-mem files
|
||||
py.test --capture=fd # point filedescriptors 1 and 2 to temp file
|
||||
|
||||
If you set capturing values in a conftest file like this::
|
||||
|
||||
# conftest.py
|
||||
option_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 in-memory files (``py.io.TextIO`` to be precise)
|
||||
that capture writes and decode non-unicode strings to a unicode object
|
||||
(using a default, usually, UTF-8, encoding).
|
||||
|
||||
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
|
||||
|
||||
def test_myoutput(capsys):
|
||||
print ("hello")
|
||||
sys.stderr.write("world\\n")
|
||||
out, err = capsys.readouterr()
|
||||
assert out == "hello\\n"
|
||||
assert err == "world\\n"
|
||||
print "next"
|
||||
out, err = capsys.readouterr()
|
||||
assert out == "next\\n"
|
||||
|
||||
The ``readouterr()`` call snapshots the output so far -
|
||||
and capturing will be continued. After the test
|
||||
function finishes the original streams will
|
||||
be restored. If you want to capture on
|
||||
the filedescriptor level you can use the ``capfd`` function
|
||||
argument which offers the same interface.
|
||||
""" plugin for configurable per-test stdout/stderr capturing mechanisms and
|
||||
``capsys`` and ``capfd`` function arguments.
|
||||
"""
|
||||
|
||||
import py
|
||||
|
||||
@@ -1,57 +1,4 @@
|
||||
"""
|
||||
safely patch object attributes, dicts and environment variables.
|
||||
|
||||
Usage
|
||||
----------------
|
||||
|
||||
Use the `monkeypatch funcarg`_ to tweak your global test environment
|
||||
for running a particular test. You can safely set/del an attribute,
|
||||
dictionary item or environment variable by respective methods
|
||||
on the monkeypatch funcarg. If you want e.g. to set an ENV1 variable
|
||||
and have os.path.expanduser return a particular directory, you can
|
||||
write it down like this:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
def test_mytest(monkeypatch):
|
||||
monkeypatch.setenv('ENV1', 'myval')
|
||||
monkeypatch.setattr(os.path, 'expanduser', lambda x: '/tmp/xyz')
|
||||
... # your test code that uses those patched values implicitely
|
||||
|
||||
After the test function finished all modifications will be undone,
|
||||
because the ``monkeypatch.undo()`` method is registered as a finalizer.
|
||||
|
||||
``monkeypatch.setattr/delattr/delitem/delenv()`` all
|
||||
by default raise an Exception if the target does not exist.
|
||||
Pass ``raising=False`` if you want to skip this check.
|
||||
|
||||
prepending to PATH or other environment variables
|
||||
---------------------------------------------------------
|
||||
|
||||
To prepend a value to an already existing environment parameter:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
def test_mypath_finding(monkeypatch):
|
||||
monkeypatch.setenv('PATH', 'x/y', prepend=":")
|
||||
# in bash language: export PATH=x/y:$PATH
|
||||
|
||||
calling "undo" finalization explicitely
|
||||
-----------------------------------------
|
||||
|
||||
At the end of function execution py.test invokes
|
||||
a teardown hook which undoes all monkeypatch changes.
|
||||
If you do not want to wait that long you can call
|
||||
finalization explicitely::
|
||||
|
||||
monkeypatch.undo()
|
||||
|
||||
This will undo previous changes. This call consumes the
|
||||
undo stack. Calling it a second time has no effect unless
|
||||
you start monkeypatching after the undo call.
|
||||
|
||||
.. _`monkeypatch blog post`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/
|
||||
"""
|
||||
""" monkeypatching and mocking functionality. """
|
||||
|
||||
import os, sys
|
||||
|
||||
@@ -72,18 +19,21 @@ def pytest_funcarg__monkeypatch(request):
|
||||
parameter determines if a KeyError or AttributeError
|
||||
will be raised if the set/deletion operation has no target.
|
||||
"""
|
||||
monkeypatch = MonkeyPatch()
|
||||
request.addfinalizer(monkeypatch.undo)
|
||||
return monkeypatch
|
||||
mpatch = monkeypatch()
|
||||
request.addfinalizer(mpatch.undo)
|
||||
return mpatch
|
||||
|
||||
notset = object()
|
||||
|
||||
class MonkeyPatch:
|
||||
class monkeypatch:
|
||||
""" object keeping a record of setattr/item/env/syspath changes. """
|
||||
def __init__(self):
|
||||
self._setattr = []
|
||||
self._setitem = []
|
||||
|
||||
def setattr(self, obj, name, value, raising=True):
|
||||
""" set attribute ``name`` on ``obj`` to ``value``, by default
|
||||
raise AttributeEror if the attribute did not exist. """
|
||||
oldval = getattr(obj, name, notset)
|
||||
if raising and oldval is notset:
|
||||
raise AttributeError("%r has no attribute %r" %(obj, name))
|
||||
@@ -91,6 +41,8 @@ class MonkeyPatch:
|
||||
setattr(obj, name, value)
|
||||
|
||||
def delattr(self, obj, name, raising=True):
|
||||
""" delete attribute ``name`` from ``obj``, by default raise
|
||||
AttributeError it the attribute did not previously exist. """
|
||||
if not hasattr(obj, name):
|
||||
if raising:
|
||||
raise AttributeError(name)
|
||||
@@ -99,10 +51,12 @@ class MonkeyPatch:
|
||||
delattr(obj, name)
|
||||
|
||||
def setitem(self, dic, name, value):
|
||||
""" set dictionary entry ``name`` to value. """
|
||||
self._setitem.insert(0, (dic, name, dic.get(name, notset)))
|
||||
dic[name] = value
|
||||
|
||||
def delitem(self, dic, name, raising=True):
|
||||
""" delete ``name`` from dict, raise KeyError if it doesn't exist."""
|
||||
if name not in dic:
|
||||
if raising:
|
||||
raise KeyError(name)
|
||||
@@ -111,20 +65,28 @@ class MonkeyPatch:
|
||||
del dic[name]
|
||||
|
||||
def setenv(self, name, value, prepend=None):
|
||||
""" set environment variable ``name`` to ``value``. if ``prepend``
|
||||
is a character, read the current environment variable value
|
||||
and prepend the ``value`` adjoined with the ``prepend`` character."""
|
||||
value = str(value)
|
||||
if prepend and name in os.environ:
|
||||
value = value + prepend + os.environ[name]
|
||||
self.setitem(os.environ, name, value)
|
||||
|
||||
def delenv(self, name, raising=True):
|
||||
""" delete ``name`` from environment, raise KeyError it not exists."""
|
||||
self.delitem(os.environ, name, raising=raising)
|
||||
|
||||
def syspath_prepend(self, path):
|
||||
""" prepend ``path`` to ``sys.path`` list of import locations. """
|
||||
if not hasattr(self, '_savesyspath'):
|
||||
self._savesyspath = sys.path[:]
|
||||
sys.path.insert(0, str(path))
|
||||
|
||||
def undo(self):
|
||||
""" undo previous changes. This call consumes the
|
||||
undo stack. Calling it a second time has no effect unless
|
||||
you do more monkeypatching after the undo call."""
|
||||
for obj, name, value in self._setattr:
|
||||
if value is not notset:
|
||||
setattr(obj, name, value)
|
||||
|
||||
@@ -527,6 +527,24 @@ class Metafunc:
|
||||
self._ids = py.builtin.set()
|
||||
|
||||
def addcall(self, funcargs=None, id=_notexists, param=_notexists):
|
||||
""" add a new call to the underlying test function during the
|
||||
collection phase of a test run.
|
||||
|
||||
:arg funcargs: argument keyword dictionary used when invoking
|
||||
the test function.
|
||||
|
||||
:arg id: used for reporting and identification purposes. If you
|
||||
don't supply an `id` the length of the currently
|
||||
list of calls to the test function will be used.
|
||||
|
||||
:arg param: will be exposed to a later funcarg factory invocation
|
||||
through the ``request.param`` attribute. Setting it (instead of
|
||||
directly providing a ``funcargs`` ditionary) is called
|
||||
*indirect parametrization*. Indirect parametrization is
|
||||
preferable if test values are expensive to setup or can
|
||||
only be created after certain fixtures or test-run related
|
||||
initialization code has been run.
|
||||
"""
|
||||
assert funcargs is None or isinstance(funcargs, dict)
|
||||
if id is None:
|
||||
raise ValueError("id=None not allowed")
|
||||
@@ -573,22 +591,29 @@ class FuncargRequest:
|
||||
|
||||
def applymarker(self, marker):
|
||||
""" apply a marker to a test function invocation.
|
||||
Usually markers can be used as decorators for test functions or
|
||||
classes. However, with parametrized testing a single
|
||||
test function may be called multiple times and ``applymarker``
|
||||
allows to mark only a single invocation.
|
||||
|
||||
The 'marker' must be created with pytest.mark.* XYZ.
|
||||
:param marker: The ``pytest.mark.*`` object to be applied to the test invocation.
|
||||
|
||||
"""
|
||||
if not isinstance(marker, py.test.mark.XYZ.__class__):
|
||||
raise ValueError("%r is not a py.test.mark.* object")
|
||||
self._pyfuncitem.keywords[marker.markname] = marker
|
||||
|
||||
def cached_setup(self, setup, teardown=None, scope="module", extrakey=None):
|
||||
""" cache and return result of calling setup().
|
||||
""" return a testing resource managed by ``setup`` &
|
||||
``teardown`` calls. ``scope`` and ``extrakey`` determine when the
|
||||
``teardown`` function will be called so that subsequent calls to
|
||||
``setup`` would recreate the resource.
|
||||
|
||||
The requested argument name, the scope and the ``extrakey``
|
||||
determine the cache key. The scope also determines when
|
||||
teardown(result) will be called. valid scopes are:
|
||||
scope == 'function': when the single test function run finishes.
|
||||
scope == 'module': when tests in a different module are run
|
||||
scope == 'session': when tests of the session have run.
|
||||
:arg teardown: function receiving a previously setup resource.
|
||||
:arg setup: a no-argument function creating a resource.
|
||||
:arg scope: a string value out of ``function``, ``module`` or
|
||||
``session`` indicating the caching lifecycle of the resource.
|
||||
:arg extrakey: added to internal caching key of (funcargname, scope).
|
||||
"""
|
||||
if not hasattr(self.config, '_setupcache'):
|
||||
self.config._setupcache = {} # XXX weakref?
|
||||
@@ -607,6 +632,13 @@ class FuncargRequest:
|
||||
return val
|
||||
|
||||
def getfuncargvalue(self, argname):
|
||||
""" Retrieve a function argument by name for this test
|
||||
function invocation. This allows one function argument factory
|
||||
to call another function argument factory. If there are two
|
||||
funcarg factories for the same test function argument the first
|
||||
factory may use ``getfuncargvalue`` to call the second one and
|
||||
do something additional with the resource.
|
||||
"""
|
||||
try:
|
||||
return self._funcargs[argname]
|
||||
except KeyError:
|
||||
@@ -637,15 +669,16 @@ class FuncargRequest:
|
||||
return None
|
||||
raise ValueError("unknown finalization scope %r" %(scope,))
|
||||
|
||||
def addfinalizer(self, finalizer):
|
||||
"""add finalizer function to be called after test function
|
||||
finished execution. """
|
||||
self._addfinalizer(finalizer, scope="function")
|
||||
|
||||
def _addfinalizer(self, finalizer, scope):
|
||||
colitem = self._getscopeitem(scope)
|
||||
self.config._setupstate.addfinalizer(
|
||||
finalizer=finalizer, colitem=colitem)
|
||||
|
||||
def addfinalizer(self, finalizer):
|
||||
""" call the given finalizer after test function finished execution. """
|
||||
self._addfinalizer(finalizer, scope="function")
|
||||
|
||||
def __repr__(self):
|
||||
return "<FuncargRequest for %r>" %(self._pyfuncitem)
|
||||
|
||||
|
||||
@@ -1,36 +1,4 @@
|
||||
"""
|
||||
helpers for asserting deprecation and other warnings.
|
||||
|
||||
Example usage
|
||||
---------------------
|
||||
|
||||
You can use the ``recwarn`` funcarg to track
|
||||
warnings within a test function:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
def test_hello(recwarn):
|
||||
from warnings import warn
|
||||
warn("hello", DeprecationWarning)
|
||||
w = recwarn.pop(DeprecationWarning)
|
||||
assert issubclass(w.category, DeprecationWarning)
|
||||
assert 'hello' in str(w.message)
|
||||
assert w.filename
|
||||
assert w.lineno
|
||||
|
||||
You can also call a global helper for checking
|
||||
taht a certain function call yields a Deprecation
|
||||
warning:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
import py
|
||||
|
||||
def test_global():
|
||||
py.test.deprecated_call(myfunction, 17)
|
||||
|
||||
|
||||
"""
|
||||
""" record warnings to allow assertions about them. """
|
||||
|
||||
import py
|
||||
import sys, os
|
||||
|
||||
Reference in New Issue
Block a user