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:
holger krekel
2010-10-10 23:45:45 +02:00
parent 854f6a98ae
commit 4ee3831ac9
82 changed files with 1673 additions and 3985 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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