advance customization docs, enhance docstrings, add more reference object docs.
--HG-- branch : trunk
This commit is contained in:
parent
431a582132
commit
b5b8e5f0c2
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Loading…
Reference in New Issue