advance customization docs, enhance docstrings, add more reference object docs.

--HG--
branch : trunk
This commit is contained in:
holger krekel 2010-10-11 12:54:28 +02:00
parent 431a582132
commit b5b8e5f0c2
5 changed files with 139 additions and 157 deletions

View File

@ -256,158 +256,87 @@ initialisation, command line and configuration hooks
generic "runtest" hooks generic "runtest" hooks
------------------------------ ------------------------------
Each test item is usually executed by calling the following three hooks: Almost runtest related hooks receive a :py:class:`pytest.collect.Item` object.
.. autofunction:: pytest_runtest_protocol
.. autofunction:: pytest_runtest_setup .. autofunction:: pytest_runtest_setup
.. autofunction:: pytest_runtest_call .. autofunction:: pytest_runtest_call
.. autofunction:: pytest_runtest_teardown .. autofunction:: pytest_runtest_teardown
.. autofunction:: pytest_runtest_makereport
For each of the three invocations a `call object`_ encapsulates For deeper understanding you may look at the default implementation of
information about the outcome of the call and is subsequently used these hooks in :py:mod:`pytest.plugin.runner` and maybe also
to make a report object: in :py:mod:`pytest.plugin.pdb` which intercepts creation
of reports in order to drop to interactive debugging.
.. sourcecode:: python The :py:mod:`pytest.plugin.terminal` reported specifically uses
the reporting hook to print information about a test run.
report = hook.pytest_runtest_makereport(item, call) collection hooks
For example, the `pytest_pdb plugin`_ uses this hook to activate
interactive debugging on failures when ``--pdb`` is specified on the
command line.
Usually three reports will be generated for a single test item for each
of the three runtest hooks respectively. If ``pytest_runtest_setup``
fails then ``pytest_runtest_teardown`` will be called but not
``pytest_runtest_call``.
Each of the up to three reports is eventually fed to the logreport hook:
.. sourcecode:: python
pytest_runtest_logreport(report)
A ``report`` object contains status and reporting information:
.. sourcecode:: python
report.longrepr = string/lines/object to print
report.when = "setup", "call" or "teardown"
report.shortrepr = letter for progress-report
report.passed = True or False
report.failed = True or False
report.skipped = True or False
The `terminal plugin`_ uses this hook to print information
about a test run.
The whole protocol described here is implemented via this hook:
.. sourcecode:: python
pytest_runtest_protocol(item) -> True
.. _`call object`:
The call object contains information about a performed call:
.. sourcecode:: python
call.excinfo = ExceptionInfo object or None
call.when = "setup", "call" or "teardown"
call.outerr = None or tuple of strings representing captured stdout/stderr
.. _`pytest_pdb plugin`: plugin/pdb.html
.. _`terminal plugin`: plugin/terminal.html
generic collection hooks
------------------------------ ------------------------------
py.test calls the following two fundamental hooks for collecting files and directories: py.test calls the following hooks for collecting files and directories:
.. sourcecode:: python .. autofunction:: pytest_ignore_collect
.. autofunction:: pytest_collect_directory
def pytest_collect_directory(path, parent): .. autofunction:: pytest_collect_file
""" return Collection node or None for the given path. """
def pytest_collect_file(path, parent):
""" return Collection node or None for the given path. """
Both return a `collection node`_ for a given path. All returned
nodes from all hook implementations will participate in the
collection and running protocol. The ``parent`` object is
the parent node and may be used to access command line
options via the ``parent.config`` object.
Python test function and module hooks
----------------------------------------------------
For influencing the collection of objects in Python modules For influencing the collection of objects in Python modules
you can use the following hook: you can use the following hook:
.. sourcecode:: python .. autofunction:: pytest_pycollect_makeitem
def pytest_pycollect_makeitem(collector, name, obj):
""" return custom item/collector for a python object in a module, or None. """
This hook will be called for each Python object in a collected
Python module. The return value is a custom `collection node`_ or None.
.. XXX or ``False`` if you want to indicate that the given item should not be collected.
Gateway initialization (distributed testing) reporting hooks
---------------------------------------------------- ------------------------------
(alpha) For distributed testing it can be useful to prepare the Collection related reporting hooks:
remote environment. For this you can implement the newgateway hook:
.. sourcecode:: python .. autofunction: pytest_collectstart
.. autofunction: pytest_log_itemcollect
.. autofunction: pytest_collectreport
.. autofunction: pytest_deselected
def pytest_gwmanage_newgateway(gateway, platinfo): And here is the central hook for reporting about
""" called after a gateway is instantiated. """ test execution:
The ``gateway`` object here has a ``spec`` attribute which is an ``execnet.XSpec`` .. autofunction: pytest_runtest_logreport
object, which has attributes that map key/values as specified from a ``--txspec``
option. The platinfo object is a dictionary with information about the remote process:
* ``version``: remote python's ``sys.version_info`` The test collection tree
* ``platform``: remote ``sys.platform``
* ``cwd``: remote ``os.getcwd``
.. _`collection process`:
.. _`collection node`:
.. _`test collection`:
Test Collection process
====================================================== ======================================================
the collection tree
---------------------------------
The collecting process is iterative so that distribution Default filesystem test discovery
and execution of tests can start as soon as the first test -----------------------------------------------
item is collected. Collection nodes with children are
called "Collectors" and terminal nodes are called "Items".
Here is an example of such a tree, generated with the
command ``py.test --collectonly py/xmlobj``::
<Directory 'xmlobj'> Test collection starts from specified paths or from the current
<Directory 'testing'> directory. All tests are collected ahead of running the first test.
<Module 'test_html.py' (py.__.xmlobj.testing.test_html)> (This used to be different in earlier versions of ``py.test`` where
<Function 'test_html_name_stickyness'> collection and running was interweaved which made test randomization
<Function 'test_stylenames'> and distributed testing harder).
<Function 'test_class_None'>
<Function 'test_alternating_style'> Collection nodes which have children are called "Collectors" and otherwise
<Module 'test_xml.py' (py.__.xmlobj.testing.test_xml)> they are called "Items" or "test items". Here is an example of such a
<Function 'test_tag_with_text'> tree::
<Function 'test_class_identity'>
<Function 'test_tag_with_text_and_attributes'> testing $ py.test --collectonly test_parseonly.py
<Function 'test_tag_with_subclassed_attr_simple'> <Directory 'testing'>
<Function 'test_tag_nested'> <Module 'test_parseopt.py'>
<Function 'test_tag_xmlname'> <Class 'TestParser'>
<Instance '()'>
<Function 'test_init'>
<Function 'test_group_add_and_get'>
<Function 'test_addgroup_deprecation'>
<Function 'test_getgroup_simple'>
<Function 'test_group_ordering'>
<Function 'test_group_addoption'>
<Function 'test_group_shortopt_lowercase'>
<Function 'test_parser_addoption'>
<Function 'test_parse'>
<Function 'test_parse_will_set_default'>
<Function 'test_parse_setoption'>
<Function 'test_parse_defaultgetter'>
<Function 'test_addoption_parser_epilog'>
By default all directories not starting with a dot are traversed, By default all directories not starting with a dot are traversed,
looking for ``test_*.py`` and ``*_test.py`` files. Those Python looking for ``test_*.py`` and ``*_test.py`` files. Those Python
@ -441,3 +370,18 @@ name. Given a filesystem ``fspath`` it is constructed as follows:
* perform ``sys.path.insert(0, basedir)``. * perform ``sys.path.insert(0, basedir)``.
* import the root package as ``root`` * import the root package as ``root``
Complete reference of objects involved in hooks
===========================================================
.. autoclass:: pytest.plugin.runner.CallInfo
:members:
.. autoclass:: pytest.plugin.runner.TestReport
:members:
.. autoclass:: pytest.collect.Node
:members:
.. autoclass:: pytest.collect.Item
:inherited-members:

View File

@ -82,7 +82,7 @@ all core features and runs unchanged under Python2 and Python3 interpreters.
.. _`Python for Windows`: http://www.imladris.com/Scripts/PythonForWindows.html .. _`Python for Windows`: http://www.imladris.com/Scripts/PythonForWindows.html
.. _`Distribute`:
.. _`Distribute for installation`: http://pypi.python.org/pypi/distribute#installation-instructions .. _`Distribute for installation`: http://pypi.python.org/pypi/distribute#installation-instructions
.. _`distribute installation`: http://pypi.python.org/pypi/distribute .. _`distribute installation`: http://pypi.python.org/pypi/distribute
.. include:: links.inc

View File

@ -25,13 +25,20 @@ class HookProxy:
class Node(object): class Node(object):
""" base class for all Nodes in the collection tree. """ base class for all Nodes in the collection tree.
Collector subclasses have children, Items are terminal nodes. Collector subclasses have children, Items are terminal nodes."""
"""
def __init__(self, name, parent=None, config=None, collection=None): def __init__(self, name, parent=None, config=None, collection=None):
#: a unique name with the scope of the parent
self.name = name self.name = name
#: the parent collector node.
self.parent = parent self.parent = parent
self.config = config or parent.config self.config = config or parent.config
#: the collection this node is part of.
self.collection = collection or getattr(parent, 'collection', None) self.collection = collection or getattr(parent, 'collection', None)
#: the file where this item is contained/collected from.
self.fspath = getattr(parent, 'fspath', None) self.fspath = getattr(parent, 'fspath', None)
self.ihook = HookProxy(self) self.ihook = HookProxy(self)
self.keywords = self.readkeywords() self.keywords = self.readkeywords()
@ -130,13 +137,8 @@ class Node(object):
repr_failure = _repr_failure_py repr_failure = _repr_failure_py
class Collector(Node): class Collector(Node):
""" """ Collector instances create children through collect()
Collector instances create children through collect() and thus iteratively build a tree.
and thus iteratively build a tree. attributes::
parent: attribute pointing to the parent collector
(or None if this is the root collector)
name: basename of this collector object
""" """
Directory = configproperty('Directory') Directory = configproperty('Directory')
Module = configproperty('Module') Module = configproperty('Module')
@ -166,6 +168,15 @@ class Collector(Node):
""" internal helper method to cache results of calling collect(). """ """ internal helper method to cache results of calling collect(). """
return self._memoizedcall('_collected', self.collect) return self._memoizedcall('_collected', self.collect)
def _prunetraceback(self, traceback):
if hasattr(self, 'fspath'):
path = self.fspath
ntraceback = traceback.cut(path=self.fspath)
if ntraceback == traceback:
ntraceback = ntraceback.cut(excludepath=py._pydir)
traceback = ntraceback.filter()
return traceback
# ********************************************************************** # **********************************************************************
# DEPRECATED METHODS # DEPRECATED METHODS
# ********************************************************************** # **********************************************************************
@ -197,15 +208,6 @@ class Collector(Node):
""" """
return self.collect_by_name(name) return self.collect_by_name(name)
def _prunetraceback(self, traceback):
if hasattr(self, 'fspath'):
path = self.fspath
ntraceback = traceback.cut(path=self.fspath)
if ntraceback == traceback:
ntraceback = ntraceback.cut(excludepath=py._pydir)
traceback = ntraceback.filter()
return traceback
class FSCollector(Collector): class FSCollector(Collector):
def __init__(self, fspath, parent=None, config=None, collection=None): def __init__(self, fspath, parent=None, config=None, collection=None):
fspath = py.path.local(fspath) fspath = py.path.local(fspath)
@ -264,7 +266,10 @@ class Directory(FSCollector):
return self.ihook.pytest_collect_directory(path=path, parent=self) return self.ihook.pytest_collect_directory(path=path, parent=self)
class Item(Node): class Item(Node):
""" a basic test item. """ """ a basic test invocation item. Note that for a single function
there might be multiple test invocation items. Attributes:
"""
def _deprecated_testexecution(self): def _deprecated_testexecution(self):
if self.__class__.run != Item.run: if self.__class__.run != Item.run:
warnoldtestrun(function=self.run) warnoldtestrun(function=self.run)

View File

@ -53,18 +53,20 @@ def pytest_log_finishcollection(collection):
""" called after collection has finished. """ """ called after collection has finished. """
def pytest_ignore_collect(path, config): def pytest_ignore_collect(path, config):
""" return true value to prevent considering this path for collection. """ return True to prevent considering this path for collection.
This hook is consulted for all files and directories prior to considering This hook is consulted for all files and directories prior to calling
collection hooks. more specific hooks.
""" """
pytest_ignore_collect.firstresult = True pytest_ignore_collect.firstresult = True
def pytest_collect_directory(path, parent): def pytest_collect_directory(path, parent):
""" return Collection node or None for the given path. """ """ return collection Node or None for the given path. Any new node
needs to have the specified ``parent`` as a parent."""
pytest_collect_directory.firstresult = True pytest_collect_directory.firstresult = True
def pytest_collect_file(path, parent): def pytest_collect_file(path, parent):
""" return Collection node or None for the given path. """ """ return collection Node or None for the given path. Any new node
needs to have the specified ``parent`` as a parent."""
# logging hooks for collection # logging hooks for collection
def pytest_collectstart(collector): def pytest_collectstart(collector):
@ -113,23 +115,30 @@ def pytest_itemstart(item, node=None):
""" (deprecated, use pytest_runtest_logstart). """ """ (deprecated, use pytest_runtest_logstart). """
def pytest_runtest_protocol(item): def pytest_runtest_protocol(item):
""" implement fixture, run and report about the given test item. """ """ implements the standard runtest_setup/call/teardown protocol including
capturing exceptions and calling reporting hooks on the results accordingly.
:return boolean: True if no further hook implementations should be invoked.
"""
pytest_runtest_protocol.firstresult = True pytest_runtest_protocol.firstresult = True
def pytest_runtest_logstart(nodeid, location, fspath): def pytest_runtest_logstart(nodeid, location, fspath):
""" signal the start of a test run. """ """ signal the start of a test run. """
def pytest_runtest_setup(item): def pytest_runtest_setup(item):
""" called before pytest_runtest_call(). """ """ called before ``pytest_runtest_call(item)``. """
def pytest_runtest_call(item): def pytest_runtest_call(item):
""" execute test item. """ """ called to execute the test ``item``. """
def pytest_runtest_teardown(item): def pytest_runtest_teardown(item):
""" called after pytest_runtest_call(). """ """ called after ``pytest_runtest_call``. """
def pytest_runtest_makereport(item, call): def pytest_runtest_makereport(item, call):
""" make a test report for the given item and call outcome. """ """ return a :py:class:`pytest.plugin.runner.TestReport` object
for the given :py:class:`pytest.collect.Item` and
:py:class:`pytest.plugin.runner.CallInfo`.
"""
pytest_runtest_makereport.firstresult = True pytest_runtest_makereport.firstresult = True
def pytest_runtest_logreport(report): def pytest_runtest_logreport(report):

View File

@ -113,8 +113,11 @@ def call_runtest_hook(item, when):
return CallInfo(lambda: ihook(item=item), when=when) return CallInfo(lambda: ihook(item=item), when=when)
class CallInfo: class CallInfo:
""" Call Information about a hook call. """
#: None or ExceptionInfo object.
excinfo = None excinfo = None
def __init__(self, func, when): def __init__(self, func, when):
#: one of "setup", "call", "teardown" specifying the runtest phase.
self.when = when self.when = when
try: try:
self.result = func() self.result = func()
@ -169,15 +172,36 @@ def pytest_runtest_makereport(item, call):
keywords, outcome, longrepr, when) keywords, outcome, longrepr, when)
class TestReport(BaseReport): class TestReport(BaseReport):
""" Basic test report object (also used for setup and teardown calls if
they fail).
"""
def __init__(self, nodeid, nodenames, fspath, location, def __init__(self, nodeid, nodenames, fspath, location,
keywords, outcome, longrepr, when): keywords, outcome, longrepr, when):
#: normalized collection node id
self.nodeid = nodeid self.nodeid = nodeid
#: list of names indicating position in collection tree.
self.nodenames = nodenames self.nodenames = nodenames
#: the collected path of the file containing the test.
self.fspath = fspath # where the test was collected self.fspath = fspath # where the test was collected
#: a (filesystempath, lineno, domaininfo) tuple indicating the
#: actual location of a test item - it might be different from the
#: collected one e.g. if a method is inherited from a different module.
self.location = location self.location = location
#: a name -> value dictionary containing all keywords and
#: markers associated with a test invocation.
self.keywords = keywords self.keywords = keywords
#: test outcome, always one of "passed", "failed", "skipped".
self.outcome = outcome self.outcome = outcome
#: None or a failure representation.
self.longrepr = longrepr self.longrepr = longrepr
#: one of 'setup', 'call', 'teardown' to indicate runtest phase.
self.when = when self.when = when
def __repr__(self): def __repr__(self):